From 846e66a56c16362779563c93fba07f7a814fe3f2 Mon Sep 17 00:00:00 2001 From: Vladimir Dostoyevskiy <99822064+VDostoyevskiy@users.noreply.github.com> Date: Wed, 27 Dec 2023 16:00:32 +0800 Subject: [PATCH] UniFile Refactors (#546) * Use DocumentsContractCompat * Make UniFile.imageSource a extension * Make UniFile.openFileDescriptor a extension * Use appCtx * Use appCtx * Use appCtx * Use appCtx * Drop saveImageToLauncher * Cleanup * Use operator resolve * Simplify * Fix Toast * Fixup --------- Co-authored-by: GG Bond --- .../main/kotlin/com/hippo/ehviewer/EhDB.kt | 9 +- .../ehviewer/coil/DownloadThumbInterceptor.kt | 2 +- .../ehviewer/download/DownloadManager.kt | 2 +- .../ehviewer/gallery/ArchivePageLoader.kt | 7 +- .../kotlin/com/hippo/ehviewer/image/Image.kt | 4 +- .../com/hippo/ehviewer/spider/SpiderDen.kt | 10 +- .../com/hippo/ehviewer/spider/SpiderQueen.kt | 7 +- .../com/hippo/ehviewer/ui/CommonOperations.kt | 2 +- .../kotlin/com/hippo/ehviewer/util/FDUtils.kt | 5 +- .../com/hippo/ehviewer/util/LauncherUtils.kt | 2 +- .../kotlin/com/hippo/unifile/Contracts.kt | 72 +++------- .../hippo/unifile/DocumentsContractApi19.kt | 136 ++++++------------ .../hippo/unifile/DocumentsContractApi21.kt | 75 +++------- .../kotlin/com/hippo/unifile/MediaContract.kt | 19 +-- .../kotlin/com/hippo/unifile/MediaFile.kt | 25 +--- .../main/kotlin/com/hippo/unifile/RawFile.kt | 18 +-- .../com/hippo/unifile/SingleDocumentFile.kt | 29 ++-- .../com/hippo/unifile/TreeDocumentFile.kt | 41 +++--- .../main/kotlin/com/hippo/unifile/UniFile.kt | 98 +++---------- .../com/hippo/unifile/UniFileExtensions.kt | 12 ++ .../main/kotlin/com/hippo/unifile/Utils.kt | 27 ---- .../tachiyomi/ui/reader/ReaderActivity.kt | 57 +++----- 22 files changed, 183 insertions(+), 476 deletions(-) delete mode 100644 app/src/main/kotlin/com/hippo/unifile/Utils.kt diff --git a/app/src/main/kotlin/com/hippo/ehviewer/EhDB.kt b/app/src/main/kotlin/com/hippo/ehviewer/EhDB.kt index d11669b75..df231c895 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/EhDB.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/EhDB.kt @@ -17,8 +17,6 @@ package com.hippo.ehviewer import android.content.Context import android.net.Uri -import android.os.ParcelFileDescriptor -import android.os.ParcelFileDescriptor.MODE_READ_ONLY import arrow.fx.coroutines.release import arrow.fx.coroutines.resource import com.hippo.ehviewer.client.data.BaseGalleryInfo @@ -32,6 +30,7 @@ import com.hippo.ehviewer.dao.LocalFavoriteInfo import com.hippo.ehviewer.dao.QuickSearch import com.hippo.ehviewer.download.DownloadManager import com.hippo.ehviewer.util.sendTo +import com.hippo.unifile.asUniFile import kotlinx.coroutines.flow.Flow import splitties.arch.room.roomDb @@ -261,11 +260,7 @@ object EhDB { db.filterDao().list().let { newDb.filterDao().insert(it) } newDb.close() val dbFile = context.getDatabasePath(ehExportName) - context.contentResolver.openFileDescriptor(uri, "rw")!!.use { toFd -> - ParcelFileDescriptor.open(dbFile, MODE_READ_ONLY).use { fromFd -> - fromFd sendTo toFd - } - } + dbFile.asUniFile() sendTo uri.asUniFile() } } diff --git a/app/src/main/kotlin/com/hippo/ehviewer/coil/DownloadThumbInterceptor.kt b/app/src/main/kotlin/com/hippo/ehviewer/coil/DownloadThumbInterceptor.kt index 3313d688f..813248f4b 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/coil/DownloadThumbInterceptor.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/coil/DownloadThumbInterceptor.kt @@ -17,7 +17,7 @@ object DownloadThumbInterceptor : Interceptor { if (magicOrUrl != null) { val (url, location) = decodeMagicRequestOrUrl(magicOrUrl) if (location != null) { - val thumb = withIOContext { downloadLocation.subFile(location).subFile(".thumb") } + val thumb = withIOContext { downloadLocation / location / ".thumb" } if (withIOContext { thumb.isFile }) { val new = chain.request.newBuilder().data(thumb.uri.toString()).build() when (val result = chain.withRequest(new).proceed()) { diff --git a/app/src/main/kotlin/com/hippo/ehviewer/download/DownloadManager.kt b/app/src/main/kotlin/com/hippo/ehviewer/download/DownloadManager.kt index 0c930e0e7..9a618258d 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/download/DownloadManager.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/download/DownloadManager.kt @@ -1037,4 +1037,4 @@ var downloadLocation: UniFile } } -val DownloadInfo.downloadDir get() = dirname?.let { downloadLocation.subFile(it) } +val DownloadInfo.downloadDir get() = dirname?.let { downloadLocation / it } diff --git a/app/src/main/kotlin/com/hippo/ehviewer/gallery/ArchivePageLoader.kt b/app/src/main/kotlin/com/hippo/ehviewer/gallery/ArchivePageLoader.kt index d4f2cb0ea..08d48064d 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/gallery/ArchivePageLoader.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/gallery/ArchivePageLoader.kt @@ -35,6 +35,7 @@ import com.hippo.ehviewer.jni.providePassword import com.hippo.ehviewer.jni.releaseByteBuffer import com.hippo.ehviewer.util.FileUtils import com.hippo.unifile.UniFile +import com.hippo.unifile.openFileDescriptor import java.nio.ByteBuffer import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineStart @@ -169,11 +170,7 @@ class ArchivePageLoader(context: Context, private val uri: Uri, passwdProvider: return true } - override fun save(index: Int, dir: UniFile, filename: String): UniFile { - val dst = dir.subFile(filename) - save(index, dst) - return dst - } + override fun save(index: Int, dir: UniFile, filename: String) = (dir / filename).apply { save(index, this) } override fun preloadPages(pages: List, pair: Pair) {} } diff --git a/app/src/main/kotlin/com/hippo/ehviewer/image/Image.kt b/app/src/main/kotlin/com/hippo/ehviewer/image/Image.kt index e7710e79e..405b52b14 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/image/Image.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/image/Image.kt @@ -44,6 +44,8 @@ import com.hippo.ehviewer.jni.rewriteGifSource import com.hippo.ehviewer.util.isAtLeastP import com.hippo.ehviewer.util.isAtLeastU import com.hippo.unifile.UniFile +import com.hippo.unifile.imageSource +import com.hippo.unifile.openFileDescriptor import com.hippo.unifile.openInputStream import java.nio.ByteBuffer import kotlin.math.min @@ -116,7 +118,7 @@ class Image private constructor(drawable: Drawable, private val src: AutoCloseab } } } - val drawable = decodeDrawable(src.source.imageSource) + val drawable = decodeDrawable(src.source.imageSource()) if (drawable !is Animatable) src.close() Image(drawable, src) } else { diff --git a/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderDen.kt b/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderDen.kt index 8c429ec4d..662b60ca0 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderDen.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderDen.kt @@ -15,8 +15,6 @@ */ package com.hippo.ehviewer.spider -import android.os.ParcelFileDescriptor -import android.os.ParcelFileDescriptor.MODE_READ_WRITE import com.hippo.ehviewer.EhApplication.Companion.imageCache as sCache import com.hippo.ehviewer.EhDB import com.hippo.ehviewer.client.EhUtils.getSuitableTitle @@ -74,11 +72,7 @@ class SpiderDen(mGalleryInfo: GalleryInfo) { sCache.read(key) { val extension = "." + metadata.toFile().readText() val file = dir.createFile(perFilename(index, extension)) ?: return false - file.openFileDescriptor("w").use { outFd -> - ParcelFileDescriptor.open(data.toFile(), MODE_READ_WRITE).use { - it sendTo outFd - } - } + data.asUniFile() sendTo file true } ?: false }.onFailure { @@ -247,5 +241,5 @@ suspend fun GalleryInfo.putToDownloadDir(): String { suspend fun getGalleryDownloadDir(gid: Long): UniFile? { val dirname = EhDB.getDownloadDirname(gid) ?: return null - return downloadLocation.subFile(dirname) + return downloadLocation / dirname } diff --git a/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderQueen.kt b/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderQueen.kt index e50214935..cbb4d09f6 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderQueen.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderQueen.kt @@ -310,11 +310,8 @@ class SpiderQueen private constructor(val galleryInfo: GalleryInfo) : CoroutineS fun save(index: Int, dir: UniFile, filename: String): UniFile? { val state = getPageState(index) - if (STATE_FINISHED != state) { - return null - } - val dst = dir.subFile(filename) - return if (!mSpiderDen.saveToUniFile(index, dst)) null else dst + if (STATE_FINISHED != state) return null + return (dir / filename).takeIf { mSpiderDen.saveToUniFile(index, it) } } fun getExtension(index: Int): String? { diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/CommonOperations.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/CommonOperations.kt index 196e7da44..06ed47d1d 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/CommonOperations.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/CommonOperations.kt @@ -81,7 +81,7 @@ import moe.tarsin.coroutines.runSuspendCatching import splitties.init.appCtx private fun removeNoMediaFile(downloadDir: UniFile) { - val noMedia = downloadDir.subFile(".nomedia") + val noMedia = downloadDir / ".nomedia" noMedia.delete() } diff --git a/app/src/main/kotlin/com/hippo/ehviewer/util/FDUtils.kt b/app/src/main/kotlin/com/hippo/ehviewer/util/FDUtils.kt index bd302888f..b7e18f913 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/util/FDUtils.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/util/FDUtils.kt @@ -4,6 +4,7 @@ import android.os.ParcelFileDescriptor import android.system.Int64Ref import android.system.Os import com.hippo.unifile.UniFile +import com.hippo.unifile.openFileDescriptor import java.io.FileDescriptor import java.io.FileInputStream import java.io.FileOutputStream @@ -20,10 +21,6 @@ private fun sendFileTotally(from: FileDescriptor, to: FileDescriptor) { } } -infix fun ParcelFileDescriptor.sendTo(fd: FileDescriptor) { - sendFileTotally(fileDescriptor, fd) -} - infix fun ParcelFileDescriptor.sendTo(fd: ParcelFileDescriptor) { sendFileTotally(fileDescriptor, fd.fileDescriptor) } diff --git a/app/src/main/kotlin/com/hippo/ehviewer/util/LauncherUtils.kt b/app/src/main/kotlin/com/hippo/ehviewer/util/LauncherUtils.kt index 453db7122..8bfb52e0d 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/util/LauncherUtils.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/util/LauncherUtils.kt @@ -42,7 +42,7 @@ private val Context.lifecycle: Lifecycle } } -private suspend fun Context.awaitActivityResult(contract: ActivityResultContract, input: I): O { +suspend fun Context.awaitActivityResult(contract: ActivityResultContract, input: I): O { val key = "activity_rq#${atomicInteger.getAndIncrement()}" var launcher: ActivityResultLauncher? = null var observer: LifecycleEventObserver? = null diff --git a/app/src/main/kotlin/com/hippo/unifile/Contracts.kt b/app/src/main/kotlin/com/hippo/unifile/Contracts.kt index 54a13f902..4c765ac22 100644 --- a/app/src/main/kotlin/com/hippo/unifile/Contracts.kt +++ b/app/src/main/kotlin/com/hippo/unifile/Contracts.kt @@ -15,67 +15,29 @@ */ package com.hippo.unifile -import android.content.Context -import android.graphics.ImageDecoder import android.net.Uri -import android.os.Build -import android.os.ParcelFileDescriptor -import java.io.IOException +import splitties.init.appCtx -internal object Contracts { - fun queryForString( - context: Context, - self: Uri, - column: String, - defaultValue: String?, - ): String? { - return runCatching { - context.contentResolver.query(self, arrayOf(column), null, null, null).use { - if (it != null && it.moveToFirst() && !it.isNull(0)) { - it.getString(0) - } else { - defaultValue - } +object Contracts { + fun queryForString(self: Uri, column: String, defaultValue: String?) = runCatching { + appCtx.contentResolver.query(self, arrayOf(column), null, null, null).use { + if (it != null && it.moveToFirst() && !it.isNull(0)) { + it.getString(0) + } else { + defaultValue } - }.getOrElse { - Utils.throwIfFatal(it) - defaultValue } - } + }.getOrDefault(defaultValue) - fun queryForInt(context: Context, self: Uri, column: String?, defaultValue: Int): Int { - return queryForLong(context, self, column, defaultValue.toLong()).toInt() - } + fun queryForInt(self: Uri, column: String?, defaultValue: Int) = queryForLong(self, column, defaultValue.toLong()).toInt() - fun queryForLong(context: Context, self: Uri, column: String?, defaultValue: Long): Long { - return runCatching { - context.contentResolver.query(self, arrayOf(column), null, null, null).use { - if (it != null && it.moveToFirst() && !it.isNull(0)) { - it.getLong(0) - } else { - defaultValue - } + fun queryForLong(self: Uri, column: String?, defaultValue: Long) = runCatching { + appCtx.contentResolver.query(self, arrayOf(column), null, null, null).use { + if (it != null && it.moveToFirst() && !it.isNull(0)) { + it.getLong(0) + } else { + defaultValue } - }.getOrElse { - Utils.throwIfFatal(it) - defaultValue } - } - - fun openFileDescriptor( - context: Context, - uri: Uri?, - mode: String?, - ): ParcelFileDescriptor { - return context.contentResolver.openFileDescriptor(uri!!, mode!!) - ?: throw IOException("Can't open ParcelFileDescriptor") - } - - fun getImageSource(context: Context, uri: Uri?): ImageDecoder.Source { - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - ImageDecoder.createSource(context.contentResolver, uri!!) - } else { - TODO("VERSION.SDK_INT < P") - } - } + }.getOrDefault(defaultValue) } diff --git a/app/src/main/kotlin/com/hippo/unifile/DocumentsContractApi19.kt b/app/src/main/kotlin/com/hippo/unifile/DocumentsContractApi19.kt index 143f0eaa3..a97b1e327 100644 --- a/app/src/main/kotlin/com/hippo/unifile/DocumentsContractApi19.kt +++ b/app/src/main/kotlin/com/hippo/unifile/DocumentsContractApi19.kt @@ -15,124 +15,70 @@ */ package com.hippo.unifile -import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.net.Uri import android.provider.DocumentsContract +import splitties.init.appCtx -internal object DocumentsContractApi19 { - fun isDocumentUri(context: Context, self: Uri): Boolean { - return DocumentsContract.isDocumentUri(context, self) - } +object DocumentsContractApi19 { - fun getName(context: Context, self: Uri): String? { - return Contracts.queryForString( - context, - self, - DocumentsContract.Document.COLUMN_DISPLAY_NAME, - null, - ) - } + fun getName(self: Uri) = Contracts.queryForString(self, DocumentsContract.Document.COLUMN_DISPLAY_NAME, null) - private fun getRawType(context: Context, self: Uri): String? { - return Contracts.queryForString( - context, - self, - DocumentsContract.Document.COLUMN_MIME_TYPE, - null, - ) - } + private fun getRawType(self: Uri) = Contracts.queryForString(self, DocumentsContract.Document.COLUMN_MIME_TYPE, null) - fun getType(context: Context, self: Uri): String? { - return getRawType(context, self).takeUnless { it == DocumentsContract.Document.MIME_TYPE_DIR } - } + fun getType(self: Uri) = getRawType(self).takeUnless { it == DocumentsContract.Document.MIME_TYPE_DIR } - fun isDirectory(context: Context, self: Uri): Boolean { - return DocumentsContract.Document.MIME_TYPE_DIR == getRawType(context, self) - } + fun isDirectory(self: Uri) = DocumentsContract.Document.MIME_TYPE_DIR == getRawType(self) - fun isFile(context: Context, self: Uri): Boolean { - val type = getRawType(context, self) + fun isFile(self: Uri): Boolean { + val type = getRawType(self) return !(DocumentsContract.Document.MIME_TYPE_DIR == type || type.isNullOrEmpty()) } - fun lastModified(context: Context, self: Uri): Long { - return Contracts.queryForLong( - context, - self, - DocumentsContract.Document.COLUMN_LAST_MODIFIED, - -1L, - ) - } + fun lastModified(self: Uri) = Contracts.queryForLong(self, DocumentsContract.Document.COLUMN_LAST_MODIFIED, -1L) - fun length(context: Context, self: Uri): Long { - return Contracts.queryForLong(context, self, DocumentsContract.Document.COLUMN_SIZE, -1L) - } + fun length(self: Uri) = Contracts.queryForLong(self, DocumentsContract.Document.COLUMN_SIZE, -1L) - fun canRead(context: Context, self: Uri): Boolean { + fun canRead(self: Uri): Boolean { + val granted = appCtx.checkCallingOrSelfUriPermission(self, Intent.FLAG_GRANT_READ_URI_PERMISSION) == PackageManager.PERMISSION_GRANTED // Ignore if grant doesn't allow read - return if ( - context.checkCallingOrSelfUriPermission(self, Intent.FLAG_GRANT_READ_URI_PERMISSION) - != PackageManager.PERMISSION_GRANTED - ) { - false - } else { - // Ignore documents without MIME - !getRawType(context, self).isNullOrEmpty() - } - } - - fun canWrite(context: Context, self: Uri): Boolean { - // Ignore if grant doesn't allow write - if (context.checkCallingOrSelfUriPermission(self, Intent.FLAG_GRANT_WRITE_URI_PERMISSION) - != PackageManager.PERMISSION_GRANTED - ) { - return false - } - val type = getRawType(context, self) - val flags = Contracts.queryForInt(context, self, DocumentsContract.Document.COLUMN_FLAGS, 0) + if (!granted) return false // Ignore documents without MIME - if (type.isNullOrEmpty()) { - return false - } + return !getRawType(self).isNullOrEmpty() + } + + fun canWrite(self: Uri): Boolean { + val granted = appCtx.checkCallingOrSelfUriPermission(self, Intent.FLAG_GRANT_WRITE_URI_PERMISSION) == PackageManager.PERMISSION_GRANTED + // Ignore if grant doesn't allow write + if (!granted) return false + + val type = getRawType(self) + val flags = Contracts.queryForInt(self, DocumentsContract.Document.COLUMN_FLAGS, 0) + // Ignore documents without MIME + if (type.isNullOrEmpty()) return false // Deletable documents considered writable - if (flags and DocumentsContract.Document.FLAG_SUPPORTS_DELETE != 0) { - return true - } + if (flags and DocumentsContract.Document.FLAG_SUPPORTS_DELETE != 0) return true + + val dirSupportCreate = DocumentsContract.Document.MIME_TYPE_DIR == type && flags and DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE != 0 + // Directories that allow create considered writable + if (dirSupportCreate) return true // Writable normal files considered writable - return if (DocumentsContract.Document.MIME_TYPE_DIR == type && flags and DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE != 0) { - // Directories that allow create considered writable - true - } else { - flags and DocumentsContract.Document.FLAG_SUPPORTS_WRITE != 0 - } + return flags and DocumentsContract.Document.FLAG_SUPPORTS_WRITE != 0 } - fun delete(context: Context, self: Uri): Boolean { - return try { - DocumentsContract.deleteDocument(context.contentResolver, self) - } catch (e: Throwable) { - Utils.throwIfFatal(e) - false - } - } + fun delete(self: Uri) = runCatching { DocumentsContract.deleteDocument(appCtx.contentResolver, self) }.getOrDefault(false) - fun exists(context: Context, self: Uri): Boolean { - return runCatching { - context.contentResolver.query( - self, - arrayOf(DocumentsContract.Document.COLUMN_DOCUMENT_ID), - null, - null, - null, - ).use { null != it && it.count > 0 } - }.getOrElse { - Utils.throwIfFatal(it) - false - } - } + fun exists(self: Uri) = runCatching { + appCtx.contentResolver.query( + self, + arrayOf(DocumentsContract.Document.COLUMN_DOCUMENT_ID), + null, + null, + null, + ).use { null != it && it.count > 0 } + }.getOrDefault(false) } diff --git a/app/src/main/kotlin/com/hippo/unifile/DocumentsContractApi21.kt b/app/src/main/kotlin/com/hippo/unifile/DocumentsContractApi21.kt index 6b9ccc521..62898a7b7 100644 --- a/app/src/main/kotlin/com/hippo/unifile/DocumentsContractApi21.kt +++ b/app/src/main/kotlin/com/hippo/unifile/DocumentsContractApi21.kt @@ -18,27 +18,17 @@ package com.hippo.unifile import android.content.Context import android.net.Uri import android.provider.DocumentsContract +import splitties.init.appCtx -internal object DocumentsContractApi21 { +object DocumentsContractApi21 { private const val PATH_DOCUMENT = "document" private const val PATH_TREE = "tree" - fun createFile(context: Context, self: Uri, mimeType: String, displayName: String): Uri? { - return try { - DocumentsContract.createDocument( - context.contentResolver, - self, - mimeType, - displayName, - ) - } catch (e: Throwable) { - Utils.throwIfFatal(e) - null - } - } + private val resolver = appCtx.contentResolver + fun createFile(self: Uri, mimeType: String, displayName: String) = runCatching { + DocumentsContract.createDocument(resolver, self, mimeType, displayName) + }.getOrNull() - fun createDirectory(context: Context, self: Uri, displayName: String): Uri? { - return createFile(context, self, DocumentsContract.Document.MIME_TYPE_DIR, displayName) - } + fun createDirectory(self: Uri, displayName: String) = createFile(self, DocumentsContract.Document.MIME_TYPE_DIR, displayName) fun prepareTreeUri(treeUri: Uri?): Uri { return DocumentsContract.buildDocumentUriUsingTree( @@ -52,7 +42,7 @@ internal object DocumentsContractApi21 { if (paths.size >= 4 && PATH_TREE == paths[0] && PATH_DOCUMENT == paths[2]) { return paths[3] } - throw IllegalArgumentException("Invalid URI: $documentUri") + error("Invalid URI: $documentUri") } fun buildChildUri(uri: Uri, displayName: String): Uri { @@ -62,43 +52,22 @@ internal object DocumentsContractApi21 { ) } - fun listFiles(context: Context, self: Uri): Array { - val childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree( - self, - DocumentsContract.getDocumentId(self), - ) - val results = ArrayList() - runCatching { - context.contentResolver.query( - childrenUri, - arrayOf(DocumentsContract.Document.COLUMN_DOCUMENT_ID), - null, - null, - null, - ).use { - if (null != it) { - while (it.moveToNext()) { - val documentId = it.getString(0) - val documentUri = DocumentsContract.buildDocumentUriUsingTree( - self, - documentId, - ) - results.add(documentUri) - } - } + fun listFiles(self: Uri) = sequence { + val childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(self, DocumentsContract.getDocumentId(self)) + val id = arrayOf(DocumentsContract.Document.COLUMN_DOCUMENT_ID) + resolver.query(childrenUri, id, null, null, null, null)?.use { + while (it.moveToNext()) { + val documentId = it.getString(0) + val documentUri = DocumentsContract.buildDocumentUriUsingTree( + self, + documentId, + ) + yield(documentUri) } - }.onFailure { - Utils.throwIfFatal(it) } - return results.toTypedArray() } - fun renameTo(context: Context, self: Uri, displayName: String): Uri? { - return try { - DocumentsContract.renameDocument(context.contentResolver, self, displayName) - } catch (e: Throwable) { - Utils.throwIfFatal(e) - null - } - } + fun renameTo(context: Context, self: Uri, displayName: String) = runCatching { + DocumentsContract.renameDocument(context.contentResolver, self, displayName) + }.getOrNull() } diff --git a/app/src/main/kotlin/com/hippo/unifile/MediaContract.kt b/app/src/main/kotlin/com/hippo/unifile/MediaContract.kt index 7f8116d8b..ab6d14052 100644 --- a/app/src/main/kotlin/com/hippo/unifile/MediaContract.kt +++ b/app/src/main/kotlin/com/hippo/unifile/MediaContract.kt @@ -15,24 +15,15 @@ */ package com.hippo.unifile -import android.content.Context import android.net.Uri import android.provider.MediaStore -internal object MediaContract { - fun getName(context: Context, self: Uri): String? { - return Contracts.queryForString(context, self, MediaStore.MediaColumns.DISPLAY_NAME, null) - } +object MediaContract { + fun getName(self: Uri) = Contracts.queryForString(self, MediaStore.MediaColumns.DISPLAY_NAME, null) - fun getType(context: Context, self: Uri): String? { - return Contracts.queryForString(context, self, MediaStore.MediaColumns.MIME_TYPE, null) - } + fun getType(self: Uri) = Contracts.queryForString(self, MediaStore.MediaColumns.MIME_TYPE, null) - fun lastModified(context: Context, self: Uri): Long { - return Contracts.queryForLong(context, self, MediaStore.MediaColumns.DATE_MODIFIED, 0) - } + fun lastModified(self: Uri) = Contracts.queryForLong(self, MediaStore.MediaColumns.DATE_MODIFIED, 0) - fun length(context: Context, self: Uri): Long { - return Contracts.queryForLong(context, self, MediaStore.MediaColumns.SIZE, 0) - } + fun length(self: Uri) = Contracts.queryForLong(self, MediaStore.MediaColumns.SIZE, 0) } diff --git a/app/src/main/kotlin/com/hippo/unifile/MediaFile.kt b/app/src/main/kotlin/com/hippo/unifile/MediaFile.kt index 62085ac9d..5d8bb742f 100644 --- a/app/src/main/kotlin/com/hippo/unifile/MediaFile.kt +++ b/app/src/main/kotlin/com/hippo/unifile/MediaFile.kt @@ -15,10 +15,8 @@ */ package com.hippo.unifile -import android.graphics.ImageDecoder import android.net.Uri import java.io.IOException -import splitties.init.appCtx class MediaFile(override val uri: Uri) : UniFile(null) { override fun createFile(displayName: String) = null @@ -26,16 +24,16 @@ class MediaFile(override val uri: Uri) : UniFile(null) { override fun createDirectory(displayName: String) = null override val name: String? - get() = MediaContract.getName(appCtx, uri) + get() = MediaContract.getName(uri) override val type: String? - get() = MediaContract.getType(appCtx, uri) + get() = MediaContract.getType(uri) override val isDirectory = false override val isFile: Boolean - get() = DocumentsContractApi19.isFile(appCtx, uri) + get() = DocumentsContractApi19.isFile(uri) - override fun lastModified() = MediaContract.lastModified(appCtx, uri) + override fun lastModified() = MediaContract.lastModified(uri) - override fun length() = MediaContract.length(appCtx, uri) + override fun length() = MediaContract.length(uri) override fun canRead() = isFile @@ -53,7 +51,7 @@ class MediaFile(override val uri: Uri) : UniFile(null) { override fun ensureFile() = isFile - override fun subFile(displayName: String) = error("MediaFile never have a children") + override fun resolve(displayName: String) = error("MediaFile never have a children") override fun delete() = false @@ -64,15 +62,4 @@ class MediaFile(override val uri: Uri) : UniFile(null) { override fun findFirst(filter: (String) -> Boolean) = null override fun renameTo(displayName: String) = false - - override val imageSource: ImageDecoder.Source - get() = Contracts.getImageSource(appCtx, uri) - - override fun openFileDescriptor(mode: String) = Contracts.openFileDescriptor(appCtx, uri, mode) - - companion object { - fun isMediaUri(uri: Uri): Boolean { - return null != MediaContract.getName(appCtx, uri) - } - } } diff --git a/app/src/main/kotlin/com/hippo/unifile/RawFile.kt b/app/src/main/kotlin/com/hippo/unifile/RawFile.kt index ce53a1c58..d78109f6e 100644 --- a/app/src/main/kotlin/com/hippo/unifile/RawFile.kt +++ b/app/src/main/kotlin/com/hippo/unifile/RawFile.kt @@ -15,14 +15,10 @@ */ package com.hippo.unifile -import android.graphics.ImageDecoder import android.net.Uri -import android.os.Build -import android.os.ParcelFileDescriptor import android.webkit.MimeTypeMap import eu.kanade.tachiyomi.util.system.logcat import java.io.File -import java.io.IOException import java.util.Locale class RawFile(parent: UniFile?, private var mFile: File) : UniFile(parent) { @@ -100,7 +96,7 @@ class RawFile(parent: UniFile?, private var mFile: File) : UniFile(parent) { return success } - override fun subFile(displayName: String) = RawFile(this, File(mFile, displayName)) + override fun resolve(displayName: String) = RawFile(this, File(mFile, displayName)) override fun delete() = mFile.deleteRecursively() @@ -118,18 +114,6 @@ class RawFile(parent: UniFile?, private var mFile: File) : UniFile(parent) { } return false } - - override val imageSource: ImageDecoder.Source - get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - ImageDecoder.createSource(mFile) - } else { - TODO("VERSION.SDK_INT < P") - } - - override fun openFileDescriptor(mode: String): ParcelFileDescriptor { - val md = ParcelFileDescriptor.parseMode(mode) - return ParcelFileDescriptor.open(mFile, md) ?: throw IOException("Can't open ParcelFileDescriptor") - } } private fun getTypeForName(name: String): String { diff --git a/app/src/main/kotlin/com/hippo/unifile/SingleDocumentFile.kt b/app/src/main/kotlin/com/hippo/unifile/SingleDocumentFile.kt index c8e2ebcc6..e00ad133d 100644 --- a/app/src/main/kotlin/com/hippo/unifile/SingleDocumentFile.kt +++ b/app/src/main/kotlin/com/hippo/unifile/SingleDocumentFile.kt @@ -15,9 +15,7 @@ */ package com.hippo.unifile -import android.graphics.ImageDecoder import android.net.Uri -import splitties.init.appCtx class SingleDocumentFile(parent: UniFile?, override val uri: Uri) : UniFile(parent) { @@ -26,40 +24,35 @@ class SingleDocumentFile(parent: UniFile?, override val uri: Uri) : UniFile(pare override fun createDirectory(displayName: String) = null override val name: String? - get() = DocumentsContractApi19.getName(appCtx, uri) + get() = DocumentsContractApi19.getName(uri) override val type: String? - get() = DocumentsContractApi19.getType(appCtx, uri) + get() = DocumentsContractApi19.getType(uri) override val isDirectory: Boolean - get() = DocumentsContractApi19.isDirectory(appCtx, uri) + get() = DocumentsContractApi19.isDirectory(uri) override val isFile: Boolean - get() = DocumentsContractApi19.isFile(appCtx, uri) + get() = DocumentsContractApi19.isFile(uri) - override fun lastModified() = DocumentsContractApi19.lastModified(appCtx, uri) + override fun lastModified() = DocumentsContractApi19.lastModified(uri) - override fun length() = DocumentsContractApi19.length(appCtx, uri) + override fun length() = DocumentsContractApi19.length(uri) - override fun canRead() = DocumentsContractApi19.canRead(appCtx, uri) + override fun canRead() = DocumentsContractApi19.canRead(uri) - override fun canWrite() = DocumentsContractApi19.canWrite(appCtx, uri) + override fun canWrite() = DocumentsContractApi19.canWrite(uri) override fun ensureDir() = isDirectory override fun ensureFile() = isFile - override fun subFile(displayName: String) = error("SingleDocumentFile never have a children") + override fun resolve(displayName: String) = error("SingleDocumentFile never have a children") - override fun delete() = DocumentsContractApi19.delete(appCtx, uri) + override fun delete() = DocumentsContractApi19.delete(uri) - override fun exists() = DocumentsContractApi19.exists(appCtx, uri) + override fun exists() = DocumentsContractApi19.exists(uri) override fun listFiles() = emptyList() override fun findFirst(filter: (String) -> Boolean) = null override fun renameTo(displayName: String) = false - - override val imageSource: ImageDecoder.Source - get() = Contracts.getImageSource(appCtx, uri) - - override fun openFileDescriptor(mode: String) = Contracts.openFileDescriptor(appCtx, uri, mode) } diff --git a/app/src/main/kotlin/com/hippo/unifile/TreeDocumentFile.kt b/app/src/main/kotlin/com/hippo/unifile/TreeDocumentFile.kt index 3d6ea33ec..4aaebfdea 100644 --- a/app/src/main/kotlin/com/hippo/unifile/TreeDocumentFile.kt +++ b/app/src/main/kotlin/com/hippo/unifile/TreeDocumentFile.kt @@ -15,9 +15,7 @@ */ package com.hippo.unifile -import android.graphics.ImageDecoder import android.net.Uri -import android.os.ParcelFileDescriptor import android.webkit.MimeTypeMap import eu.kanade.tachiyomi.util.system.logcat import splitties.init.appCtx @@ -31,10 +29,10 @@ class TreeDocumentFile( private val allChildren by lazy { cachePresent = true logcat { "Directory lookup cache created for $name" } - DocumentsContractApi21.listFiles(appCtx, uri).map { + DocumentsContractApi21.listFiles(uri).mapTo(mutableListOf()) { val name = getFilenameForUri(it) TreeDocumentFile(this, it, name) - }.toMutableList() + } } private fun popCacheIfPresent(file: TreeDocumentFile) = apply { @@ -54,7 +52,7 @@ class TreeDocumentFile( val extension = displayName.substring(index + 1) val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) if (!mimeType.isNullOrEmpty()) { - val result = DocumentsContractApi21.createFile(appCtx, uri, mimeType, name) + val result = DocumentsContractApi21.createFile(uri, mimeType, name) if (result != null) { val f = TreeDocumentFile(this, result, displayName) popCacheIfPresent(f) @@ -63,7 +61,7 @@ class TreeDocumentFile( } } else { // Not dot in displayName or dot is the first char or can't get MimeType - val result = DocumentsContractApi21.createFile(appCtx, uri, "application/octet-stream", displayName) + val result = DocumentsContractApi21.createFile(uri, "application/octet-stream", displayName) if (result != null) { val f = TreeDocumentFile(this, result, displayName) popCacheIfPresent(f) @@ -79,7 +77,7 @@ class TreeDocumentFile( if (child != null) { if (child.isDirectory) return null } else { - val result = DocumentsContractApi21.createDirectory(appCtx, uri, displayName) + val result = DocumentsContractApi21.createDirectory(uri, displayName) if (result != null) { val d = TreeDocumentFile(this, result, displayName) popCacheIfPresent(d) @@ -90,21 +88,21 @@ class TreeDocumentFile( } override val name: String? - get() = DocumentsContractApi19.getName(appCtx, uri) + get() = DocumentsContractApi19.getName(uri) override val type: String? - get() = DocumentsContractApi19.getType(appCtx, uri) + get() = DocumentsContractApi19.getType(uri) override val isDirectory: Boolean - get() = DocumentsContractApi19.isDirectory(appCtx, uri) + get() = DocumentsContractApi19.isDirectory(uri) override val isFile: Boolean - get() = DocumentsContractApi19.isFile(appCtx, uri) + get() = DocumentsContractApi19.isFile(uri) - override fun lastModified() = DocumentsContractApi19.lastModified(appCtx, uri) + override fun lastModified() = DocumentsContractApi19.lastModified(uri) - override fun length() = DocumentsContractApi19.length(appCtx, uri) + override fun length() = DocumentsContractApi19.length(uri) - override fun canRead() = DocumentsContractApi19.canRead(appCtx, uri) + override fun canRead() = DocumentsContractApi19.canRead(uri) - override fun canWrite() = DocumentsContractApi19.canWrite(appCtx, uri) + override fun canWrite() = DocumentsContractApi19.canWrite(uri) override fun ensureDir(): Boolean { if (isDirectory) return true @@ -128,14 +126,14 @@ class TreeDocumentFile( } } - override fun subFile(displayName: String): UniFile { + override fun resolve(displayName: String): UniFile { val childUri = DocumentsContractApi21.buildChildUri(uri, displayName) return TreeDocumentFile(this, childUri, displayName) } - override fun delete() = DocumentsContractApi19.delete(appCtx, uri) + override fun delete() = DocumentsContractApi19.delete(uri) - override fun exists() = DocumentsContractApi19.exists(appCtx, uri) + override fun exists() = DocumentsContractApi19.exists(uri) override fun listFiles() = allChildren @@ -149,13 +147,6 @@ class TreeDocumentFile( } return false } - - override val imageSource: ImageDecoder.Source - get() = Contracts.getImageSource(appCtx, uri) - - override fun openFileDescriptor(mode: String): ParcelFileDescriptor { - return Contracts.openFileDescriptor(appCtx, uri, mode) - } } private fun getFilenameForUri(uri: Uri): String { diff --git a/app/src/main/kotlin/com/hippo/unifile/UniFile.kt b/app/src/main/kotlin/com/hippo/unifile/UniFile.kt index 8455c01c9..cc4dd3fdd 100644 --- a/app/src/main/kotlin/com/hippo/unifile/UniFile.kt +++ b/app/src/main/kotlin/com/hippo/unifile/UniFile.kt @@ -17,11 +17,10 @@ package com.hippo.unifile import android.content.ContentResolver import android.content.Intent -import android.graphics.ImageDecoder import android.net.Uri -import android.os.Build -import android.os.ParcelFileDescriptor -import androidx.annotation.RequiresApi +import android.provider.DocumentsContract +import androidx.core.net.toFile +import androidx.core.provider.DocumentsContractCompat import java.io.File import splitties.init.appCtx @@ -175,7 +174,9 @@ abstract class UniFile internal constructor(private val parent: UniFile?) { * * @return the child file */ - abstract fun subFile(displayName: String): UniFile + abstract fun resolve(displayName: String): UniFile + + operator fun div(name: String) = resolve(name) /** * Deletes this file. @@ -205,7 +206,7 @@ abstract class UniFile internal constructor(private val parent: UniFile?) { * * @return the file if found it, or `null`. */ - fun findFile(displayName: String) = subFile(displayName).takeIf { it.exists() } + fun findFile(displayName: String) = resolve(displayName).takeIf { it.exists() } /** * Renames this file to `displayName`. @@ -229,92 +230,29 @@ abstract class UniFile internal constructor(private val parent: UniFile?) { */ abstract fun renameTo(displayName: String): Boolean - @get:RequiresApi(Build.VERSION_CODES.P) - abstract val imageSource: ImageDecoder.Source - - abstract fun openFileDescriptor(mode: String): ParcelFileDescriptor - companion object { - /** - * Create a [UniFile] representing the given [File]. - * - * @param file the file to wrap - * @return the [UniFile] representing the given [File]. - */ fun fromFile(file: File) = RawFile(null, file) - /** - * Create a [UniFile] representing the single document at the - * given [Uri]. This is only useful on devices running - * [android.os.Build.VERSION_CODES.KITKAT] or later, and will return - * `null` when called on earlier platform versions. - * - * @param singleUri the [Intent.getData] from a successful - * [Intent.ACTION_OPEN_DOCUMENT] or - * [Intent.ACTION_CREATE_DOCUMENT] request. - * @return the [UniFile] representing the given [Uri]. - */ fun fromSingleUri(singleUri: Uri) = SingleDocumentFile(null, singleUri) - /** - * Create a [UniFile] representing the document tree rooted at - * the given [Uri]. This is only useful on devices running - * [Build.VERSION_CODES.LOLLIPOP] or later, and will return - * `null` when called on earlier platform versions. - * - * @param treeUri the [Intent.getData] from a successful - * [Intent.ACTION_OPEN_DOCUMENT_TREE] request. - * @return the [UniFile] representing the given [Uri]. - */ fun fromTreeUri(treeUri: Uri) = TreeDocumentFile(null, DocumentsContractApi21.prepareTreeUri(treeUri)) - /** - * Create a [UniFile] representing the media file rooted at - * the given [Uri]. - * - * @param mediaUri the media uri to wrap - * @return the [UniFile] representing the given [Uri]. - */ fun fromMediaUri(mediaUri: Uri) = MediaFile(mediaUri) - /** - * Create a [UniFile] representing the given [Uri]. - */ - fun fromUri(uri: Uri): UniFile? { - return if (isFileUri(uri)) { - fromFile(File(uri.path!!)) - } else if (isDocumentUri(uri)) { - if (isTreeUri(uri)) { - fromTreeUri(uri) - } else { - fromSingleUri(uri) - } - } else if (MediaFile.isMediaUri(uri)) { - fromMediaUri(uri) - } else { - null - } + fun fromUri(uri: Uri) = when { + isFileUri(uri) -> fromFile(uri.toFile()) + isTreeUri(uri) -> fromTreeUri(uri) + isDocumentUri(uri) -> fromSingleUri(uri) + isMediaUri(uri) -> fromMediaUri(uri) + else -> null } - /** - * Test if given Uri is FileUri - */ - fun isFileUri(uri: Uri): Boolean { - return ContentResolver.SCHEME_FILE == uri.scheme - } + fun isFileUri(uri: Uri) = ContentResolver.SCHEME_FILE == uri.scheme - /** - * Test if given Uri is backed by a - * [android.provider.DocumentsProvider]. - */ - fun isDocumentUri(uri: Uri) = DocumentsContractApi19.isDocumentUri(appCtx, uri) + fun isDocumentUri(uri: Uri) = DocumentsContract.isDocumentUri(appCtx, uri) - /** - * Test if given Uri is TreeUri - */ - fun isTreeUri(uri: Uri): Boolean { - val paths = uri.pathSegments - return ContentResolver.SCHEME_CONTENT == uri.scheme && paths.size >= 2 && "tree" == paths[0] - } + fun isTreeUri(uri: Uri) = DocumentsContractCompat.isTreeUri(uri) + + fun isMediaUri(uri: Uri) = null != MediaContract.getName(uri) } } diff --git a/app/src/main/kotlin/com/hippo/unifile/UniFileExtensions.kt b/app/src/main/kotlin/com/hippo/unifile/UniFileExtensions.kt index 99df7c8d2..14d9a5239 100644 --- a/app/src/main/kotlin/com/hippo/unifile/UniFileExtensions.kt +++ b/app/src/main/kotlin/com/hippo/unifile/UniFileExtensions.kt @@ -1,12 +1,17 @@ package com.hippo.unifile +import android.graphics.ImageDecoder import android.net.Uri +import android.os.Build +import android.os.ParcelFileDescriptor import android.os.ParcelFileDescriptor.AutoCloseInputStream import android.os.ParcelFileDescriptor.AutoCloseOutputStream +import androidx.annotation.RequiresApi import java.io.File import java.io.FileInputStream import java.io.FileOutputStream import okio.Path +import splitties.init.appCtx /** * Use Native IO/NIO directly if possible, unless you need process file content on JVM! @@ -22,6 +27,13 @@ fun UniFile.openOutputStream(): FileOutputStream { return AutoCloseOutputStream(openFileDescriptor("w")) } +fun UniFile.openFileDescriptor(mode: String): ParcelFileDescriptor { + return appCtx.contentResolver.openFileDescriptor(uri, mode) ?: error("Can't open ParcelFileDescriptor") +} + +@RequiresApi(Build.VERSION_CODES.P) +fun UniFile.imageSource() = ImageDecoder.createSource(appCtx.contentResolver, uri) + fun File.asUniFile() = UniFile.fromFile(this) fun Path.asUniFile() = toFile().asUniFile() diff --git a/app/src/main/kotlin/com/hippo/unifile/Utils.kt b/app/src/main/kotlin/com/hippo/unifile/Utils.kt deleted file mode 100644 index ce0068826..000000000 --- a/app/src/main/kotlin/com/hippo/unifile/Utils.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2022 Tarsin Norbin - * - * This file is part of EhViewer - * - * EhViewer is free software: you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * EhViewer is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY - * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with EhViewer. - * If not, see . - */ -package com.hippo.unifile - -internal object Utils { - fun throwIfFatal(t: Throwable) { - // values here derived from https://github.com/ReactiveX/RxJava/issues/748#issuecomment-32471495 - when (t) { - is VirtualMachineError, is ThreadDeath, is LinkageError -> throw t - } - } -} diff --git a/app/src/main/kotlin/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt b/app/src/main/kotlin/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt index 18200c7b5..d52638fa2 100644 --- a/app/src/main/kotlin/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt +++ b/app/src/main/kotlin/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt @@ -30,8 +30,6 @@ import android.net.Uri import android.os.Build import android.os.Bundle import android.os.Environment -import android.os.ParcelFileDescriptor -import android.os.ParcelFileDescriptor.MODE_READ_ONLY import android.provider.MediaStore import android.view.KeyEvent import android.view.MotionEvent @@ -86,6 +84,7 @@ import com.hippo.ehviewer.ui.tools.DialogState import com.hippo.ehviewer.util.AppConfig import com.hippo.ehviewer.util.ExceptionUtils import com.hippo.ehviewer.util.FileUtils +import com.hippo.ehviewer.util.awaitActivityResult import com.hippo.ehviewer.util.getParcelableCompat import com.hippo.ehviewer.util.getParcelableExtraCompat import com.hippo.ehviewer.util.getValue @@ -107,10 +106,10 @@ import eu.kanade.tachiyomi.ui.reader.viewer.BaseViewer import eu.kanade.tachiyomi.ui.reader.viewer.pager.R2LPagerViewer import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.launchUI +import eu.kanade.tachiyomi.util.lang.withIOContext import eu.kanade.tachiyomi.util.lang.withUIContext import eu.kanade.tachiyomi.util.system.isNightMode import java.io.File -import java.io.IOException import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.debounce @@ -120,6 +119,7 @@ import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.sample import kotlinx.coroutines.launch +import moe.tarsin.coroutines.runSuspendCatching import splitties.init.appCtx import splitties.systemservices.clipboardManager @@ -139,7 +139,6 @@ class ReaderActivity : EhActivity() { private var mUri: Uri? = null private var mGalleryInfo: BaseGalleryInfo? = null private var mPage: Int = 0 - private var mCacheFileName: String? = null private val vm: GalleryModel by viewModels() private val dialogState = DialogState() @@ -147,35 +146,6 @@ class ReaderActivity : EhActivity() { * Whether the menu is currently visible. */ var menuVisible by mutableStateOf(false) - - private var saveImageToLauncher = registerForActivityResult( - CreateDocument("todo/todo"), - ) { uri -> - if (uri != null) { - val filepath = AppConfig.externalTempDir.toString() + File.separator + mCacheFileName - val cachefile = File(filepath) - lifecycleScope.launchIO { - try { - ParcelFileDescriptor.open(cachefile, MODE_READ_ONLY).use { from -> - contentResolver.openFileDescriptor(uri, "w")!!.use { - from sendTo it - } - } - } catch (e: IOException) { - e.printStackTrace() - } finally { - runOnUiThread { - Toast.makeText( - this@ReaderActivity, - getString(R.string.image_saved, uri.path), - Toast.LENGTH_SHORT, - ).show() - } - } - cachefile.delete() - } - } - } var mGalleryProvider by lazyMut { vm::galleryProvider } private var mCurrentIndex: Int = 0 @@ -583,12 +553,21 @@ class ReaderActivity : EhActivity() { Toast.makeText(this, R.string.error_cant_save_image, Toast.LENGTH_SHORT).show() return } - mCacheFileName = filename - try { - saveImageToLauncher.launch(filename) - } catch (e: Throwable) { - ExceptionUtils.throwIfFatal(e) - Toast.makeText(this, R.string.error_cant_find_activity, Toast.LENGTH_SHORT).show() + val activity = this + lifecycleScope.launchUI { + runSuspendCatching { + val uri = awaitActivityResult(CreateDocument("todo/todo"), filename) + if (uri != null) { + withIOContext { + val f = requireNotNull(AppConfig.externalTempDir).asUniFile() / filename + f sendTo uri.asUniFile() + f.delete() + } + Toast.makeText(activity, getString(R.string.image_saved, uri.path), Toast.LENGTH_SHORT).show() + } + }.onFailure { + Toast.makeText(activity, R.string.error_cant_find_activity, Toast.LENGTH_SHORT).show() + } } }