vault: fixed export to vault
This commit is contained in:
parent
cbf555a31f
commit
d96a067f18
6 changed files with 157 additions and 86 deletions
|
@ -92,19 +92,6 @@ class ImageOpStreamHandler(private val activity: Activity, private val arguments
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun delete() {
|
private suspend fun delete() {
|
||||||
if (entryMapList.isEmpty()) {
|
|
||||||
endOfStream()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// assume same provider for all entries
|
|
||||||
val firstEntry = entryMapList.first()
|
|
||||||
val provider = (firstEntry["uri"] as String?)?.let { Uri.parse(it) }?.let { getProvider(it) }
|
|
||||||
if (provider == null) {
|
|
||||||
error("delete-provider", "failed to find provider for entry=$firstEntry", null)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val entries = entryMapList.map(::AvesEntry)
|
val entries = entryMapList.map(::AvesEntry)
|
||||||
for (entry in entries) {
|
for (entry in entries) {
|
||||||
val mimeType = entry.mimeType
|
val mimeType = entry.mimeType
|
||||||
|
@ -119,12 +106,14 @@ class ImageOpStreamHandler(private val activity: Activity, private val arguments
|
||||||
if (isCancelledOp()) {
|
if (isCancelledOp()) {
|
||||||
result["skipped"] = true
|
result["skipped"] = true
|
||||||
} else {
|
} else {
|
||||||
try {
|
result["success"] = false
|
||||||
provider.delete(activity, uri, path, mimeType)
|
getProvider(uri)?.let { provider ->
|
||||||
result["success"] = true
|
try {
|
||||||
} catch (e: Exception) {
|
provider.delete(activity, uri, path, mimeType)
|
||||||
Log.w(LOG_TAG, "failed to delete entry with path=$path", e)
|
result["success"] = true
|
||||||
result["success"] = false
|
} catch (e: Exception) {
|
||||||
|
Log.w(LOG_TAG, "failed to delete entry with path=$path", e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
success(result)
|
success(result)
|
||||||
|
|
|
@ -5,6 +5,7 @@ import android.content.Context
|
||||||
import android.content.ContextWrapper
|
import android.content.ContextWrapper
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import android.webkit.MimeTypeMap
|
||||||
import deckers.thibault.aves.model.FieldMap
|
import deckers.thibault.aves.model.FieldMap
|
||||||
import deckers.thibault.aves.model.SourceEntry
|
import deckers.thibault.aves.model.SourceEntry
|
||||||
import deckers.thibault.aves.utils.LogUtils
|
import deckers.thibault.aves.utils.LogUtils
|
||||||
|
@ -12,12 +13,19 @@ import java.io.File
|
||||||
|
|
||||||
internal class FileImageProvider : ImageProvider() {
|
internal class FileImageProvider : ImageProvider() {
|
||||||
override fun fetchSingle(context: Context, uri: Uri, sourceMimeType: String?, callback: ImageOpCallback) {
|
override fun fetchSingle(context: Context, uri: Uri, sourceMimeType: String?, callback: ImageOpCallback) {
|
||||||
if (sourceMimeType == null) {
|
val mimeType = if (sourceMimeType != null) {
|
||||||
callback.onFailure(Exception("MIME type is null for uri=$uri"))
|
sourceMimeType
|
||||||
return
|
} else {
|
||||||
|
val fromExtension = MimeTypeMap.getFileExtensionFromUrl(uri.toString())
|
||||||
|
if (fromExtension != null) {
|
||||||
|
fromExtension
|
||||||
|
} else {
|
||||||
|
callback.onFailure(Exception("MIME type was not provided and cannot be guessed from extension of uri=$uri"))
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val entry = SourceEntry(SourceEntry.ORIGIN_FILE, uri, sourceMimeType)
|
val entry = SourceEntry(SourceEntry.ORIGIN_FILE, uri, mimeType)
|
||||||
|
|
||||||
val path = uri.path
|
val path = uri.path
|
||||||
if (path != null) {
|
if (path != null) {
|
||||||
|
|
|
@ -10,6 +10,7 @@ import android.net.Uri
|
||||||
import android.os.Binder
|
import android.os.Binder
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.core.net.toUri
|
||||||
import androidx.exifinterface.media.ExifInterface
|
import androidx.exifinterface.media.ExifInterface
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.load.DecodeFormat
|
import com.bumptech.glide.load.DecodeFormat
|
||||||
|
@ -31,10 +32,7 @@ import deckers.thibault.aves.metadata.Mp4ParserHelper.updateRotation
|
||||||
import deckers.thibault.aves.metadata.Mp4ParserHelper.updateXmp
|
import deckers.thibault.aves.metadata.Mp4ParserHelper.updateXmp
|
||||||
import deckers.thibault.aves.metadata.PixyMetaHelper.extendedXmpDocString
|
import deckers.thibault.aves.metadata.PixyMetaHelper.extendedXmpDocString
|
||||||
import deckers.thibault.aves.metadata.PixyMetaHelper.xmpDocString
|
import deckers.thibault.aves.metadata.PixyMetaHelper.xmpDocString
|
||||||
import deckers.thibault.aves.model.AvesEntry
|
import deckers.thibault.aves.model.*
|
||||||
import deckers.thibault.aves.model.ExifOrientationOp
|
|
||||||
import deckers.thibault.aves.model.FieldMap
|
|
||||||
import deckers.thibault.aves.model.NameConflictStrategy
|
|
||||||
import deckers.thibault.aves.utils.*
|
import deckers.thibault.aves.utils.*
|
||||||
import deckers.thibault.aves.utils.FileUtils.transferFrom
|
import deckers.thibault.aves.utils.FileUtils.transferFrom
|
||||||
import deckers.thibault.aves.utils.FileUtils.transferTo
|
import deckers.thibault.aves.utils.FileUtils.transferTo
|
||||||
|
@ -53,6 +51,19 @@ abstract class ImageProvider {
|
||||||
callback.onFailure(UnsupportedOperationException("`fetchSingle` is not supported by this image provider"))
|
callback.onFailure(UnsupportedOperationException("`fetchSingle` is not supported by this image provider"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun scanNewPath(context: Context, path: String, mimeType: String): FieldMap {
|
||||||
|
return if (StorageUtils.isInVault(context, path)) {
|
||||||
|
hashMapOf(
|
||||||
|
"origin" to SourceEntry.ORIGIN_VAULT,
|
||||||
|
"uri" to File(path).toUri().toString(),
|
||||||
|
"contentId" to null,
|
||||||
|
"path" to path,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
MediaStoreImageProvider().scanNewPathByMediaStore(context, path, mimeType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
open suspend fun delete(contextWrapper: ContextWrapper, uri: Uri, path: String?, mimeType: String) {
|
open suspend fun delete(contextWrapper: ContextWrapper, uri: Uri, path: String?, mimeType: String) {
|
||||||
throw UnsupportedOperationException("`delete` is not supported by this image provider")
|
throw UnsupportedOperationException("`delete` is not supported by this image provider")
|
||||||
}
|
}
|
||||||
|
@ -294,8 +305,7 @@ abstract class ImageProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val mediaStoreImageProvider = MediaStoreImageProvider()
|
val targetPath = MediaStoreImageProvider().createSingle(
|
||||||
val targetPath = mediaStoreImageProvider.createSingle(
|
|
||||||
activity = activity,
|
activity = activity,
|
||||||
mimeType = targetMimeType,
|
mimeType = targetMimeType,
|
||||||
targetDir = targetDir,
|
targetDir = targetDir,
|
||||||
|
@ -303,7 +313,7 @@ abstract class ImageProvider {
|
||||||
targetNameWithoutExtension = targetNameWithoutExtension,
|
targetNameWithoutExtension = targetNameWithoutExtension,
|
||||||
write = write,
|
write = write,
|
||||||
)
|
)
|
||||||
return mediaStoreImageProvider.scanNewPath(activity, targetPath, exportMimeType)
|
return scanNewPath(activity, targetPath, exportMimeType)
|
||||||
} finally {
|
} finally {
|
||||||
// clearing Glide target should happen after effectively writing the bitmap
|
// clearing Glide target should happen after effectively writing the bitmap
|
||||||
Glide.with(activity).clear(target)
|
Glide.with(activity).clear(target)
|
||||||
|
@ -422,7 +432,7 @@ abstract class ImageProvider {
|
||||||
|
|
||||||
val fileName = targetDocFile.name
|
val fileName = targetDocFile.name
|
||||||
val targetFullPath = targetDir + fileName
|
val targetFullPath = targetDir + fileName
|
||||||
val newFields = MediaStoreImageProvider().scanNewPath(contextWrapper, targetFullPath, captureMimeType)
|
val newFields = scanNewPath(contextWrapper, targetFullPath, captureMimeType)
|
||||||
callback.onSuccess(newFields)
|
callback.onSuccess(newFields)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
callback.onFailure(e)
|
callback.onFailure(e)
|
||||||
|
|
|
@ -9,7 +9,7 @@ import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.core.net.toUri
|
import androidx.annotation.RequiresApi
|
||||||
import com.commonsware.cwac.document.DocumentFileCompat
|
import com.commonsware.cwac.document.DocumentFileCompat
|
||||||
import deckers.thibault.aves.MainActivity
|
import deckers.thibault.aves.MainActivity
|
||||||
import deckers.thibault.aves.MainActivity.Companion.DELETE_SINGLE_PERMISSION_REQUEST
|
import deckers.thibault.aves.MainActivity.Companion.DELETE_SINGLE_PERMISSION_REQUEST
|
||||||
|
@ -30,6 +30,7 @@ import deckers.thibault.aves.utils.UriUtils.tryParseId
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
import java.io.SyncFailedException
|
import java.io.SyncFailedException
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
@ -474,7 +475,6 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
mimeType = mimeType,
|
mimeType = mimeType,
|
||||||
copy = copy,
|
copy = copy,
|
||||||
toBin = toBin,
|
toBin = toBin,
|
||||||
toVault = toVault,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -501,7 +501,6 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
mimeType: String,
|
mimeType: String,
|
||||||
copy: Boolean,
|
copy: Boolean,
|
||||||
toBin: Boolean,
|
toBin: Boolean,
|
||||||
toVault: Boolean,
|
|
||||||
): FieldMap {
|
): FieldMap {
|
||||||
val sourcePath = sourceFile.path
|
val sourcePath = sourceFile.path
|
||||||
val sourceDir = sourceFile.parent?.let { ensureTrailingSeparator(it) }
|
val sourceDir = sourceFile.parent?.let { ensureTrailingSeparator(it) }
|
||||||
|
@ -550,21 +549,11 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
"trashed" to true,
|
"trashed" to true,
|
||||||
"trashPath" to targetPath,
|
"trashPath" to targetPath,
|
||||||
)
|
)
|
||||||
} else if (toVault) {
|
|
||||||
hashMapOf(
|
|
||||||
"origin" to SourceEntry.ORIGIN_VAULT,
|
|
||||||
"uri" to File(targetPath).toUri().toString(),
|
|
||||||
"contentId" to null,
|
|
||||||
"path" to targetPath,
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
scanNewPath(activity, targetPath, mimeType)
|
scanNewPath(activity, targetPath, mimeType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// `DocumentsContract.moveDocument()` needs `sourceParentDocumentUri`, which could be different for each entry
|
|
||||||
// `DocumentsContract.copyDocument()` yields "Unsupported call: android:copyDocument"
|
|
||||||
// when used with entry URI as `sourceDocumentUri`, and targetDirDocFile URI as `targetParentDocumentUri`
|
|
||||||
fun createSingle(
|
fun createSingle(
|
||||||
activity: Activity,
|
activity: Activity,
|
||||||
mimeType: String,
|
mimeType: String,
|
||||||
|
@ -573,33 +562,86 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
targetNameWithoutExtension: String,
|
targetNameWithoutExtension: String,
|
||||||
write: (OutputStream) -> Unit,
|
write: (OutputStream) -> Unit,
|
||||||
): String {
|
): String {
|
||||||
|
if (StorageUtils.isInVault(activity, targetDir)) {
|
||||||
|
return insertByFile(
|
||||||
|
targetDir = targetDir,
|
||||||
|
targetFileName = "$targetNameWithoutExtension${extensionFor(mimeType)}",
|
||||||
|
write = write,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
val downloadDirPath = StorageUtils.getDownloadDirPath(activity, targetDir)
|
val downloadDirPath = StorageUtils.getDownloadDirPath(activity, targetDir)
|
||||||
val isDownloadSubdir = downloadDirPath != null && targetDir.startsWith(downloadDirPath)
|
val isDownloadSubdir = downloadDirPath != null && targetDir.startsWith(downloadDirPath)
|
||||||
if (isDownloadSubdir) {
|
if (isDownloadSubdir) {
|
||||||
val volumePath = StorageUtils.getVolumePath(activity, targetDir)
|
return insertByMediaStore(
|
||||||
val relativePath = targetDir.substring(volumePath?.length ?: 0)
|
activity = activity,
|
||||||
|
targetDir = targetDir,
|
||||||
val targetFileName = "$targetNameWithoutExtension${extensionFor(mimeType)}"
|
targetFileName = "$targetNameWithoutExtension${extensionFor(mimeType)}",
|
||||||
val values = ContentValues().apply {
|
write = write,
|
||||||
put(MediaStore.MediaColumns.DISPLAY_NAME, targetFileName)
|
)
|
||||||
put(MediaStore.MediaColumns.RELATIVE_PATH, relativePath)
|
|
||||||
put(MediaStore.MediaColumns.IS_PENDING, 1)
|
|
||||||
}
|
|
||||||
val resolver = activity.contentResolver
|
|
||||||
val uri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values)
|
|
||||||
|
|
||||||
uri?.let {
|
|
||||||
resolver.openOutputStream(uri)?.use(write)
|
|
||||||
values.clear()
|
|
||||||
values.put(MediaStore.MediaColumns.IS_PENDING, 0)
|
|
||||||
resolver.update(uri, values, null, null)
|
|
||||||
} ?: throw Exception("MediaStore failed for some reason")
|
|
||||||
|
|
||||||
return File(targetDir, targetFileName).path
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return insertByTreeDoc(
|
||||||
|
activity = activity,
|
||||||
|
mimeType = mimeType,
|
||||||
|
targetDir = targetDir,
|
||||||
|
targetDirDocFile = targetDirDocFile,
|
||||||
|
targetNameWithoutExtension = targetNameWithoutExtension,
|
||||||
|
write = write,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun insertByFile(
|
||||||
|
targetDir: String,
|
||||||
|
targetFileName: String,
|
||||||
|
write: (OutputStream) -> Unit,
|
||||||
|
): String {
|
||||||
|
val file = File(targetDir, targetFileName)
|
||||||
|
FileOutputStream(file).use(write)
|
||||||
|
return file.path
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(Build.VERSION_CODES.Q)
|
||||||
|
private fun insertByMediaStore(
|
||||||
|
activity: Activity,
|
||||||
|
targetDir: String,
|
||||||
|
targetFileName: String,
|
||||||
|
write: (OutputStream) -> Unit,
|
||||||
|
): String {
|
||||||
|
val volumePath = StorageUtils.getVolumePath(activity, targetDir)
|
||||||
|
val relativePath = targetDir.substring(volumePath?.length ?: 0)
|
||||||
|
|
||||||
|
val values = ContentValues().apply {
|
||||||
|
put(MediaStore.MediaColumns.DISPLAY_NAME, targetFileName)
|
||||||
|
put(MediaStore.MediaColumns.RELATIVE_PATH, relativePath)
|
||||||
|
put(MediaStore.MediaColumns.IS_PENDING, 1)
|
||||||
|
}
|
||||||
|
val resolver = activity.contentResolver
|
||||||
|
val uri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values)
|
||||||
|
|
||||||
|
uri?.let {
|
||||||
|
resolver.openOutputStream(uri)?.use(write)
|
||||||
|
values.clear()
|
||||||
|
values.put(MediaStore.MediaColumns.IS_PENDING, 0)
|
||||||
|
resolver.update(uri, values, null, null)
|
||||||
|
} ?: throw Exception("MediaStore failed for some reason")
|
||||||
|
|
||||||
|
return File(targetDir, targetFileName).path
|
||||||
|
}
|
||||||
|
|
||||||
|
// `DocumentsContract.moveDocument()` needs `sourceParentDocumentUri`, which could be different for each entry
|
||||||
|
// `DocumentsContract.copyDocument()` yields "Unsupported call: android:copyDocument"
|
||||||
|
// when used with entry URI as `sourceDocumentUri`, and targetDirDocFile URI as `targetParentDocumentUri`
|
||||||
|
private fun insertByTreeDoc(
|
||||||
|
activity: Activity,
|
||||||
|
mimeType: String,
|
||||||
|
targetDir: String,
|
||||||
|
targetDirDocFile: DocumentFileCompat?,
|
||||||
|
targetNameWithoutExtension: String,
|
||||||
|
write: (OutputStream) -> Unit,
|
||||||
|
): String {
|
||||||
targetDirDocFile ?: throw Exception("failed to get tree doc for directory at path=$targetDir")
|
targetDirDocFile ?: throw Exception("failed to get tree doc for directory at path=$targetDir")
|
||||||
|
|
||||||
// the file created from a `TreeDocumentFile` is also a `TreeDocumentFile`
|
// the file created from a `TreeDocumentFile` is also a `TreeDocumentFile`
|
||||||
|
@ -670,7 +712,7 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// URI should not change
|
// URI should not change
|
||||||
return scanNewPath(activity, newFile.path, mimeType)
|
return scanNewPathByMediaStore(activity, newFile.path, mimeType)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun renameSingleByTreeDoc(
|
private suspend fun renameSingleByTreeDoc(
|
||||||
|
@ -690,7 +732,7 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
throw Exception("failed to rename document at path=$oldPath")
|
throw Exception("failed to rename document at path=$oldPath")
|
||||||
}
|
}
|
||||||
scanObsoletePath(activity, oldMediaUri, oldPath, mimeType)
|
scanObsoletePath(activity, oldMediaUri, oldPath, mimeType)
|
||||||
return scanNewPath(activity, newFile.path, mimeType)
|
return scanNewPathByMediaStore(activity, newFile.path, mimeType)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun renameSingleByFile(
|
private suspend fun renameSingleByFile(
|
||||||
|
@ -706,7 +748,7 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
throw Exception("failed to rename file at path=$oldPath")
|
throw Exception("failed to rename file at path=$oldPath")
|
||||||
}
|
}
|
||||||
scanObsoletePath(activity, oldMediaUri, oldPath, mimeType)
|
scanObsoletePath(activity, oldMediaUri, oldPath, mimeType)
|
||||||
return scanNewPath(activity, newFile.path, mimeType)
|
return scanNewPathByMediaStore(activity, newFile.path, mimeType)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun scanPostMetadataEdit(context: Context, path: String, uri: Uri, mimeType: String, newFields: FieldMap, callback: ImageOpCallback) {
|
override fun scanPostMetadataEdit(context: Context, path: String, uri: Uri, mimeType: String, newFields: FieldMap, callback: ImageOpCallback) {
|
||||||
|
@ -757,10 +799,23 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun scanNewPath(context: Context, path: String, mimeType: String): FieldMap =
|
suspend fun scanNewPathByMediaStore(context: Context, path: String, mimeType: String): FieldMap =
|
||||||
suspendCoroutine { cont -> tryScanNewPath(context, path = path, mimeType = mimeType, cont) }
|
suspendCoroutine { cont ->
|
||||||
|
tryScanNewPathByMediaStore(
|
||||||
|
context = context,
|
||||||
|
path = path,
|
||||||
|
mimeType = mimeType,
|
||||||
|
cont = cont,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun tryScanNewPath(context: Context, path: String, mimeType: String, cont: Continuation<FieldMap>, iteration: Int = 0) {
|
private fun tryScanNewPathByMediaStore(
|
||||||
|
context: Context,
|
||||||
|
path: String,
|
||||||
|
mimeType: String,
|
||||||
|
cont: Continuation<FieldMap>,
|
||||||
|
iteration: Int = 0,
|
||||||
|
) {
|
||||||
// `scanFile` may (e.g. when copying to SD card on Android 10 (API 29)):
|
// `scanFile` may (e.g. when copying to SD card on Android 10 (API 29)):
|
||||||
// 1) yield no URI,
|
// 1) yield no URI,
|
||||||
// 2) yield a temporary URI that fails when queried,
|
// 2) yield a temporary URI that fails when queried,
|
||||||
|
@ -832,7 +887,7 @@ class MediaStoreImageProvider : ImageProvider() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tryScanNewPath(context, path = path, mimeType = mimeType, cont, iteration + 1)
|
tryScanNewPathByMediaStore(context, path = path, mimeType = mimeType, cont, iteration + 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -70,7 +70,7 @@ object MimeTypes {
|
||||||
|
|
||||||
fun isRaw(mimeType: String): Boolean {
|
fun isRaw(mimeType: String): Boolean {
|
||||||
return when (mimeType) {
|
return when (mimeType) {
|
||||||
ARW, CR2, DNG, NEF, NRW, ORF, PEF, RAF, RW2, SRW -> true
|
ARW, CR2, CRW, DCR, DNG, ERF, K25, KDC, MRW, NEF, NRW, ORF, PEF, RAF, RAW, RW2, SR2, SRF, SRW, X3F -> true
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,7 +105,7 @@ class MediaStoreSource extends CollectionSource {
|
||||||
// with items that may be hidden right away because of their metadata
|
// with items that may be hidden right away because of their metadata
|
||||||
addEntries(knownEntries, notify: false);
|
addEntries(knownEntries, notify: false);
|
||||||
|
|
||||||
await _addVaultEntries(directory);
|
await _loadVaultEntries(directory);
|
||||||
|
|
||||||
debugPrint('$runtimeType refresh ${stopwatch.elapsed} load metadata');
|
debugPrint('$runtimeType refresh ${stopwatch.elapsed} load metadata');
|
||||||
if (directory != null) {
|
if (directory != null) {
|
||||||
|
@ -266,6 +266,13 @@ class MediaStoreSource extends CollectionSource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await _refreshVaultEntries(
|
||||||
|
changedUris: changedUris.where(vaults.isVaultEntryUri).toSet(),
|
||||||
|
newEntries: newEntries,
|
||||||
|
entriesToRefresh: entriesToRefresh,
|
||||||
|
existingDirectories: existingDirectories,
|
||||||
|
);
|
||||||
|
|
||||||
invalidateAlbumFilterSummary(directories: existingDirectories);
|
invalidateAlbumFilterSummary(directories: existingDirectories);
|
||||||
|
|
||||||
if (newEntries.isNotEmpty) {
|
if (newEntries.isNotEmpty) {
|
||||||
|
@ -278,21 +285,21 @@ class MediaStoreSource extends CollectionSource {
|
||||||
await refreshEntries(entriesToRefresh, EntryDataType.values.toSet());
|
await refreshEntries(entriesToRefresh, EntryDataType.values.toSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
await _refreshVaultEntries(changedUris.where(vaults.isVaultEntryUri).toSet());
|
|
||||||
|
|
||||||
return tempUris;
|
return tempUris;
|
||||||
}
|
}
|
||||||
|
|
||||||
// vault
|
// vault
|
||||||
|
|
||||||
Future<void> _addVaultEntries(String? directory) async {
|
Future<void> _loadVaultEntries(String? directory) async {
|
||||||
addEntries(await metadataDb.loadEntries(origin: EntryOrigins.vault, directory: directory));
|
addEntries(await metadataDb.loadEntries(origin: EntryOrigins.vault, directory: directory));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _refreshVaultEntries(Set<String> changedUris) async {
|
Future<void> _refreshVaultEntries({
|
||||||
final entriesToRefresh = <AvesEntry>{};
|
required Set<String> changedUris,
|
||||||
final existingDirectories = <String>{};
|
required Set<AvesEntry> newEntries,
|
||||||
|
required Set<AvesEntry> entriesToRefresh,
|
||||||
|
required Set<String> existingDirectories,
|
||||||
|
}) async {
|
||||||
for (final uri in changedUris) {
|
for (final uri in changedUris) {
|
||||||
final existingEntry = allEntries.firstWhereOrNull((entry) => entry.uri == uri);
|
final existingEntry = allEntries.firstWhereOrNull((entry) => entry.uri == uri);
|
||||||
if (existingEntry != null) {
|
if (existingEntry != null) {
|
||||||
|
@ -301,13 +308,15 @@ class MediaStoreSource extends CollectionSource {
|
||||||
if (existingDirectory != null) {
|
if (existingDirectory != null) {
|
||||||
existingDirectories.add(existingDirectory);
|
existingDirectories.add(existingDirectory);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
final sourceEntry = await mediaFetchService.getEntry(uri, null);
|
||||||
|
if (sourceEntry != null) {
|
||||||
|
newEntries.add(sourceEntry.copyWith(
|
||||||
|
id: metadataDb.nextId,
|
||||||
|
origin: EntryOrigins.vault,
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
invalidateAlbumFilterSummary(directories: existingDirectories);
|
|
||||||
|
|
||||||
if (entriesToRefresh.isNotEmpty) {
|
|
||||||
await refreshEntries(entriesToRefresh, EntryDataType.values.toSet());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue