Move archive related code to :core:archive

Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
This commit is contained in:
AntsyLich 2024-09-05 12:00:46 +02:00 committed by Secozzi
parent c6b34e5bc6
commit 3599a38ad6
No known key found for this signature in database
GPG key ID: DD93E0B3A962AA86
20 changed files with 71 additions and 53 deletions

View file

@ -147,6 +147,7 @@ android {
dependencies { dependencies {
implementation(projects.i18n) implementation(projects.i18n)
implementation(projects.core.archive)
implementation(projects.core.common) implementation(projects.core.common)
implementation(projects.coreMetadata) implementation(projects.coreMetadata)
implementation(projects.sourceApi) implementation(projects.sourceApi)

View file

@ -41,7 +41,7 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.supervisorScope
import logcat.LogPriority import logcat.LogPriority
import mihon.core.common.archive.ZipWriter import mihon.core.archive.ZipWriter
import nl.adaptivity.xmlutil.serialization.XML import nl.adaptivity.xmlutil.serialization.XML
import okhttp3.Response import okhttp3.Response
import okio.Throttler import okio.Throttler

View file

@ -3,7 +3,7 @@ package eu.kanade.tachiyomi.ui.reader.loader
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
import mihon.core.common.archive.ArchiveReader import mihon.core.archive.ArchiveReader
import tachiyomi.core.common.util.system.ImageUtil import tachiyomi.core.common.util.system.ImageUtil
/** /**

View file

@ -7,7 +7,8 @@ import eu.kanade.tachiyomi.source.MangaSource
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import mihon.core.common.archive.archiveReader import mihon.core.archive.archiveReader
import mihon.core.archive.epubReader
import tachiyomi.core.common.i18n.stringResource import tachiyomi.core.common.i18n.stringResource
import tachiyomi.core.common.util.lang.withIOContext import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.core.common.util.system.logcat import tachiyomi.core.common.util.system.logcat
@ -98,7 +99,7 @@ class ChapterLoader(
when (format) { when (format) {
is Format.Directory -> DirectoryPageLoader(format.file) is Format.Directory -> DirectoryPageLoader(format.file)
is Format.Archive -> ArchivePageLoader(format.file.archiveReader(context)) is Format.Archive -> ArchivePageLoader(format.file.archiveReader(context))
is Format.Epub -> EpubPageLoader(format.file.archiveReader(context)) is Format.Epub -> EpubPageLoader(format.file.epubReader(context))
} }
} }
source is HttpSource -> HttpPageLoader(chapter, source) source is HttpSource -> HttpPageLoader(chapter, source)

View file

@ -10,7 +10,7 @@ import eu.kanade.tachiyomi.source.MangaSource
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
import mihon.core.common.archive.archiveReader import mihon.core.archive.archiveReader
import tachiyomi.domain.entries.manga.model.Manga import tachiyomi.domain.entries.manga.model.Manga
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy

View file

@ -2,27 +2,22 @@ package eu.kanade.tachiyomi.ui.reader.loader
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
import eu.kanade.tachiyomi.util.storage.EpubFile import mihon.core.archive.EpubReader
import mihon.core.common.archive.ArchiveReader
/** /**
* Loader used to load a chapter from a .epub file. * Loader used to load a chapter from a .epub file.
*/ */
internal class EpubPageLoader(reader: ArchiveReader) : PageLoader() { internal class EpubPageLoader(private val reader: EpubReader) : PageLoader() {
private val epub = EpubFile(reader)
override var isLocal: Boolean = true override var isLocal: Boolean = true
override suspend fun getPages(): List<ReaderPage> { override suspend fun getPages(): List<ReaderPage> {
return epub.getImagesFromPages() return reader.getImagesFromPages().mapIndexed { i, path ->
.mapIndexed { i, path -> ReaderPage(i).apply {
val streamFn = { epub.getInputStream(path)!! } stream = { reader.getInputStream(path)!! }
ReaderPage(i).apply { status = Page.State.READY
stream = streamFn
status = Page.State.READY
}
} }
}
} }
override suspend fun loadPage(page: ReaderPage) { override suspend fun loadPage(page: ReaderPage) {
@ -31,6 +26,6 @@ internal class EpubPageLoader(reader: ArchiveReader) : PageLoader() {
override fun recycle() { override fun recycle() {
super.recycle() super.recycle()
epub.close() reader.close()
} }
} }

1
core/archive/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

View file

@ -0,0 +1,15 @@
plugins {
id("mihon.library")
kotlin("android")
kotlin("plugin.serialization")
}
android {
namespace = "mihon.core.archive"
}
dependencies {
implementation(libs.jsoup)
implementation(libs.libarchive)
implementation(libs.unifile)
}

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest />

View file

@ -1,4 +1,4 @@
package mihon.core.common.archive package mihon.core.archive
class ArchiveEntry( class ArchiveEntry(
val name: String, val name: String,

View file

@ -1,4 +1,4 @@
package mihon.core.common.archive package mihon.core.archive
import me.zhanghai.android.libarchive.Archive import me.zhanghai.android.libarchive.Archive
import me.zhanghai.android.libarchive.ArchiveEntry import me.zhanghai.android.libarchive.ArchiveEntry
@ -7,7 +7,7 @@ import java.io.InputStream
import java.nio.ByteBuffer import java.nio.ByteBuffer
import kotlin.concurrent.Volatile import kotlin.concurrent.Volatile
class ArchiveInputStream(buffer: Long, size: Long) : InputStream() { internal class ArchiveInputStream(buffer: Long, size: Long) : InputStream() {
private val lock = Any() private val lock = Any()
@Volatile @Volatile

View file

@ -1,24 +1,22 @@
package mihon.core.common.archive package mihon.core.archive
import android.content.Context
import android.os.ParcelFileDescriptor import android.os.ParcelFileDescriptor
import android.system.Os import android.system.Os
import android.system.OsConstants import android.system.OsConstants
import com.hippo.unifile.UniFile
import me.zhanghai.android.libarchive.ArchiveException import me.zhanghai.android.libarchive.ArchiveException
import tachiyomi.core.common.storage.openFileDescriptor
import java.io.Closeable import java.io.Closeable
import java.io.InputStream import java.io.InputStream
class ArchiveReader(pfd: ParcelFileDescriptor) : Closeable { class ArchiveReader(pfd: ParcelFileDescriptor) : Closeable {
val size = pfd.statSize private val size = pfd.statSize
val address = Os.mmap(0, size, OsConstants.PROT_READ, OsConstants.MAP_PRIVATE, pfd.fileDescriptor, 0) private val address = Os.mmap(0, size, OsConstants.PROT_READ, OsConstants.MAP_PRIVATE, pfd.fileDescriptor, 0)
inline fun <T> useEntries(block: (Sequence<ArchiveEntry>) -> T): T = fun <T> useEntries(block: (Sequence<ArchiveEntry>) -> T): T = ArchiveInputStream(address, size).use {
ArchiveInputStream(address, size).use { block(generateSequence { it.getNextEntry() }) } block(generateSequence { it.getNextEntry() })
}
fun getInputStream(entryName: String): InputStream? { fun getInputStream(entryName: String): InputStream? {
val archive = ArchiveInputStream(address, size) val archive = mihon.core.archive.ArchiveInputStream(address, size)
try { try {
while (true) { while (true) {
val entry = archive.getNextEntry() ?: break val entry = archive.getNextEntry() ?: break
@ -38,5 +36,3 @@ class ArchiveReader(pfd: ParcelFileDescriptor) : Closeable {
Os.munmap(address, size) Os.munmap(address, size)
} }
} }
fun UniFile.archiveReader(context: Context) = openFileDescriptor(context, "r").use { ArchiveReader(it) }

View file

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.util.storage package mihon.core.archive
import mihon.core.common.archive.ArchiveReader
import org.jsoup.Jsoup import org.jsoup.Jsoup
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import java.io.Closeable import java.io.Closeable
@ -8,9 +7,9 @@ import java.io.File
import java.io.InputStream import java.io.InputStream
/** /**
* Wrapper over ZipFile to load files in epub format. * Wrapper over ArchiveReader to load files in epub format.
*/ */
class EpubFile(private val reader: ArchiveReader) : Closeable by reader { class EpubReader(private val reader: ArchiveReader) : Closeable by reader {
/** /**
* Path separator used by this epub. * Path separator used by this epub.

View file

@ -0,0 +1,12 @@
package mihon.core.archive
import android.content.Context
import android.os.ParcelFileDescriptor
import com.hippo.unifile.UniFile
internal fun UniFile.openFileDescriptor(context: Context, mode: String): ParcelFileDescriptor =
context.contentResolver.openFileDescriptor(uri, mode) ?: error("Failed to open file descriptor: ${filePath ?: uri.toString()}")
fun UniFile.archiveReader(context: Context) = openFileDescriptor(context, "r").use { ArchiveReader(it) }
fun UniFile.epubReader(context: Context) = EpubReader(archiveReader(context))

View file

@ -1,4 +1,4 @@
package mihon.core.common.archive package mihon.core.archive
import android.content.Context import android.content.Context
import android.system.Os import android.system.Os
@ -7,7 +7,6 @@ import com.hippo.unifile.UniFile
import me.zhanghai.android.libarchive.Archive import me.zhanghai.android.libarchive.Archive
import me.zhanghai.android.libarchive.ArchiveEntry import me.zhanghai.android.libarchive.ArchiveEntry
import me.zhanghai.android.libarchive.ArchiveException import me.zhanghai.android.libarchive.ArchiveException
import tachiyomi.core.common.storage.openFileDescriptor
import java.io.Closeable import java.io.Closeable
import java.nio.ByteBuffer import java.nio.ByteBuffer
@ -65,10 +64,10 @@ private fun StructStat.toArchiveStat() = ArchiveEntry.StructStat().apply {
stSize = st_size stSize = st_size
stBlksize = st_blksize stBlksize = st_blksize
stBlocks = st_blocks stBlocks = st_blocks
stAtim = timespec(st_atime) stAtim = st_atime.toTimespec()
stMtim = timespec(st_mtime) stMtim = st_mtime.toTimespec()
stCtim = timespec(st_ctime) stCtim = st_ctime.toTimespec()
stIno = st_ino stIno = st_ino
} }
private fun timespec(tvSec: Long) = ArchiveEntry.StructTimespec().also { it.tvSec = tvSec } private fun Long.toTimespec() = ArchiveEntry.StructTimespec().also { it.tvSec = this }

View file

@ -1,7 +1,5 @@
package tachiyomi.core.common.storage package tachiyomi.core.common.storage
import android.content.Context
import android.os.ParcelFileDescriptor
import com.hippo.unifile.UniFile import com.hippo.unifile.UniFile
val UniFile.extension: String? val UniFile.extension: String?
@ -12,6 +10,3 @@ val UniFile.nameWithoutExtension: String?
val UniFile.displayablePath: String val UniFile.displayablePath: String
get() = filePath ?: uri.toString() get() = filePath ?: uri.toString()
fun UniFile.openFileDescriptor(context: Context, mode: String): ParcelFileDescriptor =
context.contentResolver.openFileDescriptor(uri, mode) ?: error("Failed to open file descriptor: $displayablePath")

View file

@ -40,6 +40,7 @@ enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
rootProject.name = "Aniyomi" rootProject.name = "Aniyomi"
include(":app") include(":app")
include(":core-metadata") include(":core-metadata")
include(":core:archive")
include(":core:common") include(":core:common")
include(":data") include(":data")
include(":domain") include(":domain")

View file

@ -16,6 +16,7 @@ kotlin {
} }
val androidMain by getting { val androidMain by getting {
dependencies { dependencies {
implementation(projects.core.archive)
implementation(projects.core.common) implementation(projects.core.common)
implementation(projects.coreMetadata) implementation(projects.coreMetadata)

View file

@ -11,13 +11,13 @@ import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder
import eu.kanade.tachiyomi.util.storage.EpubFile
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream import kotlinx.serialization.json.decodeFromStream
import logcat.LogPriority import logcat.LogPriority
import mihon.core.common.archive.archiveReader import mihon.core.archive.archiveReader
import mihon.core.archive.epubReader
import nl.adaptivity.xmlutil.AndroidXmlReader import nl.adaptivity.xmlutil.AndroidXmlReader
import nl.adaptivity.xmlutil.serialization.XML import nl.adaptivity.xmlutil.serialization.XML
import tachiyomi.core.common.i18n.stringResource import tachiyomi.core.common.i18n.stringResource
@ -270,7 +270,7 @@ actual class LocalMangaSource(
val format = Format.valueOf(chapterFile) val format = Format.valueOf(chapterFile)
if (format is Format.Epub) { if (format is Format.Epub) {
EpubFile(format.file.archiveReader(context)).use { epub -> format.file.epubReader(context).use { epub ->
epub.fillMetadata(manga, this) epub.fillMetadata(manga, this)
} }
} }
@ -359,7 +359,7 @@ actual class LocalMangaSource(
} }
} }
is Format.Epub -> { is Format.Epub -> {
EpubFile(format.file.archiveReader(context)).use { epub -> format.file.epubReader(context).use { epub ->
val entry = epub.getImagesFromPages().firstOrNull() val entry = epub.getImagesFromPages().firstOrNull()
entry?.let { coverManager.update(manga, epub.getInputStream(it)!!) } entry?.let { coverManager.update(manga, epub.getInputStream(it)!!) }

View file

@ -2,7 +2,7 @@ package tachiyomi.source.local.metadata
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.util.storage.EpubFile import mihon.core.archive.EpubReader
import java.text.ParseException import java.text.ParseException
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
@ -10,7 +10,7 @@ import java.util.Locale
/** /**
* Fills manga and chapter metadata using this epub file's metadata. * Fills manga and chapter metadata using this epub file's metadata.
*/ */
fun EpubFile.fillMetadata(manga: SManga, chapter: SChapter) { fun EpubReader.fillMetadata(manga: SManga, chapter: SChapter) {
val ref = getPackageHref() val ref = getPackageHref()
val doc = getPackageDocument(ref) val doc = getPackageDocument(ref)