From fa42242fefb131f5f259ae30bd141b644684a908 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Fri, 2 Oct 2026 16:04:54 +0200 Subject: [PATCH 1/2] improve unified share fetch Signed-off-by: alperozturk96 --- .../ui/fragment/SharedListFragmentIT.kt | 2 +- .../utils/share/UnifiedShareSharees.kt | 18 ++++----- .../ui/adapter/OCShareToOCFileConverter.kt | 40 +++++++++++-------- .../ui/fragment/OCFileListSearchTask.kt | 26 +++++++----- 4 files changed, 49 insertions(+), 37 deletions(-) diff --git a/app/src/androidTest/java/com/owncloud/android/ui/fragment/SharedListFragmentIT.kt b/app/src/androidTest/java/com/owncloud/android/ui/fragment/SharedListFragmentIT.kt index ace51a654c22..38eb520e45f2 100644 --- a/app/src/androidTest/java/com/owncloud/android/ui/fragment/SharedListFragmentIT.kt +++ b/app/src/androidTest/java/com/owncloud/android/ui/fragment/SharedListFragmentIT.kt @@ -168,7 +168,7 @@ internal class SharedListFragmentIT : AbstractIT() { val newList = runBlocking { OCShareToOCFileConverter - .parseAndSaveShares(listOf(), shares, storageManager, user.accountName) + .parseAndSaveShares(listOf(), shares, storageManager, user) } fragment.adapter.run { prepareForSearchData(storageManager, SearchType.SHARED_FILTER) diff --git a/app/src/main/java/com/nextcloud/utils/share/UnifiedShareSharees.kt b/app/src/main/java/com/nextcloud/utils/share/UnifiedShareSharees.kt index abd6bd70ab68..3b95d32c6b9d 100644 --- a/app/src/main/java/com/nextcloud/utils/share/UnifiedShareSharees.kt +++ b/app/src/main/java/com/nextcloud/utils/share/UnifiedShareSharees.kt @@ -71,18 +71,16 @@ object UnifiedShareSharees { } } - private suspend fun supportsUnifiedShare(accountName: String, credentials: ServerCredentials): Boolean { - unifiedShareSupport[accountName]?.let { return it } - - // a failed capability request stays uncached so that the next listing can resolve it again - val supported = runCatching { credentials.supportsUnifiedShare() }.getOrNull() ?: return false - unifiedShareSupport[accountName] = supported - - return supported - } + private suspend fun supportsUnifiedShare(accountName: String, credentials: ServerCredentials): Boolean = + unifiedShareSupport[accountName] + ?: runCatching { credentials.supportsUnifiedShare() } + .getOrNull() + ?.also { unifiedShareSupport[accountName] = it } + ?: false private suspend fun ShareAvatarRepository.fetchSharees(file: OCFile) { - file.sharees = fetchShareAvatars(file.localId.toString())?.toAvatarSharees().orEmpty() + val shares = fetchShareAvatars(file.localId.toString()) ?: return + file.sharees = shares.toAvatarSharees() } private fun List.toAvatarSharees(): List = asSequence() diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/OCShareToOCFileConverter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/OCShareToOCFileConverter.kt index 9166d438168b..1a7876ec41d8 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/OCShareToOCFileConverter.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/OCShareToOCFileConverter.kt @@ -8,7 +8,9 @@ */ package com.owncloud.android.ui.adapter +import com.nextcloud.client.account.User import com.nextcloud.utils.extensions.saveShares +import com.nextcloud.utils.share.UnifiedShareSharees import com.owncloud.android.datamodel.FileDataStorageManager import com.owncloud.android.datamodel.OCFile import com.owncloud.android.lib.resources.shares.OCShare @@ -49,7 +51,7 @@ object OCShareToOCFileConverter { cachedFiles: List, data: List, storageManager: FileDataStorageManager, - accountName: String + user: User ): List = withContext(Dispatchers.IO) { if (data.isEmpty()) { return@withContext emptyList() @@ -65,27 +67,31 @@ object OCShareToOCFileConverter { } if (newShares.isEmpty()) { + UnifiedShareSharees.fill(user, cachedFiles) return@withContext cachedFiles } - val files = buildOCFilesFromShares(newShares, storageManager) - val baseSavePath = FileStorageUtils.getSavePath(accountName) - - val newFiles = files.map { file -> - if (!file.isFolder && (file.storagePath == null || !File(file.storagePath).exists())) { - val fullPath = baseSavePath + file.decryptedRemotePath - val candidate = File(fullPath) - if (candidate.exists()) { - file.storagePath = candidate.absolutePath - file.lastSyncDateForData = candidate.lastModified() - } - } - storageManager.saveFile(file) - file + val baseSavePath = FileStorageUtils.getSavePath(user.accountName) + val newFiles = buildOCFilesFromShares(newShares, storageManager).onEach { it.resolveStoragePath(baseSavePath) } + val files = (cachedFiles + newFiles).distinctBy { it.remotePath } + + UnifiedShareSharees.fill(user, files) + newFiles.forEach(storageManager::saveFile) + storageManager.saveShares(newShares, user.accountName) + + files + } + + private fun OCFile.resolveStoragePath(baseSavePath: String) { + if (isFolder || (storagePath != null && File(storagePath).exists())) { + return } - storageManager.saveShares(newShares, accountName) - (cachedFiles + newFiles).distinctBy { it.remotePath } + val candidate = File(baseSavePath + decryptedRemotePath) + if (candidate.exists()) { + storagePath = candidate.absolutePath + lastSyncDateForData = candidate.lastModified() + } } private fun buildOCFile(path: String, shares: List, storageManager: FileDataStorageManager): OCFile { 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 8afb099edd8e..7bd6762d7a20 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 @@ -133,8 +133,8 @@ class OCFileListSearchTask( sortedFilesInDb, resultData, storageManager, - currentUser.accountName - ).also { UnifiedShareSharees.fill(currentUser, it) } + currentUser + ) } else { parseAndSaveVirtuals(resultData, fragment) } @@ -211,14 +211,12 @@ class OCFileListSearchTask( val resultFiles = ArrayList() var cachedClient: Account? = null - for (obj in data) { + val files = data.filterIsInstance().mapNotNull(::toLocalOCFile) + UnifiedShareSharees.fill(currentUser, files) + + for (file in files) { try { - val remoteFile = (obj as? RemoteFile) ?: continue - var ocFile = FileStorageUtils.fillOCFile(remoteFile) - FileStorageUtils.searchForLocalFileInDefaultPath(ocFile, currentUser.accountName) - resolveLocalFileId(ocFile) - UnifiedShareSharees.fill(currentUser, listOf(ocFile)) - ocFile = storageManager.saveFileWithParent(ocFile, activity) + var ocFile = storageManager.saveFileWithParent(file, activity) ocFile = handleEncryptionIfNeeded(ocFile, storageManager, activity) { cachedClient ?: currentUser.toPlatformAccount().also { cachedClient = it } } @@ -291,6 +289,16 @@ class OCFileListSearchTask( return fileDataStorage.saveFileWithParent(ocFile, activity) } + private fun toLocalOCFile(remoteFile: RemoteFile): OCFile? = try { + FileStorageUtils.fillOCFile(remoteFile).also { + FileStorageUtils.searchForLocalFileInDefaultPath(it, currentUser.accountName) + resolveLocalFileId(it) + } + } catch (e: Exception) { + Log_OC.e(TAG, "toLocalOCFile():", e) + null + } + private fun resolveLocalFileId(ocFile: OCFile) { if (ocFile.fileId != -1L) return val localFile = storageManager.getFileByLocalId(ocFile.localId) ?: return From cbe16a224e90d06ac416e5e96cf049af29e7231a Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Mon, 5 Oct 2026 10:26:20 +0200 Subject: [PATCH 2/2] expire cache in 10 mins so that client can update itself when in foreground Signed-off-by: alperozturk96 --- .../utils/share/UnifiedShareSharees.kt | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/nextcloud/utils/share/UnifiedShareSharees.kt b/app/src/main/java/com/nextcloud/utils/share/UnifiedShareSharees.kt index 3b95d32c6b9d..4ae2a2c2c95b 100644 --- a/app/src/main/java/com/nextcloud/utils/share/UnifiedShareSharees.kt +++ b/app/src/main/java/com/nextcloud/utils/share/UnifiedShareSharees.kt @@ -11,6 +11,7 @@ import com.nextcloud.android.common.ui.network.auth.ServerCredentials import com.nextcloud.android.common.ui.share.avatar.ShareAvatarRepository import com.nextcloud.android.common.ui.share.model.api.share.Share import com.nextcloud.client.account.User +import com.nextcloud.client.core.ClockImpl import com.nextcloud.utils.extensions.supportsUnifiedShare import com.nextcloud.utils.extensions.toServerCredentials import com.owncloud.android.datamodel.OCFile @@ -24,6 +25,9 @@ import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.withPermit import kotlinx.coroutines.withContext import java.util.concurrent.ConcurrentHashMap +import kotlin.time.Duration +import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.minutes /** * Replaces the sharees PROPFIND reported with the ones the unified share API reports, at the point where the files @@ -35,8 +39,10 @@ import java.util.concurrent.ConcurrentHashMap */ object UnifiedShareSharees { private const val MAX_CONCURRENT_REQUESTS = 8 + private val UNIFIED_SHARE_SUPPORT_CACHE_TIME = 10.minutes - private val unifiedShareSupport = ConcurrentHashMap() + private val clock = ClockImpl() + private val unifiedShareSupport = ConcurrentHashMap>() suspend fun fill(user: User, files: List) { if (files.isEmpty()) { @@ -72,12 +78,20 @@ object UnifiedShareSharees { } private suspend fun supportsUnifiedShare(accountName: String, credentials: ServerCredentials): Boolean = - unifiedShareSupport[accountName] + cachedUnifiedShareSupport(accountName) ?: runCatching { credentials.supportsUnifiedShare() } .getOrNull() - ?.also { unifiedShareSupport[accountName] = it } + ?.also { unifiedShareSupport[accountName] = Pair(timeSinceBoot(), it) } ?: false + private fun cachedUnifiedShareSupport(accountName: String): Boolean? { + val (checkedAt, isSupported) = unifiedShareSupport[accountName] ?: return null + val isExpired = timeSinceBoot() - checkedAt >= UNIFIED_SHARE_SUPPORT_CACHE_TIME + return if (isExpired) null else isSupported + } + + private fun timeSinceBoot(): Duration = clock.millisSinceBoot.milliseconds + private suspend fun ShareAvatarRepository.fetchSharees(file: OCFile) { val shares = fetchShareAvatars(file.localId.toString()) ?: return file.sharees = shares.toAvatarSharees()