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';