diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 622715259c..9ea8f6ccf9 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -203,7 +203,6 @@ dependencies { // Disk implementation(libs.disklrucache) implementation(libs.unifile) - implementation(libs.bundles.archive) // Preferences implementation(libs.preferencektx) diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index f4451efda0..b2971f1b7f 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -77,9 +77,6 @@ # XmlUtil -keep public enum nl.adaptivity.xmlutil.EventType { *; } -# Apache Commons Compress --keep class * extends org.apache.commons.compress.archivers.zip.ZipExtraField { (); } - # Firebase -keep class com.google.firebase.installations.** { *; } -keep interface com.google.firebase.installations.** { *; } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt index 11bfe28d13..24049a2b7f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt @@ -40,6 +40,7 @@ import kotlinx.coroutines.supervisorScope import logcat.LogPriority import nl.adaptivity.xmlutil.serialization.XML import okhttp3.Response +import tachiyomi.core.common.archive.ZipWriter import tachiyomi.core.common.i18n.stringResource import tachiyomi.core.common.storage.extension import tachiyomi.core.common.util.lang.launchIO @@ -58,12 +59,8 @@ import tachiyomi.domain.track.interactor.GetTracks import tachiyomi.i18n.MR import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get -import java.io.BufferedOutputStream import java.io.File import java.util.Locale -import java.util.zip.CRC32 -import java.util.zip.ZipEntry -import java.util.zip.ZipOutputStream /** * This class is the one in charge of downloading chapters. @@ -594,25 +591,9 @@ class Downloader( tmpDir: UniFile, ) { val zip = mangaDir.createFile("$dirname.cbz$TMP_DIR_SUFFIX")!! - ZipOutputStream(BufferedOutputStream(zip.openOutputStream())).use { zipOut -> - zipOut.setMethod(ZipEntry.STORED) - + ZipWriter(context, zip).use { writer -> tmpDir.listFiles()?.forEach { img -> - img.openInputStream().use { input -> - val data = input.readBytes() - val size = img.length() - val entry = ZipEntry(img.name).apply { - val crc = CRC32().apply { - update(data) - } - setCrc(crc.value) - - compressedSize = size - setSize(size) - } - zipOut.putNextEntry(entry) - zipOut.write(data) - } + writer.write(img) } } zip.renameTo("$dirname.cbz") diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ArchivePageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ArchivePageLoader.kt new file mode 100644 index 0000000000..2f76efa7cb --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ArchivePageLoader.kt @@ -0,0 +1,36 @@ +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 tachiyomi.core.archive.ArchiveReader +import tachiyomi.core.util.system.ImageUtil + +/** + * Loader used to load a chapter from an archive file. + */ +internal class ArchivePageLoader(private val reader: ArchiveReader) : PageLoader() { + override var isLocal: Boolean = true + + override suspend fun getPages(): List = + reader.useEntries { entries -> + entries + .filter { it.isFile && ImageUtil.isImage(it.name) { reader.getInputStream(it.name)!! } } + .sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) } + .mapIndexed { i, entry -> + ReaderPage(i).apply { + stream = { reader.getInputStream(entry.name)!! } + status = Page.State.READY + } + }.toList() + } + + override suspend fun loadPage(page: ReaderPage) { + check(!isRecycled) + } + + override fun recycle() { + super.recycle() + reader.close() + } +} 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 1cd18bcebe..0c6f9fbffb 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 @@ -1,14 +1,13 @@ package eu.kanade.tachiyomi.ui.reader.loader import android.content.Context -import com.github.junrar.exception.UnsupportedRarV5Exception import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadProvider import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter +import tachiyomi.core.common.archive.archiveReader import tachiyomi.core.common.i18n.stringResource -import tachiyomi.core.common.storage.openReadOnlyChannel import tachiyomi.core.common.util.lang.withIOContext import tachiyomi.core.common.util.system.logcat import tachiyomi.domain.manga.model.Manga @@ -95,13 +94,8 @@ class ChapterLoader( source is LocalSource -> source.getFormat(chapter.chapter).let { format -> when (format) { is Format.Directory -> DirectoryPageLoader(format.file) - is Format.Zip -> ZipPageLoader(format.file.openReadOnlyChannel(context)) - is Format.Rar -> try { - RarPageLoader(format.file.openInputStream()) - } catch (e: UnsupportedRarV5Exception) { - error(context.stringResource(MR.strings.loader_rar5_error)) - } - is Format.Epub -> EpubPageLoader(format.file.openReadOnlyChannel(context)) + is Format.Archive -> ArchivePageLoader(format.file.archiveReader(context)) + is Format.Epub -> EpubPageLoader(format.file.archiveReader(context)) } } source is HttpSource -> HttpPageLoader(chapter, source) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt index abef28540c..1463555220 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/DownloadPageLoader.kt @@ -10,7 +10,7 @@ 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.openReadOnlyChannel +import tachiyomi.core.common.archive.archiveReader import tachiyomi.domain.manga.model.Manga import uy.kohesive.injekt.injectLazy @@ -27,7 +27,7 @@ internal class DownloadPageLoader( private val context: Application by injectLazy() - private var zipPageLoader: ZipPageLoader? = null + private var archivePageLoader: ArchivePageLoader? = null override var isLocal: Boolean = true @@ -43,11 +43,11 @@ internal class DownloadPageLoader( override fun recycle() { super.recycle() - zipPageLoader?.recycle() + archivePageLoader?.recycle() } private suspend fun getPagesFromArchive(file: UniFile): List { - val loader = ZipPageLoader(file.openReadOnlyChannel(context)).also { zipPageLoader = it } + val loader = ArchivePageLoader(file.archiveReader(context)).also { archivePageLoader = it } return loader.getPages() } @@ -63,6 +63,6 @@ internal class DownloadPageLoader( } override suspend fun loadPage(page: ReaderPage) { - zipPageLoader?.loadPage(page) + archivePageLoader?.loadPage(page) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/EpubPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/EpubPageLoader.kt index baf65324ba..9984323165 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/EpubPageLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/EpubPageLoader.kt @@ -3,21 +3,21 @@ 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.storage.EpubFile -import java.nio.channels.SeekableByteChannel +import tachiyomi.core.archive.ArchiveReader /** * Loader used to load a chapter from a .epub file. */ -internal class EpubPageLoader(channel: SeekableByteChannel) : PageLoader() { +internal class EpubPageLoader(reader: ArchiveReader) : PageLoader() { - private val epub = EpubFile(channel) + private val epub = EpubFile(reader) override var isLocal: Boolean = true override suspend fun getPages(): List { return epub.getImagesFromPages() .mapIndexed { i, path -> - val streamFn = { epub.getInputStream(epub.getEntry(path)!!) } + val streamFn = { epub.getInputStream(path)!! } ReaderPage(i).apply { stream = streamFn status = Page.State.READY diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/RarPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/RarPageLoader.kt deleted file mode 100644 index b1db3300d8..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/RarPageLoader.kt +++ /dev/null @@ -1,67 +0,0 @@ -package eu.kanade.tachiyomi.ui.reader.loader - -import com.github.junrar.Archive -import com.github.junrar.rarfile.FileHeader -import eu.kanade.tachiyomi.source.model.Page -import eu.kanade.tachiyomi.ui.reader.model.ReaderPage -import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder -import tachiyomi.core.common.util.system.ImageUtil -import java.io.InputStream -import java.io.PipedInputStream -import java.io.PipedOutputStream -import java.util.concurrent.Executors - -/** - * Loader used to load a chapter from a .rar or .cbr file. - */ -internal class RarPageLoader(inputStream: InputStream) : PageLoader() { - - private val rar = Archive(inputStream) - - override var isLocal: Boolean = true - - /** - * Pool for copying compressed files to an input stream. - */ - private val pool = Executors.newFixedThreadPool(1) - - override suspend fun getPages(): List { - 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 -> - ReaderPage(i).apply { - stream = { getStream(header) } - status = Page.State.READY - } - } - .toList() - } - - override suspend fun loadPage(page: ReaderPage) { - check(!isRecycled) - } - - override fun recycle() { - super.recycle() - rar.close() - pool.shutdown() - } - - /** - * Returns an input stream for the given [header]. - */ - private fun getStream(header: FileHeader): InputStream { - val pipeIn = PipedInputStream() - val pipeOut = PipedOutputStream(pipeIn) - pool.execute { - try { - pipeOut.use { - rar.extractFile(header, it) - } - } catch (e: Exception) { - } - } - return pipeIn - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ZipPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ZipPageLoader.kt deleted file mode 100644 index 89856bf226..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ZipPageLoader.kt +++ /dev/null @@ -1,40 +0,0 @@ -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 mihon.core.common.extensions.toZipFile -import tachiyomi.core.common.util.system.ImageUtil -import java.nio.channels.SeekableByteChannel - -/** - * Loader used to load a chapter from a .zip or .cbz file. - */ -internal class ZipPageLoader(channel: SeekableByteChannel) : PageLoader() { - - private val zip = channel.toZipFile() - - 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/core/common/build.gradle.kts b/core/common/build.gradle.kts index e31015dc46..d00fec6822 100644 --- a/core/common/build.gradle.kts +++ b/core/common/build.gradle.kts @@ -32,7 +32,7 @@ dependencies { implementation(libs.image.decoder) implementation(libs.unifile) - implementation(libs.bundles.archive) + implementation(libs.libarchive) api(kotlinx.coroutines.core) api(kotlinx.serialization.json) diff --git a/core/common/src/main/kotlin/eu/kanade/tachiyomi/util/storage/EpubFile.kt b/core/common/src/main/kotlin/eu/kanade/tachiyomi/util/storage/EpubFile.kt index 29cea58248..f5121e7c5d 100644 --- a/core/common/src/main/kotlin/eu/kanade/tachiyomi/util/storage/EpubFile.kt +++ b/core/common/src/main/kotlin/eu/kanade/tachiyomi/util/storage/EpubFile.kt @@ -1,48 +1,27 @@ package eu.kanade.tachiyomi.util.storage -import mihon.core.common.extensions.toZipFile -import org.apache.commons.compress.archivers.zip.ZipArchiveEntry import org.jsoup.Jsoup import org.jsoup.nodes.Document +import tachiyomi.core.archive.ArchiveReader import java.io.Closeable import java.io.File import java.io.InputStream -import java.nio.channels.SeekableByteChannel /** * Wrapper over ZipFile to load files in epub format. */ -class EpubFile(channel: SeekableByteChannel) : Closeable { - - /** - * Zip file of this epub. - */ - private val zip = channel.toZipFile() +class EpubFile(private val reader: ArchiveReader) : Closeable by reader { /** * Path separator used by this epub. */ private val pathSeparator = getPathSeparator() - /** - * Closes the underlying zip file. - */ - override fun close() { - zip.close() - } - /** * Returns an input stream for reading the contents of the specified zip file entry. */ - fun getInputStream(entry: ZipArchiveEntry): InputStream { - return zip.getInputStream(entry) - } - - /** - * Returns the zip file entry for the specified name, or null if not found. - */ - fun getEntry(name: String): ZipArchiveEntry? { - return zip.getEntry(name) + fun getInputStream(entryName: String): InputStream? { + return reader.getInputStream(entryName) } /** @@ -59,9 +38,9 @@ class EpubFile(channel: SeekableByteChannel) : Closeable { * Returns the path to the package document. */ fun getPackageHref(): String { - val meta = zip.getEntry(resolveZipPath("META-INF", "container.xml")) + val meta = getInputStream(resolveZipPath("META-INF", "container.xml")) if (meta != null) { - val metaDoc = zip.getInputStream(meta).use { Jsoup.parse(it, null, "") } + val metaDoc = meta.use { Jsoup.parse(it, null, "") } val path = metaDoc.getElementsByTag("rootfile").first()?.attr("full-path") if (path != null) { return path @@ -74,8 +53,7 @@ class EpubFile(channel: SeekableByteChannel) : Closeable { * Returns the package document where all the files are listed. */ fun getPackageDocument(ref: String): Document { - val entry = zip.getEntry(ref) - return zip.getInputStream(entry).use { Jsoup.parse(it, null, "") } + return getInputStream(ref)!!.use { Jsoup.parse(it, null, "") } } /** @@ -98,8 +76,7 @@ class EpubFile(channel: SeekableByteChannel) : Closeable { val basePath = getParentDirectory(packageHref) pages.forEach { page -> val entryPath = resolveZipPath(basePath, page) - val entry = zip.getEntry(entryPath) - val document = zip.getInputStream(entry).use { Jsoup.parse(it, null, "") } + val document = getInputStream(entryPath)!!.use { Jsoup.parse(it, null, "") } val imageBasePath = getParentDirectory(entryPath) document.allElements.forEach { @@ -117,8 +94,9 @@ class EpubFile(channel: SeekableByteChannel) : Closeable { * Returns the path separator used by the epub file. */ private fun getPathSeparator(): String { - val meta = zip.getEntry("META-INF\\container.xml") + val meta = getInputStream("META-INF\\container.xml") return if (meta != null) { + meta.close() "\\" } else { "/" diff --git a/core/common/src/main/kotlin/mihon/core/common/extensions/SeekableByteChannel.kt b/core/common/src/main/kotlin/mihon/core/common/extensions/SeekableByteChannel.kt deleted file mode 100644 index 69e2d7201f..0000000000 --- a/core/common/src/main/kotlin/mihon/core/common/extensions/SeekableByteChannel.kt +++ /dev/null @@ -1,8 +0,0 @@ -package mihon.core.common.extensions - -import org.apache.commons.compress.archivers.zip.ZipFile -import java.nio.channels.SeekableByteChannel - -fun SeekableByteChannel.toZipFile(): ZipFile { - return ZipFile.Builder().setSeekableByteChannel(this).get() -} diff --git a/core/common/src/main/kotlin/tachiyomi/core/common/archive/Archive.kt b/core/common/src/main/kotlin/tachiyomi/core/common/archive/Archive.kt new file mode 100644 index 0000000000..623cff0569 --- /dev/null +++ b/core/common/src/main/kotlin/tachiyomi/core/common/archive/Archive.kt @@ -0,0 +1,55 @@ +package tachiyomi.core.archive + +import me.zhanghai.android.libarchive.Archive +import me.zhanghai.android.libarchive.ArchiveEntry +import java.io.InputStream +import java.nio.ByteBuffer + +class Archive(buffer: Long, size: Long) : InputStream() { + private val archive = Archive.readNew() + + init { + try { + Archive.setCharset(archive, Charsets.UTF_8.name().toByteArray()) + Archive.readSupportFilterAll(archive) + Archive.readSupportFormatAll(archive) + Archive.readOpenMemoryUnsafe(archive, buffer, size) + } catch (e: Throwable) { + close() + throw e + } + } + + private val oneByteBuffer = ByteBuffer.allocateDirect(1) + + override fun read(): Int { + read(oneByteBuffer) + return if (oneByteBuffer.hasRemaining()) oneByteBuffer.get().toUByte().toInt() else -1 + } + + override fun read(b: ByteArray, off: Int, len: Int): Int { + val buffer = ByteBuffer.wrap(b, off, len) + read(buffer) + return if (buffer.hasRemaining()) buffer.remaining() else -1 + } + + private fun read(buffer: ByteBuffer) { + buffer.clear() + Archive.readData(archive, buffer) + buffer.flip() + } + + override fun close() { + Archive.readFree(archive) + } + + fun readEntry(): Entry? { + val entry = Archive.readNextHeader(archive) + if (entry == 0L) return null + val name = ArchiveEntry.pathnameUtf8(entry) ?: ArchiveEntry.pathname(entry)?.decodeToString() ?: return null + val isFile = ArchiveEntry.filetype(entry) == ArchiveEntry.AE_IFREG + return Entry(name, isFile) + } + + class Entry(val name: String, val isFile: Boolean) +} diff --git a/core/common/src/main/kotlin/tachiyomi/core/common/archive/ArchiveReader.kt b/core/common/src/main/kotlin/tachiyomi/core/common/archive/ArchiveReader.kt new file mode 100644 index 0000000000..f1caad4dd6 --- /dev/null +++ b/core/common/src/main/kotlin/tachiyomi/core/common/archive/ArchiveReader.kt @@ -0,0 +1,41 @@ +package tachiyomi.core.archive + +import android.content.Context +import android.os.ParcelFileDescriptor +import android.system.Os +import android.system.OsConstants +import com.hippo.unifile.UniFile +import tachiyomi.core.common.storage.openFileDescriptor +import java.io.Closeable +import java.io.InputStream + +class ArchiveReader(pfd: ParcelFileDescriptor) : Closeable { + val size = pfd.statSize + val address = Os.mmap(0, size, OsConstants.PROT_READ, OsConstants.MAP_PRIVATE, pfd.fileDescriptor, 0) + + inline fun useEntries(block: (Sequence) -> T): T = + Archive(address, size).use { block(generateSequence { it.readEntry() }) } + + fun getInputStream(entryName: String): InputStream? { + val archive = Archive(address, size) + try { + while (true) { + val entry = archive.readEntry() ?: break + if (entry.name == entryName) { + return archive + } + } + } catch (e: Throwable) { + archive.close() + throw e + } + archive.close() + return null + } + + override fun close() { + Os.munmap(address, size) + } +} + +fun UniFile.archiveReader(context: Context) = openFileDescriptor(context, "r").use { ArchiveReader(it) } diff --git a/core/common/src/main/kotlin/tachiyomi/core/common/archive/ZipWriter.kt b/core/common/src/main/kotlin/tachiyomi/core/common/archive/ZipWriter.kt new file mode 100644 index 0000000000..90745b5d00 --- /dev/null +++ b/core/common/src/main/kotlin/tachiyomi/core/common/archive/ZipWriter.kt @@ -0,0 +1,73 @@ +package tachiyomi.core.archive + +import android.content.Context +import android.system.Os +import android.system.StructStat +import com.hippo.unifile.UniFile +import me.zhanghai.android.libarchive.Archive +import me.zhanghai.android.libarchive.ArchiveEntry +import tachiyomi.core.common.storage.openFileDescriptor +import java.io.Closeable +import java.nio.ByteBuffer + +class ZipWriter(val context: Context, file: UniFile) : Closeable { + private val pfd = file.openFileDescriptor(context, "wt") + private val archive = Archive.writeNew() + private val entry = ArchiveEntry.new2(archive) + private val buffer = ByteBuffer.allocateDirect(8192) + + init { + try { + Archive.setCharset(archive, Charsets.UTF_8.name().toByteArray()) + Archive.writeSetFormatZip(archive) + Archive.writeZipSetCompressionStore(archive) + Archive.writeOpenFd(archive, pfd.fd) + } catch (e: Throwable) { + close() + throw e + } + } + + fun write(file: UniFile) { + file.openFileDescriptor(context, "r").use { + val fd = it.fileDescriptor + ArchiveEntry.clear(entry) + ArchiveEntry.setPathnameUtf8(entry, file.name) + val stat = Os.fstat(fd) + ArchiveEntry.setStat(entry, stat.toArchiveStat()) + Archive.writeHeader(archive, entry) + while (true) { + buffer.clear() + Os.read(fd, buffer) + if (buffer.position() == 0) break + buffer.flip() + Archive.writeData(archive, buffer) + } + Archive.writeFinishEntry(archive) + } + } + + override fun close() { + ArchiveEntry.free(entry) + Archive.writeFree(archive) + pfd.close() + } +} + +private fun StructStat.toArchiveStat() = ArchiveEntry.StructStat().apply { + stDev = st_dev + stMode = st_mode + stNlink = st_nlink.toInt() + stUid = st_uid + stGid = st_gid + stRdev = st_rdev + stSize = st_size + stBlksize = st_blksize + stBlocks = st_blocks + stAtim = timespec(st_atime) + stMtim = timespec(st_mtime) + stCtim = timespec(st_ctime) + stIno = st_ino +} + +private fun timespec(tvSec: Long) = ArchiveEntry.StructTimespec().also { it.tvSec = tvSec } diff --git a/core/common/src/main/kotlin/tachiyomi/core/common/storage/UniFileExtensions.kt b/core/common/src/main/kotlin/tachiyomi/core/common/storage/UniFileExtensions.kt index 257fe210d9..4b04ff4056 100644 --- a/core/common/src/main/kotlin/tachiyomi/core/common/storage/UniFileExtensions.kt +++ b/core/common/src/main/kotlin/tachiyomi/core/common/storage/UniFileExtensions.kt @@ -3,7 +3,6 @@ package tachiyomi.core.common.storage import android.content.Context import android.os.ParcelFileDescriptor import com.hippo.unifile.UniFile -import java.nio.channels.FileChannel val UniFile.extension: String? get() = name?.substringAfterLast('.') @@ -14,6 +13,5 @@ val UniFile.nameWithoutExtension: String? val UniFile.displayablePath: String get() = filePath ?: uri.toString() -fun UniFile.openReadOnlyChannel(context: Context): FileChannel { - return ParcelFileDescriptor.AutoCloseInputStream(context.contentResolver.openFileDescriptor(uri, "r")).channel -} +fun UniFile.openFileDescriptor(context: Context, mode: String): ParcelFileDescriptor = + context.contentResolver.openFileDescriptor(uri, mode) ?: error("Failed to open file descriptor: $displayablePath") diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3942f468f9..36bf039001 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -32,8 +32,7 @@ jsoup = "org.jsoup:jsoup:1.17.2" disklrucache = "com.jakewharton:disklrucache:2.0.2" unifile = "com.github.tachiyomiorg:unifile:e0def6b3dc" -common-compress = "org.apache.commons:commons-compress:1.26.2" -junrar = "com.github.junrar:junrar:7.5.5" +libarchive = "me.zhanghai.android.libarchive:library:1.1.0" sqlite-framework = { module = "androidx.sqlite:sqlite-framework", version.ref = "sqlite" } sqlite-ktx = { module = "androidx.sqlite:sqlite-ktx", version.ref = "sqlite" } @@ -104,7 +103,6 @@ detekt-rules-formatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatt detekt-rules-compose = { module = "io.nlopez.compose.rules:detekt", version.ref = "detektCompose" } [bundles] -archive = ["common-compress", "junrar"] okhttp = ["okhttp-core", "okhttp-logging", "okhttp-brotli", "okhttp-dnsoverhttps"] js-engine = ["quickjs-android"] sqlite = ["sqlite-framework", "sqlite-ktx", "sqlite-android"] diff --git a/i18n/src/commonMain/moko-resources/ar/strings.xml b/i18n/src/commonMain/moko-resources/ar/strings.xml index 7ebedaeee7..d971d61774 100644 --- a/i18n/src/commonMain/moko-resources/ar/strings.xml +++ b/i18n/src/commonMain/moko-resources/ar/strings.xml @@ -612,7 +612,6 @@ لا تتاح الأداة حال تمكين قفل التطبيق اطَّلع على ما حُدِّث مؤخَّرًا في مكتبتك حذف كل شيء - تنسيق RARv5 ليس مدعومًا موجة مد و جزر متعدد هنالك تحديث جارٍ بالفعل diff --git a/i18n/src/commonMain/moko-resources/base/strings.xml b/i18n/src/commonMain/moko-resources/base/strings.xml index b0a397b1d0..afbdf686c6 100644 --- a/i18n/src/commonMain/moko-resources/base/strings.xml +++ b/i18n/src/commonMain/moko-resources/base/strings.xml @@ -781,7 +781,6 @@ Failed to load pages: %1$s No pages found Source not found - RARv5 format is not supported Updating library diff --git a/i18n/src/commonMain/moko-resources/bg/strings.xml b/i18n/src/commonMain/moko-resources/bg/strings.xml index 817a229ef9..7b7def8fe8 100644 --- a/i18n/src/commonMain/moko-resources/bg/strings.xml +++ b/i18n/src/commonMain/moko-resources/bg/strings.xml @@ -626,7 +626,6 @@ Искате да премахнете \"%s\" от библиотеката си Затвори Всички настройки на четеца нулирани - Форматът RARv5 не се поддържа Тема, формат на датата и времето Категории, глобални обновления Режим на четене, показване, навигация diff --git a/i18n/src/commonMain/moko-resources/bn/strings.xml b/i18n/src/commonMain/moko-resources/bn/strings.xml index 31bb98cec4..d14fd3ff40 100644 --- a/i18n/src/commonMain/moko-resources/bn/strings.xml +++ b/i18n/src/commonMain/moko-resources/bn/strings.xml @@ -589,7 +589,6 @@ পঠনের ধরন, ডিসপ্লে,নেভিগেশন ম্যানুয়াল ও সয়ংক্রিয় ব্যাকআপ অ্যাপ লক,নিরাপদ পর্দা - RARv5 ধরন সমর্থিত নয় অপঠিত অধ্যায় থাকায় এড়িয়ে যাওয়া হয়েছে স্থানীয় ক্লিপবোর্ডে কপি করুন diff --git a/i18n/src/commonMain/moko-resources/ca/strings.xml b/i18n/src/commonMain/moko-resources/ca/strings.xml index d54f800aae..53b26bc6ff 100644 --- a/i18n/src/commonMain/moko-resources/ca/strings.xml +++ b/i18n/src/commonMain/moko-resources/ca/strings.xml @@ -610,7 +610,6 @@ Cadena d’agent d’usuari per defecte Restableix la cadena d’agent d’usuari per defecte El widget no està disponible quan hi ha activat el blocatge de l’aplicació - El format RARv5 no està suportat Vegeu els elements de la biblioteca actualitzats recentment Elimina-ho tot Ja s’està executant una actualització diff --git a/i18n/src/commonMain/moko-resources/cs/strings.xml b/i18n/src/commonMain/moko-resources/cs/strings.xml index e0bf3524af..505c4df777 100644 --- a/i18n/src/commonMain/moko-resources/cs/strings.xml +++ b/i18n/src/commonMain/moko-resources/cs/strings.xml @@ -610,7 +610,6 @@ Obnovit výchozí řetězec pro user agent Výchozí řetězec pro user agent Odstranit vše - Formát RARv5 není podporován Widget není k dispozici, když je povolen zámek aplikace Podívejte se na své nedávno aktualizované záznamy v knihovně Aktualizace již probíhá diff --git a/i18n/src/commonMain/moko-resources/de/strings.xml b/i18n/src/commonMain/moko-resources/de/strings.xml index 25893556d3..63c9b566fe 100644 --- a/i18n/src/commonMain/moko-resources/de/strings.xml +++ b/i18n/src/commonMain/moko-resources/de/strings.xml @@ -610,7 +610,6 @@ Standard-User-Agent-Text Standard-User-Agent-Text zurücksetzen Alles entfernen - Das RARv5-Format wird nicht unterstützt Deine kürzlich aktualisierten Bibliothekseinträge ansehen Widget ist nicht verfügbar, wenn die App-Sperre aktiviert ist Eine Aktualisierung ist bereits im Gange diff --git a/i18n/src/commonMain/moko-resources/el/strings.xml b/i18n/src/commonMain/moko-resources/el/strings.xml index 54691d40c0..2d1160b723 100644 --- a/i18n/src/commonMain/moko-resources/el/strings.xml +++ b/i18n/src/commonMain/moko-resources/el/strings.xml @@ -610,7 +610,6 @@ Προεπιλεγμένη συμβολοσειρά πράκτορα χρήστη Επαναφορά προεπιλεγμένης συμβολοσειράς πράκτορα χρήστη Καταργήστε τα πάντα - Η μορφή RARv5 δεν υποστηρίζεται Δείτε τις πρόσφατα ενημερωμένες καταχωρήσεις της βιβλιοθήκης σας Το widget δεν είναι διαθέσιμο όταν είναι ενεργοποιημένο το κλείδωμα εφαρμογών Εκτελείται ήδη μια ενημέρωση diff --git a/i18n/src/commonMain/moko-resources/es/strings.xml b/i18n/src/commonMain/moko-resources/es/strings.xml index 4e6501b9bb..b98f2e254c 100644 --- a/i18n/src/commonMain/moko-resources/es/strings.xml +++ b/i18n/src/commonMain/moko-resources/es/strings.xml @@ -610,7 +610,6 @@ Identificarse como otro navegador web («user agent») Volver a la identificación de navegador («user agent») original Quitar todo - La aplicación no es capaz de leer el formato RARv5 Aquí aparecerá el contenido más reciente de tu biblioteca El widget no está disponible cuando el bloqueo de aplicación está activo Ya se está actualizando diff --git a/i18n/src/commonMain/moko-resources/fa/strings.xml b/i18n/src/commonMain/moko-resources/fa/strings.xml index 6137d857d1..0441e04e8f 100644 --- a/i18n/src/commonMain/moko-resources/fa/strings.xml +++ b/i18n/src/commonMain/moko-resources/fa/strings.xml @@ -586,7 +586,6 @@ شما باید از پشتیبانی ها در جا های دیگر هم کپی داشته باشید. بروزرسانی دسته بندی کپی کردن به کلیپ‌برد - فرمت RARv5 پشتیبانی نشده متوقف شده باز گشایی %s پاک کردن قسمت های دانلود شده diff --git a/i18n/src/commonMain/moko-resources/fi/strings.xml b/i18n/src/commonMain/moko-resources/fi/strings.xml index 587de5deb7..2d51f1a179 100644 --- a/i18n/src/commonMain/moko-resources/fi/strings.xml +++ b/i18n/src/commonMain/moko-resources/fi/strings.xml @@ -602,7 +602,6 @@ Yksityiskohtainen kirjaaminen Kansikuva tallennettu Virhe tallentaessa kansikuvaa - RARv5-muoto ei ole tuettu Ohitettu, koska sarjassa on luettomia lukuja Ohitettu, koska sarja ei vaadi päivityksiä Olet poistamassa \"%s\" kirjastostasi diff --git a/i18n/src/commonMain/moko-resources/fil/strings.xml b/i18n/src/commonMain/moko-resources/fil/strings.xml index df8b126ad7..d457e8a980 100644 --- a/i18n/src/commonMain/moko-resources/fil/strings.xml +++ b/i18n/src/commonMain/moko-resources/fil/strings.xml @@ -610,7 +610,6 @@ I-reset ang default na string ng user agent Default na string ng user agent Burahin lahat - Di suportado ang format na RARv5 Tingnan ang mga kamakailang nai-update na entry sa iyong aklatan Di available ang widget kapag nakabukas ang lock May ina-update sa ngayon diff --git a/i18n/src/commonMain/moko-resources/fr/strings.xml b/i18n/src/commonMain/moko-resources/fr/strings.xml index 2764a75efa..a5fb72811e 100644 --- a/i18n/src/commonMain/moko-resources/fr/strings.xml +++ b/i18n/src/commonMain/moko-resources/fr/strings.xml @@ -610,7 +610,6 @@ Réinitialiser la liste d\'agents utilisateurs Liste d\'agents utilisateurs par défaut Tout retirer - Le format RARv5 n\'est pas supporté Voir les entrées de votre bibliothèque récemment mises à jour Le Widget n\'est pas disponible lorsque l\'application est verrouillée La liste d\'agents utilisateurs ne peut être vide diff --git a/i18n/src/commonMain/moko-resources/gl/strings.xml b/i18n/src/commonMain/moko-resources/gl/strings.xml index 81f69686eb..45e76dcf23 100644 --- a/i18n/src/commonMain/moko-resources/gl/strings.xml +++ b/i18n/src/commonMain/moko-resources/gl/strings.xml @@ -665,7 +665,6 @@ Xa hai unha categoría con este nome! Reiniciar tódolos capítulos deste elemento Modo de lectura - O formato RARv5 non está soportado Actualizando a biblioteca Última actualización da biblioteca: %s Cap. %1$s - %2$s diff --git a/i18n/src/commonMain/moko-resources/he/strings.xml b/i18n/src/commonMain/moko-resources/he/strings.xml index 283ad0a8ec..adc52160d1 100644 --- a/i18n/src/commonMain/moko-resources/he/strings.xml +++ b/i18n/src/commonMain/moko-resources/he/strings.xml @@ -563,7 +563,6 @@ אם המקום של העמוד המפוצל לא תואם לכיוון הקריאה כלום איזורי נגיעה - הפורמט RARv5 לא נתמך חיפוש… פתיחת פריט אקראי מדריך למתחיל diff --git a/i18n/src/commonMain/moko-resources/hi/strings.xml b/i18n/src/commonMain/moko-resources/hi/strings.xml index 75f62e2107..c3dd4279da 100644 --- a/i18n/src/commonMain/moko-resources/hi/strings.xml +++ b/i18n/src/commonMain/moko-resources/hi/strings.xml @@ -608,7 +608,6 @@ खैर, यह अजीब है आंतरिक त्रुटि : अधिक जानकारी के लिए क्रैश लॉग की जाँच करें केवल अनमीटर्ड कनेक्शन पर - RARv5 प्रारूप समर्थित नहीं है पुस्तकालय पिछली बार अपडेट किया गया: %s अपनी हाल ही में अपडेट की गई पुस्तकालय एन्ट्री देखें केवल तभी काम करता है जब वर्तमान अध्याय + अगला पहले से ही डाउनलोड किया गया हो। diff --git a/i18n/src/commonMain/moko-resources/hr/strings.xml b/i18n/src/commonMain/moko-resources/hr/strings.xml index aafcf3da67..1c9d1a7bc3 100644 --- a/i18n/src/commonMain/moko-resources/hr/strings.xml +++ b/i18n/src/commonMain/moko-resources/hr/strings.xml @@ -580,7 +580,6 @@ Na popisu čekanja Popis nedovršenih Stranica %d nije pronađena tijekom rastavljanja - RARv5 format nije podržan Radi samo ako je trenutačno i sljedeće poglavlje već preuzeto. Zadnja provjera aktualiziranja Samo na mrežom bez ograničenja diff --git a/i18n/src/commonMain/moko-resources/hu/strings.xml b/i18n/src/commonMain/moko-resources/hu/strings.xml index 51c00c3ebe..d476b7dbe8 100644 --- a/i18n/src/commonMain/moko-resources/hu/strings.xml +++ b/i18n/src/commonMain/moko-resources/hu/strings.xml @@ -610,7 +610,6 @@ Alap hálózati kliens szöveg Alap hálózati kliens szöveg visszaállítása Minden Eltávolitása - RARv5 formátum nem támogatót Lásd nemrég frissített mangádat Widget nem elérhető amikor az alkalmazás zárolva van Téma, dátum és idő formátuma diff --git a/i18n/src/commonMain/moko-resources/in/strings.xml b/i18n/src/commonMain/moko-resources/in/strings.xml index fa8ba612c1..06e96bc7da 100644 --- a/i18n/src/commonMain/moko-resources/in/strings.xml +++ b/i18n/src/commonMain/moko-resources/in/strings.xml @@ -610,7 +610,6 @@ String agen pengguna default Setel ulang string agen pengguna default Hapus semuanya - Format RARv5 tidak didukung Lihat entri pustaka Anda yang baru saja diperbarui Widget tidak tersedia saat kunci aplikasi diaktifkan Pembaruan sudah berjalan diff --git a/i18n/src/commonMain/moko-resources/it/strings.xml b/i18n/src/commonMain/moko-resources/it/strings.xml index 32b150fa54..f6673c0f87 100644 --- a/i18n/src/commonMain/moko-resources/it/strings.xml +++ b/i18n/src/commonMain/moko-resources/it/strings.xml @@ -612,7 +612,6 @@ Ripristina la stringa «user agent» del browser Stringa «user agent» del browser Rimuovi tutto - Il formato RARv5 non è supportato Widget non disponibile quando il blocco app è attivo Vedi le voci aggiornate di recente È già in corso un aggiornamento diff --git a/i18n/src/commonMain/moko-resources/ja/strings.xml b/i18n/src/commonMain/moko-resources/ja/strings.xml index 09d5268ca1..5a8a607b4e 100644 --- a/i18n/src/commonMain/moko-resources/ja/strings.xml +++ b/i18n/src/commonMain/moko-resources/ja/strings.xml @@ -610,7 +610,6 @@ デフォルトのユーザーエージェント文字列 デフォルトのユーザーエージェント文字列をリセットする 全て削除 - フォーマットRARv5は未対応です 最近更新されたライブラリの項目を見る アプリロックがONの時、ウィジェットは利用できません アップデートはすでに進行中です diff --git a/i18n/src/commonMain/moko-resources/kk/strings.xml b/i18n/src/commonMain/moko-resources/kk/strings.xml index eb5fa9e7de..7f8d392b58 100644 --- a/i18n/src/commonMain/moko-resources/kk/strings.xml +++ b/i18n/src/commonMain/moko-resources/kk/strings.xml @@ -503,7 +503,6 @@ Ақырғы оқылған тарау ашылмай тұр Кітапхананың ақырғы жаңаруы: %s Дереккөз табылмады - RARv5 пішімі қолжетімсіз MyAnimeList-ке қайтадан кіріңіз Ұқсастық табылмады Дереккөз қолжетімсіз diff --git a/i18n/src/commonMain/moko-resources/km/strings.xml b/i18n/src/commonMain/moko-resources/km/strings.xml index 04c81c222d..435981fdb6 100644 --- a/i18n/src/commonMain/moko-resources/km/strings.xml +++ b/i18n/src/commonMain/moko-resources/km/strings.xml @@ -177,7 +177,6 @@ ចេញផ្សាយចប់ហើយ កំពុងតែធ្វើបច្ចុប្បន្នភាពបណ្ណាល័យ មានឡាយសិន - ទម្រង់RARv5មិនត្រូវបានទទួលយកទេ ម​គ្គុ​ទេស​ក៍​ ស្វែងរក ទាំងអស់ diff --git a/i18n/src/commonMain/moko-resources/ko/strings.xml b/i18n/src/commonMain/moko-resources/ko/strings.xml index a01a42c1f7..7acf6f649d 100644 --- a/i18n/src/commonMain/moko-resources/ko/strings.xml +++ b/i18n/src/commonMain/moko-resources/ko/strings.xml @@ -611,7 +611,6 @@ 최근에 업데이트된 항목 보기 보류 목록 분리 중 페이지 %d을 찾을 수 없습니다 - RARv5 포맷은 지원되지 않습니다 앱 잠금 사용 중에는 위젯을 이용할 수 없습니다 파도 업데이트가 이미 실행 중입니다 diff --git a/i18n/src/commonMain/moko-resources/lt/strings.xml b/i18n/src/commonMain/moko-resources/lt/strings.xml index 056b4a1b76..0cbc2281a3 100644 --- a/i18n/src/commonMain/moko-resources/lt/strings.xml +++ b/i18n/src/commonMain/moko-resources/lt/strings.xml @@ -615,7 +615,6 @@ Nepavyko atidaryti paskutinio skaityto skyriaus Puslapis: %1$d Kitas: - RARv5 formatas nepalaikomas Įdiegtų šaltinių nerasta Potvynio banga Ieškoti… diff --git a/i18n/src/commonMain/moko-resources/lv/strings.xml b/i18n/src/commonMain/moko-resources/lv/strings.xml index 9ddae7b853..abaa8977ca 100644 --- a/i18n/src/commonMain/moko-resources/lv/strings.xml +++ b/i18n/src/commonMain/moko-resources/lv/strings.xml @@ -663,7 +663,6 @@ Kategorijas, globāli atjauninājumi, nodaļu vilkšana Rādīt nelasīto skaitu uz atjauninājumu ikonas Logrīks nav pieejams, ja ir iespējota lietotņu bloķēšana - RARv5 formāts netiek atbalstīts %dh InternalError: Par plašāku informāciju skatiet avārijas žurnālu Vai vēlaties dzēst kategoriju \"%s\"? diff --git a/i18n/src/commonMain/moko-resources/ms/strings.xml b/i18n/src/commonMain/moko-resources/ms/strings.xml index 539d2d4499..057d35a5ec 100644 --- a/i18n/src/commonMain/moko-resources/ms/strings.xml +++ b/i18n/src/commonMain/moko-resources/ms/strings.xml @@ -610,7 +610,6 @@ Untaian ejen pengguna lalai Set semula untaian ejen pengguna lalai Buang semuanya - Format RARv5 tidak disokong Widget tidak tersedia apabila kekunci aplikasi digunakan Lihat kemas kini entri pustaka terkini anda Kemas kini sedang berjalan diff --git a/i18n/src/commonMain/moko-resources/nb-rNO/strings.xml b/i18n/src/commonMain/moko-resources/nb-rNO/strings.xml index 3221848f58..1345084fb4 100644 --- a/i18n/src/commonMain/moko-resources/nb-rNO/strings.xml +++ b/i18n/src/commonMain/moko-resources/nb-rNO/strings.xml @@ -592,7 +592,6 @@ Kunne ikke finne filbanen til side %d Applås, sikker skjerm Dump krasjlogger, batterioptimaliseringer - RARv5-formatet støttes ikke Lagringstilgang er ikke gitt Ugyldig streng for brukeragent Ønskeliste diff --git a/i18n/src/commonMain/moko-resources/ne/strings.xml b/i18n/src/commonMain/moko-resources/ne/strings.xml index 62f4f896ea..ba1b809895 100644 --- a/i18n/src/commonMain/moko-resources/ne/strings.xml +++ b/i18n/src/commonMain/moko-resources/ne/strings.xml @@ -663,7 +663,6 @@ पृष्ठ विभाजन गर्दा %d फेला परेन वर्ग खाली छ आन्तरिक त्रुटि: थप जानकारीको लागि क्र्यास लगहरू जाँच गर्नुहोस् - RARv5 समर्थित छैन पढिसकेका इन्ट्रीहरू कुल पढेको diff --git a/i18n/src/commonMain/moko-resources/nl/strings.xml b/i18n/src/commonMain/moko-resources/nl/strings.xml index 9ad64c610b..0b22193062 100644 --- a/i18n/src/commonMain/moko-resources/nl/strings.xml +++ b/i18n/src/commonMain/moko-resources/nl/strings.xml @@ -579,7 +579,6 @@ Download vooruit WebView-gegevens gewist WebView-gegevens wissen - RARv5-indeling wordt niet ondersteund Pagina %d niet gevonden tijdens het splitsen InternalError: Bekijk de crash logs voor meer informatie Lavendel diff --git a/i18n/src/commonMain/moko-resources/pl/strings.xml b/i18n/src/commonMain/moko-resources/pl/strings.xml index 8fdff86427..eb4e95d5bd 100644 --- a/i18n/src/commonMain/moko-resources/pl/strings.xml +++ b/i18n/src/commonMain/moko-resources/pl/strings.xml @@ -614,7 +614,6 @@ Usuń kategorię InternalError: sprawdź log błędów po więcej informacji Usuń wszystko - Format RARv5 jest nieobsługiwany Pole user agent nie może być puste Aktualizacja już trwa Tsunami diff --git a/i18n/src/commonMain/moko-resources/pt-rBR/strings.xml b/i18n/src/commonMain/moko-resources/pt-rBR/strings.xml index 5a3ea0d86b..cec31ada1a 100644 --- a/i18n/src/commonMain/moko-resources/pt-rBR/strings.xml +++ b/i18n/src/commonMain/moko-resources/pt-rBR/strings.xml @@ -610,7 +610,6 @@ User Agent padrão Redefinir o User Agent padrão Remover tudo - O formato RARv5 não é suportado Veja seus itens da biblioteca atualizados recentemente Widget não disponível quando o bloqueio do aplicativo está habilitado Uma atualização já está em andamento diff --git a/i18n/src/commonMain/moko-resources/pt/strings.xml b/i18n/src/commonMain/moko-resources/pt/strings.xml index 70ddb44b26..76d41bd218 100644 --- a/i18n/src/commonMain/moko-resources/pt/strings.xml +++ b/i18n/src/commonMain/moko-resources/pt/strings.xml @@ -593,7 +593,6 @@ Repor as definições do leitor por série Repõe o modo de leitura e orientação de todas as séries Não foi possível encontrar o caminho do ficheiro da página %d - O formato RARv5 não é suportado Multi Transferência automática durante leitura Deseja apagar a categoria \"%s\"? diff --git a/i18n/src/commonMain/moko-resources/ro/strings.xml b/i18n/src/commonMain/moko-resources/ro/strings.xml index d43caf5c56..4448981f55 100644 --- a/i18n/src/commonMain/moko-resources/ro/strings.xml +++ b/i18n/src/commonMain/moko-resources/ro/strings.xml @@ -549,7 +549,6 @@ Este posibil să nu funcționeze corect backup/restaurare dacă MIUI Optimization este dezactivată. La fiecare 3 zile Doar prin Wi Fi - Formatul RARv5 nu este acceptat Oprit Informații despre aplicație Copertă personalizată diff --git a/i18n/src/commonMain/moko-resources/ru/strings.xml b/i18n/src/commonMain/moko-resources/ru/strings.xml index 1337da8d9c..d2510184b0 100644 --- a/i18n/src/commonMain/moko-resources/ru/strings.xml +++ b/i18n/src/commonMain/moko-resources/ru/strings.xml @@ -610,7 +610,6 @@ User agent по умолчанию Сбросить user agent по умолчанию Удалить всё - Формат RARv5 не поддерживается Виджет недоступен при включённой блокировке биометрией Посмотреть недавно обновлённые серии в библиотеке Обновление уже запущено diff --git a/i18n/src/commonMain/moko-resources/sc/strings.xml b/i18n/src/commonMain/moko-resources/sc/strings.xml index 9ec1b724c3..4956d59910 100644 --- a/i18n/src/commonMain/moko-resources/sc/strings.xml +++ b/i18n/src/commonMain/moko-resources/sc/strings.xml @@ -614,7 +614,6 @@ Istringa de agente de utente predefinida Reseta s\'istringa de agente de utente predefinida Boga totu - Su formadu RARv5 no est suportadu Su widget no est a disponimentu cando su blocu de s\'aplicatzione est abilitadu Pòmpia sos elementos de biblioteca tuos agiornados dae pagu B\'est giai un\'agiornamentu in cursu diff --git a/i18n/src/commonMain/moko-resources/sk/strings.xml b/i18n/src/commonMain/moko-resources/sk/strings.xml index b1b3e461ea..39f4a9b63c 100644 --- a/i18n/src/commonMain/moko-resources/sk/strings.xml +++ b/i18n/src/commonMain/moko-resources/sk/strings.xml @@ -627,7 +627,6 @@ Preskočené, pretože séria je dokončená Preskočené, pretože obsahuje neprečítané kapitoly Preskočené, pretože neboli prečítané žiadne kapitoly - Formát RARv5 nie je podporovaný Lokálna Stiahnuté Štatistiky diff --git a/i18n/src/commonMain/moko-resources/sq/strings.xml b/i18n/src/commonMain/moko-resources/sq/strings.xml index b4e48e6d6f..aca22b21a9 100644 --- a/i18n/src/commonMain/moko-resources/sq/strings.xml +++ b/i18n/src/commonMain/moko-resources/sq/strings.xml @@ -433,7 +433,6 @@ Nuk u gjet asnjë rezultat Titulli i panjohur Shto gjurmimin - Formati RARv5 nuk mbështetet Zgjidhni të dhënat për të përfshirë Kapitujt nuk mund të shkarkoheshin. Mund të provoni përsëri në seksionin e shkarkimeve Përditësimet e mëdha dëmtojnë burimet dhe mund të çojnë në përditësime më të ngadalta dhe gjithashtu rritje të përdorimit të baterisë. Trokit për të mësuar më shumë. diff --git a/i18n/src/commonMain/moko-resources/sr/strings.xml b/i18n/src/commonMain/moko-resources/sr/strings.xml index aab9d56c0f..68dcba68c3 100644 --- a/i18n/src/commonMain/moko-resources/sr/strings.xml +++ b/i18n/src/commonMain/moko-resources/sr/strings.xml @@ -643,7 +643,6 @@ Више језика Језик Ресетуј режим читања и оријентацију свих серија - RARv5 формат није подржан Популарно Завршени наслови Побољшава перформансе читача diff --git a/i18n/src/commonMain/moko-resources/sv/strings.xml b/i18n/src/commonMain/moko-resources/sv/strings.xml index bce82c14c2..3bbf19f2a3 100644 --- a/i18n/src/commonMain/moko-resources/sv/strings.xml +++ b/i18n/src/commonMain/moko-resources/sv/strings.xml @@ -610,7 +610,6 @@ Standardsträng för användaragent Återställ standardsträngen för användaragent Ta bort allt - RARv5 formatet stöds inte Se dina nyligen uppdaterade biblioteket inlägg Widget är inte tillgänglig när applåset är aktiverat En uppdatering pågår redan diff --git a/i18n/src/commonMain/moko-resources/th/strings.xml b/i18n/src/commonMain/moko-resources/th/strings.xml index b2b3da3bfa..1776b231a2 100644 --- a/i18n/src/commonMain/moko-resources/th/strings.xml +++ b/i18n/src/commonMain/moko-resources/th/strings.xml @@ -610,7 +610,6 @@ ตัวแทนผู้ใช้เริ่มต้น รีเซ็ตตัวแทนผู้ใช้ นำทุกอย่างออก - ไม่รับรองรูปแบบ RARv5 แสดงรายการในคลังที่อัปเดตล่าสุดของคุณ วิดเจ็ตไม่พร้อมใช้งานขณะการล็อกแอปเปิดไว้ สตริงตัวแทนผู้ใช้ไม่สามารถทิ้งว่างเปล่าไว้ได้ diff --git a/i18n/src/commonMain/moko-resources/tr/strings.xml b/i18n/src/commonMain/moko-resources/tr/strings.xml index 1aae5877e2..7d70487474 100644 --- a/i18n/src/commonMain/moko-resources/tr/strings.xml +++ b/i18n/src/commonMain/moko-resources/tr/strings.xml @@ -610,7 +610,6 @@ Öntanımlı kullanıcı aracısı dizgesi Öntanımlı kullanıcı aracısı dizgesini sıfırla Her şeyi kaldır - RARv5 biçimi desteklenmiyor Uygulama kilidi etkinleştirildiğinde widget kullanılamıyor Son güncellenen kitaplık girdilerinizi görün Bir güncelleme zaten çalışıyor diff --git a/i18n/src/commonMain/moko-resources/uk/strings.xml b/i18n/src/commonMain/moko-resources/uk/strings.xml index 276fd442d5..42c2c55c8a 100644 --- a/i18n/src/commonMain/moko-resources/uk/strings.xml +++ b/i18n/src/commonMain/moko-resources/uk/strings.xml @@ -612,7 +612,6 @@ Типовий user agent Видалити все Скинути типовий user agent - Формат RARv5 не підтримується Багатомовне Останнє оновлення бібліотеки: %s Доступ до файлової системи не надано diff --git a/i18n/src/commonMain/moko-resources/vi/strings.xml b/i18n/src/commonMain/moko-resources/vi/strings.xml index 31a3761cce..9546c1c7f4 100644 --- a/i18n/src/commonMain/moko-resources/vi/strings.xml +++ b/i18n/src/commonMain/moko-resources/vi/strings.xml @@ -614,7 +614,6 @@ Tiện ích không còn khả dụng khi khóa ứng dụng đang bật Xem các bộ truyện được cập nhật gần đây Loại bỏ tất cả mọi thứ - Định dạng RARv5 không được hỗ trợ Có một cập nhật đang chạy sẵn Sóng Thủy Triều Chuỗi đại diện người dùng không thể bỏ trống diff --git a/i18n/src/commonMain/moko-resources/zh-rCN/strings.xml b/i18n/src/commonMain/moko-resources/zh-rCN/strings.xml index 21290a04df..fed84d9bce 100644 --- a/i18n/src/commonMain/moko-resources/zh-rCN/strings.xml +++ b/i18n/src/commonMain/moko-resources/zh-rCN/strings.xml @@ -610,7 +610,6 @@ 默认 User Agent 字符串 恢复默认 User Agent 字符串 全部删除 - 不支持 RARv5 格式 应用锁开启时,小部件不能使用 查看最近更新的作品 有其他更新尚未完成 diff --git a/i18n/src/commonMain/moko-resources/zh-rTW/strings.xml b/i18n/src/commonMain/moko-resources/zh-rTW/strings.xml index e4dfbc1f66..c79bb21796 100644 --- a/i18n/src/commonMain/moko-resources/zh-rTW/strings.xml +++ b/i18n/src/commonMain/moko-resources/zh-rTW/strings.xml @@ -610,7 +610,6 @@ 重設預設使用者代理字串 預設使用者代理字串 全部清除 - 不支援 RARv5 格式 當需要解鎖才能存取應用程式時,無法使用小工具 查看書櫃中近期更新的作品 已在進行更新 diff --git a/source-local/build.gradle.kts b/source-local/build.gradle.kts index b0a720b976..25c268a345 100644 --- a/source-local/build.gradle.kts +++ b/source-local/build.gradle.kts @@ -12,7 +12,6 @@ kotlin { api(projects.i18n) implementation(libs.unifile) - implementation(libs.bundles.archive) } } 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 8efea5fd2f..7909038e44 100644 --- a/source-local/src/androidMain/kotlin/tachiyomi/source/local/LocalSource.kt +++ b/source-local/src/androidMain/kotlin/tachiyomi/source/local/LocalSource.kt @@ -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 nl.adaptivity.xmlutil.AndroidXmlReader import nl.adaptivity.xmlutil.serialization.XML +import tachiyomi.core.common.archive.archiveReader 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,12 @@ actual class LocalSource( private fun copyComicInfoFileFromArchive(chapterArchives: List, 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) - } - } - } - } - 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) - } - } + chapter.archiveReader(context).use { reader -> + reader.getInputStream(COMIC_INFO_FILE)?.let { entry -> + entry.use { stream -> + return copyComicInfoFile(stream, folder) } } - else -> {} } } return null @@ -270,7 +252,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 +310,23 @@ 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) } } - - 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) } } + 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, archive.getInputStream(it)) } + entry?.let { coverManager.update(manga, reader.getInputStream(it.name)!!) } } } is Format.Epub -> { - EpubFile(format.file.openReadOnlyChannel(context)).use { epub -> + EpubFile(format.file.archiveReader(context)).use { epub -> val entry = epub.getImagesFromPages() .firstOrNull() - ?.let { epub.getEntry(it) } - entry?.let { coverManager.update(manga, epub.getInputStream(it)) } + entry?.let { coverManager.update(manga, epub.getInputStream(it)!!) } } } } 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 e968adc7d4..abcf606f78 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 @@ -1,13 +1,17 @@ package tachiyomi.source.local.io import com.hippo.unifile.UniFile -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", "rar", "7z", "tar", "epub") fun isSupported(file: UniFile): Boolean { - return file.extension in SUPPORTED_ARCHIVE_TYPES + val name = file.name?.lowercase() ?: return false + val extension = name.substringAfterLast('.') + val nameWithoutExtension = name.substringBeforeLast('.') + return extension in SUPPORTED_ARCHIVE_TYPES || + extension.startsWith("cb") || // cbz, cbr, etc. + nameWithoutExtension.endsWith(".tar") // tar.gz, tar.bz2, etc. } } 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 5b22e41e2b..33a22e49a8 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 @@ -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 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) + isSupported(file) -> Archive(file) + else -> throw UnknownFormatException() } } }