diff options
Diffstat (limited to 'WordPress/src/main/java/org/wordpress/android/ui/media/services')
3 files changed, 419 insertions, 0 deletions
diff --git a/WordPress/src/main/java/org/wordpress/android/ui/media/services/MediaDeleteService.java b/WordPress/src/main/java/org/wordpress/android/ui/media/services/MediaDeleteService.java new file mode 100644 index 000000000..af0497946 --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/media/services/MediaDeleteService.java @@ -0,0 +1,121 @@ +package org.wordpress.android.ui.media.services; + +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.os.Handler; +import android.os.IBinder; + +import org.wordpress.android.WordPress; +import org.wordpress.android.models.MediaUploadState; +import org.xmlrpc.android.ApiHelper; + +import java.util.ArrayList; +import java.util.List; + +/** + * A service for deleting media files from the media browser. + * Only one file is deleted at a time. + */ +public class MediaDeleteService extends Service { + // time to wait before trying to delete the next file + private static final int DELETE_WAIT_TIME = 1000; + + private Context mContext; + private Handler mHandler = new Handler(); + private boolean mDeleteInProgress; + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public void onCreate() { + super.onCreate(); + + mContext = this.getApplicationContext(); + mDeleteInProgress = false; + } + + @Override + public void onStart(Intent intent, int startId) { + mHandler.post(mFetchQueueTask); + } + + private Runnable mFetchQueueTask = new Runnable() { + @Override + public void run() { + Cursor cursor = getQueueItem(); + try { + if ((cursor == null || cursor.getCount() == 0 || mContext == null) && !mDeleteInProgress) { + MediaDeleteService.this.stopSelf(); + return; + } else { + if (mDeleteInProgress) { + mHandler.postDelayed(this, DELETE_WAIT_TIME); + } else { + deleteMediaFile(cursor); + } + } + } finally { + if (cursor != null) + cursor.close(); + } + + } + }; + + private Cursor getQueueItem() { + if (WordPress.getCurrentBlog() == null) + return null; + + String blogId = String.valueOf(WordPress.getCurrentBlog().getLocalTableBlogId()); + return WordPress.wpDB.getMediaDeleteQueueItem(blogId); + } + + private void deleteMediaFile(Cursor cursor) { + if (!cursor.moveToFirst()) + return; + + mDeleteInProgress = true; + + final String blogId = cursor.getString((cursor.getColumnIndex("blogId"))); + final String mediaId = cursor.getString(cursor.getColumnIndex("mediaId")); + + ApiHelper.DeleteMediaTask task = new ApiHelper.DeleteMediaTask(mediaId, + new ApiHelper.GenericCallback() { + @Override + public void onSuccess() { + // only delete them once we get an ok from the server + if (WordPress.getCurrentBlog() != null && mediaId != null) { + WordPress.wpDB.deleteMediaFile(blogId, mediaId); + } + + mDeleteInProgress = false; + mHandler.post(mFetchQueueTask); + } + + @Override + public void onFailure(ApiHelper.ErrorType errorType, String errorMessage, Throwable throwable) { + // Ideally we would do handle the 401 (unauthorized) and 404 (not found) errors, + // but the XMLRPCExceptions don't seem to give messages when they are thrown. + + // Instead we'll just set them as "deleted" so they don't show up in the delete queue. + // Otherwise the service will continuously try to delete an item they can't delete. + + WordPress.wpDB.updateMediaUploadState(blogId, mediaId, MediaUploadState.DELETED); + + mDeleteInProgress = false; + mHandler.post(mFetchQueueTask); + } + }); + + List<Object> apiArgs = new ArrayList<Object>(); + apiArgs.add(WordPress.getCurrentBlog()); + task.execute(apiArgs) ; + + mHandler.post(mFetchQueueTask); + } +} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/media/services/MediaEvents.java b/WordPress/src/main/java/org/wordpress/android/ui/media/services/MediaEvents.java new file mode 100644 index 000000000..e9ae8d9a7 --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/media/services/MediaEvents.java @@ -0,0 +1,51 @@ +package org.wordpress.android.ui.media.services; + +public class MediaEvents { + public static class MediaUploadSucceeded { + public final String mLocalBlogId; + public final String mLocalMediaId; + public final String mRemoteMediaId; + public final String mRemoteMediaUrl; + public final String mSecondaryRemoteMediaId; + MediaUploadSucceeded(String localBlogId, String localMediaId, String remoteMediaId, String remoteMediaUrl, + String secondaryRemoteMediaId) { + mLocalBlogId = localBlogId; + mLocalMediaId = localMediaId; + mRemoteMediaId = remoteMediaId; + mRemoteMediaUrl = remoteMediaUrl; + mSecondaryRemoteMediaId = secondaryRemoteMediaId; + } + } + + public static class MediaUploadFailed { + public final String mLocalMediaId; + public final String mErrorMessage; + public final boolean mIsGenericMessage; + MediaUploadFailed(String localMediaId, String errorMessage, boolean isGenericMessage) { + mLocalMediaId = localMediaId; + mErrorMessage = errorMessage; + mIsGenericMessage = isGenericMessage; + } + MediaUploadFailed(String localMediaId, String errorMessage) { + this(localMediaId, errorMessage, false); + } + } + + public static class MediaUploadProgress { + public final String mLocalMediaId; + public final float mProgress; + MediaUploadProgress(String localMediaId, float progress) { + mLocalMediaId = localMediaId; + mProgress = progress; + } + } + + public static class MediaChanged { + public final String mLocalBlogId; + public final String mMediaId; + public MediaChanged(String localBlogId, String mediaId) { + mLocalBlogId = localBlogId; + mMediaId = mediaId; + } + } +} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/media/services/MediaUploadService.java b/WordPress/src/main/java/org/wordpress/android/ui/media/services/MediaUploadService.java new file mode 100644 index 000000000..11d0ccae5 --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/media/services/MediaUploadService.java @@ -0,0 +1,247 @@ +package org.wordpress.android.ui.media.services; + +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.os.Handler; +import android.os.IBinder; + +import org.wordpress.android.R; +import org.wordpress.android.WordPress; +import org.wordpress.android.WordPressDB; +import org.wordpress.android.models.MediaUploadState; +import org.wordpress.android.ui.media.services.MediaEvents.MediaChanged; +import org.wordpress.android.util.AppLog.T; +import org.wordpress.android.util.CrashlyticsUtils; +import org.wordpress.android.util.CrashlyticsUtils.ExceptionType; +import org.wordpress.android.util.helpers.MediaFile; +import org.xmlrpc.android.ApiHelper; +import org.xmlrpc.android.ApiHelper.ErrorType; +import org.xmlrpc.android.ApiHelper.GetMediaItemTask; + +import java.util.ArrayList; +import java.util.List; + +import de.greenrobot.event.EventBus; + +/** + * A service for uploading media files from the media browser. + * Only one file is uploaded at a time. + */ +public class MediaUploadService extends Service { + // time to wait before trying to upload the next file + private static final int UPLOAD_WAIT_TIME = 1000; + + private static MediaUploadService mInstance; + + private Context mContext; + private Handler mHandler = new Handler(); + + private boolean mUploadInProgress; + private ApiHelper.UploadMediaTask mCurrentUploadMediaTask; + private String mCurrentUploadMediaId; + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public void onCreate() { + super.onCreate(); + + mInstance = this; + + mContext = this.getApplicationContext(); + mUploadInProgress = false; + + cancelOldUploads(); + } + + @Override + public void onStart(Intent intent, int startId) { + mHandler.post(mFetchQueueTask); + } + + public static MediaUploadService getInstance() { + return mInstance; + } + + public void processQueue() { + mHandler.post(mFetchQueueTask); + } + + /** + * Returns whether the service has any media uploads in progress or queued. + */ + public boolean hasUploads() { + if (mUploadInProgress) { + return true; + } else { + Cursor queueCursor = getQueue(); + return (queueCursor == null || queueCursor.getCount() > 0); + } + } + + /** + * Cancel the upload with the given id, whether it's currently uploading or queued. + * @param mediaId the id of the media item + * @param delete whether to delete the item from the queue or mark it as failed so it can be retried later + */ + public void cancelUpload(String mediaId, boolean delete) { + if (mediaId.equals(mCurrentUploadMediaId)) { + // The media item is currently uploading - abort the upload process + mCurrentUploadMediaTask.cancel(true); + mUploadInProgress = false; + } else { + // Remove the media item from the upload queue + if (WordPress.getCurrentBlog() != null) { + String blogId = String.valueOf(WordPress.getCurrentBlog().getLocalTableBlogId()); + if (delete) { + WordPress.wpDB.deleteMediaFile(blogId, mediaId); + } else { + WordPress.wpDB.updateMediaUploadState(blogId, mediaId, MediaUploadState.FAILED); + } + } + } + } + + private Runnable mFetchQueueTask = new Runnable() { + @Override + public void run() { + Cursor cursor = getQueue(); + try { + if ((cursor == null || cursor.getCount() == 0 || mContext == null) && !mUploadInProgress) { + MediaUploadService.this.stopSelf(); + return; + } else { + if (mUploadInProgress) { + mHandler.postDelayed(this, UPLOAD_WAIT_TIME); + } else { + uploadMediaFile(cursor); + } + } + } finally { + if (cursor != null) + cursor.close(); + } + + } + }; + + private void cancelOldUploads() { + // There should be no media files with an upload state of 'uploading' at the start of this service. + // Since we won't be able to receive notifications for these, set them to 'failed'. + + if (WordPress.getCurrentBlog() != null) { + String blogId = String.valueOf(WordPress.getCurrentBlog().getLocalTableBlogId()); + WordPress.wpDB.setMediaUploadingToFailed(blogId); + } + } + + private Cursor getQueue() { + if (WordPress.getCurrentBlog() == null) + return null; + + String blogId = String.valueOf(WordPress.getCurrentBlog().getLocalTableBlogId()); + return WordPress.wpDB.getMediaUploadQueue(blogId); + } + + private void uploadMediaFile(Cursor cursor) { + if (!cursor.moveToFirst()) + return; + + mUploadInProgress = true; + + final String blogIdStr = cursor.getString((cursor.getColumnIndex(WordPressDB.COLUMN_NAME_BLOG_ID))); + final String mediaId = cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_MEDIA_ID)); + String fileName = cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_FILE_NAME)); + String filePath = cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_FILE_PATH)); + String mimeType = cursor.getString(cursor.getColumnIndex(WordPressDB.COLUMN_NAME_MIME_TYPE)); + + MediaFile mediaFile = new MediaFile(); + mediaFile.setBlogId(blogIdStr); + mediaFile.setFileName(fileName); + mediaFile.setFilePath(filePath); + mediaFile.setMimeType(mimeType); + + mCurrentUploadMediaId = mediaId; + + mCurrentUploadMediaTask = new ApiHelper.UploadMediaTask(mContext, mediaFile, + new ApiHelper.UploadMediaTask.Callback() { + @Override + public void onSuccess(String remoteId, String remoteUrl, String secondaryId) { + // once the file has been uploaded, update the local database entry (swap the id with the remote id) + // and download the new one + WordPress.wpDB.updateMediaLocalToRemoteId(blogIdStr, mediaId, remoteId); + EventBus.getDefault().post(new MediaEvents.MediaUploadSucceeded(blogIdStr, mediaId, + remoteId, remoteUrl, secondaryId)); + fetchMediaFile(remoteId); + } + + @Override + public void onFailure(ApiHelper.ErrorType errorType, String errorMessage, Throwable throwable) { + WordPress.wpDB.updateMediaUploadState(blogIdStr, mediaId, MediaUploadState.FAILED); + mUploadInProgress = false; + mCurrentUploadMediaId = ""; + + MediaEvents.MediaUploadFailed event; + if (errorMessage == null) { + event = new MediaEvents.MediaUploadFailed(mediaId, getString(R.string.upload_failed), true); + } else { + event = new MediaEvents.MediaUploadFailed(mediaId, errorMessage); + } + + EventBus.getDefault().post(event); + mHandler.post(mFetchQueueTask); + + // Only log the error if it's not caused by the network (internal inconsistency) + if (errorType != ErrorType.NETWORK_XMLRPC) { + CrashlyticsUtils.logException(throwable, ExceptionType.SPECIFIC, T.MEDIA, errorMessage); + } + } + + @Override + public void onProgressUpdate(float progress) { + EventBus.getDefault().post(new MediaEvents.MediaUploadProgress(mediaId, progress)); + } + }); + + WordPress.wpDB.updateMediaUploadState(blogIdStr, mediaId, MediaUploadState.UPLOADING); + List<Object> apiArgs = new ArrayList<Object>(); + apiArgs.add(WordPress.getCurrentBlog()); + mCurrentUploadMediaTask.execute(apiArgs); + mHandler.post(mFetchQueueTask); + } + + private void fetchMediaFile(final String id) { + List<Object> apiArgs = new ArrayList<Object>(); + apiArgs.add(WordPress.getCurrentBlog()); + GetMediaItemTask task = new GetMediaItemTask(Integer.valueOf(id), + new ApiHelper.GetMediaItemTask.Callback() { + @Override + public void onSuccess(MediaFile mediaFile) { + String blogId = mediaFile.getBlogId(); + String mediaId = mediaFile.getMediaId(); + WordPress.wpDB.updateMediaUploadState(blogId, mediaId, MediaUploadState.UPLOADED); + mUploadInProgress = false; + mCurrentUploadMediaId = ""; + mHandler.post(mFetchQueueTask); + EventBus.getDefault().post(new MediaChanged(blogId, mediaId)); + } + + @Override + public void onFailure(ApiHelper.ErrorType errorType, String errorMessage, Throwable throwable) { + mUploadInProgress = false; + mCurrentUploadMediaId = ""; + mHandler.post(mFetchQueueTask); + // Only log the error if it's not caused by the network (internal inconsistency) + if (errorType != ErrorType.NETWORK_XMLRPC) { + CrashlyticsUtils.logException(throwable, ExceptionType.SPECIFIC, T.MEDIA, errorMessage); + } + } + }); + task.execute(apiArgs); + } +} |