more idiomatic kotlin
This commit is contained in:
parent
752749bafe
commit
bb96f2f65a
7 changed files with 76 additions and 69 deletions
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue