From cb088eb263a832170948c0a54afa75c85edea792 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Mon, 15 Dec 2025 14:26:23 +0100 Subject: [PATCH 1/7] fix: search task root sub dir content fetch Signed-off-by: alperozturk --- .../ui/fragment/OCFileListSearchTask.kt | 47 +++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListSearchTask.kt b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListSearchTask.kt index ef938616dd6c..44ea3d481c05 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListSearchTask.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListSearchTask.kt @@ -11,6 +11,7 @@ package com.owncloud.android.ui.fragment import android.annotation.SuppressLint import android.app.Activity import android.content.ContentValues +import android.content.Context import androidx.lifecycle.lifecycleScope import com.nextcloud.client.account.User import com.nextcloud.client.preferences.AppPreferences @@ -40,8 +41,9 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeoutOrNull import java.lang.ref.WeakReference +import java.util.concurrent.TimeUnit -@Suppress("LongParameterList", "ReturnCount", "TooGenericExceptionCaught") +@Suppress("LongParameterList", "ReturnCount", "TooGenericExceptionCaught", "DEPRECATION", "MagicNumber") @SuppressLint("NotifyDataSetChanged") class OCFileListSearchTask( containerActivity: FileFragment.ContainerActivity, @@ -64,7 +66,6 @@ class OCFileListSearchTask( private var job: Job? = null - @Suppress("TooGenericExceptionCaught", "DEPRECATION", "ReturnCount") fun execute() { Log_OC.d(TAG, "search task running, query: ${event.searchType}") val fragment = fragmentReference.get() ?: return @@ -105,6 +106,14 @@ class OCFileListSearchTask( fragment.adapter.files } + val subDirectories = newList + .filter { it.isFolder } + .sortedBy { it.fileId } + + subDirectories.forEach { dir -> + fetchRootSubDirContent(dir, fileDataStorageManager, fragment.context) + } + val sortedNewList = sortSearchData(newList, searchType, null, setNewSortOrder = { fragment.adapter.setSortOrder(it) }) @@ -126,6 +135,39 @@ class OCFileListSearchTask( fun isFinished(): Boolean = job?.isCompleted == true + private suspend fun fetchRootSubDirContent( + folder: OCFile, + storageManager: FileDataStorageManager?, + context: Context? + ) = withContext(Dispatchers.IO) { + if (context == null || storageManager == null) { + Log_OC.e(TAG, "sub directory content cannot be fetched, context or storage manager is null") + return@withContext + } + + val currentSyncTime = System.currentTimeMillis() + val shouldIgnoreETag = (currentSyncTime - folder.lastSyncDateForProperties) > TimeUnit.MINUTES.toMillis(5) + + Log_OC.d(TAG, "fetching content for: " + folder.remotePath + " ignore eTag: " + shouldIgnoreETag) + + val operation = + RefreshFolderOperation( + folder, + currentSyncTime, + shouldIgnoreETag, + shouldIgnoreETag, + storageManager, + currentUser, + context + ) + val result = operation.execute(currentUser, context) + if (result.isSuccess) { + Log_OC.d(TAG, "sub directory content is fetched") + } else { + Log_OC.e(TAG, "sub directory content cannot be fetched") + } + } + private suspend fun loadCachedDbFiles(searchType: SearchRemoteOperation.SearchType): List { val storage = fileDataStorageManager ?: return emptyList() return if (searchType == SearchRemoteOperation.SearchType.SHARED_FILTER) { @@ -137,7 +179,6 @@ class OCFileListSearchTask( }.mapNotNull { storage.createFileInstance(it) } } - @Suppress("DEPRECATION") private suspend fun fetchRemoteResults(): RemoteOperationResult>? { val fragment = fragmentReference.get() ?: return null val context = fragment.context ?: return null From 74c9dc126d243a908ac12725218130885252f54b Mon Sep 17 00:00:00 2001 From: alperozturk Date: Mon, 15 Dec 2025 14:28:18 +0100 Subject: [PATCH 2/7] fix: search task root sub dir content fetch Signed-off-by: alperozturk --- .../com/owncloud/android/ui/fragment/OCFileListSearchTask.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListSearchTask.kt b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListSearchTask.kt index 44ea3d481c05..d14f5648088a 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListSearchTask.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListSearchTask.kt @@ -111,7 +111,7 @@ class OCFileListSearchTask( .sortedBy { it.fileId } subDirectories.forEach { dir -> - fetchRootSubDirContent(dir, fileDataStorageManager, fragment.context) + fetchDirContent(dir, fileDataStorageManager, fragment.context) } val sortedNewList = sortSearchData(newList, searchType, null, setNewSortOrder = { @@ -135,7 +135,7 @@ class OCFileListSearchTask( fun isFinished(): Boolean = job?.isCompleted == true - private suspend fun fetchRootSubDirContent( + private suspend fun fetchDirContent( folder: OCFile, storageManager: FileDataStorageManager?, context: Context? From 78638d7c16c19fe93a6166beba1a05f9fbc7a656 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Wed, 17 Dec 2025 14:06:41 +0100 Subject: [PATCH 3/7] use saveFileWithParent Signed-off-by: alperozturk --- .../datamodel/FileDataStorageManager.java | 47 +++++++++++++------ .../operations/RefreshFolderOperation.java | 4 ++ .../ui/fragment/OCFileListSearchTask.kt | 44 +---------------- 3 files changed, 38 insertions(+), 57 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 01508657858e..bf07037568d7 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java @@ -547,15 +547,28 @@ public boolean saveFile(OCFile ocFile) { } /** - * traverses a files parent tree to be able to store a file with its parents. Throws a - * RemoteOperationFailedException in case the parent can't be retrieved. + * Ensures that an {@link OCFile} and all of its parent folders are stored locally. + *

+ * If the file has no parent ID and is not the root folder, this method recursively: + *

    + *
  • Resolves the parent path
  • + *
  • Loads the parent from local storage or fetches it from the server
  • + *
  • Saves all missing parent folders
  • + *
  • Assigns the resolved parent ID to the file
  • + *
+ * + * @param ocFile the file to be saved together with its parent hierarchy + * @param context Android context used for remote operations * - * @param ocFile the file - * @param context the app context - * @return the parent file + * @return the same {@link OCFile} instance with a valid parent ID + * + * @throws RemoteOperationFailedException if a parent folder cannot be retrieved + * from the server */ public OCFile saveFileWithParent(OCFile ocFile, Context context) { if (ocFile.getParentId() == 0 && !OCFile.ROOT_PATH.equals(ocFile.getRemotePath())) { + Log_OC.d(TAG, "saving file with parents: " + ocFile.getRemotePath()); + String remotePath = ocFile.getRemotePath(); String parentPath = remotePath.substring(0, remotePath.lastIndexOf(ocFile.getFileName())); @@ -563,19 +576,22 @@ public OCFile saveFileWithParent(OCFile ocFile, Context context) { OCFile returnFile; if (parentFile == null) { - // remote request - ReadFileRemoteOperation operation = new ReadFileRemoteOperation(parentPath); - // TODO Deprecated - RemoteOperationResult result = operation.execute(getUser(), context); - if (result.isSuccess()) { - OCFile remoteFolder = FileStorageUtils.fillOCFile((RemoteFile) result.getData().get(0)); - - returnFile = saveFileWithParent(remoteFolder, context); + Log_OC.d(TAG, "Parent not found locally, fetching: " + parentPath); + + final var operation = new ReadFileRemoteOperation(parentPath); + final var result = operation.execute(getUser(), context); + + if (result.isSuccess() && result.getData().get(0) instanceof RemoteFile remoteFile) { + OCFile folder = FileStorageUtils.fillOCFile(remoteFile); + Log_OC.d(TAG, "Fetched parent folder: " + folder); + returnFile = saveFileWithParent(folder, context); } else { Exception exception = result.getException(); String message = "Error during saving file with parents: " + ocFile.getRemotePath() + " / " + result.getLogMessage(context); + Log_OC.e(TAG, message); + if (exception != null) { throw new RemoteOperationFailedException(message, exception); } else { @@ -583,10 +599,13 @@ public OCFile saveFileWithParent(OCFile ocFile, Context context) { } } } else { + Log_OC.d(TAG, "parent file exists, calling saveFileWithParent: " + ocFile.getRemotePath()); returnFile = saveFileWithParent(parentFile, context); } - ocFile.setParentId(returnFile.getFileId()); + long parentId = returnFile.getFileId(); + Log_OC.d(TAG, "saving parent id of: " + ocFile.getRemotePath() + " with: " + parentId); + ocFile.setParentId(parentId); saveFile(ocFile); } 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 43885e40ab89..d299a997b31e 100644 --- a/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java @@ -267,6 +267,10 @@ protected RemoteOperationResult run(OwnCloudClient client) { updateUserProfile(); } + fileDataStorageManager.saveFileWithParent(mLocalFolder, mContext); + + Log_OC.d(TAG, "STEP 1 --- refreshing folder " + mLocalFolder.getRemotePath()); + result = checkForChanges(client); if (result.isSuccess()) { diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListSearchTask.kt b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListSearchTask.kt index d14f5648088a..2e96e5ac4b84 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListSearchTask.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListSearchTask.kt @@ -11,7 +11,6 @@ package com.owncloud.android.ui.fragment import android.annotation.SuppressLint import android.app.Activity import android.content.ContentValues -import android.content.Context import androidx.lifecycle.lifecycleScope import com.nextcloud.client.account.User import com.nextcloud.client.preferences.AppPreferences @@ -41,7 +40,6 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeoutOrNull import java.lang.ref.WeakReference -import java.util.concurrent.TimeUnit @Suppress("LongParameterList", "ReturnCount", "TooGenericExceptionCaught", "DEPRECATION", "MagicNumber") @SuppressLint("NotifyDataSetChanged") @@ -106,14 +104,6 @@ class OCFileListSearchTask( fragment.adapter.files } - val subDirectories = newList - .filter { it.isFolder } - .sortedBy { it.fileId } - - subDirectories.forEach { dir -> - fetchDirContent(dir, fileDataStorageManager, fragment.context) - } - val sortedNewList = sortSearchData(newList, searchType, null, setNewSortOrder = { fragment.adapter.setSortOrder(it) }) @@ -135,39 +125,6 @@ class OCFileListSearchTask( fun isFinished(): Boolean = job?.isCompleted == true - private suspend fun fetchDirContent( - folder: OCFile, - storageManager: FileDataStorageManager?, - context: Context? - ) = withContext(Dispatchers.IO) { - if (context == null || storageManager == null) { - Log_OC.e(TAG, "sub directory content cannot be fetched, context or storage manager is null") - return@withContext - } - - val currentSyncTime = System.currentTimeMillis() - val shouldIgnoreETag = (currentSyncTime - folder.lastSyncDateForProperties) > TimeUnit.MINUTES.toMillis(5) - - Log_OC.d(TAG, "fetching content for: " + folder.remotePath + " ignore eTag: " + shouldIgnoreETag) - - val operation = - RefreshFolderOperation( - folder, - currentSyncTime, - shouldIgnoreETag, - shouldIgnoreETag, - storageManager, - currentUser, - context - ) - val result = operation.execute(currentUser, context) - if (result.isSuccess) { - Log_OC.d(TAG, "sub directory content is fetched") - } else { - Log_OC.e(TAG, "sub directory content cannot be fetched") - } - } - private suspend fun loadCachedDbFiles(searchType: SearchRemoteOperation.SearchType): List { val storage = fileDataStorageManager ?: return emptyList() return if (searchType == SearchRemoteOperation.SearchType.SHARED_FILTER) { @@ -179,6 +136,7 @@ class OCFileListSearchTask( }.mapNotNull { storage.createFileInstance(it) } } + @Suppress("DEPRECATION") private suspend fun fetchRemoteResults(): RemoteOperationResult>? { val fragment = fragmentReference.get() ?: return null val context = fragment.context ?: return null From cb919ef5b0e1403ece1d488ea04ed5470c9478c1 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Wed, 17 Dec 2025 14:09:34 +0100 Subject: [PATCH 4/7] use saveFileWithParent Signed-off-by: alperozturk --- .../owncloud/android/operations/RefreshFolderOperation.java | 3 +-- 1 file changed, 1 insertion(+), 2 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 d299a997b31e..30409c523140 100644 --- a/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java @@ -267,10 +267,9 @@ protected RemoteOperationResult run(OwnCloudClient client) { updateUserProfile(); } + // check first parent dirs are saved fileDataStorageManager.saveFileWithParent(mLocalFolder, mContext); - Log_OC.d(TAG, "STEP 1 --- refreshing folder " + mLocalFolder.getRemotePath()); - result = checkForChanges(client); if (result.isSuccess()) { From 3159e7c10f024789d19013866e36587d5666649c Mon Sep 17 00:00:00 2001 From: alperozturk Date: Thu, 18 Dec 2025 12:38:45 +0100 Subject: [PATCH 5/7] revert Signed-off-by: alperozturk --- .../com/owncloud/android/ui/fragment/OCFileListSearchTask.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListSearchTask.kt b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListSearchTask.kt index 2e96e5ac4b84..ef938616dd6c 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListSearchTask.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListSearchTask.kt @@ -41,7 +41,7 @@ import kotlinx.coroutines.withContext import kotlinx.coroutines.withTimeoutOrNull import java.lang.ref.WeakReference -@Suppress("LongParameterList", "ReturnCount", "TooGenericExceptionCaught", "DEPRECATION", "MagicNumber") +@Suppress("LongParameterList", "ReturnCount", "TooGenericExceptionCaught") @SuppressLint("NotifyDataSetChanged") class OCFileListSearchTask( containerActivity: FileFragment.ContainerActivity, @@ -64,6 +64,7 @@ class OCFileListSearchTask( private var job: Job? = null + @Suppress("TooGenericExceptionCaught", "DEPRECATION", "ReturnCount") fun execute() { Log_OC.d(TAG, "search task running, query: ${event.searchType}") val fragment = fragmentReference.get() ?: return From eb71854ef43d56e96e2d2b5482c2b92092505a22 Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 19 Dec 2025 11:18:52 +0100 Subject: [PATCH 6/7] fix etag saving logic Signed-off-by: alperozturk --- .../datamodel/FileDataStorageManager.java | 5 ++++- .../operations/RefreshFolderOperation.java | 20 +++++++------------ 2 files changed, 11 insertions(+), 14 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 bf07037568d7..7a4f2e51ce88 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java @@ -51,7 +51,6 @@ import com.owncloud.android.MainApp; import com.owncloud.android.db.ProviderMeta.ProviderTableMeta; import com.owncloud.android.lib.common.network.WebdavEntry; -import com.owncloud.android.lib.common.operations.RemoteOperationResult; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation; import com.owncloud.android.lib.resources.files.model.FileLockType; @@ -497,9 +496,13 @@ public List getFolderImages(OCFile folder, boolean onlyOnDevice) { } public boolean saveFile(OCFile ocFile) { + Log_OC.d(TAG, "saving file: " + ocFile.getRemotePath() + " etag: " + ocFile.getEtag()); + boolean overridden = false; final ContentValues cv = createContentValuesForFile(ocFile); if (ocFile.isFolder()) { + // only refresh folder operation must update eTag otherwise content of the folder may stay as outdated + cv.remove(ProviderTableMeta.FILE_ETAG); cv.remove(ProviderTableMeta.FILE_STORAGE_PATH); } 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 30409c523140..f760af5e0538 100644 --- a/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java +++ b/app/src/main/java/com/owncloud/android/operations/RefreshFolderOperation.java @@ -267,9 +267,6 @@ protected RemoteOperationResult run(OwnCloudClient client) { updateUserProfile(); } - // check first parent dirs are saved - fileDataStorageManager.saveFileWithParent(mLocalFolder, mContext); - result = checkForChanges(client); if (result.isSuccess()) { @@ -403,11 +400,6 @@ private void updatePredefinedStatus(ArbitraryDataProvider arbitraryDataProvider) private RemoteOperationResult checkForChanges(OwnCloudClient client) { mRemoteFolderChanged = true; - if (isMetadataSyncWorkerRunning) { - Log_OC.d(TAG, "Skipping eTag check since metadata worker already did"); - return new RemoteOperationResult<>(ResultCode.OK); - } - RemoteOperationResult result; String remotePath = mLocalFolder.getRemotePath(); @@ -417,16 +409,18 @@ private RemoteOperationResult checkForChanges(OwnCloudClient client) { result = new ReadFileRemoteOperation(remotePath).execute(client); if (result.isSuccess()) { - if (!mIgnoreETag && result.getData().get(0) instanceof RemoteFile remoteFile) { + OCFile remoteFolder = FileStorageUtils.fillOCFile((RemoteFile) result.getData().get(0)); + + if (!mIgnoreETag) { // check if remote and local folder are different - String remoteFolderETag = remoteFile.getEtag(); + String remoteFolderETag = remoteFolder.getEtag(); if (remoteFolderETag != null) { String localFolderEtag = mLocalFolder.getEtag(); mRemoteFolderChanged = StringExtensionsKt.eTagChanged(remoteFolderETag, localFolderEtag); Log_OC.d( TAG, "📂 eTag check\n" + - " Path: " + remoteFile.getRemotePath() + "\n" + + " Path: " + remoteFolder.getRemotePath() + "\n" + " Local eTag: " + localFolderEtag + "\n" + " Remote eTag: " + remoteFolderETag + "\n" + " Changed: " + mRemoteFolderChanged @@ -505,7 +499,7 @@ private void synchronizeData(List folderAndFiles) { mLocalFolder = fileDataStorageManager.getFileByPath(mLocalFolder.getRemotePath()); if (mLocalFolder == null) { - Log_OC.d(TAG,"mLocalFolder cannot be null"); + Log_OC.e(TAG,"mLocalFolder cannot be null"); return; } @@ -514,7 +508,7 @@ private void synchronizeData(List folderAndFiles) { remoteFolder.setParentId(mLocalFolder.getParentId()); remoteFolder.setFileId(mLocalFolder.getFileId()); - Log_OC.d(TAG, "Remote folder " + mLocalFolder.getRemotePath() + " changed - starting update of local data "); + Log_OC.d(TAG, "Remote folder path: " + mLocalFolder.getRemotePath() + " changed - starting update of local data "); List updatedFiles = new ArrayList<>(folderAndFiles.size() - 1); mFilesToSyncContents.clear(); From ade6bccfde2d06b45ef04d730a46fe1ea1e30c7d Mon Sep 17 00:00:00 2001 From: alperozturk Date: Fri, 19 Dec 2025 11:23:12 +0100 Subject: [PATCH 7/7] fix etag saving logic Signed-off-by: alperozturk --- .../com/owncloud/android/datamodel/FileDataStorageManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 7a4f2e51ce88..0b0bba6c6812 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java +++ b/app/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.java @@ -496,7 +496,7 @@ public List getFolderImages(OCFile folder, boolean onlyOnDevice) { } public boolean saveFile(OCFile ocFile) { - Log_OC.d(TAG, "saving file: " + ocFile.getRemotePath() + " etag: " + ocFile.getEtag()); + Log_OC.d(TAG, "saving file: " + ocFile.getRemotePath()); boolean overridden = false; final ContentValues cv = createContentValuesForFile(ocFile);