From c86534d6003d86757dcc1d51cffd2df0ba2471e9 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Thu, 25 Feb 2021 17:46:14 +0900 Subject: [PATCH] various minor fixes --- android/app/src/main/AndroidManifest.xml | 4 +-- .../streams/MediaStoreStreamHandler.kt | 2 +- .../aves/metadata/ExifInterfaceHelper.kt | 17 +++++++++---- .../model/provider/ContentImageProvider.kt | 2 +- .../aves/model/provider/FileImageProvider.kt | 2 +- .../aves/model/provider/ImageProvider.kt | 2 +- .../model/provider/MediaStoreImageProvider.kt | 25 ++++++++----------- .../deckers/thibault/aves/utils/MimeTypes.kt | 4 ++- lib/utils/mime_utils.dart | 2 ++ 9 files changed, 32 insertions(+), 28 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index dfa01f970..ea311e8df 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -27,9 +27,7 @@ - + diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/MediaStoreStreamHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/MediaStoreStreamHandler.kt index ac9fec726..69e130e33 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/MediaStoreStreamHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/streams/MediaStoreStreamHandler.kt @@ -55,7 +55,7 @@ class MediaStoreStreamHandler(private val context: Context, arguments: Any?) : E } } - private suspend fun fetchAll() { + private fun fetchAll() { MediaStoreImageProvider().fetchAll(context, knownEntries ?: emptyMap()) { success(it) } endOfStream() } diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/ExifInterfaceHelper.kt b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/ExifInterfaceHelper.kt index 9eb1de5c6..f43001cda 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/ExifInterfaceHelper.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/ExifInterfaceHelper.kt @@ -9,6 +9,8 @@ import com.drew.metadata.exif.makernotes.OlympusCameraSettingsMakernoteDirectory import com.drew.metadata.exif.makernotes.OlympusImageProcessingMakernoteDirectory import com.drew.metadata.exif.makernotes.OlympusMakernoteDirectory import deckers.thibault.aves.utils.LogUtils +import java.text.ParseException +import java.text.SimpleDateFormat import java.util.* import kotlin.math.abs import kotlin.math.floor @@ -16,6 +18,7 @@ import kotlin.math.roundToLong object ExifInterfaceHelper { private val LOG_TAG = LogUtils.createTag(ExifInterfaceHelper::class.java) + private val DATETIME_FORMAT = SimpleDateFormat("yyyy:MM:dd hh:mm:ss", Locale.ROOT) private const val precisionErrorTolerance = 1e-10 @@ -358,11 +361,15 @@ object ExifInterfaceHelper { fun ExifInterface.getSafeDateMillis(tag: String, save: (value: Long) -> Unit) { if (this.hasAttribute(tag)) { - // TODO TLAD parse date with "yyyy:MM:dd HH:mm:ss" or find the original long - val formattedDate = this.getAttribute(tag) - val value = formattedDate?.toLongOrNull() - if (value != null && value > 0) { - save(value) + val dateString = this.getAttribute(tag) + if (dateString != null) { + try { + DATETIME_FORMAT.parse(dateString)?.let { date -> + save(date.time) + } + } catch (e: ParseException) { + Log.w(LOG_TAG, "failed to parse date=$dateString", e) + } } } } diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ContentImageProvider.kt b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ContentImageProvider.kt index 96b49e980..9fc5a7f66 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ContentImageProvider.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ContentImageProvider.kt @@ -6,7 +6,7 @@ import android.provider.MediaStore import deckers.thibault.aves.model.SourceEntry internal class ContentImageProvider : ImageProvider() { - override suspend fun fetchSingle(context: Context, uri: Uri, mimeType: String?, callback: ImageOpCallback) { + override fun fetchSingle(context: Context, uri: Uri, mimeType: String?, callback: ImageOpCallback) { if (mimeType == null) { callback.onFailure(Exception("MIME type is null for uri=$uri")) return diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/FileImageProvider.kt b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/FileImageProvider.kt index 7a08724bf..c87b4b312 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/FileImageProvider.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/FileImageProvider.kt @@ -6,7 +6,7 @@ import deckers.thibault.aves.model.SourceEntry import java.io.File internal class FileImageProvider : ImageProvider() { - override suspend fun fetchSingle(context: Context, uri: Uri, mimeType: String?, callback: ImageOpCallback) { + override fun fetchSingle(context: Context, uri: Uri, mimeType: String?, callback: ImageOpCallback) { if (mimeType == null) { callback.onFailure(Exception("MIME type is null for uri=$uri")) return diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ImageProvider.kt b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ImageProvider.kt index da1fa2c49..62fa4a3cb 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ImageProvider.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/ImageProvider.kt @@ -35,7 +35,7 @@ import kotlin.coroutines.resumeWithException import kotlin.coroutines.suspendCoroutine abstract class ImageProvider { - open suspend fun fetchSingle(context: Context, uri: Uri, mimeType: String?, callback: ImageOpCallback) { + open fun fetchSingle(context: Context, uri: Uri, mimeType: String?, callback: ImageOpCallback) { callback.onFailure(UnsupportedOperationException()) } diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/MediaStoreImageProvider.kt b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/MediaStoreImageProvider.kt index 78e65b524..183d16391 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/MediaStoreImageProvider.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/model/provider/MediaStoreImageProvider.kt @@ -20,13 +20,12 @@ import deckers.thibault.aves.utils.StorageUtils.ensureTrailingSeparator import deckers.thibault.aves.utils.StorageUtils.getDocumentFile import deckers.thibault.aves.utils.StorageUtils.requireAccessPermission import deckers.thibault.aves.utils.UriUtils.tryParseId -import kotlinx.coroutines.delay import java.io.File import java.util.* import kotlin.collections.ArrayList class MediaStoreImageProvider : ImageProvider() { - suspend fun fetchAll(context: Context, knownEntries: Map, handleNewEntry: NewEntryHandler) { + fun fetchAll(context: Context, knownEntries: Map, handleNewEntry: NewEntryHandler) { val isModified = fun(contentId: Int, dateModifiedSecs: Int): Boolean { val knownDate = knownEntries[contentId] return knownDate == null || knownDate < dateModifiedSecs @@ -35,7 +34,7 @@ class MediaStoreImageProvider : ImageProvider() { fetchFrom(context, isModified, handleNewEntry, VIDEO_CONTENT_URI, VIDEO_PROJECTION) } - override suspend fun fetchSingle(context: Context, uri: Uri, mimeType: String?, callback: ImageOpCallback) { + override fun fetchSingle(context: Context, uri: Uri, mimeType: String?, callback: ImageOpCallback) { val id = uri.tryParseId() val onSuccess = fun(entry: FieldMap) { entry["uri"] = uri.toString() @@ -45,17 +44,17 @@ class MediaStoreImageProvider : ImageProvider() { if (id != null) { if (mimeType == null || isImage(mimeType)) { val contentUri = ContentUris.withAppendedId(IMAGE_CONTENT_URI, id) - if (fetchFrom(context, alwaysValid, onSuccess, contentUri, IMAGE_PROJECTION) > 0) return + if (fetchFrom(context, alwaysValid, onSuccess, contentUri, IMAGE_PROJECTION)) return } if (mimeType == null || isVideo(mimeType)) { val contentUri = ContentUris.withAppendedId(VIDEO_CONTENT_URI, id) - if (fetchFrom(context, alwaysValid, onSuccess, contentUri, VIDEO_PROJECTION) > 0) return + if (fetchFrom(context, alwaysValid, onSuccess, contentUri, VIDEO_PROJECTION)) return } } // the uri can be a file media URI (e.g. "content://0@media/external/file/30050") // without an equivalent image/video if it is shared from a file browser // but the file is not publicly visible - if (fetchFrom(context, alwaysValid, onSuccess, uri, BASE_PROJECTION, fileMimeType = mimeType) > 0) return + if (fetchFrom(context, alwaysValid, onSuccess, uri, BASE_PROJECTION, fileMimeType = mimeType)) return callback.onFailure(Exception("failed to fetch entry at uri=$uri")) } @@ -109,15 +108,15 @@ class MediaStoreImageProvider : ImageProvider() { return obsoleteIds } - private suspend fun fetchFrom( + private fun fetchFrom( context: Context, isValidEntry: NewEntryChecker, handleNewEntry: NewEntryHandler, contentUri: Uri, projection: Array, fileMimeType: String? = null, - ): Int { - var newEntryCount = 0 + ): Boolean { + var found = false val orderBy = "${MediaStore.MediaColumns.DATE_MODIFIED} DESC" try { val cursor = context.contentResolver.query(contentUri, projection, null, null, orderBy) @@ -191,11 +190,7 @@ class MediaStoreImageProvider : ImageProvider() { } handleNewEntry(entryMap) - // TODO TLAD is this necessary? - if (newEntryCount % 30 == 0) { - delay(10) - } - newEntryCount++ + found = true } } } @@ -204,7 +199,7 @@ class MediaStoreImageProvider : ImageProvider() { } catch (e: Exception) { Log.e(LOG_TAG, "failed to get entries", e) } - return newEntryCount + return found } private fun needSize(mimeType: String) = MimeTypes.SVG != mimeType diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/utils/MimeTypes.kt b/android/app/src/main/kotlin/deckers/thibault/aves/utils/MimeTypes.kt index f033dbf22..0c099863a 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/utils/MimeTypes.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/utils/MimeTypes.kt @@ -7,6 +7,7 @@ object MimeTypes { // generic raster private const val BMP = "image/bmp" + private const val DJVU = "image/vnd.djvu" const val GIF = "image/gif" const val HEIC = "image/heic" private const val HEIF = "image/heif" @@ -35,6 +36,7 @@ object MimeTypes { private const val VIDEO = "video" private const val MP2T = "video/mp2t" + private const val MP2TS = "video/mp2ts" private const val WEBM = "video/webm" fun isImage(mimeType: String?) = mimeType != null && mimeType.startsWith(IMAGE) @@ -68,7 +70,7 @@ object MimeTypes { // as of `metadata-extractor` v2.14.0 fun isSupportedByMetadataExtractor(mimeType: String) = when (mimeType) { - WBMP, MP2T, WEBM -> false + DJVU, WBMP, MP2T, MP2TS, WEBM -> false else -> true } diff --git a/lib/utils/mime_utils.dart b/lib/utils/mime_utils.dart index 52c223f29..e419cd529 100644 --- a/lib/utils/mime_utils.dart +++ b/lib/utils/mime_utils.dart @@ -3,6 +3,8 @@ class MimeUtils { switch (mime) { case 'image/x-icon': return 'ICO'; + case 'image/x-jg': + return 'ART'; case 'image/vnd.adobe.photoshop': case 'image/x-photoshop': return 'PSD';