From ed18014430eed12bab694a9b8b951651281b4228 Mon Sep 17 00:00:00 2001 From: Abdallah Mehiz Date: Tue, 2 Jan 2024 19:47:47 +0100 Subject: [PATCH 1/3] 7zip local manga it crashes --- app/build.gradle.kts | 1 + .../ui/reader/loader/ChapterLoader.kt | 1 + .../ui/reader/loader/SevenZipPageLoader.kt | 42 +++++++++++++++++++ gradle/libs.versions.toml | 5 ++- source-local/build.gradle.kts | 1 + .../tachiyomi/source/local/LocalSource.kt | 10 +++++ .../tachiyomi/source/local/io/Archive.kt | 2 +- .../tachiyomi/source/local/io/Format.kt | 2 + 8 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/SevenZipPageLoader.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 96c7ac385..6722cb764 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -213,6 +213,7 @@ dependencies { implementation(libs.disklrucache) implementation(libs.unifile) implementation(libs.junrar) + implementation(libs.bundles.sevenzip) // Preferences implementation(libs.preferencektx) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt index 6a31ed029..7ffa76c3e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt @@ -90,6 +90,7 @@ class ChapterLoader( when (format) { is Format.Directory -> DirectoryPageLoader(format.file) is Format.Zip -> ZipPageLoader(format.file.toTempFile(context)) + is Format.SevenZip -> SevenZipPageLoader(format.file.toTempFile(context)) is Format.Rar -> try { RarPageLoader(format.file.toTempFile(context)) } catch (e: UnsupportedRarV5Exception) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/SevenZipPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/SevenZipPageLoader.kt new file mode 100644 index 000000000..50b24ad54 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/SevenZipPageLoader.kt @@ -0,0 +1,42 @@ +package eu.kanade.tachiyomi.ui.reader.loader + +import eu.kanade.tachiyomi.source.model.Page +import eu.kanade.tachiyomi.ui.reader.model.ReaderPage +import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder +import org.apache.commons.compress.archivers.sevenz.SevenZFile +import tachiyomi.core.util.system.ImageUtil +import java.io.File + +/** + * Loader used to load a chapter from a .7z or .cb7 file. + */ +internal class SevenZipPageLoader(file: File) : PageLoader() { + + private val zip = SevenZFile(file) + + override var isLocal: Boolean = true + + override suspend fun getPages(): List { + return zip.entries.asSequence() + .filter { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } } + .sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) } + .mapIndexed { i, entry -> + ReaderPage(i).apply { + stream = { + zip.getInputStream(entry) + } + status = Page.State.READY + } + } + .toList() + } + + override suspend fun loadPage(page: ReaderPage) { + check(!isRecycled) + } + + override fun recycle() { + super.recycle() + zip.close() + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b04b8cd4b..3cac5c160 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -32,6 +32,8 @@ jsoup = "org.jsoup:jsoup:1.17.2" disklrucache = "com.jakewharton:disklrucache:2.0.2" unifile = "com.github.tachiyomiorg:unifile:7c257e1c64" junrar = "com.github.junrar:junrar:7.5.5" +common-compress = "org.apache.commons:commons-compress:1.25.0" +xz = "org.tukaani:xz:1.9" sqlite-framework = { module = "androidx.sqlite:sqlite-framework", version.ref = "sqlite" } sqlite-ktx = { module = "androidx.sqlite:sqlite-ktx", version.ref = "sqlite" } @@ -108,4 +110,5 @@ shizuku = ["shizuku-api", "shizuku-provider"] sqldelight = ["sqldelight-android-driver", "sqldelight-coroutines", "sqldelight-android-paging"] voyager = ["voyager-navigator", "voyager-screenmodel", "voyager-tab-navigator", "voyager-transitions"] richtext = ["richtext-commonmark", "richtext-m3"] -test = ["junit", "kotest-assertions", "mockk"] \ No newline at end of file +test = ["junit", "kotest-assertions", "mockk"] +sevenzip = ["common-compress", "xz"] \ No newline at end of file diff --git a/source-local/build.gradle.kts b/source-local/build.gradle.kts index 98eb4d55a..21f3eafa1 100644 --- a/source-local/build.gradle.kts +++ b/source-local/build.gradle.kts @@ -13,6 +13,7 @@ kotlin { implementation(libs.unifile) implementation(libs.junrar) + implementation(libs.bundles.sevenzip) } } val androidMain by getting { diff --git a/source-local/src/androidMain/kotlin/tachiyomi/source/local/LocalSource.kt b/source-local/src/androidMain/kotlin/tachiyomi/source/local/LocalSource.kt index 6177e7746..bebcec808 100644 --- a/source-local/src/androidMain/kotlin/tachiyomi/source/local/LocalSource.kt +++ b/source-local/src/androidMain/kotlin/tachiyomi/source/local/LocalSource.kt @@ -18,6 +18,7 @@ import kotlinx.serialization.json.decodeFromStream import logcat.LogPriority import nl.adaptivity.xmlutil.AndroidXmlReader import nl.adaptivity.xmlutil.serialization.XML +import org.apache.commons.compress.archivers.sevenz.SevenZFile import tachiyomi.core.i18n.stringResource import tachiyomi.core.metadata.comicinfo.COMIC_INFO_FILE import tachiyomi.core.metadata.comicinfo.ComicInfo @@ -339,6 +340,15 @@ actual class LocalSource( entry?.let { coverManager.update(manga, zip.getInputStream(it)) } } } + is Format.SevenZip -> { + SevenZFile(format.file.toTempFile(context)).use { archive -> + val entry = archive.entries.toList() + .sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) } + .find { !it.isDirectory && ImageUtil.isImage(it.name) { archive.getInputStream(it) } } + + entry?.let { coverManager.update(manga, archive.getInputStream(it)) } + } + } is Format.Rar -> { JunrarArchive(format.file.toTempFile(context)).use { archive -> val entry = archive.fileHeaders diff --git a/source-local/src/commonMain/kotlin/tachiyomi/source/local/io/Archive.kt b/source-local/src/commonMain/kotlin/tachiyomi/source/local/io/Archive.kt index a8f5a0740..c83ddac9d 100644 --- a/source-local/src/commonMain/kotlin/tachiyomi/source/local/io/Archive.kt +++ b/source-local/src/commonMain/kotlin/tachiyomi/source/local/io/Archive.kt @@ -5,7 +5,7 @@ import tachiyomi.core.storage.extension object Archive { - private val SUPPORTED_ARCHIVE_TYPES = listOf("zip", "cbz", "rar", "cbr", "epub") + private val SUPPORTED_ARCHIVE_TYPES = listOf("zip", "cbz", "7z", "cb7", "rar", "cbr", "epub") fun isSupported(file: UniFile): Boolean { return file.extension in SUPPORTED_ARCHIVE_TYPES diff --git a/source-local/src/commonMain/kotlin/tachiyomi/source/local/io/Format.kt b/source-local/src/commonMain/kotlin/tachiyomi/source/local/io/Format.kt index 0f29ae8ab..57e71cafa 100644 --- a/source-local/src/commonMain/kotlin/tachiyomi/source/local/io/Format.kt +++ b/source-local/src/commonMain/kotlin/tachiyomi/source/local/io/Format.kt @@ -6,6 +6,7 @@ import tachiyomi.core.storage.extension sealed interface Format { data class Directory(val file: UniFile) : Format data class Zip(val file: UniFile) : Format + data class SevenZip(val file: UniFile) : Format data class Rar(val file: UniFile) : Format data class Epub(val file: UniFile) : Format @@ -17,6 +18,7 @@ sealed interface Format { when { isDirectory -> Directory(this) extension.equals("zip", true) || extension.equals("cbz", true) -> Zip(this) + extension.equals("7z", true) || extension.equals("cb7", true) -> SevenZip(this) extension.equals("rar", true) || extension.equals("cbr", true) -> Rar(this) extension.equals("epub", true) -> Epub(this) else -> throw UnknownFormatException() From 9e8b14d141b6003faba972a6463af6f0c24a74ef Mon Sep 17 00:00:00 2001 From: Claudemirovsky <63046606+Claudemirovsky@users.noreply.github.com> Date: Tue, 2 Jan 2024 21:24:51 -0300 Subject: [PATCH 2/3] fix: Fix crash when reading local 7zip manga (#1) --- .../ui/reader/loader/SevenZipPageLoader.kt | 16 +++++----------- core/build.gradle.kts | 1 + .../kanade/tachiyomi/util/storage/SevenZUtil.kt | 16 ++++++++++++++++ .../kotlin/tachiyomi/source/local/LocalSource.kt | 7 +++---- 4 files changed, 25 insertions(+), 15 deletions(-) create mode 100644 core/src/main/java/eu/kanade/tachiyomi/util/storage/SevenZUtil.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/SevenZipPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/SevenZipPageLoader.kt index 50b24ad54..cd69cb1f8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/SevenZipPageLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/SevenZipPageLoader.kt @@ -2,9 +2,8 @@ package eu.kanade.tachiyomi.ui.reader.loader import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.ui.reader.model.ReaderPage -import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder +import eu.kanade.tachiyomi.util.storage.SevenZUtil.getImages import org.apache.commons.compress.archivers.sevenz.SevenZFile -import tachiyomi.core.util.system.ImageUtil import java.io.File /** @@ -12,23 +11,18 @@ import java.io.File */ internal class SevenZipPageLoader(file: File) : PageLoader() { - private val zip = SevenZFile(file) + private val zip by lazy { SevenZFile(file) } override var isLocal: Boolean = true override suspend fun getPages(): List { - return zip.entries.asSequence() - .filter { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } } - .sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) } + return zip.getImages() .mapIndexed { i, entry -> ReaderPage(i).apply { - stream = { - zip.getInputStream(entry) - } + stream = { entry } status = Page.State.READY } - } - .toList() + }.toList() } override suspend fun loadPage(page: ReaderPage) { diff --git a/core/build.gradle.kts b/core/build.gradle.kts index e90a1fd06..6267cae2b 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -32,6 +32,7 @@ dependencies { implementation(libs.image.decoder) implementation(libs.unifile) + implementation(libs.bundles.sevenzip) api(kotlinx.coroutines.core) api(kotlinx.serialization.json) diff --git a/core/src/main/java/eu/kanade/tachiyomi/util/storage/SevenZUtil.kt b/core/src/main/java/eu/kanade/tachiyomi/util/storage/SevenZUtil.kt new file mode 100644 index 000000000..908d6219e --- /dev/null +++ b/core/src/main/java/eu/kanade/tachiyomi/util/storage/SevenZUtil.kt @@ -0,0 +1,16 @@ +package eu.kanade.tachiyomi.util.storage + +import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder +import org.apache.commons.compress.archivers.sevenz.SevenZFile +import tachiyomi.core.util.system.ImageUtil +import java.io.InputStream + +object SevenZUtil { + fun SevenZFile.getImages(): Sequence { + return generateSequence { runCatching { getNextEntry() }.getOrNull() } + .filter { !it.isDirectory && ImageUtil.isImage(it.name) { getInputStream(it) } } + .sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) } + .map(::getInputStream) + .map { it.use(InputStream::readBytes).inputStream() } // ByteArrayInputStream + } +} diff --git a/source-local/src/androidMain/kotlin/tachiyomi/source/local/LocalSource.kt b/source-local/src/androidMain/kotlin/tachiyomi/source/local/LocalSource.kt index bebcec808..18f3079dc 100644 --- a/source-local/src/androidMain/kotlin/tachiyomi/source/local/LocalSource.kt +++ b/source-local/src/androidMain/kotlin/tachiyomi/source/local/LocalSource.kt @@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder import eu.kanade.tachiyomi.util.storage.EpubFile +import eu.kanade.tachiyomi.util.storage.SevenZUtil.getImages import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.serialization.json.Json @@ -342,11 +343,9 @@ actual class LocalSource( } is Format.SevenZip -> { SevenZFile(format.file.toTempFile(context)).use { archive -> - val entry = archive.entries.toList() - .sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) } - .find { !it.isDirectory && ImageUtil.isImage(it.name) { archive.getInputStream(it) } } + val entry = archive.getImages().firstOrNull() - entry?.let { coverManager.update(manga, archive.getInputStream(it)) } + entry?.let { coverManager.update(manga, it) } } } is Format.Rar -> { From 329bf1039d3f1137e02961d6263a38db05f502ad Mon Sep 17 00:00:00 2001 From: Claudemirovsky <63046606+Claudemirovsky@users.noreply.github.com> Date: Wed, 3 Jan 2024 20:26:31 -0300 Subject: [PATCH 3/3] fix(local/7z): Fix loading previous pages (#2) --- .../kanade/tachiyomi/ui/reader/loader/SevenZipPageLoader.kt | 2 +- .../main/java/eu/kanade/tachiyomi/util/storage/SevenZUtil.kt | 4 ++-- .../androidMain/kotlin/tachiyomi/source/local/LocalSource.kt | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/SevenZipPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/SevenZipPageLoader.kt index cd69cb1f8..9e4ca829a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/SevenZipPageLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/SevenZipPageLoader.kt @@ -19,7 +19,7 @@ internal class SevenZipPageLoader(file: File) : PageLoader() { return zip.getImages() .mapIndexed { i, entry -> ReaderPage(i).apply { - stream = { entry } + stream = { entry.copyOf().inputStream() } status = Page.State.READY } }.toList() diff --git a/core/src/main/java/eu/kanade/tachiyomi/util/storage/SevenZUtil.kt b/core/src/main/java/eu/kanade/tachiyomi/util/storage/SevenZUtil.kt index 908d6219e..bc30d59a4 100644 --- a/core/src/main/java/eu/kanade/tachiyomi/util/storage/SevenZUtil.kt +++ b/core/src/main/java/eu/kanade/tachiyomi/util/storage/SevenZUtil.kt @@ -6,11 +6,11 @@ import tachiyomi.core.util.system.ImageUtil import java.io.InputStream object SevenZUtil { - fun SevenZFile.getImages(): Sequence { + fun SevenZFile.getImages(): Sequence { return generateSequence { runCatching { getNextEntry() }.getOrNull() } .filter { !it.isDirectory && ImageUtil.isImage(it.name) { getInputStream(it) } } .sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) } .map(::getInputStream) - .map { it.use(InputStream::readBytes).inputStream() } // ByteArrayInputStream + .map { it.use(InputStream::readBytes) } // ByteArray } } diff --git a/source-local/src/androidMain/kotlin/tachiyomi/source/local/LocalSource.kt b/source-local/src/androidMain/kotlin/tachiyomi/source/local/LocalSource.kt index 18f3079dc..842111221 100644 --- a/source-local/src/androidMain/kotlin/tachiyomi/source/local/LocalSource.kt +++ b/source-local/src/androidMain/kotlin/tachiyomi/source/local/LocalSource.kt @@ -345,7 +345,7 @@ actual class LocalSource( SevenZFile(format.file.toTempFile(context)).use { archive -> val entry = archive.getImages().firstOrNull() - entry?.let { coverManager.update(manga, it) } + entry?.let { coverManager.update(manga, it.inputStream()) } } } is Format.Rar -> {