org.openbmap.soapclient.AsyncUploader.java Source code

Java tutorial

Introduction

Here is the source code for org.openbmap.soapclient.AsyncUploader.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.os.AsyncTask;
import android.util.Base64;
import android.util.Log;

import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;

import java.io.File;
import java.io.IOException;

/**
 * Uploads xml files as multipart message to webservice.
 */
public class AsyncUploader extends AsyncTask<String, Integer, Boolean> {

    private UploadResult mResult;

    public enum UploadResult {
        UNDEFINED, OK, ERROR, WRONG_PASSWORD
    }

    /**
     * Socket and connection parameters for http upload
     */
    //private static final int SOCKET_TIMEOUT = 30000;
    //private static final int CONNECTION_TIMEOUT = 30000;

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

    /**
     * Multipart message field: one-time token (for anonymous upload)
     */
    private static final String API_FIELD = "api";

    /**
     * Multipart message field: file
     */
    private static final String FILE_FIELD = "file";

    /**
     * Retry upload how many times on failed upload
     * 0 means no retry
     */
    private static final int MAX_RETRIES = 2;

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

    public interface FileUploadListener {
        /**
         * Callback function on successful upload
         * @param file filename
         * @param size files in byte
         * @param speed speed in kb per second
         */
        void onUploadCompleted(String file, long size, long speed);

        /**
         * Callback function on failed upload
         * @param file filename
         * @param error
         */
        void onUploadFailed(final String file, String error);
    }

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

    /**
     * Either user/password oder token must be provided
     */
    private final String mUser;
    private final String mPassword;
    private final String mToken;

    private boolean passwordValidated = false;

    /**
     * Upload target address
     */
    private final String mServer;

    private String mFile;

    /**
     * Tells the length of upload (in bytes)
     */
    private long mSize;

    /**
     * Achieved upload speed (in KB)
     */
    private long mSpeed;

    /**
     * 
     * @param listener UploadTaskListener which is informed about upload result
     * @param user      user name
     * @param password password
     * @param server   remote URL
     */
    public AsyncUploader(final FileUploadListener listener, final String user, final String password,
            final String server) {
        mListener = listener;
        mUser = user;
        mPassword = password;
        mToken = null;
        mServer = server;
    }

    /**
     *
     * @param listener UploadTaskListener which is informed about upload result
     * @param token    server generated one-time token
     * @param server   remote URL
     */
    public AsyncUploader(final FileUploadListener listener, final String token, final String server) {
        mListener = listener;
        mUser = null;
        mPassword = null;
        mToken = token;
        mServer = server;
    }

    /**
     * Background task. Note: When uploading several files
     * upload is continued even on errors (i.e. will try to upload other files)
     * @param params filenames 
     * @return true on success, false if at least one file upload failed
     */
    @Deprecated
    @Override
    protected final Boolean doInBackground(final String... params) {
        Log.i(TAG, "Uploading " + params[0]);
        mFile = params[0];
        long beforeTime = System.currentTimeMillis();

        mResult = upload(mFile);
        if (mResult == UploadResult.WRONG_PASSWORD) {
            return false;
        }

        mSpeed = calcSpeed(System.currentTimeMillis(), beforeTime, mSize);

        return (mResult == UploadResult.OK);
    }

    @Override
    protected final void onPostExecute(final Boolean success) {
        if (mListener == null) {
            Log.e(TAG, "Listener is null!");
        }

        if (mResult == UploadResult.OK) {
            mListener.onUploadCompleted(mFile, mSize, mSpeed);
            return;
        } else {
            Log.e(TAG, "Upload failed " + lastErrorMsg);
            mListener.onUploadFailed(mFile, lastErrorMsg);
            return;
        }
    }

    /**
     * Sends an authenticated http post request to upload file
     * @param file File to upload (full path)
     * @return true on response code 200, false otherwise
     */
    private UploadResult httpPostRequest(final String file) {
        // TODO check network state
        // @see http://developer.android.com/training/basics/network-ops/connecting.html

        // Adjust HttpClient parameters
        final HttpParams httpParameters = new BasicHttpParams();
        // Set the timeout in milliseconds until a connection is established.
        // The default value is zero, that means the timeout is not used.
        // HttpConnectionParams.setConnectionTimeout(httpParameters, CONNECTION_TIMEOUT);
        // Set the default socket timeout (SO_TIMEOUT)
        // in milliseconds which is the timeout for waiting for data.
        //HttpConnectionParams.setSoTimeout(httpParameters, SOCKET_TIMEOUT);
        final DefaultHttpClient httpclient = new DefaultHttpClient(httpParameters);
        final HttpPost httppost = new HttpPost(mServer);
        try {
            final MultipartEntity entity = new MultipartEntity();
            entity.addPart(FILE_FIELD, new FileBody(new File(file), "text/xml"));

            if ((mUser != null) && (mPassword != null)) {
                final String authorizationString = "Basic "
                        + Base64.encodeToString((mUser + ":" + mPassword).getBytes(), Base64.NO_WRAP);
                httppost.setHeader("Authorization", authorizationString);
            }

            if (mToken != null) {
                entity.addPart(API_FIELD, new StringBody(mToken));
            }

            httppost.setEntity(entity);
            final HttpResponse response = httpclient.execute(httppost);

            final int reply = response.getStatusLine().getStatusCode();
            if (reply == 200) {
                // everything is ok if we receive HTTP 200
                mSize = entity.getContentLength();
                Log.i(TAG, "Uploaded " + file + ": Server reply " + reply);
                return UploadResult.OK;
            } else if (reply == 401) {
                Log.e(TAG, "Wrong username or password");
                return UploadResult.WRONG_PASSWORD;
            } else {
                Log.w(TAG, "Error while uploading" + file + ": Server reply " + reply);
                return UploadResult.ERROR;
            }
            // TODO: redirects (301, 302) are NOT handled here
            // thus if something changes on the server side we're dead here
        } catch (final ClientProtocolException e) {
            Log.e(TAG, e.getMessage());
        } catch (final IOException e) {
            Log.e(TAG, "I/O exception on file " + file);
        }
        return UploadResult.UNDEFINED;
    }

    /**
     * Uploads file. If upload hasn't succeeded on first attempt, upload is tried again MAX_RETRIES times
     * @param file File to upload (full path).
     * @return true on success, false on error
     */
    private UploadResult upload(final String file) {
        UploadResult result = httpPostRequest(file);

        // simple resume upload mechanism on failed upload
        int i = 0;
        while (result != UploadResult.OK && i < MAX_RETRIES) {
            Log.w(TAG, "Upload failed: Retry " + i + ": " + file);
            result = httpPostRequest(file);
            i++;
        }

        if (result != UploadResult.OK) {
            lastErrorMsg = "Upload failed after " + i + " retries";
            Log.e(TAG, "Upload failed after " + i + " retries");
        }

        return result;
    }

    /**
     * Returns upload speed in KB (crude calculation)
     * @param afterTime
     * @param beforeTime
     * @param bytes Upload size (in bytes)
     */
    private long calcSpeed(long afterTime, long beforeTime, long bytes) {
        return Math.round(bytes / (afterTime - beforeTime));
    }

}