This commit is contained in:
@@ -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() + "\""
|
||||
|
||||
+41
-12
@@ -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,
|
||||
|
||||
+10
-1
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user