From 2e6a2fe18bcc0c3a0451b0fe22841da161bc044f Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Thu, 10 Sep 2026 09:57:31 +0200 Subject: [PATCH] fix(upload): pause Signed-off-by: alperozturk96 --- .../jobs/upload/AlbumFileUploadWorker.kt | 8 +++++++ .../client/jobs/upload/FileUploadWorker.kt | 22 +++++++++++++---- .../RemoteOperationResultExtensions.kt | 2 ++ .../datamodel/UploadsStorageManager.kt | 24 ++++++------------- .../com/owncloud/android/db/UploadResult.java | 2 +- .../operations/UploadFileOperation.java | 11 +++++++++ .../android/ui/activity/UploadListActivity.kt | 11 ++++++++- 7 files changed, 56 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/com/nextcloud/client/jobs/upload/AlbumFileUploadWorker.kt b/app/src/main/java/com/nextcloud/client/jobs/upload/AlbumFileUploadWorker.kt index c34747698f3a..e36a8972c201 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/upload/AlbumFileUploadWorker.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/upload/AlbumFileUploadWorker.kt @@ -104,6 +104,14 @@ class AlbumFileUploadWorker( } } + fun pauseActiveUploads() { + activeOperations.values.forEach { + Log_OC.d(TAG, "upload operation is paused: ${it.remotePath}") + it.pause() + } + activeOperations.clear() + } + fun getCurrentUpload(id: Long?): UploadFileOperation? = activeOperations[id] fun isUploading(remotePath: String?, accountName: String?): Boolean = activeOperations.values.any { diff --git a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt index 541e48c4827d..e698f3850869 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadWorker.kt @@ -27,6 +27,7 @@ import com.nextcloud.client.preferences.AppPreferences import com.nextcloud.utils.ForegroundServiceHelper import com.nextcloud.utils.extensions.getPercent import com.nextcloud.utils.extensions.isNonRetryable +import com.nextcloud.utils.extensions.isUserCancellation import com.nextcloud.utils.extensions.toFile import com.owncloud.android.R import com.owncloud.android.datamodel.ForegroundServiceType @@ -119,6 +120,14 @@ class FileUploadWorker( } } + fun pauseActiveUploads() { + activeOperations.values.forEach { + Log_OC.d(TAG, "upload operation is paused: ${it.remotePath}") + it.pause() + } + activeOperations.clear() + } + fun getCurrentUpload(id: Long?): UploadFileOperation? = activeOperations[id] fun isUploading(remotePath: String?, accountName: String?): Boolean = activeOperations.values.any { @@ -290,6 +299,11 @@ class FileUploadWorker( continue } + if (isUploading(upload.remotePath, accountName)) { + Log_OC.d(TAG, "skipping upload, another worker is still transferring it: ${upload.remotePath}") + continue + } + delay(retryPolicy.getDelay().milliseconds) if (!skipAutoUploadCheck && isBelongToAnySyncedFolder(upload, syncFolderHelper, syncedFolders)) { @@ -339,11 +353,9 @@ class FileUploadWorker( break } - // check upload result for worker - val uploadResult = UploadResult.fromOperationResult(result) - if (!result.isSuccess) { + if (!result.isSuccess && !result.code.isUserCancellation()) { Log_OC.e(TAG, "upload failed for ${upload.remotePath}: ${result.code}") - if (uploadResult.isNonRetryable()) { + if (UploadResult.fromOperationResult(result).isNonRetryable()) { hasNonRetryableFailure = true } else { hasRetryableFailure = true @@ -368,7 +380,7 @@ class FileUploadWorker( } private fun skip(upload: OCUpload): Boolean = when (upload.uploadStatus) { - UploadStatus.UPLOAD_SUCCEEDED -> true + UploadStatus.UPLOAD_SUCCEEDED, UploadStatus.UPLOAD_CANCELLED -> true UploadStatus.UPLOAD_FAILED -> upload.lastResult.isNonRetryable() else -> false } diff --git a/app/src/main/java/com/nextcloud/utils/extensions/RemoteOperationResultExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/RemoteOperationResultExtensions.kt index e0e29b337031..ea7410b45149 100644 --- a/app/src/main/java/com/nextcloud/utils/extensions/RemoteOperationResultExtensions.kt +++ b/app/src/main/java/com/nextcloud/utils/extensions/RemoteOperationResultExtensions.kt @@ -44,6 +44,8 @@ fun ResultCode.isFileSpecificError(): Boolean { return !generalErrorCodes.contains(this) } +fun ResultCode.isUserCancellation(): Boolean = this == ResultCode.CANCELLED || this == ResultCode.USER_CANCELLED + fun ResultCode.isConflict(): Boolean { val errorCodes = listOf( ResultCode.SYNC_CONFLICT, diff --git a/app/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.kt b/app/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.kt index ef2ede218703..47e6d8adefbb 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.kt +++ b/app/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.kt @@ -31,6 +31,7 @@ import com.nextcloud.client.jobs.upload.FileUploadHelper import com.nextcloud.client.jobs.upload.FileUploadWorker import com.nextcloud.utils.autoRename.AutoRename import com.nextcloud.utils.extensions.isConflict +import com.nextcloud.utils.extensions.isUserCancellation import com.owncloud.android.MainApp import com.owncloud.android.db.OCUpload import com.owncloud.android.db.ProviderMeta.ProviderTableMeta @@ -211,19 +212,6 @@ class UploadsStorageManager( return result } - private fun removeUpload(accountName: String?, remotePath: String?): Int { - val result = contentResolver.delete( - ProviderTableMeta.CONTENT_URI_UPLOADS, - ProviderTableMeta.UPLOADS_ACCOUNT_NAME + "=? AND " + ProviderTableMeta.UPLOADS_REMOTE_PATH + "=?", - arrayOf(accountName, remotePath) - ) - Log_OC.d(TAG, "delete returns $result for file $remotePath in $accountName") - if (result > 0) { - notifyObserversNow() - } - return result - } - fun removeUploads(accountName: String?): Int { val result = contentResolver.delete( ProviderTableMeta.CONTENT_URI_UPLOADS, @@ -476,9 +464,8 @@ class UploadsStorageManager( fun updateDatabaseUploadResult(uploadResult: RemoteOperationResult<*>, upload: UploadFileOperation) { Log_OC.d(TAG, "updateDatabaseUploadResult uploadResult: $uploadResult upload: $upload") - if (uploadResult.isCancelled) { - Log_OC.w(TAG, "upload is cancelled, removing upload") - removeUpload(upload.user.accountName, upload.remotePath) + if (upload.isPaused) { + Log_OC.d(TAG, "upload is paused, keeping it queued: ${upload.remotePath}") return } @@ -492,7 +479,10 @@ class UploadsStorageManager( var result = UploadResult.fromOperationResult(uploadResult) val code = uploadResult.code - if (uploadResult.isSuccess) { + if (code.isUserCancellation()) { + status = UploadStatus.UPLOAD_CANCELLED + result = UploadResult.CANCELLED + } else if (uploadResult.isSuccess) { status = UploadStatus.UPLOAD_SUCCEEDED result = if (upload.wasSkipped()) UploadResult.SKIPPED else UploadResult.UPLOADED } else if (code.isConflict()) { diff --git a/app/src/main/java/com/owncloud/android/db/UploadResult.java b/app/src/main/java/com/owncloud/android/db/UploadResult.java index 074503c6bfd0..7c04b77daf62 100644 --- a/app/src/main/java/com/owncloud/android/db/UploadResult.java +++ b/app/src/main/java/com/owncloud/android/db/UploadResult.java @@ -103,7 +103,7 @@ public static UploadResult fromOperationResult(RemoteOperationResult result) { case OLD_ANDROID_API -> OLD_ANDROID_API; case SYNC_CONFLICT -> SYNC_CONFLICT; case FORBIDDEN -> PRIVILEGES_ERROR; - case CANCELLED -> CANCELLED; + case CANCELLED, USER_CANCELLED -> CANCELLED; case DELAYED_FOR_WIFI -> DELAYED_FOR_WIFI; case DELAYED_FOR_CHARGING -> DELAYED_FOR_CHARGING; case DELAYED_IN_POWER_SAVE_MODE -> DELAYED_IN_POWER_SAVE_MODE; diff --git a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java index d525f37de441..c71fe7d504a6 100644 --- a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java @@ -158,6 +158,7 @@ public class UploadFileOperation extends SyncOperation { private final AtomicBoolean mCancellationRequested = new AtomicBoolean(false); private final AtomicBoolean mUploadStarted = new AtomicBoolean(false); + private final AtomicBoolean mPaused = new AtomicBoolean(false); private Context mContext; @@ -448,6 +449,7 @@ protected RemoteOperationResult run(OwnCloudClient client) { } mCancellationRequested.set(false); + mPaused.set(false); mUploadStarted.set(true); updateSize(0); @@ -1591,6 +1593,15 @@ public void cancel(ResultCode cancellationReason) { } } + public void pause() { + mPaused.set(true); + cancel(ResultCode.USER_CANCELLED); + } + + public boolean isPaused() { + return mPaused.get(); + } + /** * As soon as this method return true, upload can be cancel via cancel(). */ diff --git a/app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.kt index 73734ad91819..fe119bc9fc57 100755 --- a/app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/UploadListActivity.kt @@ -24,8 +24,10 @@ import com.google.android.material.snackbar.Snackbar import com.nextcloud.client.account.User import com.nextcloud.client.core.Clock import com.nextcloud.client.device.PowerManagementService +import com.nextcloud.client.jobs.upload.AlbumFileUploadWorker import com.nextcloud.client.jobs.upload.FileUploadEventBroadcaster import com.nextcloud.client.jobs.upload.FileUploadHelper +import com.nextcloud.client.jobs.upload.FileUploadWorker import com.nextcloud.client.jobs.utils.UploadErrorNotificationManager import com.nextcloud.client.utils.Throttler import com.nextcloud.ui.component.UploadWarningCard @@ -237,8 +239,15 @@ class UploadListActivity : @SuppressLint("NotifyDataSetChanged") private fun toggleGlobalPause(item: MenuItem) { - preferences.setGlobalUploadPaused(!preferences.isGlobalUploadPaused()) + val paused = !preferences.isGlobalUploadPaused() + preferences.setGlobalUploadPaused(paused) updateGlobalPauseIcon(item) + + if (paused) { + FileUploadWorker.pauseActiveUploads() + AlbumFileUploadWorker.pauseActiveUploads() + } + val uploadHelper = FileUploadHelper.instance() accountManager.getAllUsers().filterNotNull().forEach { user -> val ids = uploadsStorageManager.getCurrentUploadIds(user.accountName)