org.runnerup.export.UploadManager.java Source code

Java tutorial

Introduction

Here is the source code for org.runnerup.export.UploadManager.java

Source

/*
 * Copyright (C) 2012 - 2013 jonas.oreland@gmail.com
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.runnerup.export;

import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.AsyncTask;
import android.os.Build;
import android.preference.PreferenceManager;
import android.text.InputType;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.TextView;

import org.json.JSONException;
import org.json.JSONObject;
import org.runnerup.BuildConfig;
import org.runnerup.R;
import org.runnerup.db.DBHelper;
import org.runnerup.export.Uploader.AuthMethod;
import org.runnerup.export.Uploader.Status;
import org.runnerup.feed.FeedList;
import org.runnerup.tracker.WorkoutObserver;
import org.runnerup.common.util.Constants.DB;
import org.runnerup.util.Encryption;
import org.runnerup.workout.WorkoutSerializer;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

@TargetApi(Build.VERSION_CODES.FROYO)
public class UploadManager {

    public static final int CONFIGURE_REQUEST = 1;

    private DBHelper mDBHelper = null;
    private SQLiteDatabase mDB = null;
    private Activity activity = null;
    private Context context = null;
    private final Map<String, Uploader> uploaders = new HashMap<String, Uploader>();
    private final Map<Long, Uploader> uploadersById = new HashMap<Long, Uploader>();
    private ProgressDialog mSpinner = null;

    public interface Callback {
        void run(String uploader, Uploader.Status status);
    }

    public UploadManager(Activity activity) {
        this.activity = activity;
        this.context = activity;
        mDBHelper = new DBHelper(activity);
        mDB = mDBHelper.getWritableDatabase();
        mSpinner = new ProgressDialog(activity);
        mSpinner.setCancelable(false);
    }

    public UploadManager(Context context) {
        this.activity = null;
        this.context = context;
        mDBHelper = new DBHelper(context);
        mDB = mDBHelper.getWritableDatabase();
        mSpinner = new ProgressDialog(context);
        mSpinner.setCancelable(false);
    }

    public synchronized void close() {
        if (mDB != null) {
            mDB.close();
            mDB = null;
        }
        if (mDBHelper != null) {
            mDBHelper.close();
            mDBHelper = null;
        }
    }

    public synchronized boolean isClosed() {
        return mDB == null;
    }

    public void remove(String uploaderName) {
        Uploader u = uploaders.get(uploaderName);
        uploaders.remove(uploaderName);
        if (u != null) {
            this.uploadersById.remove(u.getId());
        }
    }

    public void clear() {
        uploaders.clear();
        uploadersById.clear();
    }

    public long load(String uploaderName) {
        String from[] = new String[] { "_id", DB.ACCOUNT.NAME, DB.ACCOUNT.AUTH_CONFIG, DB.ACCOUNT.FLAGS };
        String args[] = { uploaderName };
        Cursor c = mDB.query(DB.ACCOUNT.TABLE, from, DB.ACCOUNT.NAME + " = ?", args, null, null, null, null);
        long id = -1;
        if (c.moveToFirst()) {
            ContentValues config = DBHelper.get(c);
            id = config.getAsLong("_id");
            add(config);
        }
        c.close();
        return id;
    }

    @SuppressWarnings("null")
    public Uploader add(ContentValues config) {
        if (config == null) {
            System.err.println("Add null!");
            assert (false);
            return null;
        }

        String uploaderName = config.getAsString(DB.ACCOUNT.NAME);
        if (uploaderName == null) {
            System.err.println("name not found!");
            return null;
        }
        if (uploaders.containsKey(uploaderName)) {
            return uploaders.get(uploaderName);
        }
        Uploader uploader = null;
        if (uploaderName.contentEquals(RunKeeperUploader.NAME)) {
            uploader = new RunKeeperUploader(this);
        } else if (uploaderName.contentEquals(GarminUploader.NAME)) {
            uploader = new GarminUploader(this);
        } else if (uploaderName.contentEquals(FunBeatUploader.NAME)) {
            uploader = new FunBeatUploader(this);
        } else if (uploaderName.contentEquals(MapMyRunUploader.NAME)) {
            uploader = new MapMyRunUploader(this);
        } else if (uploaderName.contentEquals(NikePlus.NAME)) {
            uploader = new NikePlus(this);
        } else if (uploaderName.contentEquals(JoggSE.NAME)) {
            uploader = new JoggSE(this);
        } else if (uploaderName.contentEquals(Endomondo.NAME)) {
            uploader = new Endomondo(this);
        } else if (uploaderName.contentEquals(RunningAHEAD.NAME)) {
            uploader = new RunningAHEAD(this);
        } else if (uploaderName.contentEquals(RunnerUpLive.NAME)) {
            uploader = new RunnerUpLive(context);
        } else if (uploaderName.contentEquals(DigifitUploader.NAME)) {
            uploader = new DigifitUploader(this);
        } else if (uploaderName.contentEquals(Strava.NAME)) {
            uploader = new Strava(this);
        } else if (uploaderName.contentEquals(Facebook.NAME)) {
            uploader = new Facebook(context, this);
        } else if (uploaderName.contentEquals(GooglePlus.NAME)) {
            uploader = new GooglePlus(this);
        } else if (uploaderName.contentEquals(RuntasticUploader.NAME)) {
            uploader = new RuntasticUploader(this);
        } else if (uploaderName.contentEquals(GoogleFitUploader.NAME)) {
            uploader = new GoogleFitUploader(context, this);
        }

        if (uploader != null) {
            if (!config.containsKey(DB.ACCOUNT.FLAGS)) {
                if (BuildConfig.DEBUG) {
                    String s = null;
                    s.charAt(3);
                }
            }
            uploader.init(config);
            uploaders.put(uploaderName, uploader);
            uploadersById.put(uploader.getId(), uploader);
        }
        return uploader;
    }

    public boolean isConfigured(final String name) {
        Uploader l = uploaders.get(name);
        if (l == null) {
            return true;
        }
        return l.isConfigured();
    }

    public Uploader getUploader(long id) {
        return uploadersById.get(id);
    }

    public void connect(final Callback callback, final String name, final boolean uploading) {
        Uploader l = uploaders.get(name);
        if (l == null) {
            callback.run(name, Uploader.Status.INCORRECT_USAGE);
            return;
        }
        Status s = l.connect();
        switch (s) {
        case OK:
            callback.run(name, s);
            return;
        case CANCEL:
        case SKIP:
        case INCORRECT_USAGE:
        case ERROR:
            l.reset();
            callback.run(name, s);
            return;
        case NEED_REFRESH:
            mSpinner.show();
            handleRefreshResult(l, l.refreshToken());
        case NEED_AUTH:
            mSpinner.show();
            handleAuth(new Callback() {
                @Override
                public void run(String uploader, Status status) {
                    mSpinner.dismiss();
                    callback.run(uploader, status);
                }
            }, l, s.authMethod);
            return;
        }
    }

    private void handleRefreshResult(Uploader uploader, Status status) {
        switch (status) {
        case ERROR:
            uploader.reset();
            return;
        case OK: {
            ContentValues tmp = new ContentValues();
            tmp.put("_id", uploader.getId());
            tmp.put(DB.ACCOUNT.AUTH_CONFIG, uploader.getAuthConfig());
            String args[] = { Long.toString(uploader.getId()) };
            mDB.update(DB.ACCOUNT.TABLE, tmp, "_id = ?", args);
            return;
        }
        }
        mSpinner.dismiss();
    }

    Uploader authUploader = null;
    Callback authCallback = null;

    private void handleAuth(Callback callback, Uploader l, AuthMethod authMethod) {
        authUploader = l;
        authCallback = callback;
        switch (authMethod) {
        case OAUTH2:
            activity.startActivityForResult(l.getAuthIntent(activity), CONFIGURE_REQUEST);
            return;
        case USER_PASS:
            askUsernamePassword(l, false);
            return;
        }
    }

    private void handleAuthComplete(Uploader uploader, Status s) {
        Callback cb = authCallback;
        authCallback = null;
        authUploader = null;
        switch (s) {
        case CANCEL:
        case ERROR:
        case INCORRECT_USAGE:
        case SKIP:
            uploader.reset();
            cb.run(uploader.getName(), s);
            return;
        case OK: {
            ContentValues tmp = new ContentValues();
            tmp.put("_id", uploader.getId());
            tmp.put(DB.ACCOUNT.AUTH_CONFIG, uploader.getAuthConfig());
            String args[] = { Long.toString(uploader.getId()) };
            mDB.update(DB.ACCOUNT.TABLE, tmp, "_id = ?", args);
            cb.run(uploader.getName(), s);
            return;
        }
        case NEED_AUTH:
            handleAuth(cb, uploader, s.authMethod);
            return;
        }
    }

    private JSONObject newObj(String str) {
        try {
            return new JSONObject(str);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return null;
    }

    private void askUsernamePassword(final Uploader l, boolean showPassword) {
        AlertDialog.Builder builder = new AlertDialog.Builder(activity);
        builder.setTitle(l.getName());
        // Get the layout inflater
        LayoutInflater inflater = activity.getLayoutInflater();
        final View view = inflater.inflate(R.layout.userpass, null);
        final CheckBox cb = (CheckBox) view.findViewById(R.id.showpass);
        final TextView tv1 = (TextView) view.findViewById(R.id.username);
        final TextView tv2 = (TextView) view.findViewById(R.id.password_input);
        String authConfigStr = l.getAuthConfig();
        final JSONObject authConfig = newObj(authConfigStr);
        String username = authConfig.optString("username", "");
        String password = authConfig.optString("password", "");
        tv1.setText(username);
        tv2.setText(password);
        cb.setChecked(showPassword);
        tv2.setInputType(InputType.TYPE_CLASS_TEXT | (showPassword ? 0 : InputType.TYPE_TEXT_VARIATION_PASSWORD));
        cb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                tv2.setInputType(
                        InputType.TYPE_CLASS_TEXT | (isChecked ? 0 : InputType.TYPE_TEXT_VARIATION_PASSWORD));
            }
        });

        // Inflate and set the layout for the dialog
        // Pass null as the parent view because its going in the dialog layout
        builder.setView(view);
        builder.setPositiveButton(getResources().getString(R.string.ok), new OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                try {
                    authConfig.put("username", tv1.getText());
                    authConfig.put("password", tv2.getText());
                } catch (JSONException e) {
                    e.printStackTrace();
                }
                testUserPass(l, authConfig, cb.isChecked());
            }
        });
        builder.setNeutralButton("Skip", new OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                handleAuthComplete(l, Status.SKIP);
            }
        });
        builder.setNegativeButton(getResources().getString(R.string.cancel), new OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                handleAuthComplete(l, Status.SKIP);
            }
        });
        final AlertDialog dialog = builder.create();
        dialog.show();
    }

    private void testUserPass(final Uploader l, final JSONObject authConfig, final boolean showPassword) {
        mSpinner.setTitle("Testing login " + l.getName());

        new AsyncTask<Uploader, String, Uploader.Status>() {

            final ContentValues config = new ContentValues();

            @Override
            protected Uploader.Status doInBackground(Uploader... params) {
                config.put(DB.ACCOUNT.AUTH_CONFIG, authConfig.toString());
                config.put("_id", l.getId());
                l.init(config);
                try {
                    return params[0].connect();
                } catch (Exception ex) {
                    ex.printStackTrace();
                    return Uploader.Status.ERROR;
                }
            }

            @Override
            protected void onPostExecute(Uploader.Status result) {
                handleAuthComplete(l, result);
            }
        }.execute(l);
    }

    private long mID = 0;
    private Callback uploadCallback = null;
    private HashSet<String> pendingUploaders = null;

    public void startUploading(Callback callback, HashSet<String> uploaders, long id) {
        mID = id;
        uploadCallback = callback;
        pendingUploaders = uploaders;
        mSpinner.setTitle("Uploading (" + pendingUploaders.size() + ")");
        mSpinner.show();
        nextUploader();
    }

    private void nextUploader() {
        if (pendingUploaders.isEmpty()) {
            doneUploading();
            return;
        }

        mSpinner.setTitle("Uploading (" + pendingUploaders.size() + ")");
        final Uploader uploader = uploaders.get(pendingUploaders.iterator().next());
        pendingUploaders.remove(uploader.getName());
        doUpload(uploader);
    }

    private void doUpload(final Uploader uploader) {
        final ProgressDialog copySpinner = mSpinner;
        final SQLiteDatabase copyDB = mDBHelper.getWritableDatabase();

        copySpinner.setMessage("Uploading " + uploader.getName());

        new AsyncTask<Uploader, String, Uploader.Status>() {

            @Override
            protected Uploader.Status doInBackground(Uploader... params) {
                try {
                    return params[0].upload(copyDB, mID);
                } catch (Exception ex) {
                    ex.printStackTrace();
                    return Uploader.Status.ERROR;
                }
            }

            @Override
            protected void onPostExecute(Uploader.Status result) {
                switch (result) {
                case CANCEL:
                    disableUploader(disableUploaderCallback, uploader, false);
                    return;
                case SKIP:
                case ERROR:
                case INCORRECT_USAGE:
                    nextUploader();
                    return;
                case OK:
                    uploadOK(uploader, copySpinner, copyDB, mID);
                    nextUploader();
                    return;
                case NEED_AUTH: // should be handled inside connect "loop"
                    handleAuth(new Callback() {
                        @Override
                        public void run(String uploaderName, org.runnerup.export.Uploader.Status status) {
                            switch (status) {
                            case CANCEL:
                                disableUploader(disableUploaderCallback, uploader, false);
                                return;
                            case SKIP:
                            case ERROR:
                            case INCORRECT_USAGE:
                            case NEED_AUTH: // should be handled inside
                                            // connect "loop"
                                nextUploader();
                                return;
                            case OK:
                                doUpload(uploader);
                                return;
                            }
                        }
                    }, uploader, result.authMethod);
                    return;
                }
            }
        }.execute(uploader);
    }

    private final Callback disableUploaderCallback = new Callback() {
        @Override
        public void run(String uploader, Status status) {
            nextUploader();
        }
    };

    private void uploadOK(Uploader uploader, ProgressDialog copySpinner, SQLiteDatabase copyDB, long id) {
        copySpinner.setMessage(getResources().getString(R.string.saving));
        ContentValues tmp = new ContentValues();
        tmp.put(DB.EXPORT.ACCOUNT, uploader.getId());
        tmp.put(DB.EXPORT.ACTIVITY, id);
        tmp.put(DB.EXPORT.STATUS, 0);
        copyDB.insert(DB.EXPORT.TABLE, null, tmp);
    }

    private void doneUploading() {
        mSpinner.dismiss();
        final Callback cb = uploadCallback;
        uploadCallback = null;
        if (cb != null)
            cb.run(null, null);

    }

    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode != CONFIGURE_REQUEST)
            return;

        handleAuthComplete(authUploader, authUploader.getAuthResult(resultCode, data));
    }

    public void disableUploader(Callback disconnectCallback, String uploader, boolean clearUploads) {
        disableUploader(disconnectCallback, uploaders.get(uploader), clearUploads);
    }

    public void disableUploader(Callback callback, Uploader uploader, boolean clearUploads) {
        resetDB(callback, uploader, clearUploads);
    }

    void resetDB(final Callback callback, final Uploader uploader, final boolean clearUploads) {
        final String args[] = { Long.toString(uploader.getId()) };
        ContentValues config = new ContentValues();
        config.putNull(DB.ACCOUNT.AUTH_CONFIG);
        mDB.update(DB.ACCOUNT.TABLE, config, "_id = ?", args);

        if (clearUploads) {
            mDB.delete(DB.EXPORT.TABLE, DB.EXPORT.ACCOUNT + " = ?", args);
        }

        uploader.reset();
        callback.run(uploader.getName(), Uploader.Status.OK);
    }

    public void clearUploads(Callback callback, String uploader) {
        final String args[] = { uploader };
        mDB.delete(DB.EXPORT.TABLE, DB.EXPORT.ACCOUNT + " = ?", args);
        callback.run(uploader, Uploader.Status.OK);
    }

    public void clearUpload(String name, long id) {
        Uploader uploader = uploaders.get(name);
        if (uploader != null) {
            final String args[] = { Long.toString(uploader.getId()), Long.toString(id) };
            mDB.delete(DB.EXPORT.TABLE, DB.EXPORT.ACCOUNT + " = ? AND " + DB.EXPORT.ACTIVITY + " = ?", args);
        }
    }

    public static class WorkoutRef {
        public WorkoutRef(String uploader, String key, String name) {
            this.uploader = uploader;
            this.workoutKey = key;
            this.workoutName = name;
        }

        public final String uploader;
        public final String workoutKey;
        public final String workoutName;
    }

    Callback listWorkoutCallback = null;
    HashSet<String> pendingListWorkout = null;
    ArrayList<WorkoutRef> workoutRef = null;

    public void loadWorkoutList(ArrayList<WorkoutRef> workoutRef, Callback callback, HashSet<String> uploaders) {

        listWorkoutCallback = callback;
        pendingListWorkout = uploaders;
        this.workoutRef = workoutRef;

        mSpinner.setTitle("Loading workout list (" + pendingListWorkout.size() + ")");
        mSpinner.show();
        nextListWorkout();
    }

    public void nextListWorkout() {
        if (pendingListWorkout.isEmpty()) {
            doneListing();
            return;
        }

        mSpinner.setTitle("Loading workout list (" + pendingListWorkout.size() + ")");
        Uploader uploader = uploaders.get(pendingListWorkout.iterator().next());
        pendingListWorkout.remove(uploader.getName());
        mSpinner.setMessage("Configure " + uploader.getName());
        if (!uploader.isConfigured()) {
            nextListWorkout();
            return;
        }
        doListWorkout(uploader);
    }

    protected void doListWorkout(final Uploader uploader) {
        final ProgressDialog copySpinner = mSpinner;

        copySpinner.setMessage("Listing from " + uploader.getName());
        final ArrayList<Pair<String, String>> list = new ArrayList<Pair<String, String>>();

        new AsyncTask<Uploader, String, Uploader.Status>() {

            @Override
            protected Uploader.Status doInBackground(Uploader... params) {
                try {
                    return params[0].listWorkouts(list);
                } catch (Exception ex) {
                    ex.printStackTrace();
                    return Uploader.Status.ERROR;
                }
            }

            @Override
            protected void onPostExecute(Uploader.Status result) {
                switch (result) {
                case CANCEL:
                case ERROR:
                case INCORRECT_USAGE:
                case SKIP:
                    break;
                case OK:
                    for (Pair<String, String> w : list) {
                        workoutRef.add(new WorkoutRef(uploader.getName(), w.first, w.second));
                    }
                    break;
                case NEED_AUTH:
                    handleAuth(new Callback() {
                        @Override
                        public void run(String uploaderName, org.runnerup.export.Uploader.Status status) {
                            switch (status) {
                            case CANCEL:
                            case SKIP:
                            case ERROR:
                            case INCORRECT_USAGE:
                            case NEED_AUTH: // should be handled inside
                                            // connect "loop"
                                nextListWorkout();
                                break;
                            case OK:
                                doListWorkout(uploader);
                                break;
                            }
                        }
                    }, uploader, result.authMethod);
                    return;
                }
                nextListWorkout();
            }
        }.execute(uploader);
    }

    private void doneListing() {
        mSpinner.dismiss();
        Callback cb = listWorkoutCallback;
        listWorkoutCallback = null;
        if (cb != null)
            cb.run(null, Status.OK);
    }

    public void loadWorkouts(final HashSet<WorkoutRef> pendingWorkouts, final Callback callback) {
        int cnt = pendingWorkouts.size();
        mSpinner.setTitle("Downloading workouts (" + cnt + ")");
        mSpinner.show();
        new AsyncTask<String, String, Uploader.Status>() {

            @Override
            protected void onProgressUpdate(String... values) {
                mSpinner.setMessage("Loading " + values[0] + " from " + values[1]);
            }

            @Override
            protected Uploader.Status doInBackground(String... params0) {
                for (WorkoutRef ref : pendingWorkouts) {
                    publishProgress(ref.workoutName, ref.uploader);
                    Uploader uploader = uploaders.get(ref.uploader);
                    File f = WorkoutSerializer.getFile(context, ref.workoutName);
                    File w = f;
                    if (f.exists()) {
                        w = WorkoutSerializer.getFile(context, ref.workoutName + ".tmp");
                    }
                    try {
                        uploader.downloadWorkout(w, ref.workoutKey);
                        if (w != f) {
                            if (compareFiles(w, f) != true) {
                                System.err.println("overwriting " + f.getPath() + " with " + w.getPath());
                                // TODO dialog
                                f.delete();
                                w.renameTo(f);
                            } else {
                                System.err.println("file identical...deleting temporary " + w.getPath());
                                w.delete();
                            }
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                        w.delete();
                    }
                }
                return Uploader.Status.OK;
            }

            @Override
            protected void onPostExecute(Uploader.Status result) {
                mSpinner.dismiss();
                if (callback != null) {
                    callback.run(null, Uploader.Status.OK);
                }
            }
        }.execute("string");
    }

    public static boolean compareFiles(File w, File f) {
        if (w.length() != f.length())
            return false;

        boolean cmp = true;
        FileInputStream f1 = null;
        FileInputStream f2 = null;
        try {
            f1 = new FileInputStream(w);
            f2 = new FileInputStream(f);
            byte buf1[] = new byte[1024];
            byte buf2[] = new byte[1024];
            do {
                int cnt1 = f1.read(buf1);
                int cnt2 = f2.read(buf2);
                if (cnt1 <= 0 || cnt2 <= 0)
                    break;

                if (!java.util.Arrays.equals(buf1, buf2)) {
                    cmp = false;
                    break;
                }
            } while (true);
        } catch (Exception ex) {
        }

        if (f1 != null) {
            try {
                f1.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        if (f2 != null) {
            try {
                f2.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return cmp;
    }

    /**
     * Load uploader private data
     * 
     * @param uploader
     * @return
     * @throws Exception
     */
    String loadData(Uploader uploader) throws Exception {
        InputStream in = context.getAssets().open(uploader.getName() + ".data");
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        String key = Encryption.calculateRFC2104HMAC("RunnerUp", uploader.getName());
        Encryption.decrypt(in, out, key);

        return out.toString();
    }

    /**
     * Get preferences
     * 
     * @return
     */
    public SharedPreferences getPreferences(Uploader uploader) {
        if (uploader == null)
            return PreferenceManager.getDefaultSharedPreferences(context);
        else
            return context.getSharedPreferences(uploader.getName(), Context.MODE_PRIVATE);
    }

    public Resources getResources() {
        return context.getResources();
    }

    public Context getContext() {
        return context;
    }

    /**
     * Upload set of activities for a specific uploader
     */
    ArrayList<Long> uploadActivities = null;
    Callback uploadWorkoutsCallback = null;
    StringBuffer cancelUploading = null;

    public void uploadWorkouts(Callback uploadCallback, String uploaderName, ArrayList<Long> list,
            final StringBuffer cancel) {

        mSpinner.setTitle("Uploading activities to " + uploaderName);
        mSpinner.setButton(DialogInterface.BUTTON_NEGATIVE, "Cancel", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                synchronized (cancel) {
                    cancel.append('t');
                }
                mSpinner.setMessage(getResources().getString(R.string.cancelling_please_wait));
            }
        });
        mSpinner.setCancelable(false);
        mSpinner.setCanceledOnTouchOutside(false);
        mSpinner.setMax(list.size());

        load(uploaderName);
        final Uploader uploader = uploaders.get(uploaderName);
        if (uploader == null) {
            uploadCallback.run(uploaderName, Status.INCORRECT_USAGE);
            return;
        }
        cancelUploading = cancel;
        uploadWorkoutsCallback = uploadCallback;
        uploadActivities = list;
        mSpinner.show();

        mSpinner.getButton(DialogInterface.BUTTON_NEGATIVE).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                synchronized (cancel) {
                    cancel.append('t');
                }
                mSpinner.setMessage(getResources().getString(R.string.cancelling_please_wait));
            }
        });

        uploadNextActivity(uploader);
    }

    protected boolean checkCancel(StringBuffer cancel) {
        synchronized (cancel) {
            return cancel.length() > 0;
        }
    }

    void uploadNextActivity(final Uploader uploader) {
        if (checkCancel(cancelUploading)) {
            mSpinner.cancel();
            uploadWorkoutsCallback.run(uploader.getName(), Uploader.Status.CANCEL);
            return;
        }

        if (uploadActivities.size() == 0) {
            mSpinner.cancel();
            uploadWorkoutsCallback.run(uploader.getName(), Uploader.Status.OK);
            return;
        }

        mSpinner.setProgress(uploadActivities.size());
        long id = uploadActivities.get(0);
        uploadActivities.remove(0);
        doUploadMulti(uploader, id);
    }

    private void doUploadMulti(final Uploader uploader, final long id) {
        final ProgressDialog copySpinner = mSpinner;
        final SQLiteDatabase copyDB = mDBHelper.getWritableDatabase();

        copySpinner.setMessage(Long.toString(1 + uploadActivities.size()) + " remaining");
        new AsyncTask<Uploader, String, Uploader.Status>() {

            @Override
            protected Uploader.Status doInBackground(Uploader... params) {
                try {
                    return uploader.upload(copyDB, id);
                } catch (Exception ex) {
                    ex.printStackTrace();
                    return Uploader.Status.ERROR;
                }
            }

            @Override
            protected void onPostExecute(Uploader.Status result) {
                switch (result) {
                case CANCEL:
                case ERROR:
                case INCORRECT_USAGE:
                case SKIP:
                    break;
                case OK:
                    uploadOK(uploader, copySpinner, copyDB, id);
                    break;
                case NEED_AUTH:
                    handleAuth(new Callback() {
                        @Override
                        public void run(String uploaderName, org.runnerup.export.Uploader.Status status) {
                            switch (status) {
                            case CANCEL:
                            case SKIP:
                            case ERROR:
                            case INCORRECT_USAGE:
                            case NEED_AUTH: // should be handled inside
                                            // connect "loop"
                                uploadActivities.clear();
                                uploadNextActivity(uploader);
                                break;
                            case OK:
                                doUploadMulti(uploader, id);
                                break;
                            }
                        }
                    }, uploader, result.authMethod);

                    break;
                }
                uploadNextActivity(uploader);
            }
        }.execute(uploader);
    }

    Callback feedCallback = null;
    Set<String> feedUploaders = null;
    FeedList feedList = null;
    StringBuffer feedCancel = null;

    public void syncronizeFeed(Callback cb, Set<String> uploaders, FeedList dst, StringBuffer cancel) {
        feedCallback = cb;
        feedUploaders = uploaders;
        feedList = dst;
        feedCancel = cancel;
        nextSyncFeed();
    }

    void nextSyncFeed() {
        if (checkCancel(feedCancel)) {
            feedUploaders.clear();
        }

        if (feedUploaders.isEmpty()) {
            Callback cb = feedCallback;
            feedCallback = null;
            cb.run(null, Uploader.Status.OK);
            return;
        }

        final String uploaderName = feedUploaders.iterator().next();
        feedUploaders.remove(uploaderName);
        final Uploader uploader = uploaders.get(uploaderName);
        syncFeed(uploader);
    }

    void syncFeed(final Uploader uploader) {
        if (uploader == null) {
            nextSyncFeed();
            return;
        }

        final FeedList.FeedUpdater feedUpdater = feedList.getUpdater();
        feedUpdater.start(uploader.getName());
        new AsyncTask<Uploader, String, Uploader.Status>() {

            @Override
            protected Uploader.Status doInBackground(Uploader... params) {
                try {
                    return params[0].getFeed(feedUpdater);
                } catch (Exception ex) {
                    ex.printStackTrace();
                    return Uploader.Status.ERROR;
                }
            }

            @Override
            protected void onPostExecute(Uploader.Status result) {
                if (result == Uploader.Status.OK) {
                    feedUpdater.complete();
                } else if (result == Uploader.Status.NEED_AUTH) {
                    handleAuth(new Callback() {
                        @Override
                        public void run(String uploaderName, Uploader.Status s2) {
                            if (s2 == Uploader.Status.OK) {
                                syncFeed(uploader);
                            } else {
                                nextSyncFeed();
                            }
                        }
                    }, uploader, result.authMethod);
                    return;
                } else {
                    if (result.ex != null)
                        result.ex.printStackTrace();
                }
                nextSyncFeed();
            }
        }.execute(uploader);
    }

    public void loadLiveLoggers(List<WorkoutObserver> liveLoggers) {
        liveLoggers.clear();
        Resources res = getResources();
        String key = res.getString(R.string.pref_runneruplive_active);
        if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean(key, false) == false) {
            return;
        }

        String from[] = new String[] { "_id", DB.ACCOUNT.NAME, DB.ACCOUNT.AUTH_CONFIG, DB.ACCOUNT.FLAGS };

        Cursor c = null;
        try {
            c = mDB.query(DB.ACCOUNT.TABLE, from,
                    "( " + DB.ACCOUNT.FLAGS + "&" + (1 << DB.ACCOUNT.FLAG_LIVE) + ") != 0", null, null, null, null,
                    null);
            if (c.moveToFirst()) {
                do {
                    ContentValues config = DBHelper.get(c);
                    Uploader u = add(config);
                    if (u.isConfigured() && u.checkSupport(Uploader.Feature.LIVE)
                            && (u instanceof WorkoutObserver)) {
                        liveLoggers.add((WorkoutObserver) u);
                    }
                } while (c.moveToNext());
            }
        } finally {
            if (c != null)
                c.close();
        }
    }
}