Refactor archive support with libarchive (#949)

* Refactor archive support with libarchive

* Revert string resource changs

* Only mark archive formats as supported

Comic book archives should not be compressed.

* Fixup

* Remove epub from archive format list

* Move to mihon package

* Format

* Cleanup
This commit is contained in:
FooIbar
2024-06-26 22:54:25 +08:00
committed by GitHub
parent 36e40c0997
commit 239c38982c
22 changed files with 239 additions and 233 deletions

View File

@ -12,7 +12,6 @@ kotlin {
api(projects.i18n)
implementation(libs.unifile)
implementation(libs.bundles.archive)
}
}
val androidMain by getting {

View File

@ -17,13 +17,12 @@ import kotlinx.coroutines.awaitAll
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import logcat.LogPriority
import mihon.core.common.extensions.toZipFile
import mihon.core.common.archive.archiveReader
import nl.adaptivity.xmlutil.AndroidXmlReader
import nl.adaptivity.xmlutil.serialization.XML
import tachiyomi.core.common.i18n.stringResource
import tachiyomi.core.common.storage.extension
import tachiyomi.core.common.storage.nameWithoutExtension
import tachiyomi.core.common.storage.openReadOnlyChannel
import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.core.common.util.system.ImageUtil
import tachiyomi.core.common.util.system.logcat
@ -45,7 +44,6 @@ import uy.kohesive.injekt.injectLazy
import java.io.InputStream
import java.nio.charset.StandardCharsets
import kotlin.time.Duration.Companion.days
import com.github.junrar.Archive as JunrarArchive
import tachiyomi.domain.source.model.Source as DomainSource
actual class LocalSource(
@ -187,9 +185,7 @@ actual class LocalSource(
// Copy ComicInfo.xml from chapter archive to top level if found
noXmlFile == null -> {
val chapterArchives = mangaDirFiles
.filter(Archive::isSupported)
.toList()
val chapterArchives = mangaDirFiles.filter(Archive::isSupported)
val copiedFile = copyComicInfoFileFromArchive(chapterArchives, mangaDir)
if (copiedFile != null) {
@ -209,26 +205,10 @@ actual class LocalSource(
private fun copyComicInfoFileFromArchive(chapterArchives: List<UniFile>, folder: UniFile): UniFile? {
for (chapter in chapterArchives) {
when (Format.valueOf(chapter)) {
is Format.Zip -> {
chapter.openReadOnlyChannel(context).toZipFile().use { zip ->
zip.getEntry(COMIC_INFO_FILE)?.let { comicInfoFile ->
zip.getInputStream(comicInfoFile).buffered().use { stream ->
return copyComicInfoFile(stream, folder)
}
}
}
chapter.archiveReader(context).use { reader ->
reader.getInputStream(COMIC_INFO_FILE)?.use { stream ->
return copyComicInfoFile(stream, folder)
}
is Format.Rar -> {
JunrarArchive(chapter.openInputStream()).use { rar ->
rar.fileHeaders.firstOrNull { it.fileName == COMIC_INFO_FILE }?.let { comicInfoFile ->
rar.getInputStream(comicInfoFile).buffered().use { stream ->
return copyComicInfoFile(stream, folder)
}
}
}
}
else -> {}
}
}
return null
@ -254,7 +234,7 @@ actual class LocalSource(
override suspend fun getChapterList(manga: SManga): List<SChapter> = withIOContext {
val chapters = fileSystem.getFilesInMangaDirectory(manga.url)
// Only keep supported formats
.filter { it.isDirectory || Archive.isSupported(it) }
.filter { it.isDirectory || Archive.isSupported(it) || it.extension.equals("epub", true) }
.map { chapterFile ->
SChapter.create().apply {
url = "${manga.url}/${chapterFile.name}"
@ -270,7 +250,7 @@ actual class LocalSource(
val format = Format.valueOf(chapterFile)
if (format is Format.Epub) {
EpubFile(format.file.openReadOnlyChannel(context)).use { epub ->
EpubFile(format.file.archiveReader(context)).use { epub ->
epub.fillMetadata(manga, this)
}
}
@ -328,31 +308,22 @@ actual class LocalSource(
entry?.let { coverManager.update(manga, it.openInputStream()) }
}
is Format.Zip -> {
format.file.openReadOnlyChannel(context).toZipFile().use { zip ->
val entry = zip.entries.toList()
.sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) }
.find { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } }
is Format.Archive -> {
format.file.archiveReader(context).use { reader ->
val entry = reader.useEntries { entries ->
entries
.sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) }
.find { it.isFile && ImageUtil.isImage(it.name) { reader.getInputStream(it.name)!! } }
}
entry?.let { coverManager.update(manga, zip.getInputStream(it)) }
}
}
is Format.Rar -> {
JunrarArchive(format.file.openInputStream()).use { archive ->
val entry = archive.fileHeaders
.sortedWith { f1, f2 -> f1.fileName.compareToCaseInsensitiveNaturalOrder(f2.fileName) }
.find { !it.isDirectory && ImageUtil.isImage(it.fileName) { archive.getInputStream(it) } }
entry?.let { coverManager.update(manga, archive.getInputStream(it)) }
entry?.let { coverManager.update(manga, reader.getInputStream(it.name)!!) }
}
}
is Format.Epub -> {
EpubFile(format.file.openReadOnlyChannel(context)).use { epub ->
val entry = epub.getImagesFromPages()
.firstOrNull()
?.let { epub.getEntry(it) }
EpubFile(format.file.archiveReader(context)).use { epub ->
val entry = epub.getImagesFromPages().firstOrNull()
entry?.let { coverManager.update(manga, epub.getInputStream(it)) }
entry?.let { coverManager.update(manga, epub.getInputStream(it)!!) }
}
}
}

View File

@ -5,9 +5,9 @@ import tachiyomi.core.common.storage.extension
object Archive {
private val SUPPORTED_ARCHIVE_TYPES = listOf("zip", "cbz", "rar", "cbr", "epub")
private val SUPPORTED_ARCHIVE_TYPES = listOf("zip", "cbz", "rar", "cbr", "7z", "cb7", "tar", "cbt")
fun isSupported(file: UniFile): Boolean {
return file.extension in SUPPORTED_ARCHIVE_TYPES
return file.extension?.lowercase() in SUPPORTED_ARCHIVE_TYPES
}
}

View File

@ -2,25 +2,22 @@ package tachiyomi.source.local.io
import com.hippo.unifile.UniFile
import tachiyomi.core.common.storage.extension
import tachiyomi.source.local.io.Archive.isSupported as isArchiveSupported
sealed interface Format {
data class Directory(val file: UniFile) : Format
data class Zip(val file: UniFile) : Format
data class Rar(val file: UniFile) : Format
data class Archive(val file: UniFile) : Format
data class Epub(val file: UniFile) : Format
class UnknownFormatException : Exception()
companion object {
fun valueOf(file: UniFile) = with(file) {
when {
isDirectory -> Directory(this)
extension.equals("zip", true) || extension.equals("cbz", true) -> Zip(this)
extension.equals("rar", true) || extension.equals("cbr", true) -> Rar(this)
extension.equals("epub", true) -> Epub(this)
else -> throw UnknownFormatException()
}
fun valueOf(file: UniFile) = when {
file.isDirectory -> Directory(file)
file.extension.equals("epub", true) -> Epub(file)
isArchiveSupported(file) -> Archive(file)
else -> throw UnknownFormatException()
}
}
}