Avoid relying on implementation details (#1357)

* Avoid relying on implementation details

* Refine

* Fixup

* Extract common logic of RawFile & TreeDocumentFile

* Reuse the root download location UniFile object
This commit is contained in:
FooIbar authored and GitHub committed 2024-07-01 20:00:13 +08:00
1 parent ba3a41f0ee
commit 355289a576
18 files changed
+181 -320

No files matched your search

@@ -11,37 +11,29 @@ import com.hippo.unifile.asUniFile
import eu.kanade.tachiyomi.util.lang.withIOContext
object DownloadThumbInterceptor : Interceptor {
// TODO: Remove this after a few releases
private const val LEGACY_THUMB_FILE = ".thumb"
private const val THUMB_FILE = "thumb.jpg"
override suspend fun intercept(chain: Interceptor.Chain): ImageResult {
val magicOrUrl = chain.request.data as? String
if (magicOrUrl != null) {
val (url, location) = decodeMagicRequestOrUrl(magicOrUrl)
if (location != null) {
val thumb = withIOContext {
val legacyThumb = downloadLocation / location / LEGACY_THUMB_FILE
if (legacyThumb.isFile) {
legacyThumb.apply { renameTo(THUMB_FILE) }
} else {
downloadLocation / location / THUMB_FILE
}
}
if (withIOContext { thumb.isFile }) {
val new = chain.request.newBuilder().data(thumb.uri.toString()).build()
val downloadDir = withIOContext { downloadLocation.findFile(location) }
val thumb = withIOContext { downloadDir?.findFile(THUMB_FILE) }
thumb?.run {
val new = chain.request.newBuilder().data(uri).build()
val result = chain.withRequest(new).proceed()
if (result is SuccessResult) return result
}
val new = chain.request.newBuilder().data(url).build()
val result = chain.withRequest(new).proceed()
if (result is SuccessResult) {
if (result is SuccessResult && downloadDir != null) {
withIOContext {
// Accessing the recreated file immediately after deleting it throws
// FileNotFoundException, so we just overwrite the existing file.
if (thumb.ensureFile()) {
(thumb ?: downloadDir.createFile(THUMB_FILE))?.let {
val key = requireNotNull(chain.request.memoryCacheKey)
imageCache.read(key) {
data.asUniFile() sendTo thumb
data.asUniFile() sendTo it
}
}
}
@@ -813,17 +813,16 @@ object DownloadManager : OnSpiderListener, CoroutineScope {
private fun comparator() = SortMode.from(Settings.downloadSortMode.value).comparator()
}
var downloadLocation: UniFile
get() = with(Settings) {
val uri = Uri.Builder().apply {
scheme(downloadScheme)
encodedAuthority(downloadAuthority)
encodedPath(downloadPath)
encodedQuery(downloadQuery)
encodedFragment(downloadFragment)
}.build()
uri.asUniFileOrNull() ?: AppConfig.defaultDownloadDir?.asUniFile() ?: UniFile.Stub
}
var downloadLocation = with(Settings) {
val uri = Uri.Builder().apply {
scheme(downloadScheme)
encodedAuthority(downloadAuthority)
encodedPath(downloadPath)
encodedQuery(downloadQuery)
encodedFragment(downloadFragment)
}.build()
uri.asUniFileOrNull() ?: AppConfig.defaultDownloadDir?.asUniFile() ?: UniFile.Stub
}
set(value) = with(value.uri) {
Settings.edit {
downloadScheme = scheme
@@ -832,8 +831,9 @@ var downloadLocation: UniFile
downloadQuery = encodedQuery
downloadFragment = encodedFragment
}
field = value
}
val DownloadInfo.downloadDir get() = dirname?.let { downloadLocation / it }
val DownloadInfo.downloadDir get() = dirname?.let { downloadLocation.findFile(it) }
val DownloadInfo.archiveFile get() = downloadDir?.run { findFile("$gid.cbz") ?: findFile("$gid.zip") }
val GalleryInfo.tempDownloadDir get() = AppConfig.getTempDir("$gid")
val GalleryInfo.tempDownloadDir get() = AppConfig.getTempDir("$gid")?.asUniFile()
@@ -69,8 +69,4 @@ abstract class PageLoader2(private val gid: Long, var startPage: Int) :
}
abstract fun save(index: Int, file: UniFile): Boolean
fun saveToDir(index: Int, dir: UniFile): UniFile? = getImageFilename(index)?.let { filename ->
(dir / filename).takeIf { save(index, it) }
}
}
@@ -42,7 +42,6 @@ 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.ensureDirOrThrow
import com.hippo.unifile.openOutputStream
import com.hippo.unifile.sha1
import eu.kanade.tachiyomi.util.system.logcat
@@ -68,7 +67,7 @@ class SpiderDen(val info: GalleryInfo) {
get() = tempDownloadDir.takeIf { saveAsCbz } ?: downloadDir
constructor(info: GalleryInfo, dirname: String) : this(info) {
downloadDir = downloadLocation / dirname
downloadDir = downloadLocation.findFile(dirname)
}
@Volatile
@@ -80,10 +79,11 @@ class SpiderDen(val info: GalleryInfo) {
mode = value
if (mode == SpiderQueen.MODE_DOWNLOAD) {
if (downloadDir == null) {
downloadDir = getGalleryDownloadDir(gid)!!.apply { ensureDirOrThrow() }
val dirname = EhDB.getDownloadDirname(gid)!!
downloadDir = downloadLocation.createDirectory(dirname)!!
}
if (saveAsCbz && tempDownloadDir == null) {
tempDownloadDir = info.tempDownloadDir!!.apply { ensureDirOrThrow() }
tempDownloadDir = info.tempDownloadDir!!
}
}
}
@@ -283,10 +283,8 @@ class SpiderDen(val info: GalleryInfo) {
private suspend fun archiveTo(file: UniFile) = resourceScope {
val comicInfo = closeable {
val f = downloadDir!! / COMIC_INFO_FILE
if (!f.exists()) {
writeComicInfo()
} else if (info.pages == 0) {
val f = downloadDir!!.findFile(COMIC_INFO_FILE) ?: writeComicInfo()!!
if (info.pages == 0) {
info.pages = readComicInfo(f)!!.pageCount
}
f.openFileDescriptor("r")
@@ -301,32 +299,30 @@ class SpiderDen(val info: GalleryInfo) {
}
suspend fun initDownloadDirIfExist() {
downloadDir = getGalleryDownloadDir(gid)?.takeIf { it.isDirectory }
downloadDir = EhDB.getDownloadDirname(gid)?.let { downloadLocation.findFile(it) }
tempDownloadDir = info.tempDownloadDir?.takeIf { it.isDirectory }
}
suspend fun initDownloadDir() {
downloadDir = getGalleryDownloadDir(gid) ?: (downloadLocation / info.putToDownloadDir())
downloadDir!!.ensureDirOrThrow()
val dirname = EhDB.getDownloadDirname(gid) ?: info.putToDownloadDir()
downloadDir = downloadLocation.createDirectory(dirname)!!
}
suspend fun writeComicInfo(fetchMetadata: Boolean = true) {
downloadDir?.run {
createFile(COMIC_INFO_FILE)?.also {
runCatching {
if (info !is GalleryDetail && fetchMetadata) {
EhEngine.fillGalleryListByApi(listOf(info))
}
info.getComicInfo().apply {
write(it)
DownloadManager.getDownloadInfo(gid)?.let { downloadInfo ->
downloadInfo.artistInfoList = DownloadArtist.from(gid, penciller.orEmpty())
EhDB.putDownloadArtist(gid, downloadInfo.artistInfoList)
}
}
}.onFailure {
logcat(it)
suspend fun writeComicInfo(fetchMetadata: Boolean = true): UniFile? = downloadDir?.run {
createFile(COMIC_INFO_FILE)?.also {
runCatching {
if (info !is GalleryDetail && fetchMetadata) {
EhEngine.fillGalleryListByApi(listOf(info))
}
info.getComicInfo().apply {
write(it)
DownloadManager.getDownloadInfo(gid)?.let { downloadInfo ->
downloadInfo.artistInfoList = DownloadArtist.from(gid, penciller.orEmpty())
EhDB.putDownloadArtist(gid, downloadInfo.artistInfoList)
}
}
}.onFailure {
logcat(it)
}
}
}
@@ -348,8 +344,3 @@ suspend fun GalleryInfo.putToDownloadDir(): String {
EhDB.putDownloadDirname(gid, dirname)
return dirname
}
suspend fun getGalleryDownloadDir(gid: Long): UniFile? {
val dirname = EhDB.getDownloadDirname(gid) ?: return null
return downloadLocation / dirname
}
@@ -80,8 +80,7 @@ import moe.tarsin.coroutines.runSuspendCatching
import splitties.init.appCtx
private fun removeNoMediaFile(downloadDir: UniFile) {
val noMedia = downloadDir / ".nomedia"
noMedia.delete()
downloadDir.findFile(".nomedia")?.delete()
}
private fun ensureNoMediaFile(downloadDir: UniFile) {
@@ -227,7 +227,7 @@ class MainActivity : EhActivity() {
}
suspend fun DialogState.checkDownloadLocation() {
val valid = withIOContext { downloadLocation.ensureDir() }
val valid = withIOContext { downloadLocation.isDirectory }
if (!valid) {
awaitPermissionOrCancel(
confirmText = R.string.open_settings,
@@ -35,8 +35,9 @@ import splitties.systemservices.clipboardManager
context(PageLoader2)
private fun Context.provideImage(index: Int): Uri? {
val dir = AppConfig.externalTempDir ?: return null
val name = saveToDir(index, dir.asUniFile())?.name ?: return null
return FileProvider.getUriForFile(this, "$APPLICATION_ID.fileprovider", File(dir, name))
val name = getImageFilename(index) ?: return null
val file = File(dir, name).takeIf { save(index, it.asUniFile()) } ?: return null
return FileProvider.getUriForFile(this, "$APPLICATION_ID.fileprovider", file)
}
context(SnackbarHostState, Context, PageLoader2)
@@ -50,6 +50,7 @@ import com.hippo.ehviewer.util.isAtLeastO
import com.hippo.ehviewer.util.isAtLeastV
import com.hippo.ehviewer.util.setAppLanguage
import com.hippo.unifile.asUniFile
import com.hippo.unifile.displayPath
import com.jamal.composeprefs3.ui.prefs.DropDownPref
import com.jamal.composeprefs3.ui.prefs.SwitchPref
import com.ramcosta.composedestinations.annotation.Destination
@@ -130,7 +131,7 @@ fun AdvancedScreen(navigator: DestinationsNavigator) {
Crash.collectInfo(zipOs.writer())
Runtime.getRuntime().exec("logcat -d").inputStream.use { it.copyTo(zipOs) }
}
launchSnackBar(getString(R.string.settings_advanced_dump_logcat_to, uri.toString()))
launchSnackBar(getString(R.string.settings_advanced_dump_logcat_to, uri.displayPath))
}
}.onFailure {
launchSnackBar(dumpLogError)
@@ -201,7 +202,7 @@ fun AdvancedScreen(navigator: DestinationsNavigator) {
context.runCatching {
grantUriPermission(BuildConfig.APPLICATION_ID, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
EhDB.exportDB(context, uri.asUniFile())
launchSnackBar(getString(R.string.settings_advanced_export_data_to, uri.toString()))
launchSnackBar(getString(R.string.settings_advanced_export_data_to, uri.displayPath))
}.onFailure {
logcat(it)
launchSnackBar(exportFailed)
@@ -19,8 +19,6 @@ import android.os.Build
import android.os.Environment
import com.hippo.ehviewer.BuildConfig
import com.hippo.ehviewer.client.parser.ParserUtils
import com.hippo.unifile.UniFile
import com.hippo.unifile.asUniFile
import java.io.File
import splitties.init.appCtx
@@ -59,7 +57,8 @@ object AppConfig {
val defaultDownloadDir: File?
get() = getDirInExternalAppDir(DOWNLOAD, false)
fun getTempDir(filename: String): UniFile? = getDirInExternalAppDir(TEMP)?.run { File(this, filename).asUniFile() }
fun getTempDir(filename: String): File? =
getDirInExternalAppDir(TEMP)?.run { File(this, filename).takeIf { it.ensureDirectory() } }
val externalTempDir: File?
get() = appCtx.externalCacheDir?.run { File(this, TEMP).takeIf { it.ensureDirectory() } }
@@ -0,0 +1,49 @@
package com.hippo.unifile
abstract class CachingFile<T : UniFile>(override val parent: T?) : UniFile {
private var cachePresent = false
private val allChildren by lazy {
cachePresent = true
list() ?: mutableListOf()
}
protected abstract fun list(): MutableList<T>?
protected fun popCacheIfPresent(file: T) {
if (cachePresent) {
synchronized(allChildren) {
allChildren.add(file)
}
}
}
protected fun evictCacheIfPresent(file: T) {
if (cachePresent) {
synchronized(allChildren) {
allChildren.remove(file)
}
}
}
protected fun replaceCacheIfPresent(old: T, new: T) {
if (cachePresent) {
synchronized(allChildren) {
val index = allChildren.indexOf(old)
if (index != -1) {
allChildren[index] = new
}
}
}
}
override fun listFiles() = synchronized(allChildren) {
allChildren.toList()
}
override fun findFirst(filter: (String) -> Boolean) = synchronized(allChildren) {
allChildren.firstOrNull { filter(it.name!!) }
}
override fun findFile(displayName: String) = findFirst { it.equals(displayName, true) }
}
@@ -26,19 +26,8 @@ object DocumentsContractApi21 {
DocumentsContract.createDocument(resolver, self, mimeType, displayName)
}.getOrNull()
fun createDirectory(self: Uri, displayName: String) = createFile(self, DocumentsContract.Document.MIME_TYPE_DIR, displayName)
fun prepareTreeUri(treeUri: Uri): Uri = DocumentsContract.buildDocumentUriUsingTree(
treeUri,
DocumentsContract.getTreeDocumentId(treeUri),
)
// This is not a good approach as the document identifier is an opaque implementation detail of
// the provider, but works for ExternalStorageProvider
fun buildChildUri(uri: Uri, displayName: String): Uri = DocumentsContract.buildDocumentUriUsingTree(
uri,
DocumentsContract.getDocumentId(uri) + "/" + displayName,
)
fun prepareTreeUri(treeUri: Uri): Uri =
DocumentsContract.buildDocumentUriUsingTree(treeUri, DocumentsContract.getTreeDocumentId(treeUri))
fun renameTo(self: Uri, displayName: String) = runCatching {
DocumentsContract.renameDocument(resolver, self, displayName)
@@ -48,12 +48,6 @@ class MediaFile(override val uri: Uri) : UniFile {
return true
}
override fun ensureDir() = false
override fun ensureFile() = isFile
override fun resolve(displayName: String) = error("MediaFile never have a children")
override fun delete() = false
override fun exists() = isFile
@@ -62,5 +56,7 @@ class MediaFile(override val uri: Uri) : UniFile {
override fun findFirst(filter: (String) -> Boolean) = null
override fun renameTo(displayName: String) = false
override fun findFile(displayName: String) = null
override fun renameTo(displayName: String) = null
}
@@ -22,45 +22,29 @@ import android.os.ParcelFileDescriptor.parseMode
import android.webkit.MimeTypeMap
import java.io.File
class RawFile(override val parent: RawFile?, private var file: File) : UniFile {
private var cachePresent = false
private val allChildren by lazy {
cachePresent = true
file.listFiles().orEmpty().mapTo(mutableListOf()) { RawFile(this, it) }
class RawFile(parent: RawFile?, private val file: File) : CachingFile<RawFile>(parent) {
override fun list() = file.listFiles()?.let { children ->
MutableList(children.size) { RawFile(this, children[it]) }
}
private fun popCacheIfPresent(file: RawFile) {
if (cachePresent) {
synchronized(allChildren) {
allChildren.add(file)
}
}
}
private fun evictCacheIfPresent(file: RawFile) {
if (cachePresent) {
synchronized(allChildren) {
allChildren.remove(file)
}
}
}
override fun createFile(displayName: String): UniFile? {
val target = RawFile(this, File(file, displayName))
if (target.exists()) {
if (target.isFile) return target
private fun createFile(displayName: String, isFile: Boolean): UniFile? {
val child = findFile(displayName)
return if (child != null) {
child.takeIf { if (isFile) it.isFile else it.isDirectory }
} else {
if (target.ensureFile()) return target
val target = File(file, displayName)
val created = if (isFile) target.createNewFile() else target.mkdir()
if (created) {
RawFile(this, target).also { popCacheIfPresent(it) }
} else {
null
}
}
return null
}
override fun createDirectory(displayName: String): UniFile? {
val target = RawFile(this, File(file, displayName))
if (target.ensureDir()) return target
return null
}
override fun createFile(displayName: String) = createFile(displayName, true)
override fun createDirectory(displayName: String) = createFile(displayName, false)
override val uri: Uri
get() = Uri.fromFile(file)
@@ -86,46 +70,19 @@ class RawFile(override val parent: RawFile?, private var file: File) : UniFile {
override fun canWrite() = file.canWrite()
override fun ensureDir(): Boolean {
if (file.isDirectory) return true
if (file.mkdirs()) {
parent?.popCacheIfPresent(this)
return true
}
return false
}
override fun ensureFile(): Boolean {
if (file.exists()) return file.isFile
parent?.ensureDir()
val success = file.createNewFile()
if (success) parent?.popCacheIfPresent(this)
return success
}
override fun resolve(displayName: String) = RawFile(this, File(file, displayName))
override fun delete() = file.deleteRecursively().also {
if (it) parent?.evictCacheIfPresent(this)
}
override fun exists() = file.exists()
override fun listFiles() = synchronized(allChildren) {
allChildren.toList()
}
override fun findFirst(filter: (String) -> Boolean) = synchronized(allChildren) {
allChildren.firstOrNull { filter(it.name) }
}
override fun renameTo(displayName: String): Boolean {
override fun renameTo(displayName: String): UniFile? {
val target = File(file.parentFile, displayName)
if (file.renameTo(target)) {
file = target
return true
return if (file.renameTo(target)) {
RawFile(parent, target).also { parent?.replaceCacheIfPresent(this, it) }
} else {
null
}
return false
}
override fun openFileDescriptor(mode: String): ParcelFileDescriptor = open(file, parseMode(mode))
@@ -18,7 +18,6 @@ package com.hippo.unifile
import android.net.Uri
class SingleDocumentFile(override val uri: Uri) : UniFile {
override fun createFile(displayName: String) = null
override fun createDirectory(displayName: String) = null
@@ -41,12 +40,6 @@ class SingleDocumentFile(override val uri: Uri) : UniFile {
override fun canWrite() = DocumentsContractApi19.canWrite(uri)
override fun ensureDir() = isDirectory
override fun ensureFile() = isFile
override fun resolve(displayName: String) = error("SingleDocumentFile never have a children")
override fun delete() = DocumentsContractApi19.delete(uri)
override fun exists() = DocumentsContractApi19.exists(uri)
@@ -55,5 +48,7 @@ class SingleDocumentFile(override val uri: Uri) : UniFile {
override fun findFirst(filter: (String) -> Boolean) = null
override fun renameTo(displayName: String) = false
override fun findFile(displayName: String) = null
override fun renameTo(displayName: String) = null
}
@@ -22,77 +22,58 @@ import android.webkit.MimeTypeMap
import splitties.init.appCtx
class TreeDocumentFile(
override val parent: TreeDocumentFile?,
uri: Uri,
name: String = getFilenameForUri(uri),
parent: TreeDocumentFile?,
override val uri: Uri,
name: String? = null,
mimeType: String? = null,
) : UniFile {
private var cachePresent = false
private val allChildren by lazy {
cachePresent = true
queryChildren()
}
private fun popCacheIfPresent(file: TreeDocumentFile) {
if (cachePresent) {
synchronized(allChildren) {
allChildren.add(file)
) : CachingFile<TreeDocumentFile>(parent) {
override fun list() = runCatching {
val childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(uri, DocumentsContract.getDocumentId(uri))
appCtx.contentResolver.query(childrenUri, projection, null, null, null)?.use { c ->
MutableList(c.count) {
c.moveToNext()
val documentId = c.getString(0)
val documentUri = DocumentsContract.buildDocumentUriUsingTree(uri, documentId)
val displayName = c.getString(1)
val mimeType = c.getString(2)
TreeDocumentFile(this, documentUri, displayName, mimeType)
}
}
}
}.getOrNull()
private fun evictCacheIfPresent(file: TreeDocumentFile) {
if (cachePresent) {
synchronized(allChildren) {
allChildren.remove(file)
}
private fun createFile(displayName: String, mimeType: String) =
DocumentsContractApi21.createFile(uri, mimeType, displayName)?.let { result ->
TreeDocumentFile(this, result, displayName, mimeType).also { popCacheIfPresent(it) }
}
}
override fun createFile(displayName: String): UniFile? {
val child = findFile(displayName)
if (child != null) {
if (child.isFile) return child
return if (child != null) {
child.takeIf { it.isFile }
} else {
val extension = displayName.substringAfterLast('.', "").ifEmpty { null }?.lowercase()
val mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension) ?: "application/octet-stream"
val result = DocumentsContractApi21.createFile(uri, mimeType, displayName)
if (result != null) {
val f = TreeDocumentFile(this, result, displayName, mimeType)
popCacheIfPresent(f)
return f
}
createFile(displayName, mimeType)
}
return null
}
override fun createDirectory(displayName: String): UniFile? {
val child = findFile(displayName)
if (child != null) {
if (child.isDirectory) return null
return if (child != null) {
child.takeIf { it.isDirectory }
} else {
val result = DocumentsContractApi21.createDirectory(uri, displayName)
if (result != null) {
val d = TreeDocumentFile(this, result, displayName, Document.MIME_TYPE_DIR)
popCacheIfPresent(d)
return d
}
createFile(displayName, Document.MIME_TYPE_DIR)
}
return null
}
private var mimeType = mimeType
get() {
if (field == null) {
field = DocumentsContractApi19.getRawType(uri)
}
return field
}
override val name by lazy {
name ?: DocumentsContractApi19.getName(uri)!!
}
private val mimeType by lazy {
mimeType ?: DocumentsContractApi19.getRawType(uri)
}
override var uri = uri
private set
override var name = name
private set
override val type: String?
get() = mimeType.takeUnless { isDirectory }
override val isDirectory: Boolean
@@ -108,61 +89,18 @@ class TreeDocumentFile(
override fun canWrite() = DocumentsContractApi19.canWrite(uri)
override fun ensureDir(): Boolean {
if (isDirectory) return true
if (isFile) return false
return if (parent != null && parent.ensureDir()) {
parent.createDirectory(name) != null
} else {
false
}
}
override fun ensureFile(): Boolean {
if (isFile) return true
if (isDirectory) return false
return if (parent != null && parent.ensureDir()) {
parent.createFile(name) != null
} else {
false
}
}
override fun resolve(displayName: String): UniFile {
val childUri = DocumentsContractApi21.buildChildUri(uri, displayName)
return TreeDocumentFile(this, childUri, displayName)
}
override fun delete() = DocumentsContractApi19.delete(uri).also {
if (it) parent?.evictCacheIfPresent(this)
}
override fun exists() = DocumentsContractApi19.exists(uri)
override fun listFiles() = synchronized(allChildren) {
allChildren.toList()
}
override fun findFirst(filter: (String) -> Boolean) = synchronized(allChildren) {
allChildren.firstOrNull { filter(it.name) }
}
override fun renameTo(displayName: String): Boolean {
val result = DocumentsContractApi21.renameTo(uri, displayName)
if (result != null) {
uri = result
name = displayName
mimeType = null
return true
override fun renameTo(displayName: String): UniFile? =
DocumentsContractApi21.renameTo(uri, displayName)?.let { result ->
TreeDocumentFile(parent, result, displayName).also {
parent?.replaceCacheIfPresent(this, it)
}
}
return false
}
}
// Technically the Uris should be treated as opaque, but it works for ExternalStorageProvider
private fun getFilenameForUri(uri: Uri): String {
val path = requireNotNull(uri.path)
return path.substringAfterLast('/')
}
private val projection = arrayOf(
@@ -170,18 +108,3 @@ private val projection = arrayOf(
Document.COLUMN_DISPLAY_NAME,
Document.COLUMN_MIME_TYPE,
)
private fun TreeDocumentFile.queryChildren(): MutableList<TreeDocumentFile> {
val self = uri
val childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(self, DocumentsContract.getDocumentId(self))
return appCtx.contentResolver.query(childrenUri, projection, null, null, null)?.use { c ->
MutableList(c.count) {
c.moveToNext()
val documentId = c.getString(0)
val documentUri = DocumentsContract.buildDocumentUriUsingTree(self, documentId)
val displayName = c.getString(1)
val mimeType = c.getString(2)
TreeDocumentFile(this, documentUri, displayName, mimeType)
}
} ?: mutableListOf()
}
@@ -152,31 +152,6 @@ sealed interface UniFile {
*/
fun canWrite(): Boolean
/**
* It works like mkdirs, but it will return true if the UniFile is directory
*
* @return `true` if the directory was created
* or if the directory already existed.
*/
fun ensureDir(): Boolean
/**
* Make sure the UniFile is file
*
* @return `true` if the file can be created
* or if the file already existed.
*/
fun ensureFile(): Boolean
/**
* Get child file of this directory, the child might not exist.
*
* @return the child file
*/
fun resolve(displayName: String): UniFile
operator fun div(name: String) = resolve(name)
/**
* Deletes this file.
*
@@ -207,7 +182,7 @@ sealed interface UniFile {
*
* @return the file if found it, or `null`.
*/
fun findFile(displayName: String) = resolve(displayName).takeIf { it.exists() }
fun findFile(displayName: String): UniFile?
/**
* Renames this file to `displayName`.
@@ -218,18 +193,18 @@ sealed interface UniFile {
*
*
* Some providers may need to create a new file to reflect the rename,
* potentially with a different MIME type, so [.getUri] and
* [.getType] may change to reflect the rename.
* potentially with a different MIME type, so [uri] and
* [type] may change to reflect the rename.
*
*
* When renaming a directory, children previously enumerated through
* [.listFiles] may no longer be valid.
* [listFiles] may no longer be valid.
*
* @param displayName the new display name.
* @return true on success.
* @return the new file after the rename, or `null` if failed.
* @see android.provider.DocumentsContract.renameDocument
*/
fun renameTo(displayName: String): Boolean
fun renameTo(displayName: String): UniFile?
companion object {
fun fromFile(file: File) = RawFile(null, file)
@@ -46,6 +46,3 @@ val Uri.displayPath: String?
}
return toString()
}
fun UniFile.ensureDirOrThrow() =
check(ensureDir()) { "Failed to create directory: ${uri.displayPath}" }
@@ -20,6 +20,7 @@ import android.Manifest
import android.app.assist.AssistContent
import android.content.ClipData
import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.graphics.ColorMatrix
import android.graphics.ColorMatrixColorFilter
@@ -80,6 +81,7 @@ import com.hippo.ehviewer.gallery.EhPageLoader
import com.hippo.ehviewer.gallery.PageLoader2
import com.hippo.ehviewer.ui.EhActivity
import com.hippo.ehviewer.ui.reader.SettingsPager
import com.hippo.ehviewer.ui.screen.implicit
import com.hippo.ehviewer.ui.setMD3Content
import com.hippo.ehviewer.ui.tools.DialogState
import com.hippo.ehviewer.util.AppConfig
@@ -376,12 +378,11 @@ class ReaderActivity : EhActivity() {
private suspend fun makeToast(@StringRes resId: Int) = makeToast(getString(resId))
private fun provideImage(index: Int): Uri? = AppConfig.externalTempDir?.let { dir ->
mGalleryProvider?.saveToDir(index, dir.asUniFile())?.name?.let {
FileProvider.getUriForFile(
this,
BuildConfig.APPLICATION_ID + ".fileprovider",
File(dir, it),
)
mGalleryProvider?.run {
getImageFilename(index)?.let { filename ->
val file = File(dir, filename).takeIf { save(index, it.asUniFile()) } ?: return null
FileProvider.getUriForFile(implicit<Context>(), BuildConfig.APPLICATION_ID + ".fileprovider", file)
}
}
}