diff --git a/src/com/owncloud/android/db/UploadDbObject.java b/src/com/owncloud/android/db/UploadDbObject.java index 9fce216f3d..d353fd7462 100755 --- a/src/com/owncloud/android/db/UploadDbObject.java +++ b/src/com/owncloud/android/db/UploadDbObject.java @@ -113,6 +113,10 @@ public class UploadDbObject implements Serializable { * Upload only via wifi? */ boolean isUseWifiOnly; + /** + * Upload only if phone being charged? + */ + boolean isWhileChargingOnly; /** * Name of Owncloud account to upload file to. */ @@ -270,4 +274,12 @@ public class UploadDbObject implements Serializable { return AccountUtils.getOwnCloudAccountByName(context, getAccountName()); } + public void setWhileChargingOnly(boolean isWhileChargingOnly) { + this.isWhileChargingOnly = isWhileChargingOnly; + } + + public boolean isWhileChargingOnly() { + return isWhileChargingOnly; + } + } diff --git a/src/com/owncloud/android/files/services/FileUploadService.java b/src/com/owncloud/android/files/services/FileUploadService.java index f210c5a21c..186c4383dd 100644 --- a/src/com/owncloud/android/files/services/FileUploadService.java +++ b/src/com/owncloud/android/files/services/FileUploadService.java @@ -78,6 +78,7 @@ import com.owncloud.android.ui.activity.FileActivity; import com.owncloud.android.ui.activity.FileDisplayActivity; import com.owncloud.android.ui.activity.UploadListActivity; import com.owncloud.android.utils.ErrorMessageAdapter; +import com.owncloud.android.utils.UploadUtils; import com.owncloud.android.utils.UriUtils; /** @@ -122,6 +123,7 @@ public class FileUploadService extends IntentService { public static final String KEY_FORCE_OVERWRITE = "KEY_FORCE_OVERWRITE"; public static final String KEY_CREATE_REMOTE_FOLDER = "CREATE_REMOTE_FOLDER"; public static final String KEY_WIFI_ONLY = "WIFI_ONLY"; + public static final String KEY_WHILE_CHARGING_ONLY = "KEY_WHILE_CHARGING_ONLY"; public static final String KEY_LOCAL_BEHAVIOUR = "BEHAVIOUR"; /** @@ -255,7 +257,7 @@ public class FileUploadService extends IntentService { mDb.updateUpload(uploadDbObject); } - //TODO This service can be instantiated at any time. Move this retry call to start of app. + //TODO This service can be instantiated at any time. Better move this retry call to start of app. if(InstantUploadBroadcastReceiver.isOnline(getApplicationContext())) { Log_OC.d(TAG, "FileUploadService.retry() called by onCreate()"); FileUploadService.retry(getApplicationContext()); @@ -289,24 +291,26 @@ public class FileUploadService extends IntentService { Log_OC.i(TAG, "mPendingUploads size:" + mPendingUploads.size() + " - before adding new uploads."); if (intent == null || intent.hasExtra(KEY_RETRY)) { Log_OC.d(TAG, "Receive null intent."); - // service was restarted by OS (after return START_STICKY and kill - // service) or connectivity change was detected. ==> check persistent upload - // list. - // + // service was restarted by OS or connectivity change was detected or + // retry of pending upload was requested. + // ==> First check persistent uploads, then perform upload. + int countAddedEntries = 0; List list = mDb.getAllPendingUploads(); for (UploadDbObject uploadDbObject : list) { // store locally. String uploadKey = buildRemoteName(uploadDbObject); - UploadDbObject previous = mPendingUploads.putIfAbsent(uploadKey, uploadDbObject); - + UploadDbObject previous = mPendingUploads.putIfAbsent(uploadKey, uploadDbObject); if(previous == null) { // and store persistently. uploadDbObject.setUploadStatus(UploadStatus.UPLOAD_LATER); mDb.updateUpload(uploadDbObject); + countAddedEntries++; } else { //upload already pending. ignore. } } + Log_OC.d(TAG, "added " + countAddedEntries + + " entrie(s) to mPendingUploads (this should be 0 except for the first time)."); } else { Log_OC.d(TAG, "Receive upload intent."); UploadSingleMulti uploadType = (UploadSingleMulti) intent.getSerializableExtra(KEY_UPLOAD_TYPE); @@ -372,6 +376,7 @@ public class FileUploadService extends IntentService { boolean forceOverwrite = intent.getBooleanExtra(KEY_FORCE_OVERWRITE, false); boolean isCreateRemoteFolder = intent.getBooleanExtra(KEY_CREATE_REMOTE_FOLDER, false); boolean isUseWifiOnly = intent.getBooleanExtra(KEY_WIFI_ONLY, true); + boolean isWhileChargingOnly = intent.getBooleanExtra(KEY_WHILE_CHARGING_ONLY, true); LocalBehaviour localAction = (LocalBehaviour) intent.getSerializableExtra(KEY_LOCAL_BEHAVIOUR); if (localAction == null) localAction = LocalBehaviour.LOCAL_BEHAVIOUR_COPY; @@ -385,6 +390,7 @@ public class FileUploadService extends IntentService { uploadObject.setCreateRemoteFolder(isCreateRemoteFolder); uploadObject.setLocalAction(localAction); uploadObject.setUseWifiOnly(isUseWifiOnly); + uploadObject.setWhileChargingOnly(isWhileChargingOnly); uploadObject.setUploadStatus(UploadStatus.UPLOAD_LATER); String uploadKey = buildRemoteName(uploadObject); @@ -402,27 +408,46 @@ public class FileUploadService extends IntentService { // upload already pending. ignore. } } - - // TODO check if would be clever to read entries from - // UploadDbHandler and add to requestedUploads at this point - } // at this point mPendingUploads is filled. Log_OC.i(TAG, "mPendingUploads size:" + mPendingUploads.size() + " - before uploading."); - try { - Iterator it = mPendingUploads.keySet().iterator(); - while (it.hasNext()) { - String up = it.next(); - Log_OC.d(TAG, "Calling uploadFile for " + up); - UploadDbObject uploadDbObject = mPendingUploads.get(up); - boolean uploadSuccessful = uploadFile(uploadDbObject); + Iterator it = mPendingUploads.keySet().iterator(); + while (it.hasNext()) { + String upload = it.next(); + UploadDbObject uploadDbObject = mPendingUploads.get(upload); + + switch (canUploadFileNow(uploadDbObject)) { + case NOW: + Log_OC.d(TAG, "Calling uploadFile for " + upload); + RemoteOperationResult uploadResult = uploadFile(uploadDbObject); + + updateDataseUploadResult(uploadResult, mCurrentUpload); + notifyUploadResult(uploadResult, mCurrentUpload); + sendFinalBroadcast(uploadResult, mCurrentUpload); + if (!shouldRetryFailedUpload(uploadResult)) { + Log_OC.d(TAG, "Upload with result " + uploadResult.getCode() + ": " + uploadResult.getLogMessage() + + " will be abandoned."); + mPendingUploads.remove(buildRemoteName(uploadDbObject)); + } + mCurrentUpload = null; + break; + case LATER: + //TODO schedule retry for later upload. + break; + case FILE_GONE: + mDb.updateUpload(uploadDbObject.getLocalPath(), UploadStatus.UPLOAD_FAILED_GIVE_UP, + new RemoteOperationResult(ResultCode.FILE_NOT_FOUND)); + it.remove(); + break; + case ERROR: + Log_OC.e(TAG, "canUploadFileNow() returned ERROR. Fix that!"); + break; } - } catch (ConcurrentModificationException e) { - // for now: ignore. TODO: fix this. } + Log_OC.i(TAG, "mPendingUploads size:" + mPendingUploads.size() + " - after uploading."); Log_OC.i(TAG, "onHandleIntent end"); } @@ -563,41 +588,70 @@ public class FileUploadService extends IntentService { } } } - + + enum CanUploadFileNowStatus {NOW, LATER, FILE_GONE, ERROR}; + /** - * Core upload method: sends the file(s) to upload. This function blocks - * until upload succeeded or failed. + * Returns true when the file may be uploaded now. This methods checks all + * restraints of the passed {@link UploadDbObject}, these include + * isUseWifiOnly(), check if local file exists, check if file was already + * uploaded... + * + * If return value is CanUploadFileNowStatus.NOW, uploadFile() may be + * called. + * + * @return CanUploadFileNowStatus.NOW is upload may proceed,
+ * CanUploadFileNowStatus.LATER if upload should be performed at a + * later time,
+ * CanUploadFileNowStatus.ERROR if a severe error happened, calling + * entity should remove upload from queue. * - * @param uploadDbObject Key to access the upload to perform, contained in - * mPendingUploads - * @return true on success. */ - private boolean uploadFile(UploadDbObject uploadDbObject) { - - if(uploadDbObject.getUploadStatus() == UploadStatus.UPLOAD_SUCCEEDED) { - Log_OC.w(TAG, "Already succeeded uploadObject was again scheduled for upload. Fix that!"); - return false; - } + private CanUploadFileNowStatus canUploadFileNow(UploadDbObject uploadDbObject) { - if (mCurrentUpload != null) { - Log_OC.e(TAG, - "mCurrentUpload != null. Meaning there is another upload in progress, cannot start a new one. Fix that!"); - return false; + if (uploadDbObject.getUploadStatus() == UploadStatus.UPLOAD_SUCCEEDED) { + Log_OC.w(TAG, "Already succeeded uploadObject was again scheduled for upload. Fix that!"); + return CanUploadFileNowStatus.ERROR; } if (uploadDbObject.isUseWifiOnly() && !InstantUploadBroadcastReceiver.isConnectedViaWiFi(getApplicationContext())) { Log_OC.d(TAG, "Do not start upload because it is wifi-only."); - return false; + return CanUploadFileNowStatus.LATER; + } + + if(uploadDbObject.isWhileChargingOnly() && !UploadUtils.isCharging(getApplicationContext())) { + Log_OC.d(TAG, "Do not start upload because it is while charging only."); + return CanUploadFileNowStatus.LATER; } if (!new File(uploadDbObject.getLocalPath()).exists()) { - mDb.updateUpload(uploadDbObject.getLocalPath(), UploadStatus.UPLOAD_FAILED_GIVE_UP, - new RemoteOperationResult(ResultCode.FILE_NOT_FOUND)); Log_OC.d(TAG, "Do not start upload because local file does not exist."); - return false; + return CanUploadFileNowStatus.FILE_GONE; } + return CanUploadFileNowStatus.NOW; + } + + + /** + * Core upload method: sends the file(s) to upload. This function blocks + * until upload succeeded or failed. + * + * Before uploading starts mCurrentUpload is set. + * + * @param uploadDbObject Key to access the upload to perform, contained in + * mPendingUploads + * @return RemoteOperationResult of upload operation. + */ + private RemoteOperationResult uploadFile(UploadDbObject uploadDbObject) { + + if (mCurrentUpload != null) { + Log_OC.e(TAG, + "mCurrentUpload != null. Meaning there is another upload in progress, cannot start a new one. Fix that!"); + return new RemoteOperationResult(new IllegalStateException("mCurrentUpload != null when calling uploadFile()")); + } + AccountManager aMgr = AccountManager.get(this); Account account = uploadDbObject.getAccount(getApplicationContext()); String version = aMgr.getUserData(account, Constants.KEY_OC_VERSION); @@ -615,7 +669,7 @@ public class FileUploadService extends IntentService { } mCurrentUpload.addDatatransferProgressListener((FileUploaderBinder) mBinder); - notifyUploadStart(mCurrentUpload); + notifyUploadStart(mCurrentUpload); RemoteOperationResult uploadResult = null, grantResult = null; try { @@ -656,7 +710,6 @@ public class FileUploadService extends IntentService { uploadResult = new RemoteOperationResult(e); } finally { - mPendingUploads.remove(buildRemoteName(uploadDbObject)); Log_OC.i(TAG, "Remove CurrentUploadItem from pending upload Item Map."); if (uploadResult.isException()) { // enforce the creation of a new client object for next @@ -666,14 +719,7 @@ public class FileUploadService extends IntentService { mUploadClient = null; } } - - // notify result - notifyUploadResult(uploadResult, mCurrentUpload); - sendFinalBroadcast(mCurrentUpload, uploadResult); - - mCurrentUpload = null; - - return uploadResult.isSuccess(); + return uploadResult; } @@ -846,7 +892,14 @@ public class FileUploadService extends IntentService { mNotificationManager.notify(R.string.uploader_upload_in_progress_ticker, mNotificationBuilder.build()); - mDb.updateUpload(upload.getOriginalStoragePath(), UploadStatus.UPLOAD_IN_PROGRESS, null); + updateDatabaseUploadStart(mCurrentUpload); + } + + /** + * Updates the persistent upload database that upload is in progress. + */ + private void updateDatabaseUploadStart(UploadFileOperation upload) { + mDb.updateUpload(upload.getOriginalStoragePath(), UploadStatus.UPLOAD_IN_PROGRESS, null); } /** @@ -910,34 +963,61 @@ public class FileUploadService extends IntentService { if (uploadResult.isSuccess()) { - mDb.updateUpload(upload.getOriginalStoragePath(), UploadStatus.UPLOAD_SUCCEEDED, uploadResult); - - // remove success notification, with a delay of 2 seconds + // remove success notification, with a delay of 2 seconds NotificationDelayer.cancelWithDelay(mNotificationManager, R.string.uploader_upload_succeeded_ticker, 2000); + } + } + } + + /** + * Updates the persistent upload database with upload result. + */ + private void updateDataseUploadResult(RemoteOperationResult uploadResult, UploadFileOperation upload) { + // result: success or fail notification + if (uploadResult.isCancelled()) { + mDb.updateUpload(upload.getOriginalStoragePath(), UploadStatus.UPLOAD_CANCELLED, uploadResult); + } else { + + if (uploadResult.isSuccess()) { + mDb.updateUpload(upload.getOriginalStoragePath(), UploadStatus.UPLOAD_SUCCEEDED, uploadResult); } else { - // TODO: add other cases in which upload attempt is to be - // abandoned. - if (uploadResult.getCode() == ResultCode.QUOTA_EXCEEDED) { + if (shouldRetryFailedUpload(uploadResult)) { + mDb.updateUpload(upload.getOriginalStoragePath(), UploadStatus.UPLOAD_FAILED_RETRY, uploadResult); + } else { mDb.updateUpload(upload.getOriginalStoragePath(), UploadDbHandler.UploadStatus.UPLOAD_FAILED_GIVE_UP, uploadResult); - } else { - mDb.updateUpload(upload.getOriginalStoragePath(), UploadStatus.UPLOAD_FAILED_RETRY, uploadResult); } } - } else { - mDb.updateUpload(upload.getOriginalStoragePath(), UploadStatus.UPLOAD_FAILED_RETRY, uploadResult); + } + } + + /** + * Determines whether with given uploadResult the upload should be retried later. + * @param uploadResult + * @return true if upload should be retried later, false if is should be abandoned. + */ + private boolean shouldRetryFailedUpload(RemoteOperationResult uploadResult) { + if (uploadResult.isSuccess()) { + return false; + } + switch (uploadResult.getCode()) { + case HOST_NOT_AVAILABLE: + case NO_NETWORK_CONNECTION: + case TIMEOUT: + return true; + default: + return false; } } /** * Sends a broadcast in order to the interested activities can update their * view - * - * @param upload Finished upload operation * @param uploadResult Result of the upload operation + * @param upload Finished upload operation */ - private void sendFinalBroadcast(UploadFileOperation upload, RemoteOperationResult uploadResult) { + private void sendFinalBroadcast(RemoteOperationResult uploadResult, UploadFileOperation upload) { Intent end = new Intent(getUploadFinishMessage()); end.putExtra(EXTRA_REMOTE_PATH, upload.getRemotePath()); // real remote // path, after diff --git a/src/com/owncloud/android/utils/UploadUtils.java b/src/com/owncloud/android/utils/UploadUtils.java new file mode 100755 index 0000000000..d7de06caf1 --- /dev/null +++ b/src/com/owncloud/android/utils/UploadUtils.java @@ -0,0 +1,18 @@ +package com.owncloud.android.utils; + +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.BatteryManager; + + +public class UploadUtils { + + public static boolean isCharging(Context context) { + IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED); + Intent batteryStatus = context.registerReceiver(null, ifilter); + + int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1); + return status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL; + } +}