more idiomatic kotlin

This commit is contained in:
Thibault Deckers 2020-10-28 12:33:00 +09:00
parent 752749bafe
commit bb96f2f65a
7 changed files with 76 additions and 69 deletions

View file

@ -74,7 +74,7 @@ class ImageFileHandler(private val activity: Activity) : MethodCallHandler {
} }
} }
private fun getImageEntry(call: MethodCall, result: MethodChannel.Result) { private suspend fun getImageEntry(call: MethodCall, result: MethodChannel.Result) {
val mimeType = call.argument<String>("mimeType") // MIME type is optional val mimeType = call.argument<String>("mimeType") // MIME type is optional
val uri = call.argument<String>("uri")?.let { Uri.parse(it) } val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
if (uri == null) { if (uri == null) {
@ -94,7 +94,7 @@ class ImageFileHandler(private val activity: Activity) : MethodCallHandler {
}) })
} }
private fun rename(call: MethodCall, result: MethodChannel.Result) { private suspend fun rename(call: MethodCall, result: MethodChannel.Result) {
val entryMap = call.argument<FieldMap>("entry") val entryMap = call.argument<FieldMap>("entry")
val newName = call.argument<String>("newName") val newName = call.argument<String>("newName")
if (entryMap == null || newName == null) { if (entryMap == null || newName == null) {

View file

@ -62,7 +62,7 @@ class ImageOpStreamHandler(private val context: Context, private val arguments:
handler.post { eventSink.endOfStream() } handler.post { eventSink.endOfStream() }
} }
private fun move() { private suspend fun move() {
if (arguments !is Map<*, *> || entryMapList.isEmpty()) { if (arguments !is Map<*, *> || entryMapList.isEmpty()) {
endOfStream() endOfStream()
return return

View file

@ -40,7 +40,7 @@ class MediaStoreStreamHandler(private val context: Context, arguments: Any?) : E
handler.post { eventSink.endOfStream() } handler.post { eventSink.endOfStream() }
} }
private fun fetchAll() { private suspend fun fetchAll() {
MediaStoreImageProvider().fetchAll(context, knownEntries ?: emptyMap()) { success(it) } MediaStoreImageProvider().fetchAll(context, knownEntries ?: emptyMap()) { success(it) }
endOfStream() endOfStream()
} }

View file

@ -6,7 +6,7 @@ import android.provider.MediaStore
import deckers.thibault.aves.model.SourceImageEntry import deckers.thibault.aves.model.SourceImageEntry
internal class ContentImageProvider : ImageProvider() { internal class ContentImageProvider : ImageProvider() {
override fun fetchSingle(context: Context, uri: Uri, mimeType: String?, callback: ImageOpCallback) { override suspend fun fetchSingle(context: Context, uri: Uri, mimeType: String?, callback: ImageOpCallback) {
if (mimeType == null) { if (mimeType == null) {
callback.onFailure(Exception("MIME type is null for uri=$uri")) callback.onFailure(Exception("MIME type is null for uri=$uri"))
return return

View file

@ -6,7 +6,7 @@ import deckers.thibault.aves.model.SourceImageEntry
import java.io.File import java.io.File
internal class FileImageProvider : ImageProvider() { internal class FileImageProvider : ImageProvider() {
override fun fetchSingle(context: Context, uri: Uri, mimeType: String?, callback: ImageOpCallback) { override suspend fun fetchSingle(context: Context, uri: Uri, mimeType: String?, callback: ImageOpCallback) {
if (mimeType == null) { if (mimeType == null) {
callback.onFailure(Exception("MIME type is null for uri=$uri")) callback.onFailure(Exception("MIME type is null for uri=$uri"))
return return

View file

@ -21,9 +21,12 @@ import java.io.File
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.io.IOException import java.io.IOException
import java.util.* import java.util.*
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
abstract class ImageProvider { abstract class ImageProvider {
open fun fetchSingle(context: Context, uri: Uri, mimeType: String?, callback: ImageOpCallback) { open suspend fun fetchSingle(context: Context, uri: Uri, mimeType: String?, callback: ImageOpCallback) {
callback.onFailure(UnsupportedOperationException()) callback.onFailure(UnsupportedOperationException())
} }
@ -31,11 +34,11 @@ abstract class ImageProvider {
return Futures.immediateFailedFuture(UnsupportedOperationException()) return Futures.immediateFailedFuture(UnsupportedOperationException())
} }
open fun moveMultiple(context: Context, copy: Boolean, destinationDir: String, entries: List<AvesImageEntry>, callback: ImageOpCallback) { open suspend fun moveMultiple(context: Context, copy: Boolean, destinationDir: String, entries: List<AvesImageEntry>, callback: ImageOpCallback) {
callback.onFailure(UnsupportedOperationException()) callback.onFailure(UnsupportedOperationException())
} }
fun rename(context: Context, oldPath: String, oldMediaUri: Uri, mimeType: String, newFilename: String, callback: ImageOpCallback) { suspend fun rename(context: Context, oldPath: String, oldMediaUri: Uri, mimeType: String, newFilename: String, callback: ImageOpCallback) {
val oldFile = File(oldPath) val oldFile = File(oldPath)
val newFile = File(oldFile.parent, newFilename) val newFile = File(oldFile.parent, newFilename)
if (oldFile == newFile) { if (oldFile == newFile) {
@ -57,7 +60,11 @@ abstract class ImageProvider {
} }
MediaScannerConnection.scanFile(context, arrayOf(oldPath), arrayOf(mimeType), null) MediaScannerConnection.scanFile(context, arrayOf(oldPath), arrayOf(mimeType), null)
scanNewPath(context, newFile.path, mimeType, callback) try {
callback.onSuccess(scanNewPath(context, newFile.path, mimeType))
} catch (e: Exception) {
callback.onFailure(e)
}
} }
fun changeOrientation(context: Context, path: String, uri: Uri, mimeType: String, op: ExifOrientationOp, callback: ImageOpCallback) { fun changeOrientation(context: Context, path: String, uri: Uri, mimeType: String, op: ExifOrientationOp, callback: ImageOpCallback) {
@ -132,54 +139,56 @@ abstract class ImageProvider {
} }
} }
protected fun scanNewPath(context: Context, path: String, mimeType: String, callback: ImageOpCallback) { protected suspend fun scanNewPath(context: Context, path: String, mimeType: String): FieldMap =
MediaScannerConnection.scanFile(context, arrayOf(path), arrayOf(mimeType)) { _, newUri: Uri? -> suspendCoroutine { cont ->
var contentId: Long = 0 MediaScannerConnection.scanFile(context, arrayOf(path), arrayOf(mimeType)) { _, newUri: Uri? ->
var contentUri: Uri? = null var contentId: Long = 0
if (newUri != null) { var contentUri: Uri? = null
// `newURI` is possibly a file media URI (e.g. "content://media/12a9-8b42/file/62872") if (newUri != null) {
// but we need an image/video media URI (e.g. "content://media/external/images/media/62872") // `newURI` is possibly a file media URI (e.g. "content://media/12a9-8b42/file/62872")
contentId = ContentUris.parseId(newUri) // but we need an image/video media URI (e.g. "content://media/external/images/media/62872")
if (isImage(mimeType)) { contentId = ContentUris.parseId(newUri)
contentUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentId) if (isImage(mimeType)) {
} else if (isVideo(mimeType)) { contentUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentId)
contentUri = ContentUris.withAppendedId(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, contentId) } else if (isVideo(mimeType)) {
contentUri = ContentUris.withAppendedId(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, contentId)
}
}
if (contentUri == null) {
cont.resumeWithException(Exception("failed to get content URI of item at path=$path"))
return@scanFile
} }
}
if (contentUri == null) {
callback.onFailure(Exception("failed to get content URI of item at path=$path"))
return@scanFile
}
val newFields = HashMap<String, Any?>() val newFields = HashMap<String, Any?>()
// we retrieve updated fields as the renamed/moved file became a new entry in the Media Store // we retrieve updated fields as the renamed/moved file became a new entry in the Media Store
val projection = arrayOf( val projection = arrayOf(
MediaStore.MediaColumns.DATE_MODIFIED, MediaStore.MediaColumns.DATE_MODIFIED,
MediaStore.MediaColumns.DISPLAY_NAME, MediaStore.MediaColumns.DISPLAY_NAME,
MediaStore.MediaColumns.TITLE, MediaStore.MediaColumns.TITLE,
) )
try { try {
val cursor = context.contentResolver.query(contentUri, projection, null, null, null) val cursor = context.contentResolver.query(contentUri, projection, null, null, null)
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
newFields["uri"] = contentUri.toString() newFields["uri"] = contentUri.toString()
newFields["contentId"] = contentId newFields["contentId"] = contentId
newFields["path"] = path newFields["path"] = path
cursor.getColumnIndex(MediaStore.MediaColumns.DATE_MODIFIED).let { if (it != -1) newFields["dateModifiedSecs"] = cursor.getInt(it) } cursor.getColumnIndex(MediaStore.MediaColumns.DATE_MODIFIED).let { if (it != -1) newFields["dateModifiedSecs"] = cursor.getInt(it) }
cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME).let { if (it != -1) newFields["displayName"] = cursor.getString(it) } cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME).let { if (it != -1) newFields["displayName"] = cursor.getString(it) }
cursor.getColumnIndex(MediaStore.MediaColumns.TITLE).let { if (it != -1) newFields["title"] = cursor.getString(it) } cursor.getColumnIndex(MediaStore.MediaColumns.TITLE).let { if (it != -1) newFields["title"] = cursor.getString(it) }
cursor.close() cursor.close()
}
} catch (e: Exception) {
cont.resumeWithException(e)
return@scanFile
}
if (newFields.isEmpty()) {
cont.resumeWithException(Exception("failed to get item details from provider at contentUri=$contentUri"))
} else {
cont.resume(newFields)
} }
} catch (e: Exception) {
callback.onFailure(e)
return@scanFile
}
if (newFields.isEmpty()) {
callback.onFailure(Exception("failed to get item details from provider at contentUri=$contentUri"))
} else {
callback.onSuccess(newFields)
} }
} }
}
interface ImageOpCallback { interface ImageOpCallback {
fun onSuccess(fields: FieldMap) fun onSuccess(fields: FieldMap)

View file

@ -20,13 +20,14 @@ import deckers.thibault.aves.utils.StorageUtils.createDirectoryIfAbsent
import deckers.thibault.aves.utils.StorageUtils.ensureTrailingSeparator import deckers.thibault.aves.utils.StorageUtils.ensureTrailingSeparator
import deckers.thibault.aves.utils.StorageUtils.getDocumentFile import deckers.thibault.aves.utils.StorageUtils.getDocumentFile
import deckers.thibault.aves.utils.StorageUtils.requireAccessPermission import deckers.thibault.aves.utils.StorageUtils.requireAccessPermission
import kotlinx.coroutines.delay
import java.io.File import java.io.File
import java.io.FileNotFoundException import java.io.FileNotFoundException
import java.util.* import java.util.*
import java.util.concurrent.ExecutionException import java.util.concurrent.ExecutionException
class MediaStoreImageProvider : ImageProvider() { class MediaStoreImageProvider : ImageProvider() {
fun fetchAll(context: Context, knownEntries: Map<Int, Int?>, handleNewEntry: NewEntryHandler) { suspend fun fetchAll(context: Context, knownEntries: Map<Int, Int?>, handleNewEntry: NewEntryHandler) {
val isModified = fun(contentId: Int, dateModifiedSecs: Int): Boolean { val isModified = fun(contentId: Int, dateModifiedSecs: Int): Boolean {
val knownDate = knownEntries[contentId] val knownDate = knownEntries[contentId]
return knownDate == null || knownDate < dateModifiedSecs return knownDate == null || knownDate < dateModifiedSecs
@ -35,7 +36,7 @@ class MediaStoreImageProvider : ImageProvider() {
fetchFrom(context, isModified, handleNewEntry, VIDEO_CONTENT_URI, VIDEO_PROJECTION) fetchFrom(context, isModified, handleNewEntry, VIDEO_CONTENT_URI, VIDEO_PROJECTION)
} }
override fun fetchSingle(context: Context, uri: Uri, mimeType: String?, callback: ImageOpCallback) { override suspend fun fetchSingle(context: Context, uri: Uri, mimeType: String?, callback: ImageOpCallback) {
val id = ContentUris.parseId(uri) val id = ContentUris.parseId(uri)
val onSuccess = fun(entry: FieldMap) { val onSuccess = fun(entry: FieldMap) {
entry["uri"] = uri.toString() entry["uri"] = uri.toString()
@ -84,7 +85,7 @@ class MediaStoreImageProvider : ImageProvider() {
return foundContentIds return foundContentIds
} }
private fun fetchFrom( private suspend fun fetchFrom(
context: Context, context: Context,
isValidEntry: NewEntryChecker, isValidEntry: NewEntryChecker,
handleNewEntry: NewEntryHandler, handleNewEntry: NewEntryHandler,
@ -159,7 +160,7 @@ class MediaStoreImageProvider : ImageProvider() {
handleNewEntry(entryMap) handleNewEntry(entryMap)
// TODO TLAD is this necessary? // TODO TLAD is this necessary?
if (newEntryCount % 30 == 0) { if (newEntryCount % 30 == 0) {
Thread.sleep(10) delay(10)
} }
newEntryCount++ newEntryCount++
} }
@ -212,7 +213,7 @@ class MediaStoreImageProvider : ImageProvider() {
return future return future
} }
override fun moveMultiple( override suspend fun moveMultiple(
context: Context, context: Context,
copy: Boolean, copy: Boolean,
destinationDir: String, destinationDir: String,
@ -266,7 +267,7 @@ class MediaStoreImageProvider : ImageProvider() {
} }
} }
private fun moveSingleByTreeDocAndScan( private suspend fun moveSingleByTreeDocAndScan(
context: Context, context: Context,
sourcePath: String, sourcePath: String,
sourceUri: Uri, sourceUri: Uri,
@ -322,16 +323,13 @@ class MediaStoreImageProvider : ImageProvider() {
} }
} }
scanNewPath(context, destinationFullPath, mimeType, object : ImageOpCallback { try {
override fun onSuccess(fields: FieldMap) { val fields = scanNewPath(context, destinationFullPath, mimeType)
fields["deletedSource"] = deletedSource fields["deletedSource"] = deletedSource
future.set(fields) future.set(fields)
} } catch (e: Exception) {
future.setException(e)
override fun onFailure(throwable: Throwable) { }
future.setException(throwable)
}
})
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e(LOG_TAG, "failed to ${(if (copy) "copy" else "move")} entry", e) Log.e(LOG_TAG, "failed to ${(if (copy) "copy" else "move")} entry", e)