From e2f43cf440055480adfa0753a6cae99c2d57e5ec Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Mon, 23 Mar 2026 09:51:42 +0100 Subject: [PATCH 1/3] fix(file-upload-worker): pausing upload Signed-off-by: alperozturk96 --- .../upload/FileUploadBroadcastReceiver.kt | 2 +- .../client/jobs/upload/FileUploadHelper.kt | 5 +-- .../client/jobs/upload/FileUploadWorker.kt | 39 +++++++++++-------- .../jobs/upload/UploadNotificationManager.kt | 1 + .../utils/UploadErrorNotificationManager.kt | 1 - .../android/ui/adapter/UploadListAdapter.java | 4 +- .../ui/helpers/FileOperationsHelper.java | 2 +- 7 files changed, 30 insertions(+), 24 deletions(-) diff --git a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadBroadcastReceiver.kt b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadBroadcastReceiver.kt index 6512aecbae39..0e5fcf90e489 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadBroadcastReceiver.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadBroadcastReceiver.kt @@ -57,7 +57,7 @@ class FileUploadBroadcastReceiver : BroadcastReceiver() { val remove = intent.getBooleanExtra(REMOVE, false) - FileUploadWorker.cancelCurrentUpload(remotePath, accountName, onCompleted = {}) + FileUploadWorker.cancelUpload(remotePath, accountName) if (remove) { uploadsStorageManager.removeUpload(uploadId) diff --git a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt index 514c67811d76..634c60827d1c 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/upload/FileUploadHelper.kt @@ -19,7 +19,6 @@ import com.nextcloud.client.database.entity.toUploadEntity import com.nextcloud.client.device.BatteryStatus import com.nextcloud.client.device.PowerManagementService import com.nextcloud.client.jobs.BackgroundJobManager -import com.nextcloud.client.jobs.upload.FileUploadWorker.Companion.currentUploadFileOperation import com.nextcloud.client.network.Connectivity import com.nextcloud.client.network.ConnectivityService import com.nextcloud.client.notifications.AppWideNotificationManager @@ -440,7 +439,7 @@ class FileUploadHelper { @Suppress("ReturnCount") fun isUploadingNow(upload: OCUpload?): Boolean { - val currentUploadFileOperation = currentUploadFileOperation + val currentUploadFileOperation = FileUploadWorker.getCurrentUpload(upload?.uploadId) if (currentUploadFileOperation == null || currentUploadFileOperation.user == null) return false if (upload == null || upload.accountName != currentUploadFileOperation.user.accountName) return false @@ -610,7 +609,7 @@ class FileUploadHelper { return } - FileUploadWorker.cancelCurrentUpload(remotePath, accountName, onCompleted = { + FileUploadWorker.cancelUpload(remotePath, accountName, onCompleted = { instance().updateUploadStatus(remotePath, accountName, UploadStatus.UPLOAD_CANCELLED) }) } 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 d2b9b4d7d077..7196ec31051b 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 @@ -45,6 +45,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ensureActive import kotlinx.coroutines.withContext import java.io.File +import java.util.concurrent.ConcurrentHashMap import kotlin.random.Random @Suppress("LongParameterList", "TooGenericExceptionCaught") @@ -73,8 +74,6 @@ class FileUploadWorker( const val TOTAL_UPLOAD_SIZE = "total_upload_size" const val SHOW_SAME_FILE_ALREADY_EXISTS_NOTIFICATION = "show_same_file_already_exists_notification" - var currentUploadFileOperation: UploadFileOperation? = null - private const val BATCH_SIZE = 100 const val EXTRA_ACCOUNT_NAME = "ACCOUNT_NAME" @@ -84,21 +83,25 @@ class FileUploadWorker( const val LOCAL_BEHAVIOUR_FORGET = 2 const val LOCAL_BEHAVIOUR_DELETE = 3 - fun cancelCurrentUpload(remotePath: String, accountName: String, onCompleted: () -> Unit) { - currentUploadFileOperation?.let { - if (it.remotePath == remotePath && it.user.accountName == accountName) { - it.cancel(ResultCode.USER_CANCELLED) - onCompleted() - } + private val activeOperations = ConcurrentHashMap() + + @JvmOverloads + fun cancelUpload(remotePath: String?, accountName: String?, onCompleted: () -> Unit = {}) { + val operation = + activeOperations.values.find { it.remotePath == remotePath && it.user.accountName == accountName } + + operation?.let { + operation.cancel(ResultCode.USER_CANCELLED) + activeOperations.remove(operation.ocUploadId) } + + onCompleted() } - fun isUploading(remotePath: String?, accountName: String?): Boolean { - currentUploadFileOperation?.let { - return it.remotePath == remotePath && it.user.accountName == accountName - } + fun getCurrentUpload(id: Long?): UploadFileOperation? = activeOperations[id] - return false + fun isUploading(remotePath: String?, accountName: String?): Boolean = activeOperations.values.any { + it.remotePath == remotePath && it.user.accountName == accountName } fun getUploadAction(action: String): Int = when (action) { @@ -128,7 +131,8 @@ class FileUploadWorker( result } catch (t: Throwable) { Log_OC.e(TAG, "exception $t") - currentUploadFileOperation?.cancel(null) + activeOperations.values.forEach { it.cancel(null) } + activeOperations.clear() Result.failure() } finally { // Ensure all database operations are complete before signaling completion @@ -238,7 +242,7 @@ class FileUploadWorker( fileUploadEventBroadcaster.sendUploadEnqueued(context) val operation = createUploadFileOperation(upload, user) - currentUploadFileOperation = operation + activeOperations[upload.uploadId] = operation val currentIndex = (index + 1) val currentUploadIndex = (currentIndex + previouslyUploadedFileSize) @@ -252,7 +256,7 @@ class FileUploadWorker( val result = withContext(Dispatchers.IO) { upload(upload, operation, user, client) } - currentUploadFileOperation = null + activeOperations.remove(upload.uploadId) if (result.code == ResultCode.QUOTA_EXCEEDED) { Log_OC.w(TAG, "Quota exceeded, stopping uploads") @@ -389,6 +393,9 @@ class FileUploadWorker( if (percent != lastPercent && (currentTime - lastUpdateTime) >= minProgressUpdateInterval) { notificationManager.run { + val currentUploadFileOperation = + activeOperations.values.find { it.originalStoragePath == fileAbsoluteName } + val accountName = currentUploadFileOperation?.user?.accountName val remotePath = currentUploadFileOperation?.remotePath diff --git a/app/src/main/java/com/nextcloud/client/jobs/upload/UploadNotificationManager.kt b/app/src/main/java/com/nextcloud/client/jobs/upload/UploadNotificationManager.kt index a64ddd5ebe2a..30f0ace023c2 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/upload/UploadNotificationManager.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/upload/UploadNotificationManager.kt @@ -106,6 +106,7 @@ class UploadNotificationManager(private val context: Context, viewThemeUtils: Vi notificationManager.cancel(getId()) notificationBuilder.run { + clearActions() setContentTitle(context.getString(R.string.file_upload_worker_error_notification_title)) setContentText("") } diff --git a/app/src/main/java/com/nextcloud/client/jobs/utils/UploadErrorNotificationManager.kt b/app/src/main/java/com/nextcloud/client/jobs/utils/UploadErrorNotificationManager.kt index 8120231d2ea6..8206e345e286 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/utils/UploadErrorNotificationManager.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/utils/UploadErrorNotificationManager.kt @@ -131,7 +131,6 @@ object UploadErrorNotificationManager { ) // actions for all error types - addAction(UploadBroadcastAction.PauseAndCancel(operation).pauseAction(context)) addAction(UploadBroadcastAction.PauseAndCancel(operation).cancelAction(context)) if (result.code == ResultCode.SYNC_CONFLICT) { diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.java b/app/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.java index 645acf3d94d1..57c05af6036e 100755 --- a/app/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.java +++ b/app/src/main/java/com/owncloud/android/ui/adapter/UploadListAdapter.java @@ -186,7 +186,7 @@ public void onBindHeaderViewHolder(SectionedViewHolder holder, int section, bool uploadHelper.updateUploadStatus(upload.getRemotePath(), accountName, UploadStatus.UPLOAD_CANCELLED, new Function0() { @Override public Unit invoke() { - FileUploadWorker.Companion.cancelCurrentUpload(upload.getRemotePath(), accountName, new Function0() { + FileUploadWorker.Companion.cancelUpload(upload.getRemotePath(), accountName, new Function0() { @Override public Unit invoke() { completedCount[0]++; @@ -428,7 +428,7 @@ public void onBindViewHolder(SectionedViewHolder holder, int section, int relati itemViewHolder.binding.uploadRightButton.setVisibility(View.VISIBLE); itemViewHolder.binding.uploadRightButton.setOnClickListener(v -> { uploadHelper.updateUploadStatus(item.getRemotePath(), item.getAccountName(), UploadStatus.UPLOAD_CANCELLED, () -> { - FileUploadWorker.Companion.cancelCurrentUpload(item.getRemotePath(), item.getAccountName(), () -> { + FileUploadWorker.Companion.cancelUpload(item.getRemotePath(), item.getAccountName(), () -> { loadUploadItemsFromDb(() -> {}); return Unit.INSTANCE; }); diff --git a/app/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java b/app/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java index e5a3824db421..fa9e575e802f 100755 --- a/app/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java +++ b/app/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java @@ -1043,7 +1043,7 @@ public void cancelTransference(OCFile file) { final var fileUploadHelper = FileUploadHelper.Companion.instance(); if (fileUploadHelper.isUploading(file.getRemotePath(), currentUser.getAccountName())) { - FileUploadWorker.Companion.cancelCurrentUpload(file.getRemotePath(), currentUser.getAccountName(), () -> { + FileUploadWorker.Companion.cancelUpload(file.getRemotePath(), currentUser.getAccountName(), () -> { fileUploadHelper.updateUploadStatus(file.getRemotePath(), currentUser.getAccountName(), UploadsStorageManager.UploadStatus.UPLOAD_CANCELLED); return Unit.INSTANCE; }); From 06647d2f86d05f86233c18b70bf01662c962fb94 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Mon, 23 Mar 2026 13:03:01 +0100 Subject: [PATCH 2/3] fix(file-upload-worker): pausing upload Signed-off-by: alperozturk96 --- .../client/jobs/upload/FileUploadWorker.kt | 33 ++++++++++--------- .../utils/UploadErrorNotificationManager.kt | 6 ++-- 2 files changed, 21 insertions(+), 18 deletions(-) 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 7196ec31051b..998352e3cc1b 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 @@ -91,6 +91,7 @@ class FileUploadWorker( activeOperations.values.find { it.remotePath == remotePath && it.user.accountName == accountName } operation?.let { + Log_OC.d(TAG, "upload operation is cancelled: $remotePath") operation.cancel(ResultCode.USER_CANCELLED) activeOperations.remove(operation.ocUploadId) } @@ -351,24 +352,24 @@ class FileUploadWorker( } } result = RemoteOperationResult(e) - } finally { - if (!isStopped) { - UploadErrorNotificationManager.handleResult( - context, - notificationManager, - operation, - result, - onSameFileConflict = { - withContext(Dispatchers.Main) { - val showSameFileAlreadyExistsNotification = - inputData.getBoolean(SHOW_SAME_FILE_ALREADY_EXISTS_NOTIFICATION, false) - if (showSameFileAlreadyExistsNotification) { - notificationManager.showSameFileAlreadyExistsNotification(operation.fileName) - } + } + + if (!isStopped) { + UploadErrorNotificationManager.handleResult( + context, + notificationManager, + operation, + result, + onSameFileConflict = { + withContext(Dispatchers.Main) { + val showSameFileAlreadyExistsNotification = + inputData.getBoolean(SHOW_SAME_FILE_ALREADY_EXISTS_NOTIFICATION, false) + if (showSameFileAlreadyExistsNotification) { + notificationManager.showSameFileAlreadyExistsNotification(operation.fileName) } } - ) - } + } + ) } return@withContext result diff --git a/app/src/main/java/com/nextcloud/client/jobs/utils/UploadErrorNotificationManager.kt b/app/src/main/java/com/nextcloud/client/jobs/utils/UploadErrorNotificationManager.kt index 8206e345e286..c6289567d9fb 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/utils/UploadErrorNotificationManager.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/utils/UploadErrorNotificationManager.kt @@ -49,7 +49,7 @@ object UploadErrorNotificationManager { ) { Log_OC.d(TAG, "handle upload result with result code: " + result.code) - if (result.isSuccess || result.isCancelled || operation.isMissingPermissionThrown) { + if (result.isSuccess || operation.isMissingPermissionThrown) { Log_OC.d(TAG, "operation is successful, cancelled or lack of storage permission, notification skipped") return } @@ -59,7 +59,9 @@ object UploadErrorNotificationManager { ResultCode.DELAYED_FOR_CHARGING, ResultCode.DELAYED_IN_POWER_SAVE_MODE, ResultCode.LOCAL_FILE_NOT_FOUND, - ResultCode.LOCK_FAILED + ResultCode.LOCK_FAILED, + ResultCode.CANCELLED, + ResultCode.USER_CANCELLED ) if (result.code in silentCodes) { From f28695e30b56d89aba736a231ac0b41206882c8c Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Mon, 23 Mar 2026 13:19:02 +0100 Subject: [PATCH 3/3] fix(file-upload-worker): pausing upload Signed-off-by: alperozturk96 --- .../com/nextcloud/client/jobs/upload/UploadBroadcastAction.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/nextcloud/client/jobs/upload/UploadBroadcastAction.kt b/app/src/main/java/com/nextcloud/client/jobs/upload/UploadBroadcastAction.kt index 37b4a508b42f..e502484e4144 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/upload/UploadBroadcastAction.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/upload/UploadBroadcastAction.kt @@ -54,7 +54,7 @@ sealed class UploadBroadcastAction { context, requestCode, intent, - PendingIntent.FLAG_IMMUTABLE + PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT ) } }