org.openbmap.soapclient.ExportSessionTask.java Source code

Java tutorial

Introduction

Here is the source code for org.openbmap.soapclient.ExportSessionTask.java

Source

/*
   Radiobeacon - Openbmap wifi and cell logger
Copyright (C) 2013  wish7
    
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero 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 Affero General Public License for more details.
    
You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.openbmap.soapclient;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.preference.PreferenceManager;
import android.util.Log;
import android.widget.Toast;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.BasicHttpParams;
import org.openbmap.Preferences;
import org.openbmap.R;
import org.openbmap.RadioBeacon;
import org.openbmap.soapclient.AsyncUploader.FileUploadListener;
import org.openbmap.utils.CatalogUpdater;
import org.openbmap.utils.MediaScanner;

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;

/**
 * Manages export and upload processes
 */
public class ExportSessionTask extends AsyncTask<Void, Object, Boolean> implements FileUploadListener {

    private static final String TAG = ExportSessionTask.class.getSimpleName();

    /**
     * Max. number of parallel uploads
     */
    private final int MAX_THREADS = 5;

    /**
     * Wait for how many milliseconds for upload to be completed
     * Users have reported issues with GRACE_TIME = 30000, so give it some more time
     */
    private static final int GRACE_TIME = 120000;

    /**
     * OpenBmap cell upload address
     */
    private static final String CELL_WEBSERVICE = RadioBeacon.SERVER_BASE + "/uploads/cells";

    /**
     * OpenBmap cell anonymous upload address
     */
    private static final String CELL_ANONYMOUS_WEBSERVICE = RadioBeacon.SERVER_BASE + "/uploads/share_cells";

    /**
     * OpenBmap wifi upload address
     */
    private static final String WIFI_WEBSERVICE = RadioBeacon.SERVER_BASE + "/uploads/wifis";

    /**
     * OpenBmap cell anonymous upload address
     */
    private static final String WIFI_ANONYMOUS_WEBSERVICE = RadioBeacon.SERVER_BASE + "/uploads/share_wifis";

    private Context mAppContext;

    /**
     * Session Id to export
     */
    private final int mSession;

    /**
     * Message in case of an error
     */
    private final String errorMsg = null;

    /**
     * Used for callbacks.
     */
    private final UploadTaskListener mListener;

    /**
     * Directory where xmls files are stored
     */
    private final String mTempPath;

    /*
     *  Openbmap credentials : openbmap username
     */
    private final String mUser;
    /*
     *  Openbmap credentials : openbmap password
     */
    private final String mPassword;

    /**
     * Export and upload cells?
     */
    private boolean mExportCells = false;

    /**
     * Export and upload wifis?
     */
    private boolean mExportWifis = false;

    /**
     * Anonymous upload using one-time token
     */
    private boolean mAnonymousUpload = Preferences.VAL_ANONYMOUS_UPLOAD;

    /**
    * Upload md5ssid only?
    */
    private boolean mAnonymiseSsid = Preferences.VAL_ANONYMISE_SSID;

    /**
    * Skip upload?
    */
    private boolean mSkipUpload = Preferences.VAL_SKIP_UPLOAD;

    /**
     * Skip cleanup?
     */
    private boolean mKeepXml = Preferences.VAL_KEEP_XML;

    /**
     * Create a GPX track
     */
    private boolean mSaveGpx = Preferences.VAL_SAVE_GPX;

    /**
     * Update wifi catalog with new wifis?
     */
    private boolean mUpdateWifiCatalog = false;

    /**
     * Number of active upload tasks
     */
    private int mActiveUploads;

    /**
     * One-time token for anonymous upload
     */
    private String mToken;

    /**
     * List of all successfully uploaded files. For the moment no differentiation between cells and wifis
     */
    private ArrayList<String> mUploadedFiles;
    private long mSpeed = -1;

    public interface UploadTaskListener {
        void onUploadProgressUpdate(Object... values);

        void onUploadCompleted(final int id);

        void onDryRunCompleted(final int id);

        void onUploadFailed(final int id, final String error);
    }

    //http://stackoverflow.com/questions/9573855/second-instance-of-activity-after-orientation-change
    /**
     *
     * @param context
     * @param listener
     * @param session
     * @param tempPath
     * @param user
     * @param password
     */
    public ExportSessionTask(final Context context, final UploadTaskListener listener, final int session,
            final String tempPath, final String user, final String password, final boolean anonymous_upload) {
        mAppContext = context.getApplicationContext();
        mSession = session;
        mTempPath = tempPath;
        mUser = user;
        mPassword = password;
        mListener = listener;

        mAnonymousUpload = anonymous_upload;
        mUploadedFiles = new ArrayList<>();
    }

    /**
     * Builds cell xml files and saves/uploads them
     */
    @SuppressLint("NewApi")
    @Override
    protected final Boolean doInBackground(final Void... params) {
        ArrayList<String> wifiFiles = new ArrayList<>();
        ArrayList<String> cellFiles = new ArrayList<>();
        Boolean success = true;

        if (!mSkipUpload && mAnonymousUpload && (mExportCells || mExportWifis)) {
            mToken = getToken();
            Log.i(TAG, "Token " + mToken);
        }

        if (mExportCells) {
            Log.i(TAG, "Exporting cells");
            // export cells
            publishProgress(mAppContext.getResources().getString(R.string.please_stay_patient),
                    mAppContext.getResources().getString(R.string.exporting_cells), 0);
            cellFiles = new CellSerializer(mAppContext, mSession, mTempPath, RadioBeacon.SW_VERSION).export();

            // upload
            if (!mSkipUpload) {
                uploadAllCells(cellFiles);
            }
        } else {
            Log.i(TAG, "Cell export skipped");
        }

        if (mExportWifis) {
            Log.i(TAG, "Exporting wifis");
            // export wifis
            publishProgress(mAppContext.getResources().getString(R.string.please_stay_patient),
                    mAppContext.getResources().getString(R.string.exporting_wifis), 50);
            wifiFiles = new WifiSerializer(mAppContext, mSession, mTempPath, RadioBeacon.SW_VERSION, mAnonymiseSsid)
                    .export();

            // upload
            if (!mSkipUpload) {
                uploadAllWifis(wifiFiles);
            }
        } else {
            Log.i(TAG, "Wifi export skipped");
        }

        if (!mSkipUpload) {
            // wait max 30s for all upload tasks to finish
            final long startGrace = System.currentTimeMillis();
            sleepTillCompleted(startGrace);

            // check, whether all files are uploaded
            if (mUploadedFiles.size() != (wifiFiles.size() + cellFiles.size())) {
                Log.e(TAG, "Not all files have been uploaded!");
                // set state to failed on upload problems
                success = false;
            } else {
                Log.i(TAG, "All files uploaded");
            }

            // and cleanup
            if (!mKeepXml) {
                // delete only successfully uploaded files
                Log.i(TAG, "Deleting uploaded files");
                deleteXmlFiles(mUploadedFiles);
            } else {
                Log.i(TAG, "Deleting files skipped");
            }
        }
        // clean up a bit
        wifiFiles = null;
        cellFiles = null;
        mUploadedFiles = null;
        System.gc();

        if (mUpdateWifiCatalog) {
            Log.i(TAG, "Updating wifi catalog");
            new CatalogUpdater(mAppContext).execute((Void[]) null);
        }

        if (mSaveGpx) {
            Log.i(TAG, "Exporting gpx");
            publishProgress(mAppContext.getResources().getString(R.string.please_stay_patient),
                    mAppContext.getResources().getString(R.string.exporting_gpx), 75);

            final String filename = GpxSerializer.suggestGpxFilename(mSession);
            final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mAppContext);
            final int verbosity = Integer
                    .parseInt(prefs.getString(Preferences.KEY_GPX_VERBOSITY, Preferences.VAL_GPX_VERBOSITY));

            final SaveGpxTask task = new SaveGpxTask(mAppContext, null, mSession, mTempPath, filename, verbosity);
            task.execute();
        } else {
            Log.i(TAG, "GPX export skipped");
        }

        return success;
    }

    private void deleteXmlFiles(ArrayList<String> files) {
        for (int i = 0; i < files.size(); i++) {
            final File temp = new File(files.get(i));
            if (!temp.delete()) {
                Log.w(TAG, "Couldn't delete " + temp.getAbsolutePath());
            }
        }
    }

    private void sleepTillCompleted(long startGrace) {
        while (mActiveUploads > 0) {
            Log.i(TAG, "Waiting for uploads to complete. (Active " + String.valueOf(mActiveUploads) + ")");
            try {
                Thread.sleep(50);
            } catch (final InterruptedException e) {
            }
            if (System.currentTimeMillis() - startGrace > GRACE_TIME) {
                Log.i(TAG, "Timeout reached");
                break;
            }
        }
    }

    private void uploadAllCells(ArrayList<String> cellFiles) {
        for (int i = 0; i < cellFiles.size(); i++) {
            // thread control for the poor: spawn only MAX_THREADS tasks
            while (mActiveUploads >= allowedThreads()) {
                Log.i(TAG, "Number of upload threads exceeds max parallel threads (" + mActiveUploads + "/"
                        + allowedThreads() + "). Waiting..");
                try {
                    Thread.sleep(100);
                } catch (final InterruptedException e) {
                }
            }
            publishProgress(mAppContext.getResources().getString(R.string.please_stay_patient),
                    mAppContext.getResources().getString(R.string.uploading_cells) + "(" + "Files" + ": "
                            + String.valueOf(cellFiles.size() - i) + ")",
                    0);

            // enforce parallel execution on HONEYCOMB
            if (!mAnonymousUpload) {
                new AsyncUploader(this, mUser, mPassword, CELL_WEBSERVICE)
                        .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, cellFiles.get(i));
                mActiveUploads += 1;
            } else if (mAnonymousUpload && (mToken != null)) {
                new AsyncUploader(this, mToken, CELL_ANONYMOUS_WEBSERVICE)
                        .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, cellFiles.get(i));
                mActiveUploads += 1;
            } else {
                Log.e(TAG, "Neither user name nor token was available");
            }
        }
    }

    private void uploadAllWifis(ArrayList<String> wifiFiles) {
        for (int i = 0; i < wifiFiles.size(); i++) {
            while (mActiveUploads >= allowedThreads()) {
                Log.i(TAG, "Maximum number of upload threads reached. Waiting..");
                try {
                    Thread.sleep(50);
                } catch (final InterruptedException e) {
                }
            }
            publishProgress(mAppContext.getResources().getString(R.string.please_stay_patient),
                    mAppContext.getResources().getString(R.string.uploading_wifis) + "("
                            + mAppContext.getString(R.string.files) + ": " + String.valueOf(wifiFiles.size() - i)
                            + ")",
                    50);
            // enforce parallel execution on HONEYCOMB
            if (!mAnonymousUpload) {
                new AsyncUploader(this, mUser, mPassword, WIFI_WEBSERVICE)
                        .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, wifiFiles.get(i));
                mActiveUploads += 1;
            } else if (mAnonymousUpload && (mToken != null)) {
                new AsyncUploader(this, mToken, WIFI_ANONYMOUS_WEBSERVICE)
                        .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, wifiFiles.get(i));
                mActiveUploads += 1;
            } else {
                Log.e(TAG, "Neither user name nor token was available");
            }
        }
    }

    /**
      * Returns number of parallel uploads threads
      * @return 1 if no speed measurement available yet
      */
    private int allowedThreads() {
        if (mSpeed == -1) {
            Log.d(TAG, "No speed measurements. Only 1 thread allowed");
            return 1;
        } else if (mSpeed < 10) {
            Log.d(TAG, "Speed below 10kb. Only 1 thread allowed");
            return 1;
        } else {
            Log.d(TAG, "No thread restriction. " + MAX_THREADS + " allowed");
            return MAX_THREADS;
        }
    }

    /**
     * Updates progress bar.
     * @param values[0] contains title (as string)
     * @param values[1] contains message (as string)
     * @param values[1] contains progress (as int)
     */
    @Override
    protected final void onProgressUpdate(final Object... values) {
        if (mListener != null) {
            mListener.onUploadProgressUpdate(values);
        }
    }

    @Override
    protected final void onPostExecute(final Boolean success) {

        // rescan SD card on honeycomb devices
        // Otherwise files may not be visible when connected to desktop pc (MTP cache problem)
        Log.i(TAG, "Re-indexing SD card temp folder");
        new MediaScanner(mAppContext, new File(mTempPath));

        if (mSkipUpload) {
            // upload simulated only
            Toast.makeText(mAppContext, R.string.upload_skipped, Toast.LENGTH_LONG).show();
            if (mListener != null) {
                mListener.onDryRunCompleted(mSession);
            }
            return;
        }

        if (success && !mSkipUpload) {
            if (mListener != null) {
                mListener.onUploadCompleted(mSession);
            }
            return;
        } else {
            if (mListener != null) {
                mListener.onUploadFailed(mSession, errorMsg);
            }
            return;
        }
    }

    /**
     * Enables or disables cells export
     * @param exportCells
     */
    public final void setExportCells(final boolean exportCells) {
        mExportCells = exportCells;
    }

    /**
     * Enables or disables wifis export
     * @param exportWifis
     */
    public final void setExportWifis(final boolean exportWifis) {
        mExportWifis = exportWifis;
    }

    /**
     * Enables or disables SSID anonymisation
     * @param anonymiseSsid
     */
    public final void setAnonymiseSsid(final boolean anonymiseSsid) {
        this.mAnonymiseSsid = anonymiseSsid;
    }

    /**
     * Disables actual upload ('dry run')
     * @param skipUpload
     */
    public final void setSkipUpload(final boolean skipUpload) {
        this.mSkipUpload = skipUpload;
    }

    /**
     * Enables or disables GPX creation
     * @param saveGpx
     */
    public void setSaveGpx(boolean saveGpx) {
        this.mSaveGpx = saveGpx;
    }

    /**
     * If activated a local copy of uploaded XML files is kept
     * @param keepXml
     */
    public final void setKeepXml(final boolean keepXml) {
        this.mKeepXml = keepXml;
    }

    /* (non-Javadoc)
     * @see org.openbmap.soapclient.FileUploader.UploadTaskListener#onUploadCompleted(java.util.ArrayList)
     */
    @Override
    public final void onUploadCompleted(final String file, final long size, final long speed) {
        mUploadedFiles.add(file);
        mActiveUploads -= 1;
        mSpeed = speed;
        Log.i(TAG, "Finished upload (size " + size + " bytes, speed" + speed + "kb), pending uploads "
                + mActiveUploads);
    }

    /* (non-Javadoc)
     * @see org.openbmap.soapclient.FileUploader.UploadTaskListener#onUploadFailed(java.lang.String)
     */
    @Override
    public final void onUploadFailed(final String file, final String error) {
        Log.e(TAG, "Upload failed:" + file + " " + error);
        mActiveUploads -= 1;
        mSpeed = -1;
    }

    /**
     * @param updateCatalog
     */
    public void setUpdateWifiCatalog(final boolean updateCatalog) {
        mUpdateWifiCatalog = updateCatalog;
    }

    private String getToken() {
        final DefaultHttpClient httpclient = new DefaultHttpClient(new BasicHttpParams());
        final HttpPost httppost = new HttpPost("https://radiocells.org/openbmap/uploads/generate_api_key");
        final StringBuilder sb = new StringBuilder();

        InputStream inputStream = null;
        try {
            final HttpResponse response = httpclient.execute(httppost);
            final HttpEntity entity = response.getEntity();

            inputStream = entity.getContent();
            final BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"), 8);

            String line = null;
            while ((line = reader.readLine()) != null) {
                sb.append(line);
            }
        } catch (Exception e) {
            Log.e(TAG, "Error getting token:" + e.getMessage());
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (Exception squish) {
                return null;
            }
        }
        return sb.toString();
    }

}