From 33066f9e1412c9d04fbfd36c5e83814815dcd5a0 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Wed, 5 Aug 2026 13:46:00 +0200 Subject: [PATCH 1/7] wip Signed-off-by: alperozturk96 --- .../ui/activity/FileDisplayActivity.kt | 50 +++++++++++++------ .../ui/activity/FolderPickerActivity.kt | 28 ++++++----- .../ui/fragment/OCFileListFragment.java | 2 + 3 files changed, 53 insertions(+), 27 deletions(-) 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 29ba6415519c..2888a39552be 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 @@ -227,6 +227,8 @@ class FileDisplayActivity : private var mPlayerConnection: PlayerServiceConnection? = null private var lastDisplayedAccountName: String? = null + private var listFragmentJustCreated = false + @Inject lateinit var localBroadcastManager: LocalBroadcastManager @@ -559,6 +561,7 @@ class FileDisplayActivity : val transaction = supportFragmentManager.beginTransaction() transaction.add(R.id.left_fragment_container, listOfFiles, TAG_LIST_OF_FILES) transaction.commit() + listFragmentJustCreated = true } else { supportFragmentManager.findFragmentByTag(TAG_LIST_OF_FILES) } @@ -568,7 +571,10 @@ class FileDisplayActivity : // First fragment val listOfFiles = this.listOfFilesFragment if (listOfFiles != null && TextUtils.isEmpty(searchQuery)) { - listOfFiles.listDirectory(getCurrentDir(), file, MainApp.isOnlyOnDevice()) + if (!listFragmentJustCreated) { + listOfFiles.listDirectory(getCurrentDir(), file, MainApp.isOnlyOnDevice()) + } + listFragmentJustCreated = true } else { Log_OC.e(TAG, "Still have a chance to lose the initialization of list fragment >(") } @@ -610,10 +616,13 @@ class FileDisplayActivity : if (it::class != OCFileListFragment::class) { leftFragment = OCFileListFragment() supportFragmentManager.executePendingTransactions() + listFragmentJustCreated = true } } - browseToRoot() + // The onResume() that always follows this same-activity intent redelivery already + // lists and re-syncs the current directory, so doing it again here is redundant. + browseToRoot(performRefresh = false) } LIST_GROUPFOLDERS == action -> { @@ -1370,6 +1379,9 @@ class FileDisplayActivity : super.onResume() + val listFragmentJustCreated = this.listFragmentJustCreated + this.listFragmentJustCreated = false + folderRefreshScheduler.start() if (ocFileListFragment?.isSearchFragment == true) { @@ -1410,7 +1422,9 @@ class FileDisplayActivity : if (searchView != null && !TextUtils.isEmpty(searchQuery)) { searchView?.setQuery(searchQuery, false) } else if (!ocFileListFragment.isSearchFragment && startFile == null) { - ocFileListFragment.listDirectory(MainApp.isOnlyOnDevice()) + if (!listFragmentJustCreated) { + ocFileListFragment.listDirectory(MainApp.isOnlyOnDevice()) + } ocFileListFragment.registerFabListener() updateActionBarTitleAndHomeButton(currentDir) } else { @@ -1553,16 +1567,20 @@ class FileDisplayActivity : return } - var currentFile = file?.remotePath?.let { storageManager.getFileByPath(it) } - val currentDir = getCurrentDir()?.remotePath?.let { storageManager.getFileByPath(it) } - val isSyncFolderRemotePathRoot = OCFile.ROOT_PATH == syncFolderRemotePath + // RefreshFolderOperation always fires EVENT_SINGLE_FOLDER_SHARES_SYNCED for the same folder right after + // EVENT_SINGLE_FOLDER_CONTENTS_SYNCED, so listing the directory here would just repeat that refresh. + if (RefreshFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED != event) { + var currentFile = file?.remotePath?.let { storageManager.getFileByPath(it) } + val currentDir = getCurrentDir()?.remotePath?.let { storageManager.getFileByPath(it) } + val isSyncFolderRemotePathRoot = OCFile.ROOT_PATH == syncFolderRemotePath - if (currentDir == null && !isSyncFolderRemotePathRoot) { - handleRemovedFolder(syncFolderRemotePath) - } else if (currentDir != null) { - currentFile = handleRemovedFileFromServer(currentFile, currentDir) - updateFileList(fileListFragment, currentDir, syncFolderRemotePath) - file = currentFile + if (currentDir == null && !isSyncFolderRemotePathRoot) { + handleRemovedFolder(syncFolderRemotePath) + } else if (currentDir != null) { + currentFile = handleRemovedFileFromServer(currentFile, currentDir) + updateFileList(fileListFragment, currentDir, syncFolderRemotePath) + file = currentFile + } } handleSyncResult(event, syncResult) @@ -1914,13 +1932,15 @@ class FileDisplayActivity : } // endregion - fun browseToRoot() { + fun browseToRoot(performRefresh: Boolean = true) { listOfFilesFragment?.let { val root = storageManager.getFileByPath(OCFile.ROOT_PATH) it.resetSearchAttributes() file = root - it.listDirectory(root, MainApp.isOnlyOnDevice()) - startSyncFolderOperation(root, false) + if (performRefresh) { + it.listDirectory(root, MainApp.isOnlyOnDevice()) + startSyncFolderOperation(root, false) + } } binding.fabMain.setImageResource(R.drawable.ic_plus) diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt index b0b5c69e0b7a..e78a17089ae6 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt @@ -570,19 +570,23 @@ open class FolderPickerActivity : } if (FileSyncAdapter.EVENT_FULL_SYNC_START != event) { - var (currentFile, currentDir) = getCurrentFileAndDirectory() - - if (currentDir == null) { - browseRootForRemovedFolder() - } else { - if (currentFile == null && file?.isFolder == false) { - // currently selected file was removed in the server, and now we know it - currentFile = currentDir + // RefreshFolderOperation always fires EVENT_SINGLE_FOLDER_SHARES_SYNCED for the same folder + // right after EVENT_SINGLE_FOLDER_CONTENTS_SYNCED, so listing here would just repeat that refresh. + if (RefreshFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED != event) { + var (currentFile, currentDir) = getCurrentFileAndDirectory() + + if (currentDir == null) { + browseRootForRemovedFolder() + } else { + if (currentFile == null && file?.isFolder == false) { + // currently selected file was removed in the server, and now we know it + currentFile = currentDir + } + if (currentDir.remotePath == syncFolderRemotePath) { + listOfFilesFragment?.listDirectory(currentDir, false) + } + file = currentFile } - if (currentDir.remotePath == syncFolderRemotePath) { - listOfFilesFragment?.listDirectory(currentDir, false) - } - file = currentFile } checkCredentials(syncResult, event) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java index 1ce195ff29b8..ebec7bf1bfb1 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java @@ -1604,6 +1604,8 @@ public void listDirectory(@Nullable OCFile directory, OCFile file, boolean onlyO return; } + Log_OC.i(TAG, "listing directory: " + directory.getDecryptedRemotePath()); + mAdapter.swapDirectory( accountManager.getUser(), directory, From 12028be2b6499cacf8cab82e4d01bae543fcf41d Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Wed, 5 Aug 2026 14:08:51 +0200 Subject: [PATCH 2/7] wip Signed-off-by: alperozturk96 --- .../datamodel/FileDataStorageManager.java | 48 +++++++++++++++++-- .../operations/RefreshFolderOperation.java | 9 +++- .../ui/activity/FileDisplayActivity.kt | 27 +++++------ .../ui/activity/FolderPickerActivity.kt | 31 ++++++------ 4 files changed, 80 insertions(+), 35 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java index 0b44558170cd..d9c69beedf2b 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java @@ -1830,17 +1830,27 @@ public void removeShare(OCShare share) { } } - public void saveSharesFromRemoteFile(List shares) { + /** + * @return true if the sharees of any of the given files differ from what is currently stored locally. + */ + public boolean saveSharesFromRemoteFile(List shares) { if (shares == null || shares.isEmpty()) { - return; + return false; } - // Prepare reset operations Set uniquePaths = new HashSet<>(); for (RemoteFile share : shares) { uniquePaths.add(share.getRemotePath()); } + boolean sharesChanged = false; + for (String path : uniquePaths) { + if (shareesForPathChanged(path, shares)) { + sharesChanged = true; + } + } + + // Prepare reset operations ArrayList resetOperations = new ArrayList<>(); for (String path : uniquePaths) { resetShareFlagInAFile(path); @@ -1858,6 +1868,38 @@ public void saveSharesFromRemoteFile(List shares) { if (!insertOperations.isEmpty()) { applyBatch(insertOperations); } + + return sharesChanged; + } + + /** + * Compares the sharees reported for {@code path} in {@code remoteFiles} against the sharees + * currently stored locally for that path, identifying each sharee by (userId, shareType). + */ + private boolean shareesForPathChanged(String path, List remoteFiles) { + Set newSharees = new HashSet<>(); + for (RemoteFile remoteFile : remoteFiles) { + if (!path.equals(remoteFile.getRemotePath()) || remoteFile.getSharees() == null) { + continue; + } + + for (ShareeUser sharee : remoteFile.getSharees()) { + if (sharee == null || sharee.getShareType() == null) { + continue; + } + newSharees.add(sharee.getUserId() + ":" + sharee.getShareType().getValue()); + } + } + + Set existingSharees = new HashSet<>(); + for (OCShare share : getSharesWithForAFile(path, user.getAccountName())) { + if (share.getShareType() == null) { + continue; + } + existingSharees.add(share.getShareWith() + ":" + share.getShareType().getValue()); + } + + return !newSharees.equals(existingSharees); } /** 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 1e76a4d8bc17..54c1e13887b9 100644 --- a/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java @@ -134,6 +134,11 @@ public class RefreshFolderOperation extends RemoteOperation { */ private boolean mRemoteFolderChanged; + /** + * 'True' means that the sharees of at least one child of the folder changed + */ + private boolean mSharesChanged; + /** * 'True' means that Etag will be ignored */ @@ -307,10 +312,10 @@ protected RemoteOperationResult run(OwnCloudClient client) { } } - fileDataStorageManager.saveSharesFromRemoteFile(remoteFiles); + mSharesChanged = fileDataStorageManager.saveSharesFromRemoteFile(remoteFiles); } - if (!mSyncFullAccount && mLocalFolder != null && !isMetadataSyncWorkerRunning) { + if (!mSyncFullAccount && mSharesChanged && mLocalFolder != null && !isMetadataSyncWorkerRunning) { sendLocalBroadcast(EVENT_SINGLE_FOLDER_SHARES_SYNCED, mLocalFolder.getRemotePath(), result); } 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 2888a39552be..626a66d48236 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 @@ -1567,20 +1567,19 @@ class FileDisplayActivity : return } - // RefreshFolderOperation always fires EVENT_SINGLE_FOLDER_SHARES_SYNCED for the same folder right after - // EVENT_SINGLE_FOLDER_CONTENTS_SYNCED, so listing the directory here would just repeat that refresh. - if (RefreshFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED != event) { - var currentFile = file?.remotePath?.let { storageManager.getFileByPath(it) } - val currentDir = getCurrentDir()?.remotePath?.let { storageManager.getFileByPath(it) } - val isSyncFolderRemotePathRoot = OCFile.ROOT_PATH == syncFolderRemotePath - - if (currentDir == null && !isSyncFolderRemotePathRoot) { - handleRemovedFolder(syncFolderRemotePath) - } else if (currentDir != null) { - currentFile = handleRemovedFileFromServer(currentFile, currentDir) - updateFileList(fileListFragment, currentDir, syncFolderRemotePath) - file = currentFile - } + // EVENT_SINGLE_FOLDER_CONTENTS_SYNCED fires only when the folder's content actually changed, and + // EVENT_SINGLE_FOLDER_SHARES_SYNCED only when a sharee actually changed - each is an independent, + // already-precise signal, so both are handled here (RefreshFolderOperation.java). + var currentFile = file?.remotePath?.let { storageManager.getFileByPath(it) } + val currentDir = getCurrentDir()?.remotePath?.let { storageManager.getFileByPath(it) } + val isSyncFolderRemotePathRoot = OCFile.ROOT_PATH == syncFolderRemotePath + + if (currentDir == null && !isSyncFolderRemotePathRoot) { + handleRemovedFolder(syncFolderRemotePath) + } else if (currentDir != null) { + currentFile = handleRemovedFileFromServer(currentFile, currentDir) + updateFileList(fileListFragment, currentDir, syncFolderRemotePath) + file = currentFile } handleSyncResult(event, syncResult) diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt index e78a17089ae6..aed634b43167 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt @@ -570,23 +570,22 @@ open class FolderPickerActivity : } if (FileSyncAdapter.EVENT_FULL_SYNC_START != event) { - // RefreshFolderOperation always fires EVENT_SINGLE_FOLDER_SHARES_SYNCED for the same folder - // right after EVENT_SINGLE_FOLDER_CONTENTS_SYNCED, so listing here would just repeat that refresh. - if (RefreshFolderOperation.EVENT_SINGLE_FOLDER_CONTENTS_SYNCED != event) { - var (currentFile, currentDir) = getCurrentFileAndDirectory() - - if (currentDir == null) { - browseRootForRemovedFolder() - } else { - if (currentFile == null && file?.isFolder == false) { - // currently selected file was removed in the server, and now we know it - currentFile = currentDir - } - if (currentDir.remotePath == syncFolderRemotePath) { - listOfFilesFragment?.listDirectory(currentDir, false) - } - file = currentFile + // EVENT_SINGLE_FOLDER_CONTENTS_SYNCED fires only when the folder's content actually + // changed, and EVENT_SINGLE_FOLDER_SHARES_SYNCED only when a sharee actually changed - + // each is an independent, already-precise signal (RefreshFolderOperation.java). + var (currentFile, currentDir) = getCurrentFileAndDirectory() + + if (currentDir == null) { + browseRootForRemovedFolder() + } else { + if (currentFile == null && file?.isFolder == false) { + // currently selected file was removed in the server, and now we know it + currentFile = currentDir } + if (currentDir.remotePath == syncFolderRemotePath) { + listOfFilesFragment?.listDirectory(currentDir, false) + } + file = currentFile } checkCredentials(syncResult, event) From ee640d2932b91da63a2a5232fb1b5012c5d20005 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Wed, 5 Aug 2026 14:20:21 +0200 Subject: [PATCH 3/7] wip Signed-off-by: alperozturk96 --- .../FileDataStorageManagerExtensions.kt | 19 ++++++++++ .../datamodel/FileDataStorageManager.java | 38 +------------------ .../operations/RefreshFolderOperation.java | 6 +-- .../ui/activity/FileDisplayActivity.kt | 2 +- .../ui/fragment/OCFileListFragment.java | 9 +++-- 5 files changed, 30 insertions(+), 44 deletions(-) diff --git a/app/src/main/java/com/nextcloud/utils/extensions/FileDataStorageManagerExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/FileDataStorageManagerExtensions.kt index 403736d83333..9589f520fe42 100644 --- a/app/src/main/java/com/nextcloud/utils/extensions/FileDataStorageManagerExtensions.kt +++ b/app/src/main/java/com/nextcloud/utils/extensions/FileDataStorageManagerExtensions.kt @@ -10,10 +10,29 @@ package com.nextcloud.utils.extensions import com.nextcloud.client.database.entity.toOCCapability import com.owncloud.android.datamodel.FileDataStorageManager import com.owncloud.android.datamodel.OCFile +import com.owncloud.android.lib.resources.files.model.RemoteFile import com.owncloud.android.lib.resources.shares.OCShare import com.owncloud.android.lib.resources.status.OCCapability import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import kotlin.collections.asSequence + +fun FileDataStorageManager.areShareesChanged(path: String, remoteFiles: List): Boolean { + val newSharees = remoteFiles.asSequence() + .filter { it.remotePath == path } + .flatMap { it.sharees.orEmpty().asSequence() } + .mapNotNull { sharee -> sharee.shareType?.let { "${sharee.userId}:${it.value}" } } + .toSet() + + val existingSharees = getSharesWithForAFile(path, user.accountName).asSequence() + .mapNotNull { share -> share.shareType?.let { "${share.shareWith}:${it.value}" } } + .toSet() + + return newSharees != existingSharees +} + +fun FileDataStorageManager.areShareesChanged(paths: Set, remoteFiles: List): Boolean = + paths.any { path -> areShareesChanged(path, remoteFiles) } suspend fun FileDataStorageManager.saveShares(shares: List, accountName: String) { withContext(Dispatchers.IO) { diff --git a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java index d9c69beedf2b..774272f7b667 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java @@ -49,6 +49,7 @@ import com.nextcloud.utils.date.DateFormatPattern; import com.nextcloud.utils.e2ee.E2EVersionHelper; import com.nextcloud.utils.extensions.DateExtensionsKt; +import com.nextcloud.utils.extensions.FileDataStorageManagerExtensionsKt; import com.nextcloud.utils.extensions.FileExtensionsKt; import com.owncloud.android.MainApp; import com.owncloud.android.datamodel.e2e.v2.decrypted.DecryptedFolderMetadataFile; @@ -1843,12 +1844,7 @@ public boolean saveSharesFromRemoteFile(List shares) { uniquePaths.add(share.getRemotePath()); } - boolean sharesChanged = false; - for (String path : uniquePaths) { - if (shareesForPathChanged(path, shares)) { - sharesChanged = true; - } - } + boolean sharesChanged = FileDataStorageManagerExtensionsKt.areShareesChanged(this, uniquePaths, shares); // Prepare reset operations ArrayList resetOperations = new ArrayList<>(); @@ -1872,36 +1868,6 @@ public boolean saveSharesFromRemoteFile(List shares) { return sharesChanged; } - /** - * Compares the sharees reported for {@code path} in {@code remoteFiles} against the sharees - * currently stored locally for that path, identifying each sharee by (userId, shareType). - */ - private boolean shareesForPathChanged(String path, List remoteFiles) { - Set newSharees = new HashSet<>(); - for (RemoteFile remoteFile : remoteFiles) { - if (!path.equals(remoteFile.getRemotePath()) || remoteFile.getSharees() == null) { - continue; - } - - for (ShareeUser sharee : remoteFile.getSharees()) { - if (sharee == null || sharee.getShareType() == null) { - continue; - } - newSharees.add(sharee.getUserId() + ":" + sharee.getShareType().getValue()); - } - } - - Set existingSharees = new HashSet<>(); - for (OCShare share : getSharesWithForAFile(path, user.getAccountName())) { - if (share.getShareType() == null) { - continue; - } - existingSharees.add(share.getShareWith() + ":" + share.getShareType().getValue()); - } - - return !newSharees.equals(existingSharees); - } - /** * Prepares a list of ContentProviderOperation insert operations based on share information * found in the given iterable of RemoteFile objects. 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 54c1e13887b9..3aaab10afab0 100644 --- a/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java @@ -137,7 +137,7 @@ public class RefreshFolderOperation extends RemoteOperation { /** * 'True' means that the sharees of at least one child of the folder changed */ - private boolean mSharesChanged; + private boolean sharesChanged; /** * 'True' means that Etag will be ignored @@ -312,10 +312,10 @@ protected RemoteOperationResult run(OwnCloudClient client) { } } - mSharesChanged = fileDataStorageManager.saveSharesFromRemoteFile(remoteFiles); + sharesChanged = fileDataStorageManager.saveSharesFromRemoteFile(remoteFiles); } - if (!mSyncFullAccount && mSharesChanged && mLocalFolder != null && !isMetadataSyncWorkerRunning) { + if (!mSyncFullAccount && sharesChanged && mLocalFolder != null && !isMetadataSyncWorkerRunning) { sendLocalBroadcast(EVENT_SINGLE_FOLDER_SHARES_SYNCED, mLocalFolder.getRemotePath(), result); } 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 626a66d48236..2b3957a388e9 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 @@ -570,7 +570,7 @@ class FileDisplayActivity : private fun initFragments() { // First fragment val listOfFiles = this.listOfFilesFragment - if (listOfFiles != null && TextUtils.isEmpty(searchQuery)) { + if (listOfFiles != null && searchQuery.isNullOrEmpty()) { if (!listFragmentJustCreated) { listOfFiles.listDirectory(getCurrentDir(), file, MainApp.isOnlyOnDevice()) } diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java index ebec7bf1bfb1..4b470b74a7b5 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java +++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java @@ -1618,14 +1618,15 @@ public void listDirectory(@Nullable OCFile directory, OCFile file, boolean onlyO updateLayout(); + final var recyclerView = getRecyclerView(); if (file != null) { mAdapter.setHighlightedItem(file); int position = mAdapter.getItemPosition(file); - if (position != -1 && getRecyclerView() != null) { - getRecyclerView().scrollToPosition(position); + if (position != -1 && recyclerView != null) { + recyclerView.scrollToPosition(position); } - } else if (getRecyclerView() != null && (previousDirectory == null || !previousDirectory.equals(directory))) { - getRecyclerView().scrollToPosition(0); + } else if (recyclerView != null && (previousDirectory == null || !previousDirectory.equals(directory))) { + recyclerView.scrollToPosition(0); } } else if (isSearchEventSet(searchEvent)) { handleSearchEvent(searchEvent); From 3936271d0aebb2676d7f2385c65f302088373c15 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Wed, 5 Aug 2026 15:55:31 +0200 Subject: [PATCH 4/7] wip Signed-off-by: alperozturk96 --- .../FileDataStorageManagerExtensions.kt | 95 ++++++++++++++++--- .../datamodel/FileDataStorageManager.java | 2 +- 2 files changed, 84 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/nextcloud/utils/extensions/FileDataStorageManagerExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/FileDataStorageManagerExtensions.kt index 9589f520fe42..265b18367784 100644 --- a/app/src/main/java/com/nextcloud/utils/extensions/FileDataStorageManagerExtensions.kt +++ b/app/src/main/java/com/nextcloud/utils/extensions/FileDataStorageManagerExtensions.kt @@ -7,32 +7,103 @@ package com.nextcloud.utils.extensions +import android.os.RemoteException import com.nextcloud.client.database.entity.toOCCapability import com.owncloud.android.datamodel.FileDataStorageManager import com.owncloud.android.datamodel.OCFile +import com.owncloud.android.db.ProviderMeta.ProviderTableMeta +import com.owncloud.android.lib.common.utils.Log_OC import com.owncloud.android.lib.resources.files.model.RemoteFile import com.owncloud.android.lib.resources.shares.OCShare +import com.owncloud.android.lib.resources.shares.ShareType import com.owncloud.android.lib.resources.status.OCCapability import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import kotlin.collections.asSequence -fun FileDataStorageManager.areShareesChanged(path: String, remoteFiles: List): Boolean { - val newSharees = remoteFiles.asSequence() - .filter { it.remotePath == path } - .flatMap { it.sharees.orEmpty().asSequence() } - .mapNotNull { sharee -> sharee.shareType?.let { "${sharee.userId}:${it.value}" } } - .toSet() +// matches FileDataStorageManager.getSharesWithForAFile, excludes public link shares +private val shareableShareTypes = listOf( + ShareType.USER, + ShareType.GROUP, + ShareType.EMAIL, + ShareType.FEDERATED, + ShareType.FEDERATED_GROUP, + ShareType.ROOM, + ShareType.CIRCLE +) + +// keeps queries well under SQLite's bound-parameter limit +private const val SHARE_PATH_QUERY_CHUNK_SIZE = 400 + +fun FileDataStorageManager.areShareesChanged(remoteFiles: List): Boolean { + if (remoteFiles.isEmpty()) { + return false + } + + val newShareesByPath = remoteFiles.asSequence() + .filter { it.remotePath != null } + .groupBy { it.remotePath as String } + .mapValues { (_, files) -> + files.asSequence() + .flatMap { file -> file.sharees.orEmpty().asSequence() } + .mapNotNull { sharee -> sharee.shareType?.let { "${sharee.userId}:${it.value}" } } + .toSet() + } - val existingSharees = getSharesWithForAFile(path, user.accountName).asSequence() - .mapNotNull { share -> share.shareType?.let { "${share.shareWith}:${it.value}" } } - .toSet() + val existingShareesByPath = queryLocalShareeKeysByPath(newShareesByPath.keys, user.accountName) - return newSharees != existingSharees + return newShareesByPath.keys.any { path -> + newShareesByPath[path].orEmpty() != existingShareesByPath[path].orEmpty() + } } -fun FileDataStorageManager.areShareesChanged(paths: Set, remoteFiles: List): Boolean = - paths.any { path -> areShareesChanged(path, remoteFiles) } +private fun FileDataStorageManager.queryLocalShareeKeysByPath( + paths: Set, + accountName: String +): Map> { + val result = mutableMapOf>() + + paths.toList().chunked(SHARE_PATH_QUERY_CHUNK_SIZE).forEach { chunk -> + queryLocalShareeKeysChunk(chunk, accountName, result) + } + + return result +} + +private fun FileDataStorageManager.queryLocalShareeKeysChunk( + paths: List, + accountName: String, + result: MutableMap> +) { + val pathPlaceholders = paths.joinToString(",") { "?" } + val shareTypeFilter = shareableShareTypes.joinToString(" OR ") { "${ProviderTableMeta.OCSHARES_SHARE_TYPE} = ?" } + val selection = "${ProviderTableMeta.OCSHARES_PATH} IN ($pathPlaceholders) AND " + + "${ProviderTableMeta.OCSHARES_ACCOUNT_OWNER} = ? AND ($shareTypeFilter)" + val selectionArgs = (paths + accountName + shareableShareTypes.map { it.value.toString() }).toTypedArray() + + val cursor = if (contentResolver != null) { + contentResolver.query(ProviderTableMeta.CONTENT_URI_SHARE, null, selection, selectionArgs, null) + } else { + try { + contentProviderClient?.query(ProviderTableMeta.CONTENT_URI_SHARE, null, selection, selectionArgs, null) + } catch (e: RemoteException) { + Log_OC.e(javaClass.simpleName, "Could not get list of shares: ${e.message}", e) + null + } + } + + cursor?.use { + val pathIndex = it.getColumnIndex(ProviderTableMeta.OCSHARES_PATH) + val shareWithIndex = it.getColumnIndex(ProviderTableMeta.OCSHARES_SHARE_WITH) + val shareTypeIndex = it.getColumnIndex(ProviderTableMeta.OCSHARES_SHARE_TYPE) + + while (it.moveToNext()) { + val path = it.getString(pathIndex) ?: continue + val key = "${it.getString(shareWithIndex)}:${it.getInt(shareTypeIndex)}" + result.getOrPut(path) { mutableSetOf() }.add(key) + } + } +} suspend fun FileDataStorageManager.saveShares(shares: List, accountName: String) { withContext(Dispatchers.IO) { diff --git a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java index 774272f7b667..1db87a82ea63 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java @@ -1844,7 +1844,7 @@ public boolean saveSharesFromRemoteFile(List shares) { uniquePaths.add(share.getRemotePath()); } - boolean sharesChanged = FileDataStorageManagerExtensionsKt.areShareesChanged(this, uniquePaths, shares); + boolean sharesChanged = FileDataStorageManagerExtensionsKt.areShareesChanged(this, shares); // Prepare reset operations ArrayList resetOperations = new ArrayList<>(); From 483351fb519f3abe3bf870341a2e43df5efdddf9 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Wed, 5 Aug 2026 16:05:01 +0200 Subject: [PATCH 5/7] wip Signed-off-by: alperozturk96 --- .../nextcloud/client/database/dao/ShareDao.kt | 7 ++ .../client/database/entity/model/ShareeKey.kt | 29 ++++++++ .../FileDataStorageManagerExtensions.kt | 72 ++----------------- 3 files changed, 43 insertions(+), 65 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/client/database/entity/model/ShareeKey.kt diff --git a/app/src/main/java/com/nextcloud/client/database/dao/ShareDao.kt b/app/src/main/java/com/nextcloud/client/database/dao/ShareDao.kt index 61ea4a09c54a..220d28d2c038 100644 --- a/app/src/main/java/com/nextcloud/client/database/dao/ShareDao.kt +++ b/app/src/main/java/com/nextcloud/client/database/dao/ShareDao.kt @@ -12,6 +12,7 @@ import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query import com.nextcloud.client.database.entity.ShareEntity +import com.nextcloud.client.database.entity.model.ShareeKey @Dao interface ShareDao { @@ -21,4 +22,10 @@ interface ShareDao { @Query("DELETE FROM ocshares WHERE owner_share = :accountName") suspend fun clearSharesForAccount(accountName: String) + + @Query( + "SELECT path, shate_with, share_type FROM ocshares " + + "WHERE path IN (:paths) AND owner_share = :accountName AND share_type IN (:shareTypes)" + ) + fun getShareeKeys(paths: List, accountName: String, shareTypes: List): List } diff --git a/app/src/main/java/com/nextcloud/client/database/entity/model/ShareeKey.kt b/app/src/main/java/com/nextcloud/client/database/entity/model/ShareeKey.kt new file mode 100644 index 000000000000..4f7e0b742239 --- /dev/null +++ b/app/src/main/java/com/nextcloud/client/database/entity/model/ShareeKey.kt @@ -0,0 +1,29 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.nextcloud.client.database.entity.model + +import androidx.room.ColumnInfo +import com.owncloud.android.lib.resources.shares.ShareType + +data class ShareeKey( + @ColumnInfo(name = "path") val path: String, + @ColumnInfo(name = "shate_with") val shareWith: String?, + @ColumnInfo(name = "share_type") val shareType: Int +) { + companion object { + val shareableShareTypeValues = listOf( + ShareType.USER, + ShareType.GROUP, + ShareType.EMAIL, + ShareType.FEDERATED, + ShareType.FEDERATED_GROUP, + ShareType.ROOM, + ShareType.CIRCLE + ).map { it.value } + } +} diff --git a/app/src/main/java/com/nextcloud/utils/extensions/FileDataStorageManagerExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/FileDataStorageManagerExtensions.kt index 265b18367784..e0511e6205c8 100644 --- a/app/src/main/java/com/nextcloud/utils/extensions/FileDataStorageManagerExtensions.kt +++ b/app/src/main/java/com/nextcloud/utils/extensions/FileDataStorageManagerExtensions.kt @@ -7,30 +7,16 @@ package com.nextcloud.utils.extensions -import android.os.RemoteException +import com.nextcloud.client.database.entity.model.ShareeKey import com.nextcloud.client.database.entity.toOCCapability import com.owncloud.android.datamodel.FileDataStorageManager import com.owncloud.android.datamodel.OCFile -import com.owncloud.android.db.ProviderMeta.ProviderTableMeta -import com.owncloud.android.lib.common.utils.Log_OC import com.owncloud.android.lib.resources.files.model.RemoteFile import com.owncloud.android.lib.resources.shares.OCShare -import com.owncloud.android.lib.resources.shares.ShareType import com.owncloud.android.lib.resources.status.OCCapability import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -import kotlin.collections.asSequence - -// matches FileDataStorageManager.getSharesWithForAFile, excludes public link shares -private val shareableShareTypes = listOf( - ShareType.USER, - ShareType.GROUP, - ShareType.EMAIL, - ShareType.FEDERATED, - ShareType.FEDERATED_GROUP, - ShareType.ROOM, - ShareType.CIRCLE -) + // keeps queries well under SQLite's bound-parameter limit private const val SHARE_PATH_QUERY_CHUNK_SIZE = 400 @@ -50,61 +36,17 @@ fun FileDataStorageManager.areShareesChanged(remoteFiles: List): Boo .toSet() } - val existingShareesByPath = queryLocalShareeKeysByPath(newShareesByPath.keys, user.accountName) + val existingShareesByPath = newShareesByPath.keys + .chunked(SHARE_PATH_QUERY_CHUNK_SIZE) + .flatMap { chunk -> shareDao.getShareeKeys(chunk, user.accountName, ShareeKey.shareableShareTypeValues) } + .groupBy(ShareeKey::path) { "${it.shareWith}:${it.shareType}" } + .mapValues { (_, keys) -> keys.toSet() } return newShareesByPath.keys.any { path -> newShareesByPath[path].orEmpty() != existingShareesByPath[path].orEmpty() } } -private fun FileDataStorageManager.queryLocalShareeKeysByPath( - paths: Set, - accountName: String -): Map> { - val result = mutableMapOf>() - - paths.toList().chunked(SHARE_PATH_QUERY_CHUNK_SIZE).forEach { chunk -> - queryLocalShareeKeysChunk(chunk, accountName, result) - } - - return result -} - -private fun FileDataStorageManager.queryLocalShareeKeysChunk( - paths: List, - accountName: String, - result: MutableMap> -) { - val pathPlaceholders = paths.joinToString(",") { "?" } - val shareTypeFilter = shareableShareTypes.joinToString(" OR ") { "${ProviderTableMeta.OCSHARES_SHARE_TYPE} = ?" } - val selection = "${ProviderTableMeta.OCSHARES_PATH} IN ($pathPlaceholders) AND " + - "${ProviderTableMeta.OCSHARES_ACCOUNT_OWNER} = ? AND ($shareTypeFilter)" - val selectionArgs = (paths + accountName + shareableShareTypes.map { it.value.toString() }).toTypedArray() - - val cursor = if (contentResolver != null) { - contentResolver.query(ProviderTableMeta.CONTENT_URI_SHARE, null, selection, selectionArgs, null) - } else { - try { - contentProviderClient?.query(ProviderTableMeta.CONTENT_URI_SHARE, null, selection, selectionArgs, null) - } catch (e: RemoteException) { - Log_OC.e(javaClass.simpleName, "Could not get list of shares: ${e.message}", e) - null - } - } - - cursor?.use { - val pathIndex = it.getColumnIndex(ProviderTableMeta.OCSHARES_PATH) - val shareWithIndex = it.getColumnIndex(ProviderTableMeta.OCSHARES_SHARE_WITH) - val shareTypeIndex = it.getColumnIndex(ProviderTableMeta.OCSHARES_SHARE_TYPE) - - while (it.moveToNext()) { - val path = it.getString(pathIndex) ?: continue - val key = "${it.getString(shareWithIndex)}:${it.getInt(shareTypeIndex)}" - result.getOrPut(path) { mutableSetOf() }.add(key) - } - } -} - suspend fun FileDataStorageManager.saveShares(shares: List, accountName: String) { withContext(Dispatchers.IO) { val entities = shares.map { share -> From f5d45806f833b59cdf4bdcd46f0aedb34bb3483b Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Wed, 5 Aug 2026 16:05:35 +0200 Subject: [PATCH 6/7] wip Signed-off-by: alperozturk96 --- .../utils/extensions/FileDataStorageManagerExtensions.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/com/nextcloud/utils/extensions/FileDataStorageManagerExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/FileDataStorageManagerExtensions.kt index e0511e6205c8..90d6f9993881 100644 --- a/app/src/main/java/com/nextcloud/utils/extensions/FileDataStorageManagerExtensions.kt +++ b/app/src/main/java/com/nextcloud/utils/extensions/FileDataStorageManagerExtensions.kt @@ -17,7 +17,6 @@ import com.owncloud.android.lib.resources.status.OCCapability import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext - // keeps queries well under SQLite's bound-parameter limit private const val SHARE_PATH_QUERY_CHUNK_SIZE = 400 From 9835be5daa2f8b935bd5b6c6c00c745b9f5cdbc4 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Wed, 5 Aug 2026 16:06:26 +0200 Subject: [PATCH 7/7] wip Signed-off-by: alperozturk96 --- .../utils/extensions/FileDataStorageManagerExtensions.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/com/nextcloud/utils/extensions/FileDataStorageManagerExtensions.kt b/app/src/main/java/com/nextcloud/utils/extensions/FileDataStorageManagerExtensions.kt index 90d6f9993881..c40b06e43761 100644 --- a/app/src/main/java/com/nextcloud/utils/extensions/FileDataStorageManagerExtensions.kt +++ b/app/src/main/java/com/nextcloud/utils/extensions/FileDataStorageManagerExtensions.kt @@ -17,7 +17,6 @@ import com.owncloud.android.lib.resources.status.OCCapability import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -// keeps queries well under SQLite's bound-parameter limit private const val SHARE_PATH_QUERY_CHUNK_SIZE = 400 fun FileDataStorageManager.areShareesChanged(remoteFiles: List): Boolean {