Remove stale photo search entries
Android / test-and-build (push) Successful in 20m38s

This commit is contained in:
Курнат Андрей
2026-07-18 21:07:40 +03:00
parent e53f62b464
commit 54556b76e9
4 changed files with 54 additions and 15 deletions
+2 -2
View File
@@ -137,8 +137,8 @@ android {
testInstrumentationRunner "eu.qsfera.android.utils.OCTestAndroidJUnitRunner"
versionCode = 37
versionName = "1.3.9"
versionCode = 38
versionName = "1.3.10"
buildConfigField "String", gitRemote, "\"" + getGitOriginRemote() + "\""
buildConfigField "String", commitSHA1, "\"" + getLatestGitHash() + "\""
@@ -24,6 +24,7 @@ import eu.qsfera.android.R
import eu.qsfera.android.data.ClientManager
import eu.qsfera.android.data.executeRemoteOperation
import eu.qsfera.android.data.providers.SharedPreferencesProvider
import eu.qsfera.android.domain.exceptions.FileNotFoundException
import eu.qsfera.android.domain.files.FileRepository
import eu.qsfera.android.domain.files.model.OCFile
import eu.qsfera.android.domain.spaces.SpacesRepository
@@ -544,6 +545,7 @@ class CloudHubFragment : Fragment(R.layout.fragment_cloud_hub) {
)
}
val davPrefix = "/remote.php/dav/files/$userId"
val hiddenMedia = hiddenMediaTombstones()
val items = remoteFiles.mapNotNull { remote ->
val mimeType = remote.mimeType ?: return@mapNotNull null
if (!mimeType.startsWith("image/") && !mimeType.startsWith("video/")) return@mapNotNull null
@@ -566,7 +568,7 @@ class CloudHubFragment : Fragment(R.layout.fragment_cloud_hub) {
etag = remote.etag.orEmpty(),
spaceId = spaceReference,
)
}
}.filterNot { it.tombstoneKey in hiddenMedia }
return LoadedMediaPage(items = items, rawResultCount = remoteFiles.size)
}
@@ -822,19 +824,27 @@ class CloudHubFragment : Fragment(R.layout.fragment_cloud_hub) {
viewLifecycleOwner.lifecycleScope.launch {
val deletedItems = withContext(Dispatchers.IO) {
items.filter { item ->
runCatching {
val storedFile = fileRepository.getFileByRemotePath(item.remotePath, accountName, item.spaceId)
if (storedFile?.id != null) {
fileRepository.deleteFiles(listOf(storedFile), removeOnlyLocalCopy = false)
} else {
executeRemoteOperation {
clientManager.getFileService(accountName).removeFile(
remotePath = item.remotePath,
spaceWebDavUrl = spacesRepository.getWebDavUrlForSpace(accountName, item.spaceId),
)
val storedFile = fileRepository.getFileByRemotePath(item.remotePath, accountName, item.spaceId)
val remoteDeletedOrMissing = runCatching {
executeRemoteOperation {
clientManager.getFileService(accountName).removeFile(
remotePath = item.remotePath,
spaceWebDavUrl = spacesRepository.getWebDavUrlForSpace(accountName, item.spaceId),
)
}
}.fold(
onSuccess = { true },
onFailure = { error -> error is FileNotFoundException },
)
if (remoteDeletedOrMissing) {
storedFile?.takeIf { it.id != null }?.let { file ->
runCatching {
fileRepository.deleteFiles(listOf(file), removeOnlyLocalCopy = true)
}
}
}.isSuccess
rememberMediaTombstone(item)
}
remoteDeletedOrMissing
}
}
if (!isAdded) return@launch
@@ -854,6 +864,23 @@ class CloudHubFragment : Fragment(R.layout.fragment_cloud_hub) {
}
}
private fun hiddenMediaTombstones(): Set<String> = preferencesProvider
.getString(PREFERENCE_HIDDEN_MEDIA, null)
.orEmpty()
.lineSequence()
.filter(String::isNotBlank)
.toSet()
private fun rememberMediaTombstone(item: CloudMediaItem) {
val tombstones = LinkedHashSet(hiddenMediaTombstones())
tombstones.remove(item.tombstoneKey)
tombstones.add(item.tombstoneKey)
while (tombstones.size > MAX_HIDDEN_MEDIA) {
tombstones.remove(tombstones.first())
}
preferencesProvider.putString(PREFERENCE_HIDDEN_MEDIA, tombstones.joinToString("\n"))
}
private fun selectableMedia(): List<CloudMediaItem> {
val media = filteredMedia()
return when {
@@ -1014,6 +1041,8 @@ class CloudHubFragment : Fragment(R.layout.fragment_cloud_hub) {
private const val STATE_SELECTION_MODE = "cloud_media_selection_mode"
private const val PREFERENCE_PHOTO_GRID_MODE = "cloud_photo_grid_mode"
private const val PREFERENCE_SHOW_SCREENSHOTS = "cloud_photo_show_screenshots"
private const val PREFERENCE_HIDDEN_MEDIA = "cloud_hidden_media_tombstones"
private const val MAX_HIDDEN_MEDIA = 1_000
fun newInstance(section: CloudSection): CloudHubFragment = CloudHubFragment().apply {
arguments = bundleOf(ARG_SECTION to section.wireValue)
@@ -25,6 +25,7 @@ data class CloudMediaItem(
val isImage: Boolean get() = mimeType.startsWith("image/")
val albumKey: String get() = "${spaceId.orEmpty()}::$parentPath"
val selectionKey: String get() = "${spaceId.orEmpty()}::${webDavHref.ifBlank { remotePath }}"
val tombstoneKey: String get() = "$selectionKey::${etag.ifBlank { "no-etag" }}"
fun toOCFile(owner: String): OCFile = OCFile(
owner = owner,
@@ -44,11 +44,20 @@ class CloudMediaSelectionTest {
assertEquals(listOf(first, second), selection.items)
}
private fun media(path: String, spaceId: String? = null) = CloudMediaItem(
@Test
fun `tombstone changes when a file is uploaded again at the same path`() {
val deleted = media("/Camera/photo.jpg", etag = "old")
val uploadedAgain = media("/Camera/photo.jpg", etag = "new")
assertFalse(deleted.tombstoneKey == uploadedAgain.tombstoneKey)
}
private fun media(path: String, spaceId: String? = null, etag: String = "") = CloudMediaItem(
remotePath = path,
mimeType = "image/jpeg",
size = 1L,
modifiedAt = 1L,
etag = etag,
spaceId = spaceId,
)
}