From 806af82d5b385fa8fd870433e97bc9dd36d37828 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Mon, 28 Sep 2026 11:07:56 +0200 Subject: [PATCH 01/11] add missing items for mFilesToSyncContents Signed-off-by: alperozturk96 --- .../com/nextcloud/utils/extensions/OCFileExtensions.kt | 8 ++++++++ .../android/operations/RefreshFolderOperation.java | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/app/src/main/java/com/nextcloud/utils/extensions/OCFileExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/OCFileExtensions.kt index b9e8012aa969..4b1cb5a8bac4 100644 --- a/app/src/main/java/com/nextcloud/utils/extensions/OCFileExtensions.kt +++ b/app/src/main/java/com/nextcloud/utils/extensions/OCFileExtensions.kt @@ -85,6 +85,14 @@ private fun transformToRows(list: List, layout: GalleryRowLayout): List< .map { chunk -> GalleryRow(chunk, layout.measure(chunk)) } } +fun OCFile?.isDownloadedFileChanged(serverFile: OCFile): Boolean { + if (this == null || !isDown || serverFile.isFolder) { + return false + } + + return etag.eTagChanged(serverFile.etag) +} + fun OCFile.toEncryptionEvent(encrypt: Boolean): EncryptionEvent = EncryptionEvent( localId, remoteId, diff --git a/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java b/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java index dc27a5f236a3..688e558a2ae1 100644 --- a/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java @@ -19,6 +19,7 @@ import com.nextcloud.utils.ResultParser; import com.nextcloud.utils.e2ee.E2EVersionHelper; import com.nextcloud.utils.share.UnifiedShareSharees; +import com.nextcloud.utils.extensions.OCFileExtensionsKt; import com.nextcloud.utils.extensions.StringExtensionsKt; import com.owncloud.android.datamodel.ArbitraryDataProvider; import com.owncloud.android.datamodel.ArbitraryDataProviderImpl; @@ -619,6 +620,11 @@ private void synchronizeData(List folderAndFiles) { updatedFile.setEncrypted(encrypted); updatedFile.setReadOnly(localFile != null && localFile.isReadOnly()); + if (!encrypted && OCFileExtensionsKt.isDownloadedFileChanged(localFile, remoteFile)) { + mFilesToSyncContents.add(new SynchronizeFileOperation(updatedFile, remoteFile, user, true, mContext, + fileDataStorageManager, true)); + } + updatedFiles.add(updatedFile); } From 78ed9424a824b1dc51dde2494465d20429675a21 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Mon, 28 Sep 2026 13:23:52 +0200 Subject: [PATCH 02/11] remove sync in progress dialog Signed-off-by: alperozturk96 --- .../com/owncloud/android/ui/helpers/FileOperationsHelper.java | 3 --- 1 file changed, 3 deletions(-) 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 74284f878558..3eb22c2ca1da 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 @@ -307,8 +307,6 @@ public void openFile(OCFile file) { return; } - fileActivity.showLoadingDialog(fileActivity.getResources().getString(R.string.sync_in_progress)); - new Thread(() -> { User user = currentAccount.getUser(); final var storageManager = new FileDataStorageManager(user, fileActivity.getContentResolver()); @@ -318,7 +316,6 @@ public void openFile(OCFile file) { final var sfo = new SynchronizeFileOperation(file,null, user, true, fileActivity, storageManager, false); final var result = sfo.execute(fileActivity); - fileActivity.dismissLoadingDialog(); if (result.getCode() == RemoteOperationResult.ResultCode.SYNC_CONFLICT) { // ISSUE 5: if the user is not running the app (this is a service!), // this can be very intrusive; a notification should be preferred From e62fcd97f655a3e9858965d44e7095fd6f939c21 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Mon, 28 Sep 2026 13:40:06 +0200 Subject: [PATCH 03/11] use kotlin durations Signed-off-by: alperozturk96 --- .../owncloud/android/operations/FolderRefreshScheduler.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/operations/FolderRefreshScheduler.kt b/app/src/main/java/com/owncloud/android/operations/FolderRefreshScheduler.kt index c6106a98dbb5..c521283b8263 100644 --- a/app/src/main/java/com/owncloud/android/operations/FolderRefreshScheduler.kt +++ b/app/src/main/java/com/owncloud/android/operations/FolderRefreshScheduler.kt @@ -20,11 +20,11 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.seconds class FolderRefreshScheduler(private val activity: FileDisplayActivity) { companion object { - private const val ETAG_POLL_INTERVAL_MS = 30_000L + private val ETAG_POLL_INTERVAL = 30.seconds private const val TAG = "FolderRefreshScheduler" } @@ -35,12 +35,12 @@ class FolderRefreshScheduler(private val activity: FileDisplayActivity) { job = activity.lifecycleScope.launch { while (isActive) { - delay(ETAG_POLL_INTERVAL_MS.milliseconds) + delay(ETAG_POLL_INTERVAL) checkAndRefreshIfETagChanged() } } - Log_OC.d(TAG, "eTag polling started interval 30 seconds") + Log_OC.d(TAG, "eTag polling started interval $ETAG_POLL_INTERVAL") } fun stop() { From a4cfa38128e70e8239c7c753d0e633322196ef78 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Mon, 28 Sep 2026 13:42:19 +0200 Subject: [PATCH 04/11] reduce auto folder refresh to 10 seconds Signed-off-by: alperozturk96 --- .../com/owncloud/android/operations/FolderRefreshScheduler.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/owncloud/android/operations/FolderRefreshScheduler.kt b/app/src/main/java/com/owncloud/android/operations/FolderRefreshScheduler.kt index c521283b8263..7b1ee0ec86f7 100644 --- a/app/src/main/java/com/owncloud/android/operations/FolderRefreshScheduler.kt +++ b/app/src/main/java/com/owncloud/android/operations/FolderRefreshScheduler.kt @@ -24,7 +24,7 @@ import kotlin.time.Duration.Companion.seconds class FolderRefreshScheduler(private val activity: FileDisplayActivity) { companion object { - private val ETAG_POLL_INTERVAL = 30.seconds + private val ETAG_POLL_INTERVAL = 10.seconds private const val TAG = "FolderRefreshScheduler" } From 2f098c149d709ccf10b548003ebc3c8c43fdf6e5 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Mon, 5 Oct 2026 12:09:49 +0200 Subject: [PATCH 05/11] Only add when user open the folder first time not every 10 seconds. Signed-off-by: alperozturk96 --- .../android/operations/FolderRefreshScheduler.kt | 4 ++-- .../operations/RefreshFolderOperation.java | 9 ++++++++- .../android/ui/activity/FileDisplayActivity.kt | 15 +++++++++++---- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/operations/FolderRefreshScheduler.kt b/app/src/main/java/com/owncloud/android/operations/FolderRefreshScheduler.kt index 7b1ee0ec86f7..ffb38d0a20f2 100644 --- a/app/src/main/java/com/owncloud/android/operations/FolderRefreshScheduler.kt +++ b/app/src/main/java/com/owncloud/android/operations/FolderRefreshScheduler.kt @@ -91,7 +91,7 @@ class FolderRefreshScheduler(private val activity: FileDisplayActivity) { when (result.code) { RemoteOperationResult.ResultCode.ETAG_CHANGED -> { Log_OC.i(TAG, "eTag poll → eTag changed for '${currentDir.remotePath}', triggering sync") - activity.startSyncFolderOperation(currentDir, ignoreETag = true) + activity.startSyncFolderOperation(currentDir, ignoreETag = true, syncChangedDownloadedFiles = false) } RemoteOperationResult.ResultCode.ETAG_UNCHANGED -> { @@ -100,7 +100,7 @@ class FolderRefreshScheduler(private val activity: FileDisplayActivity) { RemoteOperationResult.ResultCode.FILE_NOT_FOUND -> { Log_OC.w(TAG, "eTag poll → directory not found on server") - activity.startSyncFolderOperation(currentDir, ignoreETag = true) + activity.startSyncFolderOperation(currentDir, ignoreETag = true, syncChangedDownloadedFiles = false) } else -> { diff --git a/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java b/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java index 688e558a2ae1..b08f6c109ec1 100644 --- a/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java @@ -155,6 +155,8 @@ public class RefreshFolderOperation extends RemoteOperation { private final List mFilesToSyncContents; // this will be used for every file when 'folder synchronization' replaces 'folder download' + private boolean syncChangedDownloadedFiles = true; + /** * Creates a new instance of {@link RefreshFolderOperation}. @@ -244,6 +246,10 @@ public Map getForgottenLocalFiles() { return mForgottenLocalFiles; } + public void setSyncChangedDownloadedFiles(boolean syncChangedDownloadedFiles) { + this.syncChangedDownloadedFiles = syncChangedDownloadedFiles; + } + /** * Returns the list of files and folders contained in the synchronized folder, if called after synchronization is * complete. @@ -620,7 +626,8 @@ private void synchronizeData(List folderAndFiles) { updatedFile.setEncrypted(encrypted); updatedFile.setReadOnly(localFile != null && localFile.isReadOnly()); - if (!encrypted && OCFileExtensionsKt.isDownloadedFileChanged(localFile, remoteFile)) { + if (syncChangedDownloadedFiles && !encrypted && + OCFileExtensionsKt.isDownloadedFileChanged(localFile, remoteFile)) { mFilesToSyncContents.add(new SynchronizeFileOperation(updatedFile, remoteFile, user, true, mContext, fileDataStorageManager, true)); } diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt index 27d5ebeea7b7..fd9a4ff720a5 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt @@ -2598,14 +2598,19 @@ class FileDisplayActivity : * @param ignoreFocus reloads file list even without focus, e.g. on tablet mode, focus can still be in detail view */ @JvmOverloads - fun startSyncFolderOperation(folder: OCFile?, ignoreETag: Boolean, ignoreFocus: Boolean = false) { + fun startSyncFolderOperation( + folder: OCFile?, + ignoreETag: Boolean, + ignoreFocus: Boolean = false, + syncChangedDownloadedFiles: Boolean = true + ) { Log_OC.d(TAG, "startSyncFolderOperation called, ignoreEtag: $ignoreETag, ignoreFocus: $ignoreFocus") if (!searchQuery.isNullOrEmpty() || !user.isPresent) { return } - val syncFolder = Runnable { executeSyncFolderOperation(folder, ignoreETag) } + val syncFolder = Runnable { executeSyncFolderOperation(folder, ignoreETag, syncChangedDownloadedFiles) } // The refresh must not run while another window floats over the activity, e.g. a dialog that is being // dismissed or a rotation. Rather than waiting a fixed delay run right away when it already has focus @@ -2631,7 +2636,7 @@ class FileDisplayActivity : } } - private fun executeSyncFolderOperation(folder: OCFile?, ignoreETag: Boolean) { + private fun executeSyncFolderOperation(folder: OCFile?, ignoreETag: Boolean, syncChangedDownloadedFiles: Boolean) { val folder = folder ?: return user.ifPresent { user -> @@ -2645,7 +2650,9 @@ class FileDisplayActivity : storageManager, user, applicationContext - ).execute( + ).apply { + setSyncChangedDownloadedFiles(syncChangedDownloadedFiles) + }.execute( account, this, { _, _ -> onSyncFinished() }, From 7adb65c55f273064061c44de91a598994f8f29f1 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Mon, 5 Oct 2026 12:38:14 +0200 Subject: [PATCH 06/11] Check local eTag if it's outdated then run SynchronizeFileOperation else skip. Signed-off-by: alperozturk96 --- .../utils/extensions/OCFileExtensions.kt | 6 +++- .../ui/helpers/FileOperationsHelper.java | 28 +++++++++++++------ 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/nextcloud/utils/extensions/OCFileExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/OCFileExtensions.kt index 4b1cb5a8bac4..2551263f1329 100644 --- a/app/src/main/java/com/nextcloud/utils/extensions/OCFileExtensions.kt +++ b/app/src/main/java/com/nextcloud/utils/extensions/OCFileExtensions.kt @@ -19,7 +19,6 @@ import com.owncloud.android.datamodel.OCFileDepth.Root import com.owncloud.android.ui.events.EncryptionEvent import com.owncloud.android.utils.FileStorageUtils import java.util.Calendar -import java.util.Date fun List.filterFilenames(): List = distinctBy { it.fileName } @@ -93,6 +92,11 @@ fun OCFile?.isDownloadedFileChanged(serverFile: OCFile): Boolean { return etag.eTagChanged(serverFile.etag) } +fun OCFile.isLocalETagOutdated(): Boolean { + val serverETag = etagInConflict + return !serverETag.isNullOrEmpty() && etag.eTagChanged(serverETag) +} + fun OCFile.toEncryptionEvent(encrypt: Boolean): EncryptionEvent = EncryptionEvent( localId, remoteId, 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 3eb22c2ca1da..1aa6f54258ac 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 @@ -310,6 +310,14 @@ public void openFile(OCFile file) { new Thread(() -> { User user = currentAccount.getUser(); final var storageManager = new FileDataStorageManager(user, fileActivity.getContentResolver()); + final var storedFile = storageManager.getFileById(file.getFileId()); + final boolean isDownloadedAndUpToDate = storedFile != null && storedFile.isDown() && + !OCFileExtensionsKt.isLocalETagOutdated(storedFile); + if (isDownloadedAndUpToDate) { + startOpenFileIntent(openFileWithIntent); + return; + } + // a fresh object is needed; many things could have occurred to the file // since it was registered to observe again, assuming that local files // are linked to a remote file AT MOST, SOMETHING TO BE DONE; @@ -345,17 +353,21 @@ public void openFile(OCFile file) { } } - fileActivity.runOnUiThread(() -> { - try { - openFileWithIntent.setFlags(openFileWithIntent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK); - fileActivity.startActivity(openFileWithIntent); - } catch (ActivityNotFoundException exception) { - SnackbarUtil.show(fileActivity, R.string.file_list_no_app_for_file_type); - } - }); + startOpenFileIntent(openFileWithIntent); }).start(); } + private void startOpenFileIntent(Intent openFileWithIntent) { + fileActivity.runOnUiThread(() -> { + try { + openFileWithIntent.setFlags(openFileWithIntent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK); + fileActivity.startActivity(openFileWithIntent); + } catch (ActivityNotFoundException exception) { + SnackbarUtil.show(fileActivity, R.string.file_list_no_app_for_file_type); + } + }); + } + public void openFileAsRichDocument(OCFile file, Context context) { Intent collaboraWebViewIntent = new Intent(context, RichDocumentsEditorWebView.class); collaboraWebViewIntent.putExtra(ExternalSiteWebView.EXTRA_TITLE, "Collabora"); From 9914ebd7ad72e58477e74f41c7559629af4fa371 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Mon, 5 Oct 2026 12:56:01 +0200 Subject: [PATCH 07/11] simplify openFile function Signed-off-by: alperozturk96 --- .../ui/helpers/FileOperationsHelper.java | 104 ++++++++++-------- 1 file changed, 56 insertions(+), 48 deletions(-) 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 1aa6f54258ac..9adcf301898b 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 @@ -295,66 +295,74 @@ public void openFile(OCFile file) { // first always try to use available apps if (availableApps.isEmpty()) { - Optional optionalUser = fileActivity.getUser(); + openFileWithoutAvailableApps(file); + return; + } - if (optionalUser.isPresent() && editorUtils.isEditorAvailable(optionalUser.get(), file.getMimeType())) { - TextEditorWebView.Companion.startTextEditor(file, fileActivity); - return; - } + new Thread(() -> syncAndOpenFile(file, openFileWithIntent)).start(); + } - openRichDocumentFileWithoutAvailableApps(file); + private void openFileWithoutAvailableApps(OCFile file) { + Optional optionalUser = fileActivity.getUser(); + if (optionalUser.isPresent() && editorUtils.isEditorAvailable(optionalUser.get(), file.getMimeType())) { + TextEditorWebView.Companion.startTextEditor(file, fileActivity); return; } - new Thread(() -> { - User user = currentAccount.getUser(); - final var storageManager = new FileDataStorageManager(user, fileActivity.getContentResolver()); - final var storedFile = storageManager.getFileById(file.getFileId()); - final boolean isDownloadedAndUpToDate = storedFile != null && storedFile.isDown() && - !OCFileExtensionsKt.isLocalETagOutdated(storedFile); - if (isDownloadedAndUpToDate) { - startOpenFileIntent(openFileWithIntent); - return; - } + openRichDocumentFileWithoutAvailableApps(file); + } - // a fresh object is needed; many things could have occurred to the file - // since it was registered to observe again, assuming that local files - // are linked to a remote file AT MOST, SOMETHING TO BE DONE; - final var sfo = new SynchronizeFileOperation(file,null, user, true, fileActivity, storageManager, false); - final var result = sfo.execute(fileActivity); - - if (result.getCode() == RemoteOperationResult.ResultCode.SYNC_CONFLICT) { - // ISSUE 5: if the user is not running the app (this is a service!), - // this can be very intrusive; a notification should be preferred - Intent intent = ConflictsResolveActivity.createIntent(file, - user, - -1, - Intent.FLAG_ACTIVITY_NEW_TASK, - fileActivity); - fileActivity.startActivity(intent); - return; - } + private void syncAndOpenFile(OCFile file, Intent openFileWithIntent) { + User user = currentAccount.getUser(); + final var storageManager = new FileDataStorageManager(user, fileActivity.getContentResolver()); + if (isDownloadedAndUpToDate(storageManager.getFileById(file.getFileId()))) { + startOpenFileIntent(openFileWithIntent); + return; + } - if (availableApps.isEmpty()) { - fileActivity.runOnUiThread(() -> SnackbarUtil.show(fileActivity, R.string.file_list_no_app_for_file_type)); + // a fresh object is needed; many things could have occurred to the file + // since it was registered to observe again, assuming that local files + // are linked to a remote file AT MOST, SOMETHING TO BE DONE; + final var sfo = new SynchronizeFileOperation(file,null, user, true, fileActivity, storageManager, false); + final var result = sfo.execute(fileActivity); - return; - } + if (result.getCode() == RemoteOperationResult.ResultCode.SYNC_CONFLICT) { + startConflictsResolveActivity(file, user); + return; + } - if (!result.isSuccess()) { - fileActivity.runOnUiThread(() -> SnackbarUtil.show(fileActivity, R.string.file_not_synced)); + if (!result.isSuccess()) { + showFileNotSyncedMessage(); + } - // Sleep to show snackbar message - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - Log_OC.e(TAG, "Failed to sleep"); - } - } + startOpenFileIntent(openFileWithIntent); + } - startOpenFileIntent(openFileWithIntent); - }).start(); + private boolean isDownloadedAndUpToDate(@Nullable OCFile storedFile) { + return storedFile != null && storedFile.isDown() && !OCFileExtensionsKt.isLocalETagOutdated(storedFile); + } + + private void startConflictsResolveActivity(OCFile file, User user) { + // ISSUE 5: if the user is not running the app (this is a service!), + // this can be very intrusive; a notification should be preferred + Intent intent = ConflictsResolveActivity.createIntent(file, + user, + -1, + Intent.FLAG_ACTIVITY_NEW_TASK, + fileActivity); + fileActivity.startActivity(intent); + } + + private void showFileNotSyncedMessage() { + fileActivity.runOnUiThread(() -> SnackbarUtil.show(fileActivity, R.string.file_not_synced)); + + // Sleep to show snackbar message + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + Log_OC.e(TAG, "Failed to sleep"); + } } private void startOpenFileIntent(Intent openFileWithIntent) { From 225b806296303872cc151165d9b0f23517a42f61 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Mon, 5 Oct 2026 13:04:50 +0200 Subject: [PATCH 08/11] fix spotbugs: (This write of this shared primitive variable may not be visible to other threads) Signed-off-by: alperozturk96 --- .../operations/RefreshFolderOperation.java | 41 ++++++++++++------- .../ui/activity/FileDisplayActivity.kt | 7 ++-- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java b/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java index b08f6c109ec1..f0320b819047 100644 --- a/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java @@ -56,6 +56,7 @@ import java.util.List; import java.util.Map; import java.util.Vector; +import java.util.concurrent.atomic.AtomicInteger; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -114,12 +115,12 @@ public class RefreshFolderOperation extends RemoteOperation { /** * Counter of conflicts found between local and remote files */ - private int mConflictsFound; + private final AtomicInteger mConflictsFound = new AtomicInteger(); /** * Counter of failed operations in synchronization of kept-in-sync files */ - private int mFailsInKeptInSyncFound; + private final AtomicInteger mFailsInKeptInSyncFound = new AtomicInteger(); /** * Map of remote and local paths to files that where locally stored in a location out of the ownCloud folder and @@ -135,7 +136,7 @@ public class RefreshFolderOperation extends RemoteOperation { /** * 'True' means that the remote folder changed and should be fetched */ - private boolean mRemoteFolderChanged; + private volatile boolean mRemoteFolderChanged; /** * 'True' means that the sharees of at least one child of the folder changed @@ -155,7 +156,7 @@ public class RefreshFolderOperation extends RemoteOperation { private final List mFilesToSyncContents; // this will be used for every file when 'folder synchronization' replaces 'folder download' - private boolean syncChangedDownloadedFiles = true; + private final boolean syncChangedDownloadedFiles; /** @@ -177,6 +178,17 @@ public RefreshFolderOperation(OCFile folder, FileDataStorageManager dataStorageManager, User user, Context context) { + this(folder, currentSyncTime, syncFullAccount, ignoreETag, dataStorageManager, user, context, true); + } + + public RefreshFolderOperation(OCFile folder, + long currentSyncTime, + boolean syncFullAccount, + boolean ignoreETag, + FileDataStorageManager dataStorageManager, + User user, + Context context, + boolean syncChangedDownloadedFiles) { mLocalFolder = folder; mCurrentSyncTime = currentSyncTime; mSyncFullAccount = syncFullAccount; @@ -188,6 +200,7 @@ public RefreshFolderOperation(OCFile folder, mIgnoreETag = ignoreETag; mOnlyFileMetadata = false; mFilesToSyncContents = new Vector<>(); + this.syncChangedDownloadedFiles = syncChangedDownloadedFiles; } /** @@ -208,6 +221,7 @@ public RefreshFolderOperation(OCFile folder, mIgnoreETag = false; mOnlyFileMetadata = true; mFilesToSyncContents = new Vector<>(); + syncChangedDownloadedFiles = true; // since metadata worker working in background for sub-folders no need send folder refresh event isMetadataSyncWorkerRunning = true; @@ -232,24 +246,21 @@ public RefreshFolderOperation(OCFile folder, mIgnoreETag = ignoreETag; mOnlyFileMetadata = onlyFileMetadata; mFilesToSyncContents = new Vector<>(); + syncChangedDownloadedFiles = true; } public int getConflictsFound() { - return mConflictsFound; + return mConflictsFound.get(); } public int getFailsInKeptInSyncFound() { - return mFailsInKeptInSyncFound; + return mFailsInKeptInSyncFound.get(); } public Map getForgottenLocalFiles() { return mForgottenLocalFiles; } - public void setSyncChangedDownloadedFiles(boolean syncChangedDownloadedFiles) { - this.syncChangedDownloadedFiles = syncChangedDownloadedFiles; - } - /** * Returns the list of files and folders contained in the synchronized folder, if called after synchronization is * complete. @@ -268,8 +279,8 @@ public List getChildren() { @Override protected RemoteOperationResult run(OwnCloudClient client) { RemoteOperationResult result; - mFailsInKeptInSyncFound = 0; - mConflictsFound = 0; + mFailsInKeptInSyncFound.set(0); + mConflictsFound.set(0); mForgottenLocalFiles.clear(); if (mLocalFolder == null) { @@ -477,7 +488,7 @@ private RemoteOperationResult fetchAndSyncRemoteFolder(OwnCloudClient client) { if (result.isSuccess()) { synchronizeData(result.getData()); - if (mConflictsFound > 0 || mFailsInKeptInSyncFound > 0) { + if (mConflictsFound.get() > 0 || mFailsInKeptInSyncFound.get() > 0) { result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT); // should be a different result code, but will do the job } @@ -835,9 +846,9 @@ private void startContentSynchronizations(List filesTo contentsResult = op.execute(mContext); // async if (!contentsResult.isSuccess()) { if (contentsResult.getCode() == ResultCode.SYNC_CONFLICT) { - mConflictsFound++; + mConflictsFound.incrementAndGet(); } else { - mFailsInKeptInSyncFound++; + mFailsInKeptInSyncFound.incrementAndGet(); if (contentsResult.getException() != null) { Log_OC.e(TAG, "Error while synchronizing favourites : " + contentsResult.getLogMessage(), contentsResult.getException()); diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt index fd9a4ff720a5..15bfd02a9029 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt @@ -2649,10 +2649,9 @@ class FileDisplayActivity : ignoreETag, storageManager, user, - applicationContext - ).apply { - setSyncChangedDownloadedFiles(syncChangedDownloadedFiles) - }.execute( + applicationContext, + syncChangedDownloadedFiles + ).execute( account, this, { _, _ -> onSyncFinished() }, From bb16d500336a96044d310413f889777f913c1e1f Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Mon, 5 Oct 2026 13:43:07 +0200 Subject: [PATCH 09/11] simplify startContentSynchronizations Signed-off-by: alperozturk96 --- .../operations/RefreshFolderOperation.java | 42 +++++++++++-------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java b/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java index f0320b819047..b6b594d7b626 100644 --- a/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java @@ -841,24 +841,32 @@ public static Map prefillLocalFilesMap(Object metadata, List filesToSyncContents) { - RemoteOperationResult contentsResult; - for (SynchronizeFileOperation op : filesToSyncContents) { - contentsResult = op.execute(mContext); // async - if (!contentsResult.isSuccess()) { - if (contentsResult.getCode() == ResultCode.SYNC_CONFLICT) { - mConflictsFound.incrementAndGet(); - } else { - mFailsInKeptInSyncFound.incrementAndGet(); - if (contentsResult.getException() != null) { - Log_OC.e(TAG, "Error while synchronizing favourites : " - + contentsResult.getLogMessage(), contentsResult.getException()); - } else { - Log_OC.e(TAG, "Error while synchronizing favourites : " - + contentsResult.getLogMessage()); - } - } - } // won't let these fails break the synchronization process + // won't let these fails break the synchronization process + for (SynchronizeFileOperation operation : filesToSyncContents) { + final RemoteOperationResult result = operation.execute(mContext); + if (result.isSuccess()) { + continue; + } + + if (result.getCode() == ResultCode.SYNC_CONFLICT) { + mConflictsFound.incrementAndGet(); + continue; + } + + mFailsInKeptInSyncFound.incrementAndGet(); + logContentSynchronizationFailure(result); + } + } + + private void logContentSynchronizationFailure(RemoteOperationResult result) { + final String message = "Error while synchronizing favourites : " + result.getLogMessage(mContext); + final Exception exception = result.getException(); + if (exception == null) { + Log_OC.e(TAG, message); + return; } + + Log_OC.e(TAG, message, exception); } /** From 95f666c818f66ca984a77f0ce2c9a0efe59eff8a Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Mon, 5 Oct 2026 13:54:46 +0200 Subject: [PATCH 10/11] print results Signed-off-by: alperozturk96 --- .../android/operations/RefreshFolderOperation.java | 3 +++ .../android/operations/SynchronizeFileOperation.kt | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java b/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java index b6b594d7b626..227f1a4f0d96 100644 --- a/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java @@ -845,14 +845,17 @@ private void startContentSynchronizations(List filesTo for (SynchronizeFileOperation operation : filesToSyncContents) { final RemoteOperationResult result = operation.execute(mContext); if (result.isSuccess()) { + operation.printSuccess(); continue; } if (result.getCode() == ResultCode.SYNC_CONFLICT) { + operation.printConflict(); mConflictsFound.incrementAndGet(); continue; } + operation.printFailure(); mFailsInKeptInSyncFound.incrementAndGet(); logContentSynchronizationFailure(result); } diff --git a/app/src/main/java/com/owncloud/android/operations/SynchronizeFileOperation.kt b/app/src/main/java/com/owncloud/android/operations/SynchronizeFileOperation.kt index 345c56dd882b..a6db2efbbdef 100644 --- a/app/src/main/java/com/owncloud/android/operations/SynchronizeFileOperation.kt +++ b/app/src/main/java/com/owncloud/android/operations/SynchronizeFileOperation.kt @@ -257,6 +257,18 @@ class SynchronizeFileOperation : SyncOperation { } } + fun printSuccess() { + Log_OC.i(TAG, "content synchronized, remote path: $remotePath") + } + + fun printConflict() { + Log_OC.w(TAG, "content has conflict, remote path: $remotePath") + } + + fun printFailure() { + Log_OC.e(TAG, "content not synchronized, remote path: $remotePath") + } + companion object { private val TAG: String = SynchronizeFileOperation::class.java.simpleName } From 9bf50de3e131c67ab1838821c7137a8d980975ed Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Mon, 5 Oct 2026 13:56:55 +0200 Subject: [PATCH 11/11] only sync when user get in directory Signed-off-by: alperozturk96 --- .../owncloud/android/operations/FolderRefreshScheduler.kt | 4 ++-- .../owncloud/android/operations/RefreshFolderOperation.java | 6 +++--- .../com/owncloud/android/ui/activity/FileDisplayActivity.kt | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/operations/FolderRefreshScheduler.kt b/app/src/main/java/com/owncloud/android/operations/FolderRefreshScheduler.kt index ffb38d0a20f2..7b1ee0ec86f7 100644 --- a/app/src/main/java/com/owncloud/android/operations/FolderRefreshScheduler.kt +++ b/app/src/main/java/com/owncloud/android/operations/FolderRefreshScheduler.kt @@ -91,7 +91,7 @@ class FolderRefreshScheduler(private val activity: FileDisplayActivity) { when (result.code) { RemoteOperationResult.ResultCode.ETAG_CHANGED -> { Log_OC.i(TAG, "eTag poll → eTag changed for '${currentDir.remotePath}', triggering sync") - activity.startSyncFolderOperation(currentDir, ignoreETag = true, syncChangedDownloadedFiles = false) + activity.startSyncFolderOperation(currentDir, ignoreETag = true) } RemoteOperationResult.ResultCode.ETAG_UNCHANGED -> { @@ -100,7 +100,7 @@ class FolderRefreshScheduler(private val activity: FileDisplayActivity) { RemoteOperationResult.ResultCode.FILE_NOT_FOUND -> { Log_OC.w(TAG, "eTag poll → directory not found on server") - activity.startSyncFolderOperation(currentDir, ignoreETag = true, syncChangedDownloadedFiles = false) + activity.startSyncFolderOperation(currentDir, ignoreETag = true) } else -> { diff --git a/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java b/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java index 227f1a4f0d96..eecd9468489c 100644 --- a/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java @@ -178,7 +178,7 @@ public RefreshFolderOperation(OCFile folder, FileDataStorageManager dataStorageManager, User user, Context context) { - this(folder, currentSyncTime, syncFullAccount, ignoreETag, dataStorageManager, user, context, true); + this(folder, currentSyncTime, syncFullAccount, ignoreETag, dataStorageManager, user, context, false); } public RefreshFolderOperation(OCFile folder, @@ -221,7 +221,7 @@ public RefreshFolderOperation(OCFile folder, mIgnoreETag = false; mOnlyFileMetadata = true; mFilesToSyncContents = new Vector<>(); - syncChangedDownloadedFiles = true; + syncChangedDownloadedFiles = false; // since metadata worker working in background for sub-folders no need send folder refresh event isMetadataSyncWorkerRunning = true; @@ -246,7 +246,7 @@ public RefreshFolderOperation(OCFile folder, mIgnoreETag = ignoreETag; mOnlyFileMetadata = onlyFileMetadata; mFilesToSyncContents = new Vector<>(); - syncChangedDownloadedFiles = true; + syncChangedDownloadedFiles = false; } public int getConflictsFound() { diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt index 15bfd02a9029..80e1b43d4683 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt @@ -2014,7 +2014,7 @@ class FileDisplayActivity : override fun onBrowsedDownTo(directory: OCFile?) { file = directory resetScrollingAndUpdateActionBar() - startSyncFolderOperation(directory, false) + startSyncFolderOperation(directory, ignoreETag = false, syncChangedDownloadedFiles = true) startMetadataSyncForCurrentDir() } @@ -2602,7 +2602,7 @@ class FileDisplayActivity : folder: OCFile?, ignoreETag: Boolean, ignoreFocus: Boolean = false, - syncChangedDownloadedFiles: Boolean = true + syncChangedDownloadedFiles: Boolean = false ) { Log_OC.d(TAG, "startSyncFolderOperation called, ignoreEtag: $ignoreETag, ignoreFocus: $ignoreFocus")