Skip to content

Commit

Permalink
vaults: fixed moved out item getting duplicated, fixed renaming item
Browse files Browse the repository at this point in the history
  • Loading branch information
deckerst committed Feb 20, 2023
1 parent a8159f9 commit df53b91
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 81 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import java.util.*

class ImageOpStreamHandler(private val activity: Activity, private val arguments: Any?) : EventChannel.StreamHandler {
private val ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
Expand Down Expand Up @@ -219,18 +220,20 @@ class ImageOpStreamHandler(private val activity: Activity, private val arguments
entriesToNewName[AvesEntry(rawEntry)] = newName
}

// assume same provider for all entries
val firstEntry = entriesToNewName.keys.first()
val provider = getProvider(firstEntry.uri)
if (provider == null) {
error("rename-provider", "failed to find provider for entry=$firstEntry", null)
return
val byProvider = entriesToNewName.entries.groupBy { kv -> getProvider(kv.key.uri) }
for ((provider, entryList) in byProvider) {
if (provider == null) {
error("rename-provider", "failed to find provider for entry=${entryList.firstOrNull()}", null)
return
}

val entryMap = mapOf(*entryList.map { Pair(it.key, it.value) }.toTypedArray())
provider.renameMultiple(activity, entryMap, ::isCancelledOp, object : ImageOpCallback {
override fun onSuccess(fields: FieldMap) = success(fields)
override fun onFailure(throwable: Throwable) = error("rename-failure", "failed to rename", throwable.message)
})
}

provider.renameMultiple(activity, entriesToNewName, ::isCancelledOp, object : ImageOpCallback {
override fun onSuccess(fields: FieldMap) = success(fields)
override fun onFailure(throwable: Throwable) = error("rename-failure", "failed to rename", throwable.message)
})
endOfStream()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package deckers.thibault.aves.model.provider

import android.app.Activity
import android.content.Context
import android.content.ContextWrapper
import android.net.Uri
Expand Down Expand Up @@ -53,6 +54,26 @@ internal class FileImageProvider : ImageProvider() {
throw Exception("failed to delete entry with uri=$uri path=$path")
}

override suspend fun renameSingle(
activity: Activity,
mimeType: String,
oldMediaUri: Uri,
oldPath: String,
newFile: File,
): FieldMap {
Log.d(LOG_TAG, "rename file at path=$oldPath")
val renamed = File(oldPath).renameTo(newFile)
if (!renamed) {
throw Exception("failed to rename file at path=$oldPath")
}

return hashMapOf(
"uri" to Uri.fromFile(newFile).toString(),
"path" to newFile.path,
"dateModifiedSecs" to newFile.lastModified() / 1000,
)
}

override fun scanPostMetadataEdit(context: Context, path: String, uri: Uri, mimeType: String, newFields: FieldMap, callback: ImageOpCallback) {
try {
val file = File(path)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,75 @@ abstract class ImageProvider {
callback.onFailure(UnsupportedOperationException("`moveMultiple` is not supported by this image provider"))
}

open suspend fun renameMultiple(
suspend fun renameMultiple(
activity: Activity,
entriesToNewName: Map<AvesEntry, String>,
isCancelledOp: CancelCheck,
callback: ImageOpCallback,
) {
callback.onFailure(UnsupportedOperationException("`renameMultiple` is not supported by this image provider"))
for (kv in entriesToNewName) {
val entry = kv.key
val desiredName = kv.value

val sourceUri = entry.uri
val sourcePath = entry.path
val mimeType = entry.mimeType

val result: FieldMap = hashMapOf(
"uri" to sourceUri.toString(),
"success" to false,
)

// prevent naming with a `.` prefix as it would hide the file and remove it from the Media Store
if (sourcePath != null && !desiredName.startsWith('.')) {
try {
var newFields: FieldMap = skippedFieldMap
if (!isCancelledOp()) {
val desiredNameWithoutExtension = desiredName.substringBeforeLast(".")

val oldFile = File(sourcePath)
if (oldFile.nameWithoutExtension != desiredNameWithoutExtension) {
oldFile.parent?.let { dir ->
resolveTargetFileNameWithoutExtension(
contextWrapper = activity,
dir = dir,
desiredNameWithoutExtension = desiredNameWithoutExtension,
mimeType = mimeType,
conflictStrategy = NameConflictStrategy.RENAME,
)?.let { targetNameWithoutExtension ->
val targetFileName = "$targetNameWithoutExtension${extensionFor(mimeType)}"
val newFile = File(dir, targetFileName)
if (oldFile != newFile) {
newFields = renameSingle(
activity = activity,
mimeType = mimeType,
oldMediaUri = sourceUri,
oldPath = sourcePath,
newFile = newFile,
)
}
}
}
}
}
result["newFields"] = newFields
result["success"] = true
} catch (e: Exception) {
Log.w(LOG_TAG, "failed to rename to newFileName=$desiredName entry with sourcePath=$sourcePath", e)
}
}
callback.onSuccess(result)
}
}

open suspend fun renameSingle(
activity: Activity,
mimeType: String,
oldMediaUri: Uri,
oldPath: String,
newFile: File,
): FieldMap {
throw UnsupportedOperationException("`renameSingle` is not supported by this image provider")
}

open fun scanPostMetadataEdit(context: Context, path: String, uri: Uri, mimeType: String, newFields: FieldMap, callback: ImageOpCallback) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -552,10 +552,10 @@ class MediaStoreImageProvider : ImageProvider() {
)
} else if (toVault) {
hashMapOf(
"origin" to SourceEntry.ORIGIN_VAULT,
"uri" to File(targetPath).toUri().toString(),
"contentId" to null,
"path" to targetPath,
"origin" to SourceEntry.ORIGIN_VAULT,
)
} else {
scanNewPath(activity, targetPath, mimeType)
Expand Down Expand Up @@ -626,74 +626,16 @@ class MediaStoreImageProvider : ImageProvider() {
return targetDir + fileName
}

override suspend fun renameMultiple(
activity: Activity,
entriesToNewName: Map<AvesEntry, String>,
isCancelledOp: CancelCheck,
callback: ImageOpCallback,
) {
for (kv in entriesToNewName) {
val entry = kv.key
val desiredName = kv.value

val sourceUri = entry.uri
val sourcePath = entry.path
val mimeType = entry.mimeType

val result: FieldMap = hashMapOf(
"uri" to sourceUri.toString(),
"success" to false,
)

// prevent naming with a `.` prefix as it would hide the file and remove it from the Media Store
if (sourcePath != null && !desiredName.startsWith('.')) {
try {
val newFields = if (isCancelledOp()) skippedFieldMap else renameSingle(
activity = activity,
mimeType = mimeType,
oldMediaUri = sourceUri,
oldPath = sourcePath,
desiredName = desiredName,
)
result["newFields"] = newFields
result["success"] = true
} catch (e: Exception) {
Log.w(LOG_TAG, "failed to rename to newFileName=$desiredName entry with sourcePath=$sourcePath", e)
}
}
callback.onSuccess(result)
}
}

private suspend fun renameSingle(
override suspend fun renameSingle(
activity: Activity,
mimeType: String,
oldMediaUri: Uri,
oldPath: String,
desiredName: String,
): FieldMap {
val desiredNameWithoutExtension = desiredName.substringBeforeLast(".")

val oldFile = File(oldPath)
if (oldFile.nameWithoutExtension == desiredNameWithoutExtension) return skippedFieldMap

val dir = oldFile.parent ?: return skippedFieldMap
val targetNameWithoutExtension = resolveTargetFileNameWithoutExtension(
contextWrapper = activity,
dir = dir,
desiredNameWithoutExtension = desiredNameWithoutExtension,
mimeType = mimeType,
conflictStrategy = NameConflictStrategy.RENAME,
) ?: return skippedFieldMap
val targetFileName = "$targetNameWithoutExtension${extensionFor(mimeType)}"

val newFile = File(dir, targetFileName)
return when {
oldFile == newFile -> skippedFieldMap
StorageUtils.canEditByFile(activity, oldPath) -> renameSingleByFile(activity, mimeType, oldMediaUri, oldPath, newFile)
isMediaUriPermissionGranted(activity, oldMediaUri, mimeType) -> renameSingleByMediaStore(activity, mimeType, oldMediaUri, newFile)
else -> renameSingleByTreeDoc(activity, mimeType, oldMediaUri, oldPath, newFile)
}
newFile: File,
): FieldMap = when {
StorageUtils.canEditByFile(activity, oldPath) -> renameSingleByFile(activity, mimeType, oldMediaUri, oldPath, newFile)
isMediaUriPermissionGranted(activity, oldMediaUri, mimeType) -> renameSingleByMediaStore(activity, mimeType, oldMediaUri, newFile)
else -> renameSingleByTreeDoc(activity, mimeType, oldMediaUri, oldPath, newFile)
}

private suspend fun renameSingleByMediaStore(
Expand Down Expand Up @@ -851,10 +793,12 @@ class MediaStoreImageProvider : ImageProvider() {
try {
val cursor = context.contentResolver.query(uri, projection, null, null, null)
if (cursor != null && cursor.moveToFirst()) {
val newFields = HashMap<String, Any?>()
newFields["uri"] = uri.toString()
newFields["contentId"] = uri.tryParseId()
newFields["path"] = path
val newFields = hashMapOf<String, Any?>(
"origin" to SourceEntry.ORIGIN_MEDIA_STORE_CONTENT,
"uri" to uri.toString(),
"contentId" to uri.tryParseId(),
"path" to path,
)
cursor.getColumnIndex(MediaStore.MediaColumns.DATE_ADDED).let { if (it != -1) newFields["dateAddedSecs"] = cursor.getInt(it) }
cursor.getColumnIndex(MediaStore.MediaColumns.DATE_MODIFIED).let { if (it != -1) newFields["dateModifiedSecs"] = cursor.getInt(it) }
cursor.close()
Expand Down

0 comments on commit df53b91

Please sign in to comment.