Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
30 changes: 21 additions & 9 deletions app/src/main/java/com/nextcloud/utils/share/UnifiedShareSharees.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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<String, Boolean>()
private val clock = ClockImpl()
private val unifiedShareSupport = ConcurrentHashMap<String, Pair<Duration, Boolean>>()

suspend fun fill(user: User, files: List<OCFile>) {
if (files.isEmpty()) {
Expand Down Expand Up @@ -71,18 +77,24 @@ object UnifiedShareSharees {
}
}

private suspend fun supportsUnifiedShare(accountName: String, credentials: ServerCredentials): Boolean {
unifiedShareSupport[accountName]?.let { return it }
private suspend fun supportsUnifiedShare(accountName: String, credentials: ServerCredentials): Boolean =
cachedUnifiedShareSupport(accountName)
?: runCatching { credentials.supportsUnifiedShare() }
.getOrNull()
?.also { unifiedShareSupport[accountName] = Pair(timeSinceBoot(), it) }
?: false

// 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 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) {
file.sharees = fetchShareAvatars(file.localId.toString())?.toAvatarSharees().orEmpty()
val shares = fetchShareAvatars(file.localId.toString()) ?: return

@alperozturk96 alperozturk96 Oct 5, 2026 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should just return not with empty thus we don't override via false data

file.sharees = shares.toAvatarSharees()
}

private fun List<Share>.toAvatarSharees(): List<ShareeUser> = asSequence()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -49,7 +51,7 @@ object OCShareToOCFileConverter {
cachedFiles: List<OCFile>,
data: List<Any>,
storageManager: FileDataStorageManager,
accountName: String
user: User
): List<OCFile> = withContext(Dispatchers.IO) {
if (data.isEmpty()) {
return@withContext emptyList()
Expand All @@ -65,27 +67,31 @@ object OCShareToOCFileConverter {
}

if (newShares.isEmpty()) {
UnifiedShareSharees.fill(user, cachedFiles)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adds missing call

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<OCShare>, storageManager: FileDataStorageManager): OCFile {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,8 @@ class OCFileListSearchTask(
sortedFilesInDb,
resultData,
storageManager,
currentUser.accountName
).also { UnifiedShareSharees.fill(currentUser, it) }
currentUser
)
} else {
parseAndSaveVirtuals(resultData, fragment)
}
Expand Down Expand Up @@ -211,14 +211,12 @@ class OCFileListSearchTask(
val resultFiles = ArrayList<OCFile>()
var cachedClient: Account? = null

for (obj in data) {
val files = data.filterIsInstance<RemoteFile>().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 }
}
Expand Down Expand Up @@ -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
Expand Down
Loading