From 169539f42fde82e4829401f5dc911d29fbc99e9b Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sun, 19 Jan 2025 15:18:07 +0200 Subject: [PATCH] Support closing Okio FileSystem --- .../local/data/input/LocalMangaParser.kt | 240 ++++++++++-------- 1 file changed, 132 insertions(+), 108 deletions(-) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/local/data/input/LocalMangaParser.kt b/app/src/main/kotlin/org/koitharu/kotatsu/local/data/input/LocalMangaParser.kt index f8f1e20b3..b28e5a52d 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/local/data/input/LocalMangaParser.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/local/data/input/LocalMangaParser.kt @@ -55,117 +55,119 @@ class LocalMangaParser(private val uri: Uri) { private val rootFile: File = File(uri.schemeSpecificPart) suspend fun getManga(withDetails: Boolean): LocalManga = runInterruptible(Dispatchers.IO) { - val (fileSystem, rootPath) = uri.resolveFsAndPath() - val index = MangaIndex.read(fileSystem, rootPath / ENTRY_NAME_INDEX) - val mangaInfo = index?.getMangaInfo() - if (mangaInfo != null) { - val coverEntry: Path? = index.getCoverEntry()?.let { rootPath / it } ?: fileSystem.findFirstImage(rootPath) - mangaInfo.copy( - source = LocalMangaSource, - url = rootFile.toUri().toString(), - coverUrl = coverEntry?.let { uri.child(it, resolve = true).toString() }.orEmpty(), - largeCoverUrl = null, - chapters = if (withDetails) { - mangaInfo.chapters?.mapNotNull { c -> - val path = index.getChapterFileName(c.id)?.toPath() - if (path != null && !fileSystem.exists(rootPath / path)) { - null - } else { - c.copy( - url = path?.let { - uri.child(it, resolve = false).toString() - } ?: uri.toString(), + (uri.resolveFsAndPath()).use { (fileSystem, rootPath) -> + val index = MangaIndex.read(fileSystem, rootPath / ENTRY_NAME_INDEX) + val mangaInfo = index?.getMangaInfo() + if (mangaInfo != null) { + val coverEntry: Path? = + index.getCoverEntry()?.let { rootPath / it } ?: fileSystem.findFirstImage(rootPath) + mangaInfo.copy( + source = LocalMangaSource, + url = rootFile.toUri().toString(), + coverUrl = coverEntry?.let { uri.child(it, resolve = true).toString() }, + largeCoverUrl = null, + chapters = if (withDetails) { + mangaInfo.chapters?.mapNotNull { c -> + val path = index.getChapterFileName(c.id)?.toPath() + if (path != null && !fileSystem.exists(rootPath / path)) { + null + } else { + c.copy( + url = path?.let { + uri.child(it, resolve = false).toString() + } ?: uri.toString(), + source = LocalMangaSource, + ) + } + } + } else { + null + }, + ) + } else { + val title = rootFile.name.fileNameToTitle() + val coverEntry = fileSystem.findFirstImage(rootPath) + Manga( + id = rootFile.absolutePath.longHashCode(), + title = title, + url = rootFile.toUri().toString(), + publicUrl = rootFile.toUri().toString(), + source = LocalMangaSource, + coverUrl = coverEntry?.let { uri.child(it, resolve = true).toString() }, + chapters = if (withDetails) { + val chapters = fileSystem.listRecursively(rootPath) + .mapNotNullTo(HashSet()) { path -> + when { + path == coverEntry -> null + !fileSystem.isRegularFile(path) -> null + path.isImage() -> path.parent + hasZipExtension(path.name) -> path + else -> null + } + }.sortedWith(compareBy(AlphanumComparator()) { x -> x.toString() }) + chapters.mapIndexed { i, p -> + val s = if (p.root == rootPath.root) { + p.relativeTo(rootPath).toString() + } else { + p + }.toString().removePrefix(Path.DIRECTORY_SEPARATOR) + MangaChapter( + id = "$i$s".longHashCode(), + name = s.fileNameToTitle().ifEmpty { title }, + number = 0f, + volume = 0, source = LocalMangaSource, + uploadDate = 0L, + url = uri.child(p.relativeTo(rootPath), resolve = false).toString(), + scanlator = null, + branch = null, ) } - } - } else { - null - }, - ) - } else { - val title = rootFile.name.fileNameToTitle() - val coverEntry = fileSystem.findFirstImage(rootPath) - Manga( - id = rootFile.absolutePath.longHashCode(), - title = title, - url = rootFile.toUri().toString(), - publicUrl = rootFile.toUri().toString(), - source = LocalMangaSource, - coverUrl = coverEntry?.let { - uri.child(it, resolve = true).toString() - }.orEmpty(), - chapters = if (withDetails) { - val chapters = fileSystem.listRecursively(rootPath) - .mapNotNullTo(HashSet()) { path -> - when { - path == coverEntry -> null - !fileSystem.isRegularFile(path) -> null - path.isImage() -> path.parent - hasZipExtension(path.name) -> path - else -> null - } - }.sortedWith(compareBy(AlphanumComparator()) { x -> x.toString() }) - chapters.mapIndexed { i, p -> - val s = if (p.root == rootPath.root) { - p.relativeTo(rootPath).toString() - } else { - p - }.toString().removePrefix(Path.DIRECTORY_SEPARATOR) - MangaChapter( - id = "$i$s".longHashCode(), - name = s.fileNameToTitle().ifEmpty { title }, - number = 0f, - volume = 0, - source = LocalMangaSource, - uploadDate = 0L, - url = uri.child(p.relativeTo(rootPath), resolve = false).toString(), - scanlator = null, - branch = null, - ) - } - } else { - null - }, - altTitle = null, - rating = -1f, - isNsfw = false, - tags = setOf(), - state = null, - author = null, - largeCoverUrl = null, - description = null, - ) - }.let { LocalManga(it, rootFile) } + } else { + null + }, + altTitle = null, + rating = -1f, + isNsfw = false, + tags = setOf(), + state = null, + author = null, + largeCoverUrl = null, + description = null, + ) + }.let { LocalManga(it, rootFile) } + } } suspend fun getMangaInfo(): Manga? = runInterruptible(Dispatchers.IO) { - val (fileSystem, rootPath) = uri.resolveFsAndPath() - val index = MangaIndex.read(fileSystem, rootPath / ENTRY_NAME_INDEX) - index?.getMangaInfo() + uri.resolveFsAndPath().use { (fileSystem, rootPath) -> + val index = MangaIndex.read(fileSystem, rootPath / ENTRY_NAME_INDEX) + index?.getMangaInfo() + } } suspend fun getPages(chapter: MangaChapter): List = runInterruptible(Dispatchers.IO) { val chapterUri = chapter.url.toUri().resolve() - val (fileSystem, rootPath) = chapterUri.resolveFsAndPath() - val index = MangaIndex.read(fileSystem, rootPath / ENTRY_NAME_INDEX) - val entries = fileSystem.listRecursively(rootPath) - .filter { fileSystem.isRegularFile(it) } - if (index != null) { - val pattern = index.getChapterNamesPattern(chapter) - entries.filter { x -> x.name.substringBefore('.').matches(pattern) } - } else { - entries.filter { x -> x.isImage() && x.parent == rootPath } - }.toListSorted(compareBy(AlphanumComparator()) { x -> x.toString() }) - .map { x -> - val entryUri = chapterUri.child(x, resolve = true).toString() - MangaPage( - id = entryUri.longHashCode(), - url = entryUri, - preview = null, - source = LocalMangaSource, - ) - } + chapterUri.resolveFsAndPath().use { (fileSystem, rootPath) -> + val index = MangaIndex.read(fileSystem, rootPath / ENTRY_NAME_INDEX) + val entries = fileSystem.listRecursively(rootPath) + .filter { fileSystem.isRegularFile(it) } + if (index != null) { + val pattern = index.getChapterNamesPattern(chapter) + entries.filter { x -> x.name.substringBefore('.').matches(pattern) } + } else { + entries.filter { x -> x.isImage() && x.parent == rootPath } + }.toListSorted(compareBy(AlphanumComparator()) { x -> x.toString() }) + .map { x -> + val entryUri = chapterUri.child(x, resolve = true).toString() + MangaPage( + id = entryUri.longHashCode(), + url = entryUri, + preview = null, + source = LocalMangaSource, + ) + } + } } private fun Uri.child(path: Path, resolve: Boolean): Uri { @@ -184,6 +186,23 @@ class LocalMangaParser(private val uri: Uri) { return builder.build() } + private class FsAndPath( + val fileSystem: FileSystem, + val path: Path, + private val isCloseable: Boolean, + ) : AutoCloseable { + + override fun close() { + if (isCloseable) { + fileSystem.close() + } + } + + operator fun component1() = fileSystem + + operator fun component2() = path + } + companion object { @Blocking @@ -240,20 +259,25 @@ class LocalMangaParser(private val uri: Uri) { } @Blocking - private fun Uri.resolveFsAndPath(): Pair { + private fun Uri.resolveFsAndPath(): FsAndPath { val resolved = resolve() return when { - resolved.isZipUri() -> { - FileSystem.SYSTEM.openZip(resolved.schemeSpecificPart.toPath()) to resolved.fragment.orEmpty() - .toRootedPath() - } + resolved.isZipUri() -> FsAndPath( + FileSystem.SYSTEM.openZip(resolved.schemeSpecificPart.toPath()), + resolved.fragment.orEmpty().toRootedPath(), + isCloseable = true, + ) isFileUri() -> { val file = toFile() if (file.isZipArchive) { - FileSystem.SYSTEM.openZip(schemeSpecificPart.toPath()) to fragment.orEmpty().toRootedPath() + FsAndPath( + FileSystem.SYSTEM.openZip(schemeSpecificPart.toPath()), + fragment.orEmpty().toRootedPath(), + isCloseable = true, + ) } else { - FileSystem.SYSTEM to file.toOkioPath() + FsAndPath(FileSystem.SYSTEM, file.toOkioPath(), isCloseable = false) } }