more idiomatic kotlin, removed guava
This commit is contained in:
parent
bb96f2f65a
commit
7aa50e7880
7 changed files with 74 additions and 126 deletions
|
@ -100,7 +100,6 @@ dependencies {
|
|||
implementation 'com.commonsware.cwac:document:0.4.1'
|
||||
implementation 'com.drewnoakes:metadata-extractor:2.15.0'
|
||||
implementation 'com.github.bumptech.glide:glide:4.11.0'
|
||||
implementation 'com.google.guava:guava:30.0-android'
|
||||
|
||||
kapt 'androidx.annotation:annotation:1.1.0'
|
||||
kapt 'com.github.bumptech.glide:compiler:4.11.0'
|
||||
|
|
|
@ -11,6 +11,7 @@ import deckers.thibault.aves.model.provider.MediaStoreImageProvider
|
|||
import io.flutter.plugin.common.MethodCall
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.math.roundToInt
|
||||
|
@ -27,9 +28,9 @@ class ImageFileHandler(private val activity: Activity) : MethodCallHandler {
|
|||
GlobalScope.launch { Glide.get(activity).clearDiskCache() }
|
||||
result.success(null)
|
||||
}
|
||||
"rename" -> GlobalScope.launch { rename(call, Coresult(result)) }
|
||||
"rotate" -> GlobalScope.launch { rotate(call, Coresult(result)) }
|
||||
"flip" -> GlobalScope.launch { flip(call, Coresult(result)) }
|
||||
"rename" -> GlobalScope.launch(Dispatchers.IO) { rename(call, Coresult(result)) }
|
||||
"rotate" -> GlobalScope.launch(Dispatchers.IO) { rotate(call, Coresult(result)) }
|
||||
"flip" -> GlobalScope.launch(Dispatchers.IO) { flip(call, Coresult(result)) }
|
||||
else -> result.notImplemented()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@ import com.adobe.internal.xmp.XMPUtils
|
|||
import com.adobe.internal.xmp.properties.XMPPropertyInfo
|
||||
import com.bumptech.glide.load.resource.bitmap.TransformationUtils
|
||||
import com.drew.imaging.ImageMetadataReader
|
||||
import com.drew.imaging.ImageProcessingException
|
||||
import com.drew.lang.Rational
|
||||
import com.drew.metadata.exif.ExifDirectoryBase
|
||||
import com.drew.metadata.exif.ExifIFD0Directory
|
||||
|
@ -59,7 +58,6 @@ import io.flutter.plugin.common.MethodChannel.MethodCallHandler
|
|||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
import kotlin.math.roundToLong
|
||||
|
||||
|
@ -568,9 +566,7 @@ class MetadataHandler(private val context: Context) : MethodCallHandler {
|
|||
}
|
||||
}
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Log.w(LOG_TAG, "failed to extract xmp thumbnail", e)
|
||||
} catch (e: ImageProcessingException) {
|
||||
} catch (e: Exception) {
|
||||
Log.w(LOG_TAG, "failed to extract xmp thumbnail", e)
|
||||
} catch (e: NoClassDefFoundError) {
|
||||
Log.w(LOG_TAG, "failed to extract xmp thumbnail", e)
|
||||
|
|
|
@ -13,10 +13,10 @@ import deckers.thibault.aves.utils.LogUtils.createTag
|
|||
import deckers.thibault.aves.utils.StorageUtils
|
||||
import io.flutter.plugin.common.EventChannel
|
||||
import io.flutter.plugin.common.EventChannel.EventSink
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.*
|
||||
import java.util.concurrent.ExecutionException
|
||||
|
||||
class ImageOpStreamHandler(private val context: Context, private val arguments: Any?) : EventChannel.StreamHandler {
|
||||
private lateinit var eventSink: EventSink
|
||||
|
@ -41,8 +41,8 @@ class ImageOpStreamHandler(private val context: Context, private val arguments:
|
|||
handler = Handler(Looper.getMainLooper())
|
||||
|
||||
when (op) {
|
||||
"delete" -> GlobalScope.launch { delete() }
|
||||
"move" -> GlobalScope.launch { move() }
|
||||
"delete" -> GlobalScope.launch(Dispatchers.IO) { delete() }
|
||||
"move" -> GlobalScope.launch(Dispatchers.IO) { move() }
|
||||
else -> endOfStream()
|
||||
}
|
||||
}
|
||||
|
@ -92,7 +92,7 @@ class ImageOpStreamHandler(private val context: Context, private val arguments:
|
|||
endOfStream()
|
||||
}
|
||||
|
||||
private fun delete() {
|
||||
private suspend fun delete() {
|
||||
if (entryMapList.isEmpty()) {
|
||||
endOfStream()
|
||||
return
|
||||
|
@ -114,12 +114,9 @@ class ImageOpStreamHandler(private val context: Context, private val arguments:
|
|||
"uri" to uri.toString(),
|
||||
)
|
||||
try {
|
||||
provider.delete(context, uri, path).get()
|
||||
provider.delete(context, uri, path)
|
||||
result["success"] = true
|
||||
} catch (e: ExecutionException) {
|
||||
Log.w(LOG_TAG, "failed to delete entry with path=$path", e)
|
||||
result["success"] = false
|
||||
} catch (e: InterruptedException) {
|
||||
} catch (e: Exception) {
|
||||
Log.w(LOG_TAG, "failed to delete entry with path=$path", e)
|
||||
result["success"] = false
|
||||
}
|
||||
|
|
|
@ -8,8 +8,6 @@ import android.provider.MediaStore
|
|||
import android.util.Log
|
||||
import androidx.exifinterface.media.ExifInterface
|
||||
import com.commonsware.cwac.document.DocumentFileCompat
|
||||
import com.google.common.util.concurrent.Futures
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import deckers.thibault.aves.model.AvesImageEntry
|
||||
import deckers.thibault.aves.model.ExifOrientationOp
|
||||
import deckers.thibault.aves.utils.LogUtils.createTag
|
||||
|
@ -30,8 +28,8 @@ abstract class ImageProvider {
|
|||
callback.onFailure(UnsupportedOperationException())
|
||||
}
|
||||
|
||||
open fun delete(context: Context, uri: Uri, path: String?): ListenableFuture<Any?> {
|
||||
return Futures.immediateFailedFuture(UnsupportedOperationException())
|
||||
open suspend fun delete(context: Context, uri: Uri, path: String?) {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
open suspend fun moveMultiple(context: Context, copy: Boolean, destinationDir: String, entries: List<AvesImageEntry>, callback: ImageOpCallback) {
|
||||
|
@ -49,6 +47,7 @@ abstract class ImageProvider {
|
|||
|
||||
val df = getDocumentFile(context, oldPath, oldMediaUri)
|
||||
try {
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
val renamed = df != null && df.renameTo(newFilename)
|
||||
if (!renamed) {
|
||||
callback.onFailure(Exception("failed to rename entry at path=$oldPath"))
|
||||
|
|
|
@ -8,8 +8,6 @@ import android.os.Build
|
|||
import android.provider.MediaStore
|
||||
import android.util.Log
|
||||
import com.commonsware.cwac.document.DocumentFileCompat
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import com.google.common.util.concurrent.SettableFuture
|
||||
import deckers.thibault.aves.model.AvesImageEntry
|
||||
import deckers.thibault.aves.model.SourceImageEntry
|
||||
import deckers.thibault.aves.utils.LogUtils.createTag
|
||||
|
@ -22,9 +20,7 @@ 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() {
|
||||
suspend fun fetchAll(context: Context, knownEntries: Map<Int, Int?>, handleNewEntry: NewEntryHandler) {
|
||||
|
@ -176,41 +172,21 @@ class MediaStoreImageProvider : ImageProvider() {
|
|||
private fun needSize(mimeType: String) = MimeTypes.SVG != mimeType
|
||||
|
||||
// `uri` is a media URI, not a document URI
|
||||
override fun delete(context: Context, uri: Uri, path: String?): ListenableFuture<Any?> {
|
||||
val future = SettableFuture.create<Any?>()
|
||||
|
||||
if (path == null) {
|
||||
future.setException(Exception("failed to delete file because path is null"))
|
||||
return future
|
||||
}
|
||||
override suspend fun delete(context: Context, uri: Uri, path: String?) {
|
||||
path ?: throw Exception("failed to delete file because path is null")
|
||||
|
||||
if (requireAccessPermission(context, path)) {
|
||||
// if the file is on SD card, calling the content resolver `delete()` removes the entry from the Media Store
|
||||
// but it doesn't delete the file, even if the app has the permission
|
||||
try {
|
||||
val df = getDocumentFile(context, path, uri)
|
||||
if (df != null && df.delete()) {
|
||||
future.set(null)
|
||||
} else {
|
||||
future.setException(Exception("failed to delete file with df=$df"))
|
||||
}
|
||||
} catch (e: FileNotFoundException) {
|
||||
future.setException(e)
|
||||
}
|
||||
return future
|
||||
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
if (df != null && df.delete()) return
|
||||
throw Exception("failed to delete file with df=$df")
|
||||
}
|
||||
|
||||
try {
|
||||
if (context.contentResolver.delete(uri, null, null) > 0) {
|
||||
future.set(null)
|
||||
} else {
|
||||
future.setException(Exception("failed to delete row from content provider"))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(LOG_TAG, "failed to delete entry", e)
|
||||
future.setException(e)
|
||||
}
|
||||
return future
|
||||
if (context.contentResolver.delete(uri, null, null) > 0) return
|
||||
throw Exception("failed to delete row from content provider")
|
||||
}
|
||||
|
||||
override suspend fun moveMultiple(
|
||||
|
@ -252,14 +228,12 @@ class MediaStoreImageProvider : ImageProvider() {
|
|||
// - there is no documentation regarding support for usage with removable storage
|
||||
// - the Media Store only allows inserting in specific primary directories ("DCIM", "Pictures") when using scoped storage
|
||||
try {
|
||||
val newFieldsFuture = moveSingleByTreeDocAndScan(
|
||||
val newFields = moveSingleByTreeDocAndScan(
|
||||
context, sourcePath, sourceUri, destinationDir, destinationDirDocFile, mimeType, copy,
|
||||
)
|
||||
result["newFields"] = newFieldsFuture.get()
|
||||
result["newFields"] = newFields
|
||||
result["success"] = true
|
||||
} catch (e: ExecutionException) {
|
||||
Log.w(LOG_TAG, "failed to move to destinationDir=$destinationDir entry with sourcePath=$sourcePath", e)
|
||||
} catch (e: InterruptedException) {
|
||||
} catch (e: Exception) {
|
||||
Log.w(LOG_TAG, "failed to move to destinationDir=$destinationDir entry with sourcePath=$sourcePath", e)
|
||||
}
|
||||
}
|
||||
|
@ -275,26 +249,26 @@ class MediaStoreImageProvider : ImageProvider() {
|
|||
destinationDirDocFile: DocumentFileCompat,
|
||||
mimeType: String,
|
||||
copy: Boolean,
|
||||
): ListenableFuture<FieldMap> {
|
||||
val future = SettableFuture.create<FieldMap>()
|
||||
|
||||
try {
|
||||
): FieldMap {
|
||||
val sourceFile = File(sourcePath)
|
||||
val sourceDir = sourceFile.parent?.let { ensureTrailingSeparator(it) }
|
||||
if (sourceDir == destinationDir) {
|
||||
if (copy) {
|
||||
future.setException(Exception("file at path=$sourcePath is already in destination directory"))
|
||||
} else {
|
||||
future.set(HashMap<String, Any?>())
|
||||
if (copy) throw Exception("file at path=$sourcePath is already in destination directory")
|
||||
return HashMap<String, Any?>()
|
||||
}
|
||||
} else {
|
||||
|
||||
val sourceFileName = sourceFile.name
|
||||
val desiredNameWithoutExtension = sourceFileName.replaceFirst("[.][^.]+$".toRegex(), "")
|
||||
|
||||
if (File(destinationDir, sourceFileName).exists()) {
|
||||
throw Exception("file with name=$sourceFileName already exists in destination directory")
|
||||
}
|
||||
|
||||
// the file created from a `TreeDocumentFile` is also a `TreeDocumentFile`
|
||||
// but in order to open an output stream to it, we need to use a `SingleDocumentFile`
|
||||
// through a document URI, not a tree URI
|
||||
// note that `DocumentFile.getParentFile()` returns null if we did not pick a tree first
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
val destinationTreeFile = destinationDirDocFile.createFile(mimeType, desiredNameWithoutExtension)
|
||||
val destinationDocFile = DocumentFileCompat.fromSingleUri(context, destinationTreeFile.uri)
|
||||
|
||||
|
@ -302,10 +276,11 @@ class MediaStoreImageProvider : ImageProvider() {
|
|||
// `DocumentsContract.copyDocument()` yields "Unsupported call: android:copyDocument"
|
||||
// when used with entry URI as `sourceDocumentUri`, and destinationDirDocFile URI as `targetParentDocumentUri`
|
||||
val source = DocumentFileCompat.fromSingleUri(context, sourceUri)
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
source.copyTo(destinationDocFile)
|
||||
|
||||
// the source file name and the created document file name can be different when:
|
||||
// - a file with the same name already exists, so the name gets a suffix like ` (1)`
|
||||
// - a file with the same name already exists, some implementations give a suffix like ` (1)`, some *do not*
|
||||
// - the original extension does not match the extension added by the underlying provider
|
||||
val fileName = destinationDocFile.name
|
||||
val destinationFullPath = destinationDir + fileName
|
||||
|
@ -314,30 +289,17 @@ class MediaStoreImageProvider : ImageProvider() {
|
|||
if (!copy) {
|
||||
// delete original entry
|
||||
try {
|
||||
delete(context, sourceUri, sourcePath).get()
|
||||
delete(context, sourceUri, sourcePath)
|
||||
deletedSource = true
|
||||
} catch (e: ExecutionException) {
|
||||
Log.w(LOG_TAG, "failed to delete entry with path=$sourcePath", e)
|
||||
} catch (e: InterruptedException) {
|
||||
} catch (e: Exception) {
|
||||
Log.w(LOG_TAG, "failed to delete entry with path=$sourcePath", e)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
val fields = scanNewPath(context, destinationFullPath, mimeType)
|
||||
fields["deletedSource"] = deletedSource
|
||||
future.set(fields)
|
||||
} catch (e: Exception) {
|
||||
future.setException(e)
|
||||
return scanNewPath(context, destinationFullPath, mimeType).apply {
|
||||
put("deletedSource", deletedSource)
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(LOG_TAG, "failed to ${(if (copy) "copy" else "move")} entry", e)
|
||||
future.setException(e)
|
||||
}
|
||||
|
||||
return future
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOG_TAG = createTag(MediaStoreImageProvider::class.java)
|
||||
|
|
|
@ -37,12 +37,6 @@ class Constants {
|
|||
licenseUrl: 'https://github.com/bumptech/glide/blob/master/LICENSE',
|
||||
sourceUrl: 'https://github.com/bumptech/glide',
|
||||
),
|
||||
Dependency(
|
||||
name: 'Guava',
|
||||
license: 'Apache 2.0',
|
||||
licenseUrl: 'https://github.com/google/guava/blob/master/COPYING',
|
||||
sourceUrl: 'https://github.com/google/guava',
|
||||
),
|
||||
Dependency(
|
||||
name: 'Metadata Extractor',
|
||||
license: 'Apache 2.0',
|
||||
|
|
Loading…
Reference in a new issue