From 302f949e3314a954042c6f40197a4f8acfbd04aa Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 10 Jul 2026 10:24:56 +0200 Subject: [PATCH 1/2] fix(upload-list): distinguish between skip and complete Signed-off-by: alperozturk96 --- .../jobs/autoUpload/AutoUploadWorker.kt | 7 +++-- .../utils/extensions/OCUploadExtensions.kt | 25 +++++++++--------- .../datamodel/UploadsStorageManager.kt | 5 ++-- .../com/owncloud/android/db/UploadResult.java | 6 +++-- .../operations/UploadFileOperation.java | 6 +++++ .../adapter/uploadList/UploadListAdapter.kt | 17 +++++++----- .../uploadList/model/UploadListSection.kt | 7 ----- .../owncloud/android/db/UploadResultTest.kt | 26 +++++++++++++++++++ 8 files changed, 66 insertions(+), 33 deletions(-) create mode 100644 app/src/test/java/com/owncloud/android/db/UploadResultTest.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 777d03235757..ff897db233d6 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 @@ -411,10 +411,9 @@ class AutoUploadWorker( } // 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") + val alreadyHandled = upload.lastResult == UploadResult.UPLOADED || upload.lastResult == UploadResult.SKIPPED + if (alreadyHandled && syncedFolder.nameCollisionPolicy == NameCollisionPolicy.SKIP) { + Log_OC.d(TAG, "no need to create and process this entity file is already uploaded or skipped") return AutoUploadEntityResult.Uploaded } diff --git a/app/src/main/java/com/nextcloud/utils/extensions/OCUploadExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/OCUploadExtensions.kt index b9c4cbba4011..0aa8eb8a65ec 100644 --- a/app/src/main/java/com/nextcloud/utils/extensions/OCUploadExtensions.kt +++ b/app/src/main/java/com/nextcloud/utils/extensions/OCUploadExtensions.kt @@ -8,17 +8,13 @@ package com.nextcloud.utils.extensions import android.content.Context -import com.nextcloud.client.preferences.AppPreferences import com.owncloud.android.R 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 fun List.getUploadIds(): LongArray = map { it.uploadId }.toLongArray() -fun Array.getUploadIds(): LongArray = map { it.uploadId }.toLongArray() - fun List.sortedByUploadOrder(): List = sortedWith( compareBy { it.fixedUploadStatus } .thenByDescending { it.isFixedUploadingNow } @@ -41,14 +37,19 @@ fun OCUpload.getStatusText(activity: Context, isGlobalUploadPaused: Boolean, isU } UploadsStorageManager.UploadStatus.UPLOAD_SUCCEEDED -> { - status = if (lastResult == UploadResult.SAME_FILE_CONFLICT) { - res.getString(R.string.uploads_view_upload_status_succeeded_same_file) - } else if (lastResult == UploadResult.FILE_NOT_FOUND) { - lastResult.getFailedStatusText(activity) - } else if (nameCollisionPolicy == NameCollisionPolicy.SKIP) { - res.getString(R.string.uploads_view_upload_status_skip_reason) - } else { - res.getString(R.string.uploads_view_upload_status_succeeded) + status = when (lastResult) { + UploadResult.SAME_FILE_CONFLICT -> { + res.getString(R.string.uploads_view_upload_status_succeeded_same_file) + } + UploadResult.FILE_NOT_FOUND -> { + lastResult.getFailedStatusText(activity) + } + UploadResult.SKIPPED -> { + res.getString(R.string.uploads_view_upload_status_skip_reason) + } + else -> { + res.getString(R.string.uploads_view_upload_status_succeeded) + } } } 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 e294114fb003..48bcc19f4012 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.kt +++ b/app/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.kt @@ -452,6 +452,7 @@ class UploadsStorageManager( val deleted = contentResolver.delete( ProviderTableMeta.CONTENT_URI_UPLOADS, ProviderTableMeta.UPLOADS_STATUS + EQUAL + UploadStatus.UPLOAD_SUCCEEDED.value + + AND + ProviderTableMeta.UPLOADS_LAST_RESULT + ANGLE_BRACKETS + UploadResult.SKIPPED.value + AND + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + IS_EQUAL, arrayOf(user.accountName) ) @@ -464,7 +465,7 @@ class UploadsStorageManager( val deleted = contentResolver.delete( ProviderTableMeta.CONTENT_URI_UPLOADS, ProviderTableMeta.UPLOADS_STATUS + EQUAL + UploadStatus.UPLOAD_SUCCEEDED.value + - AND + ProviderTableMeta.UPLOADS_NAME_COLLISION_POLICY + EQUAL + NameCollisionPolicy.SKIP.serialize() + + AND + ProviderTableMeta.UPLOADS_LAST_RESULT + EQUAL + UploadResult.SKIPPED.value + AND + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + IS_EQUAL, arrayOf(user.accountName) ) @@ -493,7 +494,7 @@ class UploadsStorageManager( if (uploadResult.isSuccess) { status = UploadStatus.UPLOAD_SUCCEEDED - result = UploadResult.UPLOADED + result = if (upload.wasSkipped()) UploadResult.SKIPPED else UploadResult.UPLOADED } else if (code.isConflict()) { val isSame = FileUploadHelper().isSameFileOnRemote( upload.user, 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 79f25d9a4db6..074503c6bfd0 100644 --- a/app/src/main/java/com/owncloud/android/db/UploadResult.java +++ b/app/src/main/java/com/owncloud/android/db/UploadResult.java @@ -41,7 +41,8 @@ public enum UploadResult { CANNOT_CREATE_FILE(20), LOCAL_STORAGE_NOT_COPIED(21), QUOTA_EXCEEDED(22), - SAME_FILE_CONFLICT(23); + SAME_FILE_CONFLICT(23), + SKIPPED(24); private final int value; @@ -81,7 +82,8 @@ public int getValue() { Map.entry(20, CANNOT_CREATE_FILE), Map.entry(21, LOCAL_STORAGE_NOT_COPIED), Map.entry(22, QUOTA_EXCEEDED), - Map.entry(23, SAME_FILE_CONFLICT) + Map.entry(23, SAME_FILE_CONFLICT), + Map.entry(24, SKIPPED) ); public static UploadResult fromValue(int value) { return valueMap.getOrDefault(value, UNKNOWN); 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 bb11eb490dda..31955301f791 100644 --- a/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/UploadFileOperation.java @@ -145,6 +145,7 @@ public class UploadFileOperation extends SyncOperation { private final boolean mDisableRetries; private boolean mWasRenamed; + private boolean mWasSkipped; private long mOCUploadId; /** * Local path to file which is to be uploaded (before any possible renaming or moving). @@ -345,6 +346,10 @@ public boolean wasRenamed() { return mWasRenamed; } + public boolean wasSkipped() { + return mWasSkipped; + } + public void setCreatedBy(int createdBy) { mCreatedBy = createdBy; if (createdBy < CREATED_BY_USER || CREATED_AS_INSTANT_VIDEO < createdBy) { @@ -1285,6 +1290,7 @@ private RemoteOperationResult checkNameCollision(OCFile parentFile, switch (mNameCollisionPolicy) { case SKIP: Log_OC.d(TAG, "user choose to skip upload if same file exists"); + mWasSkipped = true; return new RemoteOperationResult<>(ResultCode.OK); case RENAME: mRemotePath = getNewAvailableRemotePath(client, mRemotePath, fileNames, encrypted); diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListAdapter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListAdapter.kt index 1f60d8558591..c218bea7d16b 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListAdapter.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/UploadListAdapter.kt @@ -635,13 +635,18 @@ class UploadListAdapter( val capabilities = optionalCapabilities.get() activity.lifecycleScope.launch(Dispatchers.IO) { + val succeededUploads = uploadHelper.getUploadsByStatus( + accountName, + UploadsStorageManager.UploadStatus.UPLOAD_SUCCEEDED, + capabilities + ) + val updatedSections = uploadListSections.map { sec -> - val uploads = uploadHelper.getUploadsByStatus( - accountName, - sec.status, - capabilities, - sec.collisionPolicy - ) + val uploads = when (sec.type) { + UploadListType.COMPLETED -> succeededUploads.filter { it.lastResult != UploadResult.SKIPPED } + UploadListType.SKIPPED -> succeededUploads.filter { it.lastResult == UploadResult.SKIPPED } + else -> uploadHelper.getUploadsByStatus(accountName, sec.status, capabilities) + } uploads.forEach { it.setDataFixed(uploadHelper) } sec.withItems(uploads.sortedByUploadOrder()) } diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/model/UploadListSection.kt b/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/model/UploadListSection.kt index 6513a19d4fd9..e3c79121f255 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/model/UploadListSection.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/uploadList/model/UploadListSection.kt @@ -10,13 +10,11 @@ package com.owncloud.android.ui.adapter.uploadList.model import com.owncloud.android.R import com.owncloud.android.datamodel.UploadsStorageManager import com.owncloud.android.db.OCUpload -import com.owncloud.android.files.services.NameCollisionPolicy data class UploadListSection( val type: UploadListType?, val titleRes: Int, val status: UploadsStorageManager.UploadStatus, - val collisionPolicy: NameCollisionPolicy?, val items: List ) { fun withItems(newItems: List) = copy(items = newItems) @@ -27,35 +25,30 @@ data class UploadListSection( UploadListType.CURRENT, R.string.uploads_view_group_current_uploads, UploadsStorageManager.UploadStatus.UPLOAD_IN_PROGRESS, - null, listOf() ), UploadListSection( UploadListType.FAILED, R.string.uploads_view_group_failed_uploads, UploadsStorageManager.UploadStatus.UPLOAD_FAILED, - null, listOf() ), UploadListSection( UploadListType.CANCELLED, R.string.uploads_view_group_manually_cancelled_uploads, UploadsStorageManager.UploadStatus.UPLOAD_CANCELLED, - null, listOf() ), UploadListSection( UploadListType.COMPLETED, R.string.uploads_view_group_completed_uploads, UploadsStorageManager.UploadStatus.UPLOAD_SUCCEEDED, - NameCollisionPolicy.ASK_USER, listOf() ), UploadListSection( UploadListType.SKIPPED, R.string.uploads_view_upload_status_skip, UploadsStorageManager.UploadStatus.UPLOAD_SUCCEEDED, - NameCollisionPolicy.SKIP, listOf() ) ) diff --git a/app/src/test/java/com/owncloud/android/db/UploadResultTest.kt b/app/src/test/java/com/owncloud/android/db/UploadResultTest.kt new file mode 100644 index 000000000000..d2e8322c21db --- /dev/null +++ b/app/src/test/java/com/owncloud/android/db/UploadResultTest.kt @@ -0,0 +1,26 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +package com.owncloud.android.db + +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotEquals +import org.junit.Test + +class UploadResultTest { + + @Test + fun skippedRoundTripsThroughStoredValue() { + val stored = UploadResult.SKIPPED.value + assertEquals(UploadResult.SKIPPED, UploadResult.fromValue(stored)) + } + + @Test + fun skippedIsDistinctFromUploaded() { + assertNotEquals(UploadResult.SKIPPED.value, UploadResult.UPLOADED.value) + assertEquals(UploadResult.UPLOADED, UploadResult.fromValue(UploadResult.UPLOADED.value)) + } +} From 652c35b7f209e6a5e60ec203e5ecb53567d3742f Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 10 Jul 2026 10:35:10 +0200 Subject: [PATCH 2/2] clear confusion Signed-off-by: alperozturk96 --- .../android/datamodel/UploadsStorageManager.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) 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 48bcc19f4012..ef2ede218703 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.kt +++ b/app/src/main/java/com/owncloud/android/datamodel/UploadsStorageManager.kt @@ -421,10 +421,10 @@ class UploadsStorageManager( val deleted = contentResolver.delete( ProviderTableMeta.CONTENT_URI_UPLOADS, ProviderTableMeta.UPLOADS_STATUS + EQUAL + UploadStatus.UPLOAD_FAILED.value + - AND + ProviderTableMeta.UPLOADS_LAST_RESULT + ANGLE_BRACKETS + UploadResult.LOCK_FAILED.value + - AND + ProviderTableMeta.UPLOADS_LAST_RESULT + ANGLE_BRACKETS + UploadResult.DELAYED_FOR_WIFI.value + - AND + ProviderTableMeta.UPLOADS_LAST_RESULT + ANGLE_BRACKETS + UploadResult.DELAYED_FOR_CHARGING.value + - AND + ProviderTableMeta.UPLOADS_LAST_RESULT + ANGLE_BRACKETS + + AND + ProviderTableMeta.UPLOADS_LAST_RESULT + NOT_EQUAL + UploadResult.LOCK_FAILED.value + + AND + ProviderTableMeta.UPLOADS_LAST_RESULT + NOT_EQUAL + UploadResult.DELAYED_FOR_WIFI.value + + AND + ProviderTableMeta.UPLOADS_LAST_RESULT + NOT_EQUAL + UploadResult.DELAYED_FOR_CHARGING.value + + AND + ProviderTableMeta.UPLOADS_LAST_RESULT + NOT_EQUAL + UploadResult.DELAYED_IN_POWER_SAVE_MODE.value + AND + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + IS_EQUAL, arrayOf(user.accountName) @@ -452,7 +452,7 @@ class UploadsStorageManager( val deleted = contentResolver.delete( ProviderTableMeta.CONTENT_URI_UPLOADS, ProviderTableMeta.UPLOADS_STATUS + EQUAL + UploadStatus.UPLOAD_SUCCEEDED.value + - AND + ProviderTableMeta.UPLOADS_LAST_RESULT + ANGLE_BRACKETS + UploadResult.SKIPPED.value + + AND + ProviderTableMeta.UPLOADS_LAST_RESULT + NOT_EQUAL + UploadResult.SKIPPED.value + AND + ProviderTableMeta.UPLOADS_ACCOUNT_NAME + IS_EQUAL, arrayOf(user.accountName) ) @@ -601,7 +601,7 @@ class UploadsStorageManager( private const val EQUAL = "==" private const val OR = " OR " private const val AND = " AND " - private const val ANGLE_BRACKETS = "<>" + private const val NOT_EQUAL = "<>" private const val SINGLE_RESULT = 1 private const val QUERY_PAGE_SIZE: Long = 100