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 <whatever@tagkong.com>
This commit is contained in:
1 parent
5cec635e52
commit
846e66a56c
22 files changed
+183
-476
No files matched your search
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -1037,4 +1037,4 @@ var downloadLocation: UniFile
|
||||
}
|
||||
}
|
||||
|
||||
val DownloadInfo.downloadDir get() = dirname?.let { downloadLocation.subFile(it) }
|
||||
val DownloadInfo.downloadDir get() = dirname?.let { downloadLocation / it }
|
||||
@@ -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<Int>, pair: Pair<Int, Int>) {}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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? {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ private val Context.lifecycle: Lifecycle
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun <I, O> Context.awaitActivityResult(contract: ActivityResultContract<I, O>, input: I): O {
|
||||
suspend fun <I, O> Context.awaitActivityResult(contract: ActivityResultContract<I, O>, input: I): O {
|
||||
val key = "activity_rq#${atomicInteger.getAndIncrement()}"
|
||||
var launcher: ActivityResultLauncher<I>? = null
|
||||
var observer: LifecycleEventObserver? = null
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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<Uri> {
|
||||
val childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(
|
||||
self,
|
||||
DocumentsContract.getDocumentId(self),
|
||||
)
|
||||
val results = ArrayList<Uri>()
|
||||
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<Uri>()
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<SingleDocumentFile>()
|
||||
|
||||
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)
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user