vaults: fixed moved out item getting duplicated, fixed renaming item
This commit is contained in:
parent
a8159f9525
commit
df53b91bdf
4 changed files with 111 additions and 81 deletions
|
@ -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)
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
@ -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(
|
||||
|
@ -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()
|
||||
|
|
Loading…
Reference in a new issue