From bdd34b94709236f75bf7bd7edb13088ff23320ea Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Mon, 26 Jan 2026 09:29:57 +0100 Subject: [PATCH 1/5] fix(auto-upload): handle uploaded files Signed-off-by: alperozturk96 --- .../jobs/autoUpload/AutoUploadEntityResult.kt | 18 +++++++++++ .../jobs/autoUpload/AutoUploadWorker.kt | 30 +++++++++++-------- 2 files changed, 35 insertions(+), 13 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadEntityResult.kt diff --git a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadEntityResult.kt b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadEntityResult.kt new file mode 100644 index 000000000000..41610b7f5617 --- /dev/null +++ b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadEntityResult.kt @@ -0,0 +1,18 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.nextcloud.client.jobs.autoUpload + +import com.nextcloud.client.database.entity.UploadEntity +import com.owncloud.android.db.OCUpload + +sealed class AutoUploadEntityResult { + data object SyncConflict : AutoUploadEntityResult() + data object CreationError : AutoUploadEntityResult() + data object Uploaded : AutoUploadEntityResult() + data class Success(val data: Pair) : AutoUploadEntityResult() +} diff --git a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt index db464ccfa11d..dcf6fa6dcc2c 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt @@ -16,7 +16,6 @@ import androidx.work.ForegroundInfo import androidx.work.WorkerParameters import com.nextcloud.client.account.User import com.nextcloud.client.account.UserAccountManager -import com.nextcloud.client.database.entity.UploadEntity import com.nextcloud.client.database.entity.toOCUpload import com.nextcloud.client.database.entity.toUploadEntity import com.nextcloud.client.device.PowerManagementService @@ -35,6 +34,7 @@ import com.owncloud.android.datamodel.SyncedFolderProvider import com.owncloud.android.datamodel.UploadsStorageManager import com.owncloud.android.db.OCUpload import com.owncloud.android.db.UploadResult +import com.owncloud.android.files.services.NameCollisionPolicy import com.owncloud.android.lib.common.OwnCloudAccount import com.owncloud.android.lib.common.OwnCloudClientManagerFactory import com.owncloud.android.lib.common.operations.RemoteOperationResult @@ -304,14 +304,14 @@ class AutoUploadWorker( ) try { - val result = createEntityAndUpload(user, localPath, remotePath) - if (result == null) { + val entityResult = getEntityResult(user, localPath, remotePath) + if (entityResult !is AutoUploadEntityResult.Success) { repository.markFileAsHandled(localPath, syncedFolder) - Log_OC.d(TAG, "Marked file as handled due to existing conflict: $localPath") + Log_OC.d(TAG, "marked file as handled: $localPath") continue } - var (uploadEntity, upload) = result + var (uploadEntity, upload) = entityResult.data // if local file deleted, upload process cannot be started or retriable thus needs to be removed if (path.isEmpty() || !file.exists()) { @@ -391,11 +391,7 @@ class AutoUploadWorker( } @Suppress("ReturnCount") - private fun createEntityAndUpload( - user: User, - localPath: String, - remotePath: String - ): Pair? { + private fun getEntityResult(user: User, localPath: String, remotePath: String): AutoUploadEntityResult { val (needsCharging, needsWifi, uploadAction) = getUploadSettings(syncedFolder) Log_OC.d(TAG, "creating oc upload for ${user.accountName}") @@ -409,14 +405,22 @@ class AutoUploadWorker( val lastUploadResult = uploadEntity?.lastResult?.let { UploadResult.fromValue(it) } if (lastUploadResult == UploadResult.SYNC_CONFLICT) { Log_OC.w(TAG, "Conflict already exists, skipping auto-upload: $localPath") - return null + return AutoUploadEntityResult.SyncConflict } val upload = try { uploadEntity?.toOCUpload(null) ?: OCUpload(localPath, remotePath, user.accountName) } catch (_: IllegalArgumentException) { Log_OC.e(TAG, "cannot construct oc upload") - return null + return AutoUploadEntityResult.CreationError + } + + // only valid for skip collision policy other scenarios will be handled in UploadFileOperation.java + if (upload.lastResult == UploadResult.UPLOADED && + syncedFolder.nameCollisionPolicy == NameCollisionPolicy.SKIP + ) { + Log_OC.d(TAG, "no need to create and process this entity file is already uploaded") + return AutoUploadEntityResult.Uploaded } upload.apply { @@ -433,7 +437,7 @@ class AutoUploadWorker( } } - return upload.toUploadEntity() to upload + return AutoUploadEntityResult.Success(upload.toUploadEntity() to upload) } private fun createUploadFileOperation(upload: OCUpload, user: User): UploadFileOperation = UploadFileOperation( From 33123616e49ec7f518a5c40b43e1612e2757dd33 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Mon, 26 Jan 2026 14:10:07 +0100 Subject: [PATCH 2/5] fix(auto-upload): handle upload results Signed-off-by: alperozturk96 --- .../jobs/autoUpload/AutoUploadEntityResult.kt | 2 +- .../jobs/autoUpload/AutoUploadWorker.kt | 11 +++++-- .../RemoteOperationResultExtensions.kt | 20 ------------ .../extensions/UploadResultExtensions.kt | 31 +++++++++++++++++++ 4 files changed, 40 insertions(+), 24 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/utils/extensions/UploadResultExtensions.kt diff --git a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadEntityResult.kt b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadEntityResult.kt index 41610b7f5617..99805cea1177 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadEntityResult.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadEntityResult.kt @@ -11,7 +11,7 @@ import com.nextcloud.client.database.entity.UploadEntity import com.owncloud.android.db.OCUpload sealed class AutoUploadEntityResult { - data object SyncConflict : AutoUploadEntityResult() + data object PermanentFailure : AutoUploadEntityResult() data object CreationError : AutoUploadEntityResult() data object Uploaded : AutoUploadEntityResult() data class Success(val data: Pair) : AutoUploadEntityResult() diff --git a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt index dcf6fa6dcc2c..fae1f636e002 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt @@ -24,6 +24,7 @@ import com.nextcloud.client.jobs.upload.FileUploadWorker import com.nextcloud.client.jobs.utils.UploadErrorNotificationManager import com.nextcloud.client.network.ConnectivityService import com.nextcloud.client.preferences.SubFolderRule +import com.nextcloud.utils.extensions.isPermanentFailure import com.nextcloud.utils.extensions.updateStatus import com.owncloud.android.R import com.owncloud.android.datamodel.ArbitraryDataProviderImpl @@ -403,9 +404,13 @@ class AutoUploadWorker( ) val lastUploadResult = uploadEntity?.lastResult?.let { UploadResult.fromValue(it) } - if (lastUploadResult == UploadResult.SYNC_CONFLICT) { - Log_OC.w(TAG, "Conflict already exists, skipping auto-upload: $localPath") - return AutoUploadEntityResult.SyncConflict + if (lastUploadResult?.isPermanentFailure() == true) { + Log_OC.w( + TAG, + "last upload failed with permanent failure, skipping auto-upload: $localPath," + + " failure: ${lastUploadResult.value}" + ) + return AutoUploadEntityResult.PermanentFailure } val upload = try { 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 caf0ad8b99f8..3a9f73c81f5f 100644 --- a/app/src/main/java/com/nextcloud/utils/extensions/RemoteOperationResultExtensions.kt +++ b/app/src/main/java/com/nextcloud/utils/extensions/RemoteOperationResultExtensions.kt @@ -9,13 +9,10 @@ package com.nextcloud.utils.extensions import com.owncloud.android.MainApp import com.owncloud.android.R -import com.owncloud.android.datamodel.OCFile import com.owncloud.android.lib.common.operations.RemoteOperation import com.owncloud.android.lib.common.operations.RemoteOperationResult import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode -import com.owncloud.android.lib.resources.files.model.RemoteFile import com.owncloud.android.utils.ErrorMessageAdapter -import com.owncloud.android.utils.FileStorageUtils @Suppress("ReturnCount") fun Pair?, RemoteOperation<*>?>?.getErrorMessage(): String { @@ -45,20 +42,3 @@ fun ResultCode.isFileSpecificError(): Boolean { return !errorCodes.contains(this) } - -@Suppress("Deprecation") -fun RemoteOperationResult<*>?.toOCFile(): List? = if (this?.isSuccess == true) { - data?.toOCFileList() -} else { - null -} - -private fun ArrayList.toOCFileList(): List = this.mapNotNull { - val remoteFile = (it as? RemoteFile) - - remoteFile?.let { - remoteFile.toOCFile() - } -} - -private fun RemoteFile?.toOCFile(): OCFile = FileStorageUtils.fillOCFile(this) diff --git a/app/src/main/java/com/nextcloud/utils/extensions/UploadResultExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/UploadResultExtensions.kt new file mode 100644 index 000000000000..a93892c0ce7a --- /dev/null +++ b/app/src/main/java/com/nextcloud/utils/extensions/UploadResultExtensions.kt @@ -0,0 +1,31 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.nextcloud.utils.extensions + +import com.owncloud.android.db.UploadResult + +fun UploadResult.isPermanentFailure(): Boolean = when (this) { + UploadResult.FILE_NOT_FOUND, + UploadResult.FILE_ERROR, + UploadResult.FOLDER_ERROR, + UploadResult.CANNOT_CREATE_FILE, + UploadResult.SYNC_CONFLICT, + UploadResult.LOCAL_STORAGE_NOT_COPIED, + UploadResult.VIRUS_DETECTED, + UploadResult.QUOTA_EXCEEDED, + UploadResult.SAME_FILE_CONFLICT, + UploadResult.PRIVILEGES_ERROR, + UploadResult.CREDENTIAL_ERROR, + UploadResult.UNKNOWN, + + // user's choice + UploadResult.CANCELLED -> true + + // Everything else may succeed after retry + else -> false +} From f96b4fde1cfeca59cf0d6fac9ac312d080f52c82 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Mon, 26 Jan 2026 14:47:44 +0100 Subject: [PATCH 3/5] fix(auto-upload): handle upload results Signed-off-by: alperozturk96 --- .../nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt | 7 +++---- .../nextcloud/utils/extensions/UploadResultExtensions.kt | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt index fae1f636e002..b81b87bd651c 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt @@ -24,7 +24,7 @@ import com.nextcloud.client.jobs.upload.FileUploadWorker import com.nextcloud.client.jobs.utils.UploadErrorNotificationManager import com.nextcloud.client.network.ConnectivityService import com.nextcloud.client.preferences.SubFolderRule -import com.nextcloud.utils.extensions.isPermanentFailure +import com.nextcloud.utils.extensions.isNonRetryable import com.nextcloud.utils.extensions.updateStatus import com.owncloud.android.R import com.owncloud.android.datamodel.ArbitraryDataProviderImpl @@ -404,11 +404,10 @@ class AutoUploadWorker( ) val lastUploadResult = uploadEntity?.lastResult?.let { UploadResult.fromValue(it) } - if (lastUploadResult?.isPermanentFailure() == true) { + if (lastUploadResult?.isNonRetryable() == true) { Log_OC.w( TAG, - "last upload failed with permanent failure, skipping auto-upload: $localPath," + - " failure: ${lastUploadResult.value}" + "last upload failed with ${lastUploadResult.value}, skipping auto-upload: $localPath" ) return AutoUploadEntityResult.PermanentFailure } diff --git a/app/src/main/java/com/nextcloud/utils/extensions/UploadResultExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/UploadResultExtensions.kt index a93892c0ce7a..510c45098502 100644 --- a/app/src/main/java/com/nextcloud/utils/extensions/UploadResultExtensions.kt +++ b/app/src/main/java/com/nextcloud/utils/extensions/UploadResultExtensions.kt @@ -9,7 +9,7 @@ package com.nextcloud.utils.extensions import com.owncloud.android.db.UploadResult -fun UploadResult.isPermanentFailure(): Boolean = when (this) { +fun UploadResult.isNonRetryable(): Boolean = when (this) { UploadResult.FILE_NOT_FOUND, UploadResult.FILE_ERROR, UploadResult.FOLDER_ERROR, @@ -26,6 +26,6 @@ fun UploadResult.isPermanentFailure(): Boolean = when (this) { // user's choice UploadResult.CANCELLED -> true - // Everything else may succeed after retry + // everything else may succeed after retry else -> false } From bae2f988c7c0cc289d9f00a21db3c0b83e3002a2 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Mon, 26 Jan 2026 14:48:12 +0100 Subject: [PATCH 4/5] fix(auto-upload): handle upload results Signed-off-by: alperozturk96 --- .../nextcloud/client/jobs/autoUpload/AutoUploadEntityResult.kt | 2 +- .../com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadEntityResult.kt b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadEntityResult.kt index 99805cea1177..bd6b8a208d5c 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadEntityResult.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadEntityResult.kt @@ -11,7 +11,7 @@ import com.nextcloud.client.database.entity.UploadEntity import com.owncloud.android.db.OCUpload sealed class AutoUploadEntityResult { - data object PermanentFailure : AutoUploadEntityResult() + data object NonRetryable : AutoUploadEntityResult() data object CreationError : AutoUploadEntityResult() data object Uploaded : AutoUploadEntityResult() data class Success(val data: Pair) : AutoUploadEntityResult() diff --git a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt index b81b87bd651c..603a98e6a0bd 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/autoUpload/AutoUploadWorker.kt @@ -409,7 +409,7 @@ class AutoUploadWorker( TAG, "last upload failed with ${lastUploadResult.value}, skipping auto-upload: $localPath" ) - return AutoUploadEntityResult.PermanentFailure + return AutoUploadEntityResult.NonRetryable } val upload = try { From 9e047e347661ddbac78dcf1c88b9e47130fd6482 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Mon, 26 Jan 2026 14:51:04 +0100 Subject: [PATCH 5/5] fix(auto-upload): handle upload results Signed-off-by: alperozturk96 --- .../com/nextcloud/utils/extensions/UploadResultExtensions.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/java/com/nextcloud/utils/extensions/UploadResultExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/UploadResultExtensions.kt index 510c45098502..9c1f1218e57a 100644 --- a/app/src/main/java/com/nextcloud/utils/extensions/UploadResultExtensions.kt +++ b/app/src/main/java/com/nextcloud/utils/extensions/UploadResultExtensions.kt @@ -21,6 +21,9 @@ fun UploadResult.isNonRetryable(): Boolean = when (this) { UploadResult.SAME_FILE_CONFLICT, UploadResult.PRIVILEGES_ERROR, UploadResult.CREDENTIAL_ERROR, + + // most cases covered and mapped from RemoteOperationResult. Most likely UploadResult.UNKNOWN this error will + // occur again UploadResult.UNKNOWN, // user's choice