implement mihonapp/mihon#326 (#1104)
* implement mihonapp/mihon#326 Archives are now being read from channels Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com> Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> * disable parallelisms for loading into memory * switched to mutex * detekt changes * more detekt baseline changes --------- Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com> Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
This commit is contained in:
@@ -215,7 +215,7 @@ dependencies {
|
||||
// Disk
|
||||
implementation(libs.disklrucache)
|
||||
implementation(libs.unifile)
|
||||
implementation(libs.junrar)
|
||||
implementation(libs.bundles.archive)
|
||||
// SY -->
|
||||
implementation(libs.zip4j)
|
||||
// SY <--
|
||||
|
||||
Vendored
+3
@@ -122,6 +122,9 @@
|
||||
# XmlUtil
|
||||
-keep public enum nl.adaptivity.xmlutil.EventType { *; }
|
||||
|
||||
# Apache Commons Compress
|
||||
-keep class * extends org.apache.commons.compress.archivers.zip.ZipExtraField { <init>(); }
|
||||
|
||||
# Firebase
|
||||
-keep class com.google.firebase.installations.** { *; }
|
||||
-keep interface com.google.firebase.installations.** { *; }
|
||||
|
||||
+8
-4
@@ -570,10 +570,14 @@ object SettingsReaderScreen : SearchableSettings {
|
||||
.toMap()
|
||||
.toImmutableMap(),
|
||||
),
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
pref = readerPreferences.cacheArchiveMangaOnDisk(),
|
||||
title = stringResource(SYMR.strings.cache_archived_manga_to_disk),
|
||||
subtitle = stringResource(SYMR.strings.cache_archived_manga_to_disk_subtitle),
|
||||
Preference.PreferenceItem.ListPreference(
|
||||
pref = readerPreferences.archiveReaderMode(),
|
||||
title = stringResource(SYMR.strings.pref_archive_reader_mode),
|
||||
subtitle = stringResource(SYMR.strings.pref_archive_reader_mode_summary),
|
||||
entries = ReaderPreferences.archiveModeTypes
|
||||
.mapIndexed { index, it -> index to stringResource(it) }
|
||||
.toMap()
|
||||
.toImmutableMap(),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -13,7 +13,6 @@ import eu.kanade.tachiyomi.source.UnmeteredSource
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import eu.kanade.tachiyomi.util.storage.CbzCrypto
|
||||
import eu.kanade.tachiyomi.util.storage.CbzCrypto.addFilesToZip
|
||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||
import eu.kanade.tachiyomi.util.storage.DiskUtil.NOMEDIA_FILE
|
||||
import eu.kanade.tachiyomi.util.storage.saveTo
|
||||
@@ -46,6 +45,7 @@ import logcat.LogPriority
|
||||
import nl.adaptivity.xmlutil.serialization.XML
|
||||
import okhttp3.Response
|
||||
import tachiyomi.core.common.i18n.stringResource
|
||||
import tachiyomi.core.common.storage.addFilesToZip
|
||||
import tachiyomi.core.common.storage.extension
|
||||
import tachiyomi.core.common.util.lang.launchIO
|
||||
import tachiyomi.core.common.util.lang.launchNow
|
||||
@@ -572,10 +572,6 @@ class Downloader(
|
||||
tmpDir,
|
||||
imageFile,
|
||||
filenamePrefix,
|
||||
// SY -->
|
||||
zip4jFile = null,
|
||||
zip4jEntry = null,
|
||||
// SY <--
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR, e) { "Failed to split downloaded image" }
|
||||
|
||||
@@ -389,7 +389,6 @@ class ReaderViewModel @JvmOverloads constructor(
|
||||
context = context,
|
||||
downloadManager = downloadManager,
|
||||
downloadProvider = downloadProvider,
|
||||
tempFileManager = tempFileManager,
|
||||
manga = manga,
|
||||
source = source, /* SY --> */
|
||||
sourceManager = sourceManager,
|
||||
|
||||
@@ -10,7 +10,6 @@ import eu.kanade.tachiyomi.source.online.all.MergedSource
|
||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
||||
import tachiyomi.core.common.i18n.stringResource
|
||||
import tachiyomi.core.common.storage.UniFileTempFileManager
|
||||
import tachiyomi.core.common.util.lang.withIOContext
|
||||
import tachiyomi.core.common.util.system.logcat
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
@@ -28,7 +27,6 @@ class ChapterLoader(
|
||||
private val context: Context,
|
||||
private val downloadManager: DownloadManager,
|
||||
private val downloadProvider: DownloadProvider,
|
||||
private val tempFileManager: UniFileTempFileManager,
|
||||
private val manga: Manga,
|
||||
private val source: Source,
|
||||
// SY -->
|
||||
@@ -121,36 +119,39 @@ class ChapterLoader(
|
||||
source = source,
|
||||
downloadManager = downloadManager,
|
||||
downloadProvider = downloadProvider,
|
||||
tempFileManager = tempFileManager,
|
||||
)
|
||||
source is HttpSource -> HttpPageLoader(chapter, source)
|
||||
source is LocalSource -> source.getFormat(chapter.chapter).let { format ->
|
||||
when (format) {
|
||||
is Format.Directory -> DirectoryPageLoader(format.file)
|
||||
is Format.Zip -> ZipPageLoader(tempFileManager.createTempFile(format.file))
|
||||
is Format.Zip -> ZipPageLoader(format.file, context)
|
||||
is Format.Rar -> try {
|
||||
RarPageLoader(tempFileManager.createTempFile(format.file))
|
||||
RarPageLoader(format.file)
|
||||
} catch (e: UnsupportedRarV5Exception) {
|
||||
error(context.stringResource(MR.strings.loader_rar5_error))
|
||||
}
|
||||
is Format.Epub -> EpubPageLoader(tempFileManager.createTempFile(format.file))
|
||||
is Format.Epub -> EpubPageLoader(format.file, context)
|
||||
}
|
||||
}
|
||||
else -> error(context.stringResource(MR.strings.loader_not_implemented_error))
|
||||
}
|
||||
}
|
||||
// SY <--
|
||||
isDownloaded -> DownloadPageLoader(chapter, manga, source, downloadManager, downloadProvider, tempFileManager)
|
||||
isDownloaded -> DownloadPageLoader(chapter, manga, source, downloadManager, downloadProvider)
|
||||
source is LocalSource -> source.getFormat(chapter.chapter).let { format ->
|
||||
when (format) {
|
||||
is Format.Directory -> DirectoryPageLoader(format.file)
|
||||
is Format.Zip -> ZipPageLoader(tempFileManager.createTempFile(format.file))
|
||||
// SY -->
|
||||
is Format.Zip -> ZipPageLoader(format.file, context)
|
||||
is Format.Rar -> try {
|
||||
RarPageLoader(tempFileManager.createTempFile(format.file))
|
||||
RarPageLoader(format.file)
|
||||
// SY <--
|
||||
} catch (e: UnsupportedRarV5Exception) {
|
||||
error(context.stringResource(MR.strings.loader_rar5_error))
|
||||
}
|
||||
is Format.Epub -> EpubPageLoader(tempFileManager.createTempFile(format.file))
|
||||
// SY -->
|
||||
is Format.Epub -> EpubPageLoader(format.file, context)
|
||||
// SY <--
|
||||
}
|
||||
}
|
||||
source is HttpSource -> HttpPageLoader(chapter, source)
|
||||
|
||||
@@ -10,7 +10,6 @@ import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
|
||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
||||
import tachiyomi.core.common.storage.UniFileTempFileManager
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
@@ -23,7 +22,6 @@ internal class DownloadPageLoader(
|
||||
private val source: Source,
|
||||
private val downloadManager: DownloadManager,
|
||||
private val downloadProvider: DownloadProvider,
|
||||
private val tempFileManager: UniFileTempFileManager,
|
||||
) : PageLoader() {
|
||||
|
||||
private val context: Application by injectLazy()
|
||||
@@ -48,7 +46,9 @@ internal class DownloadPageLoader(
|
||||
}
|
||||
|
||||
private suspend fun getPagesFromArchive(file: UniFile): List<ReaderPage> {
|
||||
val loader = ZipPageLoader(tempFileManager.createTempFile(file)).also { zipPageLoader = it }
|
||||
// SY -->
|
||||
val loader = ZipPageLoader(file, context).also { zipPageLoader = it }
|
||||
// SY <--
|
||||
return loader.getPages()
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
package eu.kanade.tachiyomi.ui.reader.loader
|
||||
|
||||
import android.content.Context
|
||||
import com.hippo.unifile.UniFile
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
||||
import eu.kanade.tachiyomi.util.storage.EpubFile
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Loader used to load a chapter from a .epub file.
|
||||
*/
|
||||
internal class EpubPageLoader(file: File) : PageLoader() {
|
||||
// SY -->
|
||||
internal class EpubPageLoader(file: UniFile, context: Context) : PageLoader() {
|
||||
|
||||
private val epub = EpubFile(file)
|
||||
private val epub = EpubFile(file, context)
|
||||
// SY <--
|
||||
|
||||
override var isLocal: Boolean = true
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package eu.kanade.tachiyomi.ui.reader.loader
|
||||
|
||||
import android.app.Application
|
||||
import android.os.Build
|
||||
import com.github.junrar.Archive
|
||||
import com.github.junrar.rarfile.FileHeader
|
||||
import com.hippo.unifile.UniFile
|
||||
@@ -8,6 +9,14 @@ import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
||||
import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import tachiyomi.core.common.storage.UniFileTempFileManager
|
||||
import tachiyomi.core.common.util.system.ImageUtil
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.File
|
||||
@@ -19,11 +28,17 @@ import java.util.concurrent.Executors
|
||||
/**
|
||||
* Loader used to load a chapter from a .rar or .cbr file.
|
||||
*/
|
||||
internal class RarPageLoader(file: File) : PageLoader() {
|
||||
|
||||
private val rar = Archive(file)
|
||||
internal class RarPageLoader(file: UniFile) : PageLoader() {
|
||||
|
||||
// SY -->
|
||||
private val tempFileManager: UniFileTempFileManager by injectLazy()
|
||||
|
||||
private val rar = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
|
||||
Archive(tempFileManager.createTempFile(file))
|
||||
} else {
|
||||
Archive(file.openInputStream())
|
||||
}
|
||||
|
||||
private val context: Application by injectLazy()
|
||||
private val readerPreferences: ReaderPreferences by injectLazy()
|
||||
private val tmpDir = File(context.externalCacheDir, "reader_${file.hashCode()}").also {
|
||||
@@ -31,20 +46,21 @@ internal class RarPageLoader(file: File) : PageLoader() {
|
||||
}
|
||||
|
||||
init {
|
||||
if (readerPreferences.cacheArchiveMangaOnDisk().get()) {
|
||||
if (readerPreferences.archiveReaderMode().get() == ReaderPreferences.ArchiveReaderMode.CACHE_TO_DISK) {
|
||||
tmpDir.mkdirs()
|
||||
Archive(file).use { rar ->
|
||||
rar.fileHeaders.asSequence()
|
||||
.filterNot { it.isDirectory }
|
||||
.forEach { header ->
|
||||
val pageOutputStream = File(tmpDir, header.fileName.substringAfterLast("/"))
|
||||
.also { it.createNewFile() }
|
||||
.outputStream()
|
||||
getStream(header).use {
|
||||
it.copyTo(pageOutputStream)
|
||||
rar.fileHeaders.asSequence()
|
||||
.filter { !it.isDirectory && ImageUtil.isImage(it.fileName) { rar.getInputStream(it) } }
|
||||
.sortedWith { f1, f2 -> f1.fileName.compareToCaseInsensitiveNaturalOrder(f2.fileName) }
|
||||
.forEach { header ->
|
||||
File(tmpDir, header.fileName.substringAfterLast("/"))
|
||||
.also { it.createNewFile() }
|
||||
.outputStream()
|
||||
.use { output ->
|
||||
rar.getInputStream(header).use { input ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// SY <--
|
||||
@@ -58,16 +74,37 @@ internal class RarPageLoader(file: File) : PageLoader() {
|
||||
|
||||
override suspend fun getPages(): List<ReaderPage> {
|
||||
// SY -->
|
||||
if (readerPreferences.cacheArchiveMangaOnDisk().get()) {
|
||||
if (readerPreferences.archiveReaderMode().get() == ReaderPreferences.ArchiveReaderMode.CACHE_TO_DISK) {
|
||||
return DirectoryPageLoader(UniFile.fromFile(tmpDir)!!).getPages()
|
||||
}
|
||||
val mutex = Mutex()
|
||||
// SY <--
|
||||
return rar.fileHeaders.asSequence()
|
||||
.filter { !it.isDirectory && ImageUtil.isImage(it.fileName) { rar.getInputStream(it) } }
|
||||
.sortedWith { f1, f2 -> f1.fileName.compareToCaseInsensitiveNaturalOrder(f2.fileName) }
|
||||
.mapIndexed { i, header ->
|
||||
// SY -->
|
||||
val imageBytesDeferred: Deferred<ByteArray>? =
|
||||
when (readerPreferences.archiveReaderMode().get()) {
|
||||
ReaderPreferences.ArchiveReaderMode.LOAD_INTO_MEMORY -> {
|
||||
CoroutineScope(Dispatchers.IO).async {
|
||||
mutex.withLock {
|
||||
getStream(header).buffered().use { stream ->
|
||||
stream.readBytes()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else -> null
|
||||
}
|
||||
|
||||
val imageBytes by lazy { runBlocking { imageBytesDeferred?.await() } }
|
||||
// SY <--
|
||||
ReaderPage(i).apply {
|
||||
stream = { getStream(header) }
|
||||
// SY -->
|
||||
stream = { imageBytes?.copyOf()?.inputStream() ?: getStream(header) }
|
||||
// SY <--
|
||||
status = Page.State.READY
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package eu.kanade.tachiyomi.ui.reader.loader
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import com.hippo.unifile.UniFile
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
@@ -8,107 +8,155 @@ import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
||||
import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
|
||||
import eu.kanade.tachiyomi.util.storage.CbzCrypto
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import org.apache.commons.compress.archivers.zip.ZipFile
|
||||
import tachiyomi.core.common.i18n.stringResource
|
||||
import tachiyomi.core.common.storage.UniFileTempFileManager
|
||||
import tachiyomi.core.common.storage.isEncryptedZip
|
||||
import tachiyomi.core.common.storage.openReadOnlyChannel
|
||||
import tachiyomi.core.common.storage.testCbzPassword
|
||||
import tachiyomi.core.common.storage.unzip
|
||||
import tachiyomi.core.common.util.system.ImageUtil
|
||||
import tachiyomi.i18n.sy.SYMR
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.File
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.util.zip.ZipFile
|
||||
import java.nio.channels.SeekableByteChannel
|
||||
import net.lingala.zip4j.ZipFile as Zip4jFile
|
||||
|
||||
/**
|
||||
* Loader used to load a chapter from a .zip or .cbz file.
|
||||
*/
|
||||
internal class ZipPageLoader(file: File) : PageLoader() {
|
||||
internal class ZipPageLoader(file: UniFile, context: Context) : PageLoader() {
|
||||
|
||||
// SY -->
|
||||
private val context: Application by injectLazy()
|
||||
private val channel: SeekableByteChannel = file.openReadOnlyChannel(context)
|
||||
private val tempFileManager: UniFileTempFileManager by injectLazy()
|
||||
private val readerPreferences: ReaderPreferences by injectLazy()
|
||||
private val tmpDir = File(context.externalCacheDir, "reader_${file.hashCode()}").also {
|
||||
it.deleteRecursively()
|
||||
}
|
||||
private val zip4j: Zip4jFile = Zip4jFile(file)
|
||||
private val zip: ZipFile? =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
if (!zip4j.isEncrypted) ZipFile(file, StandardCharsets.ISO_8859_1) else null
|
||||
|
||||
private val apacheZip: ZipFile? = if (!file.isEncryptedZip() && Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {
|
||||
ZipFile(channel)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
private val tmpFile =
|
||||
if (
|
||||
apacheZip == null &&
|
||||
readerPreferences.archiveReaderMode().get() != ReaderPreferences.ArchiveReaderMode.CACHE_TO_DISK
|
||||
) {
|
||||
tempFileManager.createTempFile(file)
|
||||
} else {
|
||||
if (!zip4j.isEncrypted) ZipFile(file) else null
|
||||
null
|
||||
}
|
||||
|
||||
private val zip4j =
|
||||
if (apacheZip == null && tmpFile != null) {
|
||||
Zip4jFile(tmpFile)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
init {
|
||||
Zip4jFile(file).use { zip ->
|
||||
if (zip.isEncrypted) {
|
||||
if (!CbzCrypto.checkCbzPassword(zip, CbzCrypto.getDecryptedPasswordCbz())) {
|
||||
this.recycle()
|
||||
throw IllegalStateException(context.stringResource(SYMR.strings.wrong_cbz_archive_password))
|
||||
}
|
||||
zip4j.setPassword(CbzCrypto.getDecryptedPasswordCbz())
|
||||
if (readerPreferences.cacheArchiveMangaOnDisk().get()) {
|
||||
unzip()
|
||||
}
|
||||
} else {
|
||||
if (readerPreferences.cacheArchiveMangaOnDisk().get()) {
|
||||
unzip()
|
||||
}
|
||||
if (file.isEncryptedZip()) {
|
||||
if (!file.testCbzPassword()) {
|
||||
this.recycle()
|
||||
throw IllegalStateException(context.stringResource(SYMR.strings.wrong_cbz_archive_password))
|
||||
}
|
||||
zip4j?.setPassword(CbzCrypto.getDecryptedPasswordCbz())
|
||||
}
|
||||
if (readerPreferences.archiveReaderMode().get() == ReaderPreferences.ArchiveReaderMode.CACHE_TO_DISK) {
|
||||
file.unzip(tmpDir, onlyCopyImages = true)
|
||||
}
|
||||
}
|
||||
|
||||
// SY <--
|
||||
override fun recycle() {
|
||||
super.recycle()
|
||||
zip?.close()
|
||||
apacheZip?.close()
|
||||
// SY -->
|
||||
zip4j.close()
|
||||
zip4j?.close()
|
||||
tmpDir.deleteRecursively()
|
||||
}
|
||||
private fun unzip() {
|
||||
tmpDir.mkdirs()
|
||||
zip4j.fileHeaders.asSequence()
|
||||
.filter { !it.isDirectory && ImageUtil.isImage(it.fileName) { zip4j.getInputStream(it) } }
|
||||
.forEach { entry ->
|
||||
zip4j.extractFile(entry, tmpDir.absolutePath)
|
||||
}
|
||||
}
|
||||
|
||||
override var isLocal: Boolean = true
|
||||
|
||||
override suspend fun getPages(): List<ReaderPage> {
|
||||
if (readerPreferences.cacheArchiveMangaOnDisk().get()) {
|
||||
if (readerPreferences.archiveReaderMode().get() == ReaderPreferences.ArchiveReaderMode.CACHE_TO_DISK) {
|
||||
return DirectoryPageLoader(UniFile.fromFile(tmpDir)!!).getPages()
|
||||
}
|
||||
|
||||
if (zip == null) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
zip4j.charset = StandardCharsets.ISO_8859_1
|
||||
}
|
||||
|
||||
return zip4j.fileHeaders.asSequence()
|
||||
.filter { !it.isDirectory && ImageUtil.isImage(it.fileName) { zip4j.getInputStream(it) } }
|
||||
.sortedWith { f1, f2 -> f1.fileName.compareToCaseInsensitiveNaturalOrder(f2.fileName) }
|
||||
.mapIndexed { i, entry ->
|
||||
ReaderPage(i).apply {
|
||||
stream = { zip4j.getInputStream(entry) }
|
||||
status = Page.State.READY
|
||||
zip4jFile = zip4j
|
||||
zip4jEntry = entry
|
||||
}
|
||||
}.toList()
|
||||
return if (apacheZip == null) {
|
||||
loadZip4j()
|
||||
} else {
|
||||
// SY <--
|
||||
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()
|
||||
loadApacheZip(apacheZip)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadZip4j(): List<ReaderPage> {
|
||||
val mutex = Mutex()
|
||||
return zip4j!!.fileHeaders.asSequence()
|
||||
.filter { !it.isDirectory && ImageUtil.isImage(it.fileName) { zip4j.getInputStream(it) } }
|
||||
.sortedWith { f1, f2 -> f1.fileName.compareToCaseInsensitiveNaturalOrder(f2.fileName) }
|
||||
.mapIndexed { i, entry ->
|
||||
val imageBytesDeferred: Deferred<ByteArray>? =
|
||||
when (readerPreferences.archiveReaderMode().get()) {
|
||||
ReaderPreferences.ArchiveReaderMode.LOAD_INTO_MEMORY -> {
|
||||
CoroutineScope(Dispatchers.IO).async {
|
||||
mutex.withLock {
|
||||
zip4j.getInputStream(entry).buffered().use { stream ->
|
||||
stream.readBytes()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else -> null
|
||||
}
|
||||
val imageBytes by lazy { runBlocking { imageBytesDeferred?.await() } }
|
||||
ReaderPage(i).apply {
|
||||
stream = { imageBytes?.copyOf()?.inputStream() ?: zip4j.getInputStream(entry) }
|
||||
status = Page.State.READY
|
||||
}
|
||||
}.toList()
|
||||
}
|
||||
|
||||
private fun loadApacheZip(zip: ZipFile): List<ReaderPage> {
|
||||
val mutex = Mutex()
|
||||
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 ->
|
||||
val imageBytesDeferred: Deferred<ByteArray>? =
|
||||
when (readerPreferences.archiveReaderMode().get()) {
|
||||
ReaderPreferences.ArchiveReaderMode.LOAD_INTO_MEMORY -> {
|
||||
CoroutineScope(Dispatchers.IO).async {
|
||||
mutex.withLock {
|
||||
zip.getInputStream(entry).buffered().use { stream ->
|
||||
stream.readBytes()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else -> null
|
||||
}
|
||||
val imageBytes by lazy { runBlocking { imageBytesDeferred?.await() } }
|
||||
ReaderPage(i).apply {
|
||||
stream = { imageBytes?.copyOf()?.inputStream() ?: zip.getInputStream(entry) }
|
||||
status = Page.State.READY
|
||||
}
|
||||
}.toList()
|
||||
}
|
||||
// SY <--
|
||||
|
||||
/**
|
||||
* No additional action required to load the page
|
||||
*/
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package eu.kanade.tachiyomi.ui.reader.model
|
||||
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import net.lingala.zip4j.ZipFile
|
||||
import net.lingala.zip4j.model.FileHeader
|
||||
import java.io.InputStream
|
||||
|
||||
open class ReaderPage(
|
||||
@@ -10,9 +8,6 @@ open class ReaderPage(
|
||||
url: String = "",
|
||||
imageUrl: String? = null,
|
||||
// SY -->
|
||||
/** zip4j inputStreams do not support mark() and release(), so they must be passed to ImageUtil */
|
||||
var zip4jFile: ZipFile? = null,
|
||||
var zip4jEntry: FileHeader? = null,
|
||||
/** Value to check if this page is used to as if it was too wide */
|
||||
var shiftedPage: Boolean = false,
|
||||
/** Value to check if a page is can be doubled up, but can't because the next page is too wide */
|
||||
|
||||
@@ -180,9 +180,9 @@ class ReaderPreferences(
|
||||
|
||||
fun centerMarginType() = preferenceStore.getInt("center_margin_type", PagerConfig.CenterMarginType.NONE)
|
||||
|
||||
fun cacheArchiveMangaOnDisk() = preferenceStore.getBoolean("cache_archive_manga_on_disk", false)
|
||||
fun archiveReaderMode() = preferenceStore.getInt("archive_reader_mode", ArchiveReaderMode.LOAD_FROM_FILE)
|
||||
|
||||
fun markReadDupe() = preferenceStore.getBoolean("mark_read_dupe", false)
|
||||
fun markReadDupe() = preferenceStore.getBoolean("mark_read_dupe", false)
|
||||
// SY <--
|
||||
|
||||
enum class TappingInvertMode(
|
||||
@@ -203,6 +203,12 @@ class ReaderPreferences(
|
||||
LOWEST(47),
|
||||
}
|
||||
|
||||
object ArchiveReaderMode {
|
||||
const val LOAD_FROM_FILE = 0
|
||||
const val LOAD_INTO_MEMORY = 1
|
||||
const val CACHE_TO_DISK = 2
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val WEBTOON_PADDING_MIN = 0
|
||||
const val WEBTOON_PADDING_MAX = 25
|
||||
@@ -264,6 +270,12 @@ class ReaderPreferences(
|
||||
SYMR.strings.center_margin_wide_page,
|
||||
SYMR.strings.center_margin_double_and_wide_page,
|
||||
)
|
||||
|
||||
val archiveModeTypes = listOf(
|
||||
SYMR.strings.archive_mode_load_from_file,
|
||||
SYMR.strings.archive_mode_load_into_memory,
|
||||
SYMR.strings.archive_mode_cache_to_disk
|
||||
)
|
||||
// SY <--
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,13 +230,7 @@ class PagerPageHolder(
|
||||
return splitInHalf(imageStream)
|
||||
}
|
||||
|
||||
val isDoublePage = ImageUtil.isWideImage(
|
||||
imageStream,
|
||||
// SY -->
|
||||
page.zip4jFile,
|
||||
page.zip4jEntry,
|
||||
// SY <--
|
||||
)
|
||||
val isDoublePage = ImageUtil.isWideImage(imageStream)
|
||||
if (!isDoublePage) {
|
||||
return imageStream
|
||||
}
|
||||
@@ -247,13 +241,7 @@ class PagerPageHolder(
|
||||
}
|
||||
|
||||
private fun rotateDualPage(imageStream: BufferedInputStream): InputStream {
|
||||
val isDoublePage = ImageUtil.isWideImage(
|
||||
imageStream,
|
||||
// SY -->
|
||||
page.zip4jFile,
|
||||
page.zip4jEntry,
|
||||
// SY <--
|
||||
)
|
||||
val isDoublePage = ImageUtil.isWideImage(imageStream)
|
||||
return if (isDoublePage) {
|
||||
val rotation = if (viewer.config.dualPageRotateToFitInvert) -90f else 90f
|
||||
ImageUtil.rotateImage(imageStream, rotation)
|
||||
@@ -267,13 +255,7 @@ class PagerPageHolder(
|
||||
if (imageStream2 == null) {
|
||||
return if (imageStream is BufferedInputStream &&
|
||||
!ImageUtil.isAnimatedAndSupported(imageStream) &&
|
||||
ImageUtil.isWideImage(
|
||||
imageStream,
|
||||
// SY -->
|
||||
page.zip4jFile,
|
||||
page.zip4jEntry,
|
||||
// SY <--
|
||||
) &&
|
||||
ImageUtil.isWideImage(imageStream) &&
|
||||
viewer.config.centerMarginType and PagerConfig.CenterMarginType.WIDE_PAGE_CENTER_MARGIN > 0 &&
|
||||
!viewer.config.imageCropBorders
|
||||
) {
|
||||
|
||||
+2
-14
@@ -224,13 +224,7 @@ class WebtoonPageHolder(
|
||||
}
|
||||
|
||||
if (viewer.config.dualPageSplit) {
|
||||
val isDoublePage = ImageUtil.isWideImage(
|
||||
imageStream,
|
||||
// SY -->
|
||||
page?.zip4jFile,
|
||||
page?.zip4jEntry,
|
||||
// SY <--
|
||||
)
|
||||
val isDoublePage = ImageUtil.isWideImage(imageStream)
|
||||
if (isDoublePage) {
|
||||
val upperSide = if (viewer.config.dualPageInvert) ImageUtil.Side.LEFT else ImageUtil.Side.RIGHT
|
||||
return ImageUtil.splitAndMerge(imageStream, upperSide)
|
||||
@@ -241,13 +235,7 @@ class WebtoonPageHolder(
|
||||
}
|
||||
|
||||
private fun rotateDualPage(imageStream: BufferedInputStream): InputStream {
|
||||
val isDoublePage = ImageUtil.isWideImage(
|
||||
imageStream,
|
||||
// SY -->
|
||||
page?.zip4jFile,
|
||||
page?.zip4jEntry,
|
||||
// SY <--
|
||||
)
|
||||
val isDoublePage = ImageUtil.isWideImage(imageStream)
|
||||
return if (isDoublePage) {
|
||||
val rotation = if (viewer.config.dualPageRotateToFitInvert) -90f else 90f
|
||||
ImageUtil.rotateImage(imageStream, rotation)
|
||||
|
||||
@@ -656,6 +656,12 @@ object EXHMigrations {
|
||||
remove(Preference.appStateKey("trusted_signatures"))
|
||||
}
|
||||
}
|
||||
if (oldVersion under 66) {
|
||||
val cacheImagesToDisk = prefs.getBoolean("cache_archive_manga_on_disk", false)
|
||||
if (cacheImagesToDisk) {
|
||||
readerPreferences.archiveReaderMode().set(ReaderPreferences.ArchiveReaderMode.CACHE_TO_DISK)
|
||||
}
|
||||
}
|
||||
|
||||
if (oldVersion under 66) {
|
||||
if (prefs.getBoolean(Preference.privateKey("encrypt_database"), false)) {
|
||||
|
||||
@@ -187,7 +187,6 @@ class InterceptActivity : BaseActivity() {
|
||||
lifecycleScope.launchIO {
|
||||
loadGalleryEnd(gallery, sources[index])
|
||||
}
|
||||
|
||||
}
|
||||
.show()
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user