fixed file extension loss on move via tree doc
This commit is contained in:
parent
43cb2cd101
commit
8c3d0f1b83
5 changed files with 54 additions and 12 deletions
|
@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file.
|
|||
|
||||
### Fixed
|
||||
|
||||
- moved file losing its extension and no longer being detected as media in some cases
|
||||
- opening home when launching app as media picker
|
||||
- removing groups with obsolete albums
|
||||
- loading group custom covers
|
||||
|
|
|
@ -311,7 +311,7 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler {
|
|||
embeddedByteStream: InputStream,
|
||||
embeddedByteLength: Long,
|
||||
) {
|
||||
val extension = extensionFor(mimeType)
|
||||
val extension = extensionFor(mimeType, defaultExtension = null)
|
||||
val targetFile = StorageUtils.createTempFile(context, extension).apply {
|
||||
transferFrom(embeddedByteStream, embeddedByteLength)
|
||||
}
|
||||
|
@ -319,7 +319,7 @@ class EmbeddedDataHandler(private val context: Context) : MethodCallHandler {
|
|||
val authority = "${context.applicationContext.packageName}.file_provider"
|
||||
val uri = if (displayName != null) {
|
||||
// add extension to ease type identification when sharing this content
|
||||
val displayNameWithExtension = if (extension == null || displayName.endsWith(extension, ignoreCase = true)) {
|
||||
val displayNameWithExtension = if (displayName.endsWith(extension, ignoreCase = true)) {
|
||||
displayName
|
||||
} else {
|
||||
"$displayName$extension"
|
||||
|
|
|
@ -142,16 +142,18 @@ abstract class ImageProvider {
|
|||
|
||||
val oldFile = File(sourcePath)
|
||||
if (oldFile.nameWithoutExtension != desiredNameWithoutExtension) {
|
||||
val defaultExtension = oldFile.extension
|
||||
oldFile.parent?.let { dir ->
|
||||
val resolution = resolveTargetFileNameWithoutExtension(
|
||||
contextWrapper = activity,
|
||||
dir = dir,
|
||||
desiredNameWithoutExtension = desiredNameWithoutExtension,
|
||||
mimeType = mimeType,
|
||||
defaultExtension = defaultExtension,
|
||||
conflictStrategy = NameConflictStrategy.RENAME,
|
||||
)
|
||||
resolution.nameWithoutExtension?.let { targetNameWithoutExtension ->
|
||||
val targetFileName = "$targetNameWithoutExtension${extensionFor(mimeType)}"
|
||||
val targetFileName = "$targetNameWithoutExtension${extensionFor(mimeType, defaultExtension)}"
|
||||
val newFile = File(dir, targetFileName)
|
||||
if (oldFile != newFile) {
|
||||
newFields = renameSingle(
|
||||
|
@ -277,11 +279,17 @@ abstract class ImageProvider {
|
|||
val page = if (sourceMimeType == MimeTypes.TIFF) pageId + 1 else pageId
|
||||
desiredNameWithoutExtension += "_${page.toString().padStart(3, '0')}"
|
||||
}
|
||||
|
||||
// there is no benefit providing input extension
|
||||
// for known output MIME type
|
||||
val defaultExtension = null
|
||||
|
||||
val resolution = resolveTargetFileNameWithoutExtension(
|
||||
contextWrapper = activity,
|
||||
dir = targetDir,
|
||||
desiredNameWithoutExtension = desiredNameWithoutExtension,
|
||||
mimeType = exportMimeType,
|
||||
defaultExtension = defaultExtension,
|
||||
conflictStrategy = nameConflictStrategy,
|
||||
)
|
||||
val targetNameWithoutExtension = resolution.nameWithoutExtension ?: return skippedFieldMap
|
||||
|
@ -358,6 +366,7 @@ abstract class ImageProvider {
|
|||
targetDir = targetDir,
|
||||
targetDirDocFile = targetDirDocFile,
|
||||
targetNameWithoutExtension = targetNameWithoutExtension,
|
||||
defaultExtension = defaultExtension,
|
||||
write = write,
|
||||
)
|
||||
|
||||
|
@ -465,6 +474,7 @@ abstract class ImageProvider {
|
|||
dir = targetDir,
|
||||
desiredNameWithoutExtension = desiredNameWithoutExtension,
|
||||
mimeType = captureMimeType,
|
||||
defaultExtension = null,
|
||||
conflictStrategy = nameConflictStrategy,
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
|
@ -571,13 +581,14 @@ abstract class ImageProvider {
|
|||
dir: String,
|
||||
desiredNameWithoutExtension: String,
|
||||
mimeType: String,
|
||||
defaultExtension: String?,
|
||||
conflictStrategy: NameConflictStrategy,
|
||||
): NameConflictResolution {
|
||||
val sanitizedNameWithoutExtension = sanitizeDesiredFileName(desiredNameWithoutExtension)
|
||||
var resolvedName: String? = sanitizedNameWithoutExtension
|
||||
var replacementFile: File? = null
|
||||
|
||||
val extension = extensionFor(mimeType)
|
||||
val extension = extensionFor(mimeType, defaultExtension)
|
||||
val targetFile = File(dir, "$sanitizedNameWithoutExtension$extension")
|
||||
when (conflictStrategy) {
|
||||
NameConflictStrategy.RENAME -> {
|
||||
|
|
|
@ -557,6 +557,7 @@ class MediaStoreImageProvider : ImageProvider() {
|
|||
toBin: Boolean,
|
||||
): FieldMap {
|
||||
val sourcePath = sourceFile?.path
|
||||
val sourceExtension = sourceFile?.extension
|
||||
val sourceDir = sourceFile?.parent?.let { ensureTrailingSeparator(it) }
|
||||
if (sourceDir == targetDir && !(copy && nameConflictStrategy == NameConflictStrategy.RENAME)) {
|
||||
// nothing to do unless it's a renamed copy
|
||||
|
@ -569,6 +570,7 @@ class MediaStoreImageProvider : ImageProvider() {
|
|||
dir = targetDir,
|
||||
desiredNameWithoutExtension = desiredNameWithoutExtension,
|
||||
mimeType = mimeType,
|
||||
defaultExtension = sourceExtension,
|
||||
conflictStrategy = nameConflictStrategy,
|
||||
)
|
||||
val targetNameWithoutExtension = resolution.nameWithoutExtension ?: return skippedFieldMap
|
||||
|
@ -580,6 +582,7 @@ class MediaStoreImageProvider : ImageProvider() {
|
|||
targetDir = targetDir,
|
||||
targetDirDocFile = targetDirDocFile,
|
||||
targetNameWithoutExtension = targetNameWithoutExtension,
|
||||
defaultExtension = sourceExtension,
|
||||
) { output: OutputStream ->
|
||||
try {
|
||||
sourceDocFile.copyTo(output)
|
||||
|
@ -615,12 +618,13 @@ class MediaStoreImageProvider : ImageProvider() {
|
|||
targetDir: String,
|
||||
targetDirDocFile: DocumentFileCompat?,
|
||||
targetNameWithoutExtension: String,
|
||||
defaultExtension: String?,
|
||||
write: (OutputStream) -> Unit,
|
||||
): String {
|
||||
if (StorageUtils.isInVault(activity, targetDir)) {
|
||||
return insertByFile(
|
||||
targetDir = targetDir,
|
||||
targetFileName = "$targetNameWithoutExtension${extensionFor(mimeType)}",
|
||||
targetFileName = "$targetNameWithoutExtension${extensionFor(mimeType, defaultExtension)}",
|
||||
write = write,
|
||||
)
|
||||
}
|
||||
|
@ -630,7 +634,7 @@ class MediaStoreImageProvider : ImageProvider() {
|
|||
return insertByMediaStore(
|
||||
activity = activity,
|
||||
targetDir = targetDir,
|
||||
targetFileName = "$targetNameWithoutExtension${extensionFor(mimeType)}",
|
||||
targetFileName = "$targetNameWithoutExtension${extensionFor(mimeType, defaultExtension)}",
|
||||
write = write,
|
||||
)
|
||||
}
|
||||
|
@ -642,6 +646,7 @@ class MediaStoreImageProvider : ImageProvider() {
|
|||
targetDir = targetDir,
|
||||
targetDirDocFile = targetDirDocFile,
|
||||
targetNameWithoutExtension = targetNameWithoutExtension,
|
||||
defaultExtension = defaultExtension,
|
||||
write = write,
|
||||
)
|
||||
}
|
||||
|
@ -700,6 +705,7 @@ class MediaStoreImageProvider : ImageProvider() {
|
|||
targetDir: String,
|
||||
targetDirDocFile: DocumentFileCompat?,
|
||||
targetNameWithoutExtension: String,
|
||||
defaultExtension: String?,
|
||||
write: (OutputStream) -> Unit,
|
||||
): String {
|
||||
targetDirDocFile ?: throw Exception("failed to get tree doc for directory at path=$targetDir")
|
||||
|
@ -708,9 +714,22 @@ class MediaStoreImageProvider : ImageProvider() {
|
|||
// but in order to open an output stream to it, we need to use a `SingleDocumentFile`
|
||||
// through a document URI, not a tree URI
|
||||
// note that `DocumentFile.getParentFile()` returns null if we did not pick a tree first
|
||||
val targetTreeFile = targetDirDocFile.createFile(mimeType, targetNameWithoutExtension)
|
||||
val targetDocFile = DocumentFileCompat.fromSingleUri(activity, targetTreeFile.uri)
|
||||
// TODO TLAD [missing extension] check whether targetDocFile.name has a valid extension
|
||||
var targetTreeFile = targetDirDocFile.createFile(mimeType, targetNameWithoutExtension)
|
||||
var targetDocFile = DocumentFileCompat.fromSingleUri(activity, targetTreeFile.uri)
|
||||
|
||||
// providing a display name and a MIME type does not guarantee
|
||||
// that the created document will be backed by a file with a valid media extension,
|
||||
// but having an extension is essential for media detection by Android,
|
||||
// so we retry with a display name that includes the extension
|
||||
if ((targetDocFile.extension == null || targetDocFile.extension.isEmpty() || targetDocFile.extension == "bin") && defaultExtension != null) {
|
||||
if (targetDocFile.exists()) {
|
||||
targetDocFile.delete()
|
||||
}
|
||||
|
||||
val extension = if (defaultExtension.startsWith(".")) defaultExtension else ".$defaultExtension"
|
||||
targetTreeFile = targetDirDocFile.createFile(mimeType, "$targetNameWithoutExtension$extension")
|
||||
targetDocFile = DocumentFileCompat.fromSingleUri(activity, targetTreeFile.uri)
|
||||
}
|
||||
|
||||
try {
|
||||
targetDocFile.openOutputStream().use(write)
|
||||
|
|
|
@ -163,13 +163,24 @@ object MimeTypes {
|
|||
|
||||
// among other refs:
|
||||
// - https://android.googlesource.com/platform/external/mime-support/+/refs/heads/master/mime.types
|
||||
fun extensionFor(mimeType: String): String? = when (mimeType) {
|
||||
fun extensionFor(mimeType: String, defaultExtension: String?): String = when (mimeType) {
|
||||
AVI, AVI_VND -> ".avi"
|
||||
DNG, DNG_ADOBE -> ".dng"
|
||||
HEIC, HEIF -> ".heif"
|
||||
MP2T, MP2TS -> ".m2ts"
|
||||
PSD_VND, PSD_X -> ".psd"
|
||||
// TODO TLAD [missing extension] check whether to define more manual mapping and raise exception on miss
|
||||
else -> MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType)?.let { ".$it" }
|
||||
else -> {
|
||||
val ext = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType) ?: defaultExtension
|
||||
if (ext != null) {
|
||||
// fallback to provided extension when available,
|
||||
// typically the original file extension when moving/renaming
|
||||
if (ext.startsWith(".")) ext else ".$ext"
|
||||
} else {
|
||||
// fallback to generic extensions,
|
||||
// as incorrect file extensions are better than none for media detection
|
||||
if (isVideo(mimeType)) ".mp4" else ".jpg"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val TIFF_EXTENSION_PATTERN = Regex(".*\\.tiff?", RegexOption.IGNORE_CASE)
|
||||
|
|
Loading…
Reference in a new issue