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 uri = call.argument<String>("uri")?.let { Uri.parse(it) }
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 newName = call.argument<String>("newName")
if (entryMap == null || newName == null) {

View file

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

View file

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

View file

@ -6,7 +6,7 @@ import android.provider.MediaStore
import deckers.thibault.aves.model.SourceImageEntry
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) {
callback.onFailure(Exception("MIME type is null for uri=$uri"))
return

View file

@ -6,7 +6,7 @@ import deckers.thibault.aves.model.SourceImageEntry
import java.io.File
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) {
callback.onFailure(Exception("MIME type is null for uri=$uri"))
return

View file

@ -21,9 +21,12 @@ import java.io.File
import java.io.FileNotFoundException
import java.io.IOException
import java.util.*
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
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())
}
@ -31,11 +34,11 @@ abstract class ImageProvider {
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())
}
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 newFile = File(oldFile.parent, newFilename)
if (oldFile == newFile) {
@ -57,7 +60,11 @@ abstract class ImageProvider {
}
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) {
@ -132,54 +139,56 @@ abstract class ImageProvider {
}
}
protected fun scanNewPath(context: Context, path: String, mimeType: String, callback: ImageOpCallback) {
MediaScannerConnection.scanFile(context, arrayOf(path), arrayOf(mimeType)) { _, newUri: Uri? ->
var contentId: Long = 0
var contentUri: Uri? = null
if (newUri != null) {
// `newURI` is possibly a file media URI (e.g. "content://media/12a9-8b42/file/62872")
// but we need an image/video media URI (e.g. "content://media/external/images/media/62872")
contentId = ContentUris.parseId(newUri)
if (isImage(mimeType)) {
contentUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentId)
} else if (isVideo(mimeType)) {
contentUri = ContentUris.withAppendedId(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, contentId)
protected suspend fun scanNewPath(context: Context, path: String, mimeType: String): FieldMap =
suspendCoroutine { cont ->
MediaScannerConnection.scanFile(context, arrayOf(path), arrayOf(mimeType)) { _, newUri: Uri? ->
var contentId: Long = 0
var contentUri: Uri? = null
if (newUri != null) {
// `newURI` is possibly a file media URI (e.g. "content://media/12a9-8b42/file/62872")
// but we need an image/video media URI (e.g. "content://media/external/images/media/62872")
contentId = ContentUris.parseId(newUri)
if (isImage(mimeType)) {
contentUri = ContentUris.withAppendedId(MediaStore.Images.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?>()
// we retrieve updated fields as the renamed/moved file became a new entry in the Media Store
val projection = arrayOf(
MediaStore.MediaColumns.DATE_MODIFIED,
MediaStore.MediaColumns.DISPLAY_NAME,
MediaStore.MediaColumns.TITLE,
)
try {
val cursor = context.contentResolver.query(contentUri, projection, null, null, null)
if (cursor != null && cursor.moveToFirst()) {
newFields["uri"] = contentUri.toString()
newFields["contentId"] = contentId
newFields["path"] = path
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.TITLE).let { if (it != -1) newFields["title"] = cursor.getString(it) }
cursor.close()
val newFields = HashMap<String, Any?>()
// we retrieve updated fields as the renamed/moved file became a new entry in the Media Store
val projection = arrayOf(
MediaStore.MediaColumns.DATE_MODIFIED,
MediaStore.MediaColumns.DISPLAY_NAME,
MediaStore.MediaColumns.TITLE,
)
try {
val cursor = context.contentResolver.query(contentUri, projection, null, null, null)
if (cursor != null && cursor.moveToFirst()) {
newFields["uri"] = contentUri.toString()
newFields["contentId"] = contentId
newFields["path"] = path
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.TITLE).let { if (it != -1) newFields["title"] = cursor.getString(it) }
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 {
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.getDocumentFile
import deckers.thibault.aves.utils.StorageUtils.requireAccessPermission
import kotlinx.coroutines.delay
import java.io.File
import java.io.FileNotFoundException
import java.util.*
import java.util.concurrent.ExecutionException
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 knownDate = knownEntries[contentId]
return knownDate == null || knownDate < dateModifiedSecs
@ -35,7 +36,7 @@ class MediaStoreImageProvider : ImageProvider() {
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 onSuccess = fun(entry: FieldMap) {
entry["uri"] = uri.toString()
@ -84,7 +85,7 @@ class MediaStoreImageProvider : ImageProvider() {
return foundContentIds
}
private fun fetchFrom(
private suspend fun fetchFrom(
context: Context,
isValidEntry: NewEntryChecker,
handleNewEntry: NewEntryHandler,
@ -159,7 +160,7 @@ class MediaStoreImageProvider : ImageProvider() {
handleNewEntry(entryMap)
// TODO TLAD is this necessary?
if (newEntryCount % 30 == 0) {
Thread.sleep(10)
delay(10)
}
newEntryCount++
}
@ -212,7 +213,7 @@ class MediaStoreImageProvider : ImageProvider() {
return future
}
override fun moveMultiple(
override suspend fun moveMultiple(
context: Context,
copy: Boolean,
destinationDir: String,
@ -266,7 +267,7 @@ class MediaStoreImageProvider : ImageProvider() {
}
}
private fun moveSingleByTreeDocAndScan(
private suspend fun moveSingleByTreeDocAndScan(
context: Context,
sourcePath: String,
sourceUri: Uri,
@ -322,16 +323,13 @@ class MediaStoreImageProvider : ImageProvider() {
}
}
scanNewPath(context, destinationFullPath, mimeType, object : ImageOpCallback {
override fun onSuccess(fields: FieldMap) {
fields["deletedSource"] = deletedSource
future.set(fields)
}
override fun onFailure(throwable: Throwable) {
future.setException(throwable)
}
})
try {
val fields = scanNewPath(context, destinationFullPath, mimeType)
fields["deletedSource"] = deletedSource
future.set(fields)
} catch (e: Exception) {
future.setException(e)
}
}
} catch (e: Exception) {
Log.e(LOG_TAG, "failed to ${(if (copy) "copy" else "move")} entry", e)