From 140687dc88544df0187a17616ac0cc776c5ac3ae Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Tue, 28 Jul 2026 10:29:20 +0200 Subject: [PATCH] wip Signed-off-by: alperozturk96 --- .../jobs/autoUpload/AutoUploadWorker.kt | 6 +++ .../client/jobs/upload/FileUploadWorker.kt | 5 ++ .../extensions/UploadResultExtensions.kt | 1 - .../operations/UploadFileOperation.java | 36 +++++++------ .../operations/upload/RemoteFileExistence.kt | 26 ++++++++++ .../upload/RemoteFileExistenceTest.kt | 52 +++++++++++++++++++ 6 files changed, 109 insertions(+), 17 deletions(-) create mode 100644 app/src/main/java/com/owncloud/android/operations/upload/RemoteFileExistence.kt create mode 100644 app/src/test/java/com/owncloud/android/operations/upload/RemoteFileExistenceTest.kt 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 ff897db233d6..9531d34db80c 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 @@ -40,6 +40,7 @@ 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 +import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode import com.owncloud.android.lib.common.utils.Log_OC import com.owncloud.android.lib.resources.status.OCCapability import com.owncloud.android.operations.UploadFileOperation @@ -330,6 +331,11 @@ class AutoUploadWorker( "❌ upload failed $localPath (${upload.accountName}): ${result.logMessage}" ) + if (result.code == ResultCode.UNAUTHORIZED) { + Log_OC.e(TAG, "🔑 credentials are no longer valid, stopping auto upload") + return@withContext + } + // Mark CONFLICT files as handled to prevent retries if (result.code.isConflict()) { repository.markFileAsHandled(localPath, syncedFolder) 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 02fd23b8c166..ca8c9144e351 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 @@ -327,6 +327,11 @@ class FileUploadWorker( } sendUploadFinishEvent(totalUploadSize, currentUploadIndex, operation, result) + + if (result.code == ResultCode.UNAUTHORIZED) { + Log_OC.e(TAG, "credentials are no longer valid, stopping uploads") + break + } } return@withContext uploadFilesResult.toWorkerResult() 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 4ca297751522..005198f5f4e7 100644 --- a/app/src/main/java/com/nextcloud/utils/extensions/UploadResultExtensions.kt +++ b/app/src/main/java/com/nextcloud/utils/extensions/UploadResultExtensions.kt @@ -23,7 +23,6 @@ fun UploadResult.isNonRetryable(): Boolean = when (this) { UploadResult.VIRUS_DETECTED, UploadResult.QUOTA_EXCEEDED, UploadResult.PRIVILEGES_ERROR, - UploadResult.CREDENTIAL_ERROR, // most cases covered and mapped from RemoteOperationResult. Most likely UploadResult.UNKNOWN this error will // occur again 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 dd7f88d33588..be27f80f040c 100644 --- a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java @@ -60,6 +60,7 @@ import com.owncloud.android.operations.e2e.E2EClientData; import com.owncloud.android.operations.e2e.E2EData; import com.owncloud.android.operations.e2e.E2EFiles; +import com.owncloud.android.operations.upload.RemoteFileExistence; import com.owncloud.android.operations.upload.UploadFileException; import com.owncloud.android.operations.upload.UploadFileOperationExtensionsKt; import com.owncloud.android.utils.EncryptionUtils; @@ -1273,9 +1274,14 @@ private RemoteOperationResult checkNameCollision(OCFile parentFile, throws OperationCancelledException { Log_OC.d(TAG, "Checking name collision in server"); - boolean isFileExists = existsFile(client, mRemotePath, fileNames, encrypted); + final var existence = existsFile(client, mRemotePath, fileNames, encrypted); - if (isFileExists) { + if (existence == RemoteFileExistence.UNAUTHORIZED) { + Log_OC.e(TAG, "existence check answered with unauthorized, credentials are no longer valid"); + return new RemoteOperationResult<>(ResultCode.UNAUTHORIZED); + } + + if (existence == RemoteFileExistence.EXISTS) { switch (mNameCollisionPolicy) { case SKIP: Log_OC.d(TAG, "user choose to skip upload if same file exists"); @@ -1513,39 +1519,37 @@ public static String getNewAvailableRemotePath(OwnCloudClient client, } int count = 2; - boolean exists; + RemoteFileExistence existence; String newPath; - // FIXME: Causing infinite loop during tests due to ExistenceCheckRemoteOperation result do { suffix = " (" + count + ")"; newPath = extPos >= 0 ? remotePathWithoutExtension + suffix + "." + extension : remotePath + suffix; - exists = existsFile(client, newPath, fileNames, encrypted); + existence = existsFile(client, newPath, fileNames, encrypted); count++; - } while (exists); + } while (existence == RemoteFileExistence.EXISTS); return newPath; } - private static boolean existsFile(OwnCloudClient client, - String remotePath, - List fileNames, - boolean encrypted) { + private static RemoteFileExistence existsFile(OwnCloudClient client, + String remotePath, + List fileNames, + boolean encrypted) { if (encrypted) { String fileName = new File(remotePath).getName(); for (String name : fileNames) { if (name.equalsIgnoreCase(fileName)) { - return true; + return RemoteFileExistence.EXISTS; } } - return false; - } else { - ExistenceCheckRemoteOperation existsOperation = new ExistenceCheckRemoteOperation(remotePath, false); - final var result = existsOperation.execute(client); - return result.isSuccess(); + return RemoteFileExistence.DOES_NOT_EXIST; } + + ExistenceCheckRemoteOperation existsOperation = new ExistenceCheckRemoteOperation(remotePath, false); + return RemoteFileExistence.fromExistenceCheck(existsOperation.execute(client)); } /** diff --git a/app/src/main/java/com/owncloud/android/operations/upload/RemoteFileExistence.kt b/app/src/main/java/com/owncloud/android/operations/upload/RemoteFileExistence.kt new file mode 100644 index 000000000000..79a023da7831 --- /dev/null +++ b/app/src/main/java/com/owncloud/android/operations/upload/RemoteFileExistence.kt @@ -0,0 +1,26 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.owncloud.android.operations.upload + +import com.owncloud.android.lib.common.operations.RemoteOperationResult +import org.apache.commons.httpclient.HttpStatus + +enum class RemoteFileExistence { + EXISTS, + DOES_NOT_EXIST, + UNAUTHORIZED; + + companion object { + @JvmStatic + fun fromExistenceCheck(result: RemoteOperationResult<*>): RemoteFileExistence = when { + result.httpCode == HttpStatus.SC_UNAUTHORIZED -> UNAUTHORIZED + result.isSuccess -> EXISTS + else -> DOES_NOT_EXIST + } + } +} diff --git a/app/src/test/java/com/owncloud/android/operations/upload/RemoteFileExistenceTest.kt b/app/src/test/java/com/owncloud/android/operations/upload/RemoteFileExistenceTest.kt new file mode 100644 index 000000000000..1720c01b835f --- /dev/null +++ b/app/src/test/java/com/owncloud/android/operations/upload/RemoteFileExistenceTest.kt @@ -0,0 +1,52 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.owncloud.android.operations.upload + +import com.nextcloud.utils.extensions.isNonRetryable +import com.owncloud.android.db.UploadResult +import com.owncloud.android.lib.common.operations.RemoteOperationResult +import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode +import org.apache.commons.httpclient.Header +import org.apache.commons.httpclient.HttpStatus +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Test + +class RemoteFileExistenceTest { + + private val noHeaders: Array
? = null + + @Test + fun unauthorizedExistenceCheckIsNotReportedAsExistingFile() { + val result = RemoteOperationResult(true, HttpStatus.SC_UNAUTHORIZED, "Unauthorized", noHeaders) + + assertEquals(RemoteFileExistence.UNAUTHORIZED, RemoteFileExistence.fromExistenceCheck(result)) + } + + @Test + fun successfulExistenceCheckMeansFileExists() { + val result = RemoteOperationResult(true, HttpStatus.SC_OK, "OK", noHeaders) + + assertEquals(RemoteFileExistence.EXISTS, RemoteFileExistence.fromExistenceCheck(result)) + } + + @Test + fun notFoundExistenceCheckMeansFileDoesNotExist() { + val result = RemoteOperationResult(false, HttpStatus.SC_NOT_FOUND, "Not Found", noHeaders) + + assertEquals(RemoteFileExistence.DOES_NOT_EXIST, RemoteFileExistence.fromExistenceCheck(result)) + } + + @Test + fun credentialErrorStaysRetryable() { + val uploadResult = UploadResult.fromOperationResult(RemoteOperationResult(ResultCode.UNAUTHORIZED)) + + assertEquals(UploadResult.CREDENTIAL_ERROR, uploadResult) + assertFalse(uploadResult.isNonRetryable()) + } +}