Implement lookup cache for UniFile (#545)
* Implement lookup cache for RawFile * Implement lookup cache for TreeDocumentFile * Drop unused UriHandler * UniFile.subFile never return null * Nonnull UniFile.fromFile * SingleDocumentFile use appCtx * TreeDocumentFile use appCtx * MediaFile use appCtx * Refine * Refine * Refine * optimize TreeDocumentFile cache * optimize RawFile cache * Take care of dir lookup cache cause memleak * Fixup * Fixup --------- Co-authored-by: GG Bond <whatever@tagkong.com>
This commit is contained in:
1 parent
51b58bb797
commit
8d0dc88125
15 files changed
+267
-500
No files matched your search
@@ -8,7 +8,7 @@ import com.hippo.ehviewer.EhApplication.Companion.imageCache
|
||||
import com.hippo.ehviewer.download.downloadLocation
|
||||
import com.hippo.ehviewer.spider.DownloadInfoMagics.decodeMagicRequestOrUrl
|
||||
import com.hippo.ehviewer.util.sendTo
|
||||
import com.hippo.unifile.UniFile
|
||||
import com.hippo.unifile.asUniFile
|
||||
import eu.kanade.tachiyomi.util.lang.withIOContext
|
||||
|
||||
object DownloadThumbInterceptor : Interceptor {
|
||||
@@ -17,25 +17,22 @@ object DownloadThumbInterceptor : Interceptor {
|
||||
if (magicOrUrl != null) {
|
||||
val (url, location) = decodeMagicRequestOrUrl(magicOrUrl)
|
||||
if (location != null) {
|
||||
val thumb = withIOContext { downloadLocation.subFile(location)?.subFile(".thumb") }
|
||||
if (thumb != null) {
|
||||
if (withIOContext { thumb.isFile }) {
|
||||
val new = chain.request.newBuilder().data(thumb.uri.toString()).build()
|
||||
when (val result = chain.withRequest(new).proceed()) {
|
||||
is SuccessResult -> return result
|
||||
is ErrorResult -> withIOContext { thumb.delete() }
|
||||
}
|
||||
val thumb = withIOContext { downloadLocation.subFile(location).subFile(".thumb") }
|
||||
if (withIOContext { thumb.isFile }) {
|
||||
val new = chain.request.newBuilder().data(thumb.uri.toString()).build()
|
||||
when (val result = chain.withRequest(new).proceed()) {
|
||||
is SuccessResult -> return result
|
||||
is ErrorResult -> withIOContext { thumb.delete() }
|
||||
}
|
||||
val new = chain.request.newBuilder().data(url).build()
|
||||
return chain.withRequest(new).proceed().also {
|
||||
if (it is SuccessResult) {
|
||||
withIOContext {
|
||||
if (thumb.ensureFile()) {
|
||||
val key = requireNotNull(chain.request.memoryCacheKey)
|
||||
imageCache.read(key) {
|
||||
val cache = UniFile.fromFile(data.toFile())
|
||||
requireNotNull(cache) sendTo thumb
|
||||
}
|
||||
}
|
||||
val new = chain.request.newBuilder().data(url).build()
|
||||
return chain.withRequest(new).proceed().also {
|
||||
if (it is SuccessResult) {
|
||||
withIOContext {
|
||||
if (thumb.ensureFile()) {
|
||||
val key = requireNotNull(chain.request.memoryCacheKey)
|
||||
imageCache.read(key) {
|
||||
data.asUniFile() sendTo thumb
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,8 @@ import com.hippo.ehviewer.util.assertNotMainThread
|
||||
import com.hippo.ehviewer.util.mapNotNull
|
||||
import com.hippo.ehviewer.util.runAssertingNotMainThread
|
||||
import com.hippo.unifile.UniFile
|
||||
import com.hippo.unifile.asUniFile
|
||||
import com.hippo.unifile.asUniFileOrNull
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import eu.kanade.tachiyomi.util.lang.withIOContext
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
@@ -53,7 +55,6 @@ import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.transform
|
||||
import kotlinx.coroutines.launch
|
||||
import splitties.init.appCtx
|
||||
import splitties.preferences.edit
|
||||
|
||||
object DownloadManager : OnSpiderListener {
|
||||
@@ -1017,16 +1018,14 @@ object DownloadManager : OnSpiderListener {
|
||||
|
||||
var downloadLocation: UniFile
|
||||
get() = with(Settings) {
|
||||
UniFile.fromUri(
|
||||
appCtx,
|
||||
Uri.Builder().apply {
|
||||
scheme(downloadScheme)
|
||||
encodedAuthority(downloadAuthority)
|
||||
encodedPath(downloadPath)
|
||||
encodedQuery(downloadQuery)
|
||||
encodedFragment(downloadFragment)
|
||||
}.build(),
|
||||
) ?: UniFile.fromFile(AppConfig.defaultDownloadDir)!!
|
||||
val uri = Uri.Builder().apply {
|
||||
scheme(downloadScheme)
|
||||
encodedAuthority(downloadAuthority)
|
||||
encodedPath(downloadPath)
|
||||
encodedQuery(downloadQuery)
|
||||
encodedFragment(downloadFragment)
|
||||
}.build()
|
||||
uri.asUniFileOrNull() ?: requireNotNull(AppConfig.defaultDownloadDir).asUniFile()
|
||||
}
|
||||
set(value) = with(value.uri) {
|
||||
Settings.edit {
|
||||
|
||||
@@ -171,7 +171,7 @@ class ArchivePageLoader(context: Context, private val uri: Uri, passwdProvider:
|
||||
|
||||
override fun save(index: Int, dir: UniFile, filename: String): UniFile {
|
||||
val dst = dir.subFile(filename)
|
||||
save(index, dst!!)
|
||||
save(index, dst)
|
||||
return dst
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@ import com.hippo.ehviewer.image.Image.UniFileSource
|
||||
import com.hippo.ehviewer.util.FileUtils
|
||||
import com.hippo.ehviewer.util.sendTo
|
||||
import com.hippo.unifile.UniFile
|
||||
import com.hippo.unifile.asUniFile
|
||||
import com.hippo.unifile.openOutputStream
|
||||
import io.ktor.client.plugins.onDownload
|
||||
import io.ktor.client.statement.HttpResponse
|
||||
@@ -154,7 +155,7 @@ class SpiderDen(mGalleryInfo: GalleryInfo) {
|
||||
val key = getImageKey(mGid, index)
|
||||
return sCache.suspendEdit(key) {
|
||||
metadata.toFile().writeText(ext)
|
||||
fops(UniFile.fromFile(data.toFile())!!)
|
||||
fops(data.asUniFile())
|
||||
}
|
||||
}
|
||||
return false
|
||||
@@ -172,34 +173,28 @@ class SpiderDen(mGalleryInfo: GalleryInfo) {
|
||||
}
|
||||
|
||||
fun saveToUniFile(index: Int, file: UniFile): Boolean {
|
||||
file.openFileDescriptor("w").use { toFd ->
|
||||
val key = getImageKey(mGid, index)
|
||||
val key = getImageKey(mGid, index)
|
||||
|
||||
// Read from diskCache first
|
||||
sCache.read(key) {
|
||||
runCatching {
|
||||
UniFile.fromFile(data.toFile())!!.openFileDescriptor("r").use {
|
||||
it sendTo toFd
|
||||
}
|
||||
return true
|
||||
}.onFailure {
|
||||
it.printStackTrace()
|
||||
return false
|
||||
}
|
||||
// Read from diskCache first
|
||||
sCache.read(key) {
|
||||
runCatching {
|
||||
data.asUniFile() sendTo file
|
||||
return true
|
||||
}.onFailure {
|
||||
it.printStackTrace()
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Read from download dir
|
||||
downloadDir?.let { uniFile ->
|
||||
runCatching {
|
||||
findImageFile(uniFile, index)?.openFileDescriptor("r")?.use {
|
||||
it sendTo toFd
|
||||
}
|
||||
}.onFailure {
|
||||
it.printStackTrace()
|
||||
return false
|
||||
}.onSuccess {
|
||||
return true
|
||||
}
|
||||
// Read from download dir
|
||||
downloadDir?.let { uniFile ->
|
||||
runCatching {
|
||||
requireNotNull(findImageFile(uniFile, index)) sendTo file
|
||||
}.onFailure {
|
||||
it.printStackTrace()
|
||||
return false
|
||||
}.onSuccess {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
@@ -216,9 +211,8 @@ class SpiderDen(mGalleryInfo: GalleryInfo) {
|
||||
val key = getImageKey(mGid, index)
|
||||
val snapshot = sCache.openSnapshot(key)
|
||||
if (snapshot != null) {
|
||||
val source = UniFile.fromFile(snapshot.data.toFile())!!
|
||||
return object : UniFileSource, AutoCloseable by snapshot {
|
||||
override val source = source
|
||||
override val source = snapshot.data.asUniFile()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,7 +313,7 @@ class SpiderQueen private constructor(val galleryInfo: GalleryInfo) : CoroutineS
|
||||
if (STATE_FINISHED != state) {
|
||||
return null
|
||||
}
|
||||
val dst = dir.subFile(filename) ?: return null
|
||||
val dst = dir.subFile(filename)
|
||||
return if (!mSpiderDen.saveToUniFile(index, dst)) null else dst
|
||||
}
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ import moe.tarsin.coroutines.runSuspendCatching
|
||||
import splitties.init.appCtx
|
||||
|
||||
private fun removeNoMediaFile(downloadDir: UniFile) {
|
||||
val noMedia = downloadDir.subFile(".nomedia") ?: return
|
||||
val noMedia = downloadDir.subFile(".nomedia")
|
||||
noMedia.delete()
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ import com.hippo.ehviewer.ui.tools.observed
|
||||
import com.hippo.ehviewer.ui.tools.rememberedAccessor
|
||||
import com.hippo.ehviewer.util.AppConfig
|
||||
import com.hippo.unifile.UniFile
|
||||
import com.hippo.unifile.asUniFile
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
import eu.kanade.tachiyomi.util.lang.launchNonCancellable
|
||||
@@ -82,11 +83,9 @@ fun DownloadScreen(navigator: DestinationsNavigator) {
|
||||
coroutineScope.launch {
|
||||
context.runCatching {
|
||||
contentResolver.takePersistableUriPermission(treeUri, FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
UniFile.fromTreeUri(context, treeUri)!!.run {
|
||||
downloadLocationState = this
|
||||
coroutineScope.launchNonCancellable {
|
||||
keepNoMediaFileStatus()
|
||||
}
|
||||
downloadLocationState = treeUri.asUniFile()
|
||||
coroutineScope.launchNonCancellable {
|
||||
keepNoMediaFileStatus()
|
||||
}
|
||||
}.onFailure {
|
||||
launchSnackBar(cannotGetDownloadLocation)
|
||||
@@ -105,7 +104,7 @@ fun DownloadScreen(navigator: DestinationsNavigator) {
|
||||
.setMessage(file.uri.toString())
|
||||
.setPositiveButton(R.string.pick_new_download_location) { _, _ -> selectDownloadDirLauncher.launch(null) }
|
||||
.setNeutralButton(R.string.reset_download_location) { _, _ ->
|
||||
val uniFile = UniFile.fromFile(AppConfig.defaultDownloadDir)
|
||||
val uniFile = AppConfig.defaultDownloadDir?.asUniFile()
|
||||
if (uniFile != null) {
|
||||
downloadLocationState = uniFile
|
||||
coroutineScope.launchNonCancellable { keepNoMediaFileStatus() }
|
||||
@@ -160,7 +159,6 @@ fun DownloadScreen(navigator: DestinationsNavigator) {
|
||||
summary = stringResource(id = R.string.settings_download_download_origin_image_summary),
|
||||
value = Settings::downloadOriginImage,
|
||||
)
|
||||
val restoreFailed = stringResource(id = R.string.settings_download_restore_failed)
|
||||
WorkPreference(
|
||||
title = stringResource(id = R.string.settings_download_restore_download_items),
|
||||
summary = stringResource(id = R.string.settings_download_restore_download_items_summary),
|
||||
@@ -191,25 +189,21 @@ fun DownloadScreen(navigator: DestinationsNavigator) {
|
||||
}.getOrNull()
|
||||
}
|
||||
runCatching {
|
||||
val result = downloadLocation.listFiles()?.mapNotNull { getRestoreItem(it) }?.apply {
|
||||
val result = downloadLocation.listFiles().mapNotNull { getRestoreItem(it) }.apply {
|
||||
runSuspendCatching {
|
||||
fillGalleryListByApi(this, EhUrl.referer)
|
||||
}.onFailure {
|
||||
it.printStackTrace()
|
||||
}
|
||||
}
|
||||
if (result == null) {
|
||||
launchSnackBar(restoreFailed)
|
||||
if (result.isEmpty()) {
|
||||
launchSnackBar(RESTORE_COUNT_MSG(restoreDirCount))
|
||||
} else {
|
||||
if (result.isEmpty()) {
|
||||
launchSnackBar(RESTORE_COUNT_MSG(restoreDirCount))
|
||||
} else {
|
||||
val count = result.filterNot { it.title.isNullOrBlank() }.map {
|
||||
EhDB.putDownloadDirname(it.gid, it.dirname)
|
||||
DownloadManager.restoreDownload(it.galleryInfo, it.dirname)
|
||||
}.size
|
||||
launchSnackBar(RESTORE_COUNT_MSG(count + restoreDirCount))
|
||||
}
|
||||
val count = result.filterNot { it.title.isNullOrBlank() }.map {
|
||||
EhDB.putDownloadDirname(it.gid, it.dirname)
|
||||
DownloadManager.restoreDownload(it.galleryInfo, it.dirname)
|
||||
}.size
|
||||
launchSnackBar(RESTORE_COUNT_MSG(count + restoreDirCount))
|
||||
}
|
||||
}.onFailure {
|
||||
it.printStackTrace()
|
||||
@@ -233,7 +227,7 @@ fun DownloadScreen(navigator: DestinationsNavigator) {
|
||||
return true
|
||||
}
|
||||
runSuspendCatching {
|
||||
val cnt = downloadLocation.listFiles()?.sumOf { clearFile(it).compareTo(false) } ?: 0
|
||||
val cnt = downloadLocation.listFiles().sumOf { clearFile(it).compareTo(false) }
|
||||
launchSnackBar(FINAL_CLEAR_REDUNDANCY_MSG(cnt))
|
||||
}.onFailure {
|
||||
it.printStackTrace()
|
||||
|
||||
@@ -15,29 +15,27 @@
|
||||
*/
|
||||
package com.hippo.unifile
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.ImageDecoder
|
||||
import android.net.Uri
|
||||
import java.io.IOException
|
||||
import splitties.init.appCtx
|
||||
|
||||
internal class MediaFile(context: Context, override val uri: Uri) : UniFile(null) {
|
||||
private val mContext = context.applicationContext
|
||||
|
||||
class MediaFile(override val uri: Uri) : UniFile(null) {
|
||||
override fun createFile(displayName: String) = null
|
||||
|
||||
override fun createDirectory(displayName: String) = null
|
||||
|
||||
override val name: String?
|
||||
get() = MediaContract.getName(mContext, uri)
|
||||
get() = MediaContract.getName(appCtx, uri)
|
||||
override val type: String?
|
||||
get() = MediaContract.getType(mContext, uri)
|
||||
get() = MediaContract.getType(appCtx, uri)
|
||||
override val isDirectory = false
|
||||
override val isFile: Boolean
|
||||
get() = DocumentsContractApi19.isFile(mContext, uri)
|
||||
get() = DocumentsContractApi19.isFile(appCtx, uri)
|
||||
|
||||
override fun lastModified() = MediaContract.lastModified(mContext, uri)
|
||||
override fun lastModified() = MediaContract.lastModified(appCtx, uri)
|
||||
|
||||
override fun length() = MediaContract.length(mContext, uri)
|
||||
override fun length() = MediaContract.length(appCtx, uri)
|
||||
|
||||
override fun canRead() = isFile
|
||||
|
||||
@@ -55,28 +53,26 @@ internal class MediaFile(context: Context, override val uri: Uri) : UniFile(null
|
||||
|
||||
override fun ensureFile() = isFile
|
||||
|
||||
override fun subFile(displayName: String) = null
|
||||
override fun subFile(displayName: String) = error("MediaFile never have a children")
|
||||
|
||||
override fun delete() = false
|
||||
|
||||
override fun exists() = isFile
|
||||
|
||||
override fun listFiles() = null
|
||||
override fun listFiles() = emptyList<MediaFile>()
|
||||
|
||||
override fun findFirst(filter: (String) -> Boolean) = null
|
||||
|
||||
override fun findFile(displayName: String) = null
|
||||
|
||||
override fun renameTo(displayName: String) = false
|
||||
|
||||
override val imageSource: ImageDecoder.Source
|
||||
get() = Contracts.getImageSource(mContext, uri)
|
||||
get() = Contracts.getImageSource(appCtx, uri)
|
||||
|
||||
override fun openFileDescriptor(mode: String) = Contracts.openFileDescriptor(mContext, uri, mode)
|
||||
override fun openFileDescriptor(mode: String) = Contracts.openFileDescriptor(appCtx, uri, mode)
|
||||
|
||||
companion object {
|
||||
fun isMediaUri(context: Context, uri: Uri): Boolean {
|
||||
return null != MediaContract.getName(context, uri)
|
||||
fun isMediaUri(uri: Uri): Boolean {
|
||||
return null != MediaContract.getName(appCtx, uri)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,40 +19,45 @@ import android.graphics.ImageDecoder
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.util.Log
|
||||
import android.webkit.MimeTypeMap
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.util.Locale
|
||||
|
||||
internal class RawFile(parent: UniFile?, var mFile: File) : UniFile(parent) {
|
||||
override fun createFile(displayName: String): UniFile? {
|
||||
val target = File(mFile, displayName)
|
||||
return if (target.exists()) {
|
||||
if (target.isFile) {
|
||||
RawFile(this, target)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} else {
|
||||
runCatching {
|
||||
FileOutputStream(target).use {}
|
||||
RawFile(this, target)
|
||||
}.getOrElse {
|
||||
Log.w(TAG, "Failed to createFile $displayName: $it")
|
||||
null
|
||||
}
|
||||
class RawFile(parent: UniFile?, private var mFile: File) : UniFile(parent) {
|
||||
val parent = parent as? RawFile
|
||||
|
||||
var cachePresent = false
|
||||
private val allChildren by lazy {
|
||||
val current = this
|
||||
cachePresent = true
|
||||
logcat { "Directory lookup cache created for $name" }
|
||||
mutableListOf<RawFile>().apply {
|
||||
val fs = mFile.listFiles()?.map { RawFile(current, it) }
|
||||
if (fs != null) addAll(fs)
|
||||
}
|
||||
}
|
||||
|
||||
override fun createDirectory(displayName: String): UniFile? {
|
||||
val target = File(mFile, displayName)
|
||||
return if (target.isDirectory || target.mkdirs()) {
|
||||
RawFile(this, target)
|
||||
private fun popParentCacheIfPresent() = apply {
|
||||
val present = parent?.cachePresent ?: false
|
||||
if (present) parent?.allChildren?.add(this)
|
||||
}
|
||||
|
||||
override fun createFile(displayName: String): UniFile? {
|
||||
val target = RawFile(this, File(mFile, displayName))
|
||||
if (target.exists()) {
|
||||
if (target.isFile) return target
|
||||
} else {
|
||||
null
|
||||
if (target.ensureFile()) return target
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun createDirectory(displayName: String): UniFile? {
|
||||
val target = RawFile(this, File(mFile, displayName))
|
||||
if (target.ensureDir()) return target
|
||||
return null
|
||||
}
|
||||
|
||||
override val uri: Uri
|
||||
@@ -70,74 +75,48 @@ internal class RawFile(parent: UniFile?, var mFile: File) : UniFile(parent) {
|
||||
override val isFile: Boolean
|
||||
get() = mFile.isFile
|
||||
|
||||
override fun lastModified(): Long {
|
||||
return mFile.lastModified()
|
||||
}
|
||||
override fun lastModified() = mFile.lastModified()
|
||||
|
||||
override fun length(): Long {
|
||||
return mFile.length()
|
||||
}
|
||||
override fun length() = mFile.length()
|
||||
|
||||
override fun canRead(): Boolean {
|
||||
return mFile.canRead()
|
||||
}
|
||||
override fun canRead() = mFile.canRead()
|
||||
|
||||
override fun canWrite(): Boolean {
|
||||
return mFile.canWrite()
|
||||
}
|
||||
override fun canWrite() = mFile.canWrite()
|
||||
|
||||
override fun ensureDir(): Boolean {
|
||||
return mFile.isDirectory || mFile.mkdirs()
|
||||
if (mFile.isDirectory) return true
|
||||
if (mFile.mkdirs()) {
|
||||
popParentCacheIfPresent()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun ensureFile(): Boolean {
|
||||
return if (mFile.exists()) {
|
||||
mFile.isFile
|
||||
} else {
|
||||
runCatching {
|
||||
FileOutputStream(mFile).use {}
|
||||
true
|
||||
}.getOrDefault(false)
|
||||
}
|
||||
if (mFile.exists()) return mFile.isFile
|
||||
parent?.ensureDir()
|
||||
val success = mFile.createNewFile()
|
||||
if (success) popParentCacheIfPresent()
|
||||
return success
|
||||
}
|
||||
|
||||
override fun subFile(displayName: String): UniFile {
|
||||
return RawFile(this, File(mFile, displayName))
|
||||
}
|
||||
override fun subFile(displayName: String) = RawFile(this, File(mFile, displayName))
|
||||
|
||||
override fun delete(): Boolean {
|
||||
deleteContents(mFile)
|
||||
return mFile.delete()
|
||||
}
|
||||
override fun delete() = mFile.deleteRecursively()
|
||||
|
||||
override fun exists(): Boolean {
|
||||
return mFile.exists()
|
||||
}
|
||||
override fun exists() = mFile.exists()
|
||||
|
||||
override fun listFiles(): Array<UniFile>? {
|
||||
val files = mFile.listFiles() ?: return null
|
||||
return files.map { RawFile(this, it) }.toTypedArray()
|
||||
}
|
||||
override fun listFiles() = allChildren
|
||||
|
||||
override fun findFirst(filter: (String) -> Boolean) = mFile.listFiles { _, name ->
|
||||
filter(name)
|
||||
}?.firstOrNull()?.let {
|
||||
RawFile(this, it)
|
||||
}
|
||||
|
||||
override fun findFile(displayName: String): UniFile? {
|
||||
val child = File(mFile, displayName)
|
||||
return if (child.exists()) RawFile(this, child) else null
|
||||
}
|
||||
override fun findFirst(filter: (String) -> Boolean) = allChildren.firstOrNull { filter(it.name) }
|
||||
|
||||
override fun renameTo(displayName: String): Boolean {
|
||||
val target = File(mFile.parentFile, displayName)
|
||||
return if (mFile.renameTo(target)) {
|
||||
if (mFile.renameTo(target)) {
|
||||
mFile = target
|
||||
true
|
||||
} else {
|
||||
false
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override val imageSource: ImageDecoder.Source
|
||||
@@ -149,39 +128,18 @@ internal class RawFile(parent: UniFile?, var mFile: File) : UniFile(parent) {
|
||||
|
||||
override fun openFileDescriptor(mode: String): ParcelFileDescriptor {
|
||||
val md = ParcelFileDescriptor.parseMode(mode)
|
||||
return ParcelFileDescriptor.open(mFile, md)
|
||||
?: throw IOException("Can't open ParcelFileDescriptor")
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = RawFile::class.java.simpleName
|
||||
private fun getTypeForName(name: String): String {
|
||||
val lastDot = name.lastIndexOf('.')
|
||||
if (lastDot >= 0) {
|
||||
val extension = name.substring(lastDot + 1).lowercase(Locale.getDefault())
|
||||
val mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
|
||||
if (mime != null) {
|
||||
return mime
|
||||
}
|
||||
}
|
||||
return "application/octet-stream"
|
||||
}
|
||||
|
||||
private fun deleteContents(dir: File): Boolean {
|
||||
val files = dir.listFiles()
|
||||
var success = true
|
||||
if (files != null) {
|
||||
for (file in files) {
|
||||
if (file.isDirectory) {
|
||||
success = success and deleteContents(file)
|
||||
}
|
||||
if (!file.delete()) {
|
||||
Log.w(TAG, "Failed to delete $file")
|
||||
success = false
|
||||
}
|
||||
}
|
||||
}
|
||||
return success
|
||||
}
|
||||
return ParcelFileDescriptor.open(mFile, md) ?: throw IOException("Can't open ParcelFileDescriptor")
|
||||
}
|
||||
}
|
||||
|
||||
private fun getTypeForName(name: String): String {
|
||||
val lastDot = name.lastIndexOf('.')
|
||||
if (lastDot >= 0) {
|
||||
val extension = name.substring(lastDot + 1).lowercase(Locale.getDefault())
|
||||
val mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
|
||||
if (mime != null) {
|
||||
return mime
|
||||
}
|
||||
}
|
||||
return "application/octet-stream"
|
||||
}
|
||||
@@ -15,54 +15,51 @@
|
||||
*/
|
||||
package com.hippo.unifile
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.ImageDecoder
|
||||
import android.net.Uri
|
||||
import splitties.init.appCtx
|
||||
|
||||
class SingleDocumentFile(parent: UniFile?, context: Context, override val uri: Uri) : UniFile(parent) {
|
||||
private val mContext = context.applicationContext
|
||||
class SingleDocumentFile(parent: UniFile?, override val uri: Uri) : UniFile(parent) {
|
||||
|
||||
override fun createFile(displayName: String) = null
|
||||
|
||||
override fun createDirectory(displayName: String) = null
|
||||
|
||||
override val name: String?
|
||||
get() = DocumentsContractApi19.getName(mContext, uri)
|
||||
get() = DocumentsContractApi19.getName(appCtx, uri)
|
||||
override val type: String?
|
||||
get() = DocumentsContractApi19.getType(mContext, uri)
|
||||
get() = DocumentsContractApi19.getType(appCtx, uri)
|
||||
override val isDirectory: Boolean
|
||||
get() = DocumentsContractApi19.isDirectory(mContext, uri)
|
||||
get() = DocumentsContractApi19.isDirectory(appCtx, uri)
|
||||
override val isFile: Boolean
|
||||
get() = DocumentsContractApi19.isFile(mContext, uri)
|
||||
get() = DocumentsContractApi19.isFile(appCtx, uri)
|
||||
|
||||
override fun lastModified() = DocumentsContractApi19.lastModified(mContext, uri)
|
||||
override fun lastModified() = DocumentsContractApi19.lastModified(appCtx, uri)
|
||||
|
||||
override fun length() = DocumentsContractApi19.length(mContext, uri)
|
||||
override fun length() = DocumentsContractApi19.length(appCtx, uri)
|
||||
|
||||
override fun canRead() = DocumentsContractApi19.canRead(mContext, uri)
|
||||
override fun canRead() = DocumentsContractApi19.canRead(appCtx, uri)
|
||||
|
||||
override fun canWrite() = DocumentsContractApi19.canWrite(mContext, uri)
|
||||
override fun canWrite() = DocumentsContractApi19.canWrite(appCtx, uri)
|
||||
|
||||
override fun ensureDir() = isDirectory
|
||||
|
||||
override fun ensureFile() = isFile
|
||||
|
||||
override fun subFile(displayName: String) = null
|
||||
override fun subFile(displayName: String) = error("SingleDocumentFile never have a children")
|
||||
|
||||
override fun delete() = DocumentsContractApi19.delete(mContext, uri)
|
||||
override fun delete() = DocumentsContractApi19.delete(appCtx, uri)
|
||||
|
||||
override fun exists() = DocumentsContractApi19.exists(mContext, uri)
|
||||
override fun exists() = DocumentsContractApi19.exists(appCtx, uri)
|
||||
|
||||
override fun listFiles() = null
|
||||
override fun listFiles() = emptyList<SingleDocumentFile>()
|
||||
|
||||
override fun findFirst(filter: (String) -> Boolean) = null
|
||||
|
||||
override fun findFile(displayName: String) = null
|
||||
|
||||
override fun renameTo(displayName: String) = false
|
||||
|
||||
override val imageSource: ImageDecoder.Source
|
||||
get() = Contracts.getImageSource(mContext, uri)
|
||||
get() = Contracts.getImageSource(appCtx, uri)
|
||||
|
||||
override fun openFileDescriptor(mode: String) = Contracts.openFileDescriptor(mContext, uri, mode)
|
||||
override fun openFileDescriptor(mode: String) = Contracts.openFileDescriptor(appCtx, uri, mode)
|
||||
}
|
||||
@@ -15,44 +15,38 @@
|
||||
*/
|
||||
package com.hippo.unifile
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.ImageDecoder
|
||||
import android.net.Uri
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.util.Log
|
||||
import android.webkit.MimeTypeMap
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import splitties.init.appCtx
|
||||
|
||||
internal class TreeDocumentFile : UniFile {
|
||||
private val mContext: Context
|
||||
override var uri: Uri
|
||||
private set
|
||||
private var mFilename: String? = null
|
||||
|
||||
constructor(parent: UniFile?, context: Context, uri: Uri) : super(parent) {
|
||||
mContext = context.applicationContext
|
||||
this.uri = uri
|
||||
class TreeDocumentFile(
|
||||
parent: UniFile?,
|
||||
override var uri: Uri,
|
||||
val filename: String,
|
||||
) : UniFile(parent) {
|
||||
private var cachePresent = false
|
||||
private val allChildren by lazy {
|
||||
cachePresent = true
|
||||
logcat { "Directory lookup cache created for $name" }
|
||||
DocumentsContractApi21.listFiles(appCtx, uri).map {
|
||||
val name = getFilenameForUri(it)
|
||||
TreeDocumentFile(this, it, name)
|
||||
}.toMutableList()
|
||||
}
|
||||
|
||||
private constructor(parent: UniFile, context: Context, uri: Uri, filename: String?) : super(
|
||||
parent,
|
||||
) {
|
||||
mContext = context.applicationContext
|
||||
this.uri = uri
|
||||
mFilename = filename
|
||||
private fun popCacheIfPresent(file: TreeDocumentFile) = apply {
|
||||
if (cachePresent) allChildren.add(file)
|
||||
}
|
||||
|
||||
constructor(parent: UniFile?, uri: Uri) : this(parent, uri, getFilenameForUri(uri))
|
||||
|
||||
override fun createFile(displayName: String): UniFile? {
|
||||
val child = findFile(displayName)
|
||||
return if (child != null) {
|
||||
if (child.isFile) {
|
||||
child
|
||||
} else {
|
||||
Log.w(
|
||||
TAG,
|
||||
"Try to create file $displayName, but it is not file",
|
||||
)
|
||||
null
|
||||
}
|
||||
if (child != null) {
|
||||
if (child.isFile) return child
|
||||
} else {
|
||||
val index = displayName.lastIndexOf('.')
|
||||
if (index > 0) {
|
||||
@@ -60,111 +54,75 @@ internal class TreeDocumentFile : UniFile {
|
||||
val extension = displayName.substring(index + 1)
|
||||
val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension)
|
||||
if (!mimeType.isNullOrEmpty()) {
|
||||
val result = DocumentsContractApi21.createFile(mContext, uri, mimeType, name)
|
||||
return if (result != null) {
|
||||
TreeDocumentFile(
|
||||
this,
|
||||
mContext,
|
||||
result,
|
||||
displayName,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
val result = DocumentsContractApi21.createFile(appCtx, uri, mimeType, name)
|
||||
if (result != null) {
|
||||
val f = TreeDocumentFile(this, result, displayName)
|
||||
popCacheIfPresent(f)
|
||||
return f
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Not dot in displayName or dot is the first char or can't get MimeType
|
||||
val result = DocumentsContractApi21.createFile(
|
||||
mContext,
|
||||
uri,
|
||||
"application/octet-stream",
|
||||
displayName,
|
||||
)
|
||||
if (result != null) {
|
||||
TreeDocumentFile(
|
||||
this,
|
||||
mContext,
|
||||
result,
|
||||
displayName,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
// 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)
|
||||
if (result != null) {
|
||||
val f = TreeDocumentFile(this, result, displayName)
|
||||
popCacheIfPresent(f)
|
||||
return f
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun createDirectory(displayName: String): UniFile? {
|
||||
val child = findFile(displayName)
|
||||
return if (child != null) {
|
||||
if (child.isDirectory) {
|
||||
child
|
||||
} else {
|
||||
null
|
||||
}
|
||||
if (child != null) {
|
||||
if (child.isDirectory) return null
|
||||
} else {
|
||||
val result = DocumentsContractApi21.createDirectory(mContext, uri, displayName)
|
||||
val result = DocumentsContractApi21.createDirectory(appCtx, uri, displayName)
|
||||
if (result != null) {
|
||||
TreeDocumentFile(
|
||||
this,
|
||||
mContext,
|
||||
result,
|
||||
displayName,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
val d = TreeDocumentFile(this, result, displayName)
|
||||
popCacheIfPresent(d)
|
||||
return d
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override val name: String?
|
||||
get() = DocumentsContractApi19.getName(mContext, uri)
|
||||
get() = DocumentsContractApi19.getName(appCtx, uri)
|
||||
override val type: String?
|
||||
get() = DocumentsContractApi19.getType(mContext, uri)
|
||||
get() = DocumentsContractApi19.getType(appCtx, uri)
|
||||
override val isDirectory: Boolean
|
||||
get() = DocumentsContractApi19.isDirectory(mContext, uri)
|
||||
get() = DocumentsContractApi19.isDirectory(appCtx, uri)
|
||||
override val isFile: Boolean
|
||||
get() = DocumentsContractApi19.isFile(mContext, uri)
|
||||
get() = DocumentsContractApi19.isFile(appCtx, uri)
|
||||
|
||||
override fun lastModified(): Long {
|
||||
return DocumentsContractApi19.lastModified(mContext, uri)
|
||||
}
|
||||
override fun lastModified() = DocumentsContractApi19.lastModified(appCtx, uri)
|
||||
|
||||
override fun length(): Long {
|
||||
return DocumentsContractApi19.length(mContext, uri)
|
||||
}
|
||||
override fun length() = DocumentsContractApi19.length(appCtx, uri)
|
||||
|
||||
override fun canRead(): Boolean {
|
||||
return DocumentsContractApi19.canRead(mContext, uri)
|
||||
}
|
||||
override fun canRead() = DocumentsContractApi19.canRead(appCtx, uri)
|
||||
|
||||
override fun canWrite(): Boolean {
|
||||
return DocumentsContractApi19.canWrite(mContext, uri)
|
||||
}
|
||||
override fun canWrite() = DocumentsContractApi19.canWrite(appCtx, uri)
|
||||
|
||||
override fun ensureDir(): Boolean {
|
||||
if (isDirectory) {
|
||||
return true
|
||||
} else if (isFile) {
|
||||
return false
|
||||
}
|
||||
if (isDirectory) return true
|
||||
if (isFile) return false
|
||||
val parent = parentFile
|
||||
return if (parent != null && parent.ensureDir() && mFilename != null) {
|
||||
parent.createDirectory(mFilename!!) != null
|
||||
return if (parent != null && parent.ensureDir()) {
|
||||
parent.createDirectory(filename) != null
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun ensureFile(): Boolean {
|
||||
if (isFile) {
|
||||
return true
|
||||
} else if (isDirectory) {
|
||||
return false
|
||||
}
|
||||
if (isFile) return true
|
||||
if (isDirectory) return false
|
||||
val parent = parentFile
|
||||
return if (parent != null && parent.ensureDir() && mFilename != null) {
|
||||
parent.createFile(mFilename!!) != null
|
||||
return if (parent != null && parent.ensureDir()) {
|
||||
parent.createFile(filename) != null
|
||||
} else {
|
||||
false
|
||||
}
|
||||
@@ -172,76 +130,40 @@ internal class TreeDocumentFile : UniFile {
|
||||
|
||||
override fun subFile(displayName: String): UniFile {
|
||||
val childUri = DocumentsContractApi21.buildChildUri(uri, displayName)
|
||||
return TreeDocumentFile(this, mContext, childUri, displayName)
|
||||
return TreeDocumentFile(this, childUri, displayName)
|
||||
}
|
||||
|
||||
override fun delete(): Boolean {
|
||||
return DocumentsContractApi19.delete(mContext, uri)
|
||||
}
|
||||
override fun delete() = DocumentsContractApi19.delete(appCtx, uri)
|
||||
|
||||
override fun exists(): Boolean {
|
||||
return DocumentsContractApi19.exists(mContext, uri)
|
||||
}
|
||||
override fun exists() = DocumentsContractApi19.exists(appCtx, uri)
|
||||
|
||||
private fun getFilenameForUri(uri: Uri): String? {
|
||||
val path = uri.path
|
||||
if (path != null) {
|
||||
val index = path.lastIndexOf('/')
|
||||
if (index >= 0) {
|
||||
return path.substring(index + 1)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
override fun listFiles() = allChildren
|
||||
|
||||
override fun listFiles(): Array<UniFile> {
|
||||
val result = DocumentsContractApi21.listFiles(mContext, uri)
|
||||
return result.map { TreeDocumentFile(this, mContext, it, getFilenameForUri(it)) }.toTypedArray()
|
||||
}
|
||||
|
||||
override fun findFirst(filter: (String) -> Boolean): UniFile? {
|
||||
val result = DocumentsContractApi21.listFiles(mContext, uri)
|
||||
for (uri in result) {
|
||||
val name = getFilenameForUri(uri)
|
||||
if (name != null && filter(name)) {
|
||||
return TreeDocumentFile(this, mContext, uri, name)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun findFile(displayName: String): UniFile? {
|
||||
val childUri = DocumentsContractApi21.buildChildUri(uri, displayName)
|
||||
return if (DocumentsContractApi19.exists(mContext, childUri)) {
|
||||
TreeDocumentFile(
|
||||
this,
|
||||
mContext,
|
||||
childUri,
|
||||
displayName,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
override fun findFirst(filter: (String) -> Boolean) = allChildren.firstOrNull { filter(it.filename) }
|
||||
|
||||
override fun renameTo(displayName: String): Boolean {
|
||||
val result = DocumentsContractApi21.renameTo(mContext, uri, displayName)
|
||||
return if (result != null) {
|
||||
val result = DocumentsContractApi21.renameTo(appCtx, uri, displayName)
|
||||
if (result != null) {
|
||||
uri = result
|
||||
true
|
||||
} else {
|
||||
false
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override val imageSource: ImageDecoder.Source
|
||||
get() = Contracts.getImageSource(mContext, uri)
|
||||
get() = Contracts.getImageSource(appCtx, uri)
|
||||
|
||||
override fun openFileDescriptor(mode: String): ParcelFileDescriptor {
|
||||
return Contracts.openFileDescriptor(mContext, uri, mode)
|
||||
return Contracts.openFileDescriptor(appCtx, uri, mode)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = TreeDocumentFile::class.java.simpleName
|
||||
private fun getFilenameForUri(uri: Uri): String {
|
||||
val path = requireNotNull(uri.path)
|
||||
val index = path.lastIndexOf('/')
|
||||
return if (index >= 0) {
|
||||
path.substring(index + 1)
|
||||
} else {
|
||||
return path
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,6 @@
|
||||
package com.hippo.unifile
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.ImageDecoder
|
||||
import android.net.Uri
|
||||
@@ -24,6 +23,7 @@ import android.os.Build
|
||||
import android.os.ParcelFileDescriptor
|
||||
import androidx.annotation.RequiresApi
|
||||
import java.io.File
|
||||
import splitties.init.appCtx
|
||||
|
||||
/**
|
||||
* In Android files can be accessed via [java.io.File] and [android.net.Uri].
|
||||
@@ -173,9 +173,9 @@ abstract class UniFile internal constructor(private val parent: UniFile?) {
|
||||
/**
|
||||
* Get child file of this directory, the child might not exist.
|
||||
*
|
||||
* @return the child file, `null` if not supported
|
||||
* @return the child file
|
||||
*/
|
||||
abstract fun subFile(displayName: String): UniFile?
|
||||
abstract fun subFile(displayName: String): UniFile
|
||||
|
||||
/**
|
||||
* Deletes this file.
|
||||
@@ -196,14 +196,7 @@ abstract class UniFile internal constructor(private val parent: UniFile?) {
|
||||
*/
|
||||
abstract fun exists(): Boolean
|
||||
|
||||
/**
|
||||
* Returns an array of files contained in the directory represented by this
|
||||
* file.
|
||||
*
|
||||
* @return an array of files or `null`.
|
||||
* @see android.provider.DocumentsContract.buildChildDocumentsUriUsingTree
|
||||
*/
|
||||
abstract fun listFiles(): Array<UniFile>?
|
||||
abstract fun listFiles(): List<UniFile>
|
||||
|
||||
abstract fun findFirst(filter: (String) -> Boolean): UniFile?
|
||||
|
||||
@@ -212,7 +205,7 @@ abstract class UniFile internal constructor(private val parent: UniFile?) {
|
||||
*
|
||||
* @return the file if found it, or `null`.
|
||||
*/
|
||||
abstract fun findFile(displayName: String): UniFile?
|
||||
fun findFile(displayName: String) = subFile(displayName).takeIf { it.exists() }
|
||||
|
||||
/**
|
||||
* Renames this file to `displayName`.
|
||||
@@ -242,36 +235,13 @@ abstract class UniFile internal constructor(private val parent: UniFile?) {
|
||||
abstract fun openFileDescriptor(mode: String): ParcelFileDescriptor
|
||||
|
||||
companion object {
|
||||
private var sUriHandlerArray: MutableList<UriHandler>? = null
|
||||
|
||||
/**
|
||||
* Add a UriHandler to get UniFile from uri
|
||||
*/
|
||||
fun addUriHandler(handler: UriHandler) {
|
||||
if (sUriHandlerArray == null) {
|
||||
sUriHandlerArray = ArrayList()
|
||||
}
|
||||
sUriHandlerArray!!.add(handler)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the UriHandler added before
|
||||
*/
|
||||
fun removeUriHandler(handler: UriHandler) {
|
||||
if (sUriHandlerArray != null) {
|
||||
sUriHandlerArray!!.remove(handler)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a [UniFile] representing the given [File].
|
||||
*
|
||||
* @param file the file to wrap
|
||||
* @return the [UniFile] representing the given [File].
|
||||
*/
|
||||
fun fromFile(file: File?): UniFile? {
|
||||
return if (file != null) RawFile(null, file) else null
|
||||
}
|
||||
fun fromFile(file: File) = RawFile(null, file)
|
||||
|
||||
/**
|
||||
* Create a [UniFile] representing the single document at the
|
||||
@@ -284,14 +254,7 @@ abstract class UniFile internal constructor(private val parent: UniFile?) {
|
||||
* [Intent.ACTION_CREATE_DOCUMENT] request.
|
||||
* @return the [UniFile] representing the given [Uri].
|
||||
*/
|
||||
fun fromSingleUri(context: Context, singleUri: Uri): UniFile? {
|
||||
val version = Build.VERSION.SDK_INT
|
||||
return if (version >= 19) {
|
||||
SingleDocumentFile(null, context, singleUri)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
fun fromSingleUri(singleUri: Uri) = SingleDocumentFile(null, singleUri)
|
||||
|
||||
/**
|
||||
* Create a [UniFile] representing the document tree rooted at
|
||||
@@ -303,18 +266,7 @@ abstract class UniFile internal constructor(private val parent: UniFile?) {
|
||||
* [Intent.ACTION_OPEN_DOCUMENT_TREE] request.
|
||||
* @return the [UniFile] representing the given [Uri].
|
||||
*/
|
||||
fun fromTreeUri(context: Context, treeUri: Uri): UniFile? {
|
||||
val version = Build.VERSION.SDK_INT
|
||||
return if (version >= 21) {
|
||||
TreeDocumentFile(
|
||||
null,
|
||||
context,
|
||||
DocumentsContractApi21.prepareTreeUri(treeUri),
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
fun fromTreeUri(treeUri: Uri) = TreeDocumentFile(null, DocumentsContractApi21.prepareTreeUri(treeUri))
|
||||
|
||||
/**
|
||||
* Create a [UniFile] representing the media file rooted at
|
||||
@@ -323,36 +275,22 @@ abstract class UniFile internal constructor(private val parent: UniFile?) {
|
||||
* @param mediaUri the media uri to wrap
|
||||
* @return the [UniFile] representing the given [Uri].
|
||||
*/
|
||||
fun fromMediaUri(context: Context, mediaUri: Uri): UniFile {
|
||||
return MediaFile(context, mediaUri)
|
||||
}
|
||||
fun fromMediaUri(mediaUri: Uri) = MediaFile(mediaUri)
|
||||
|
||||
/**
|
||||
* Create a [UniFile] representing the given [Uri].
|
||||
*/
|
||||
fun fromUri(context: Context, uri: Uri): UniFile? {
|
||||
// Custom handler
|
||||
if (sUriHandlerArray != null) {
|
||||
var i = 0
|
||||
val size = sUriHandlerArray!!.size
|
||||
while (i < size) {
|
||||
val file = sUriHandlerArray!![i].fromUri(context, uri)
|
||||
if (file != null) {
|
||||
return file
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
fun fromUri(uri: Uri): UniFile? {
|
||||
return if (isFileUri(uri)) {
|
||||
fromFile(File(uri.path!!))
|
||||
} else if (isDocumentUri(context, uri)) {
|
||||
} else if (isDocumentUri(uri)) {
|
||||
if (isTreeUri(uri)) {
|
||||
fromTreeUri(context, uri)
|
||||
fromTreeUri(uri)
|
||||
} else {
|
||||
fromSingleUri(context, uri)
|
||||
fromSingleUri(uri)
|
||||
}
|
||||
} else if (MediaFile.isMediaUri(context, uri)) {
|
||||
MediaFile(context, uri)
|
||||
} else if (MediaFile.isMediaUri(uri)) {
|
||||
fromMediaUri(uri)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
@@ -369,14 +307,7 @@ abstract class UniFile internal constructor(private val parent: UniFile?) {
|
||||
* Test if given Uri is backed by a
|
||||
* [android.provider.DocumentsProvider].
|
||||
*/
|
||||
fun isDocumentUri(context: Context, uri: Uri): Boolean {
|
||||
val version = Build.VERSION.SDK_INT
|
||||
return if (version >= 19) {
|
||||
DocumentsContractApi19.isDocumentUri(context, uri)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
fun isDocumentUri(uri: Uri) = DocumentsContractApi19.isDocumentUri(appCtx, uri)
|
||||
|
||||
/**
|
||||
* Test if given Uri is TreeUri
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package com.hippo.unifile
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.ParcelFileDescriptor.AutoCloseInputStream
|
||||
import android.os.ParcelFileDescriptor.AutoCloseOutputStream
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
import okio.Path
|
||||
|
||||
/**
|
||||
* Use Native IO/NIO directly if possible, unless you need process file content on JVM!
|
||||
@@ -18,3 +21,11 @@ fun UniFile.openInputStream(): FileInputStream {
|
||||
fun UniFile.openOutputStream(): FileOutputStream {
|
||||
return AutoCloseOutputStream(openFileDescriptor("w"))
|
||||
}
|
||||
|
||||
fun File.asUniFile() = UniFile.fromFile(this)
|
||||
|
||||
fun Path.asUniFile() = toFile().asUniFile()
|
||||
|
||||
fun Uri.asUniFileOrNull() = UniFile.fromUri(this)
|
||||
|
||||
fun Uri.asUniFile() = requireNotNull(asUniFileOrNull())
|
||||
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.hippo.unifile
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
|
||||
/*
|
||||
* Created by Hippo on 8/16/2016.
|
||||
*/
|
||||
/**
|
||||
* A UriHandler is to get UniFile from custom uri for extensions
|
||||
*/
|
||||
interface UriHandler {
|
||||
/**
|
||||
* Create a [UniFile] representing the uri
|
||||
*/
|
||||
fun fromUri(context: Context?, uri: Uri?): UniFile?
|
||||
}
|
||||
@@ -96,7 +96,7 @@ import com.hippo.ehviewer.util.lazyMut
|
||||
import com.hippo.ehviewer.util.requestPermission
|
||||
import com.hippo.ehviewer.util.sendTo
|
||||
import com.hippo.ehviewer.util.setValue
|
||||
import com.hippo.unifile.UniFile
|
||||
import com.hippo.unifile.asUniFile
|
||||
import dev.chrisbanes.insetter.applyInsetter
|
||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
||||
@@ -408,7 +408,7 @@ class ReaderActivity : EhActivity() {
|
||||
}
|
||||
val file = mGalleryProvider!!.save(
|
||||
page,
|
||||
UniFile.fromFile(dir)!!,
|
||||
dir.asUniFile(),
|
||||
mGalleryProvider!!.getImageFilenameWithExtension(page),
|
||||
)
|
||||
if (file == null) {
|
||||
@@ -466,7 +466,7 @@ class ReaderActivity : EhActivity() {
|
||||
}
|
||||
val file = mGalleryProvider!!.save(
|
||||
page,
|
||||
UniFile.fromFile(dir)!!,
|
||||
dir.asUniFile(),
|
||||
mGalleryProvider!!.getImageFilenameWithExtension(page),
|
||||
)
|
||||
if (file == null) {
|
||||
@@ -534,7 +534,7 @@ class ReaderActivity : EhActivity() {
|
||||
Toast.makeText(this@ReaderActivity, R.string.error_cant_save_image, Toast.LENGTH_SHORT).show()
|
||||
return@launchUI
|
||||
}
|
||||
if (!mGalleryProvider!!.save(page, UniFile.fromMediaUri(this@ReaderActivity, imageUri))) {
|
||||
if (!mGalleryProvider!!.save(page, imageUri.asUniFile())) {
|
||||
try {
|
||||
resolver.delete(imageUri, null, null)
|
||||
} catch (e: Exception) {
|
||||
@@ -571,7 +571,7 @@ class ReaderActivity : EhActivity() {
|
||||
}
|
||||
val file = mGalleryProvider!!.save(
|
||||
page,
|
||||
UniFile.fromFile(dir)!!,
|
||||
dir.asUniFile(),
|
||||
mGalleryProvider!!.getImageFilenameWithExtension(page),
|
||||
)
|
||||
if (file == null) {
|
||||
|
||||
Reference in new issue
Block a user