Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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<String> fileNames,
boolean encrypted) {
private static RemoteFileExistence existsFile(OwnCloudClient client,
String remotePath,
List<String> 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));
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2026 Alper Ozturk <alper.ozturk@nextcloud.com>
* 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
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2026 Alper Ozturk <alper.ozturk@nextcloud.com>
* 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<Header>? = null

@Test
fun unauthorizedExistenceCheckIsNotReportedAsExistingFile() {
val result = RemoteOperationResult<Any?>(true, HttpStatus.SC_UNAUTHORIZED, "Unauthorized", noHeaders)

assertEquals(RemoteFileExistence.UNAUTHORIZED, RemoteFileExistence.fromExistenceCheck(result))
}

@Test
fun successfulExistenceCheckMeansFileExists() {
val result = RemoteOperationResult<Any?>(true, HttpStatus.SC_OK, "OK", noHeaders)

assertEquals(RemoteFileExistence.EXISTS, RemoteFileExistence.fromExistenceCheck(result))
}

@Test
fun notFoundExistenceCheckMeansFileDoesNotExist() {
val result = RemoteOperationResult<Any?>(false, HttpStatus.SC_NOT_FOUND, "Not Found", noHeaders)

assertEquals(RemoteFileExistence.DOES_NOT_EXIST, RemoteFileExistence.fromExistenceCheck(result))
}

@Test
fun credentialErrorStaysRetryable() {
val uploadResult = UploadResult.fromOperationResult(RemoteOperationResult<Any?>(ResultCode.UNAUTHORIZED))

assertEquals(UploadResult.CREDENTIAL_ERROR, uploadResult)
assertFalse(uploadResult.isNonRetryable())
}
}
Loading