refactor: Move archive related code to core.archive module

This commit is contained in:
Ahmad Ansori Palembani 2025-01-01 09:26:15 +07:00
parent 54a3059730
commit b4377a4609
Signed by: null2264
GPG key ID: BA64F8B60AF3EFB6
19 changed files with 61 additions and 55 deletions

View file

@ -144,6 +144,7 @@ android {
} }
dependencies { dependencies {
implementation(projects.core.archive)
implementation(projects.core.main) implementation(projects.core.main)
implementation(projects.data) implementation(projects.data)
implementation(projects.domain) implementation(projects.domain)

View file

@ -12,8 +12,6 @@ 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.DiskUtil import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.storage.EpubFile
import eu.kanade.tachiyomi.util.storage.fillMetadata
import eu.kanade.tachiyomi.util.system.ImageUtil import eu.kanade.tachiyomi.util.system.ImageUtil
import eu.kanade.tachiyomi.util.system.extension import eu.kanade.tachiyomi.util.system.extension
import eu.kanade.tachiyomi.util.system.nameWithoutExtension import eu.kanade.tachiyomi.util.system.nameWithoutExtension
@ -33,7 +31,8 @@ import nl.adaptivity.xmlutil.serialization.XML
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import yokai.core.archive.archiveReader import yokai.core.archive.util.archiveReader
import yokai.core.archive.util.epubReader
import yokai.core.metadata.COMIC_INFO_FILE import yokai.core.metadata.COMIC_INFO_FILE
import yokai.core.metadata.ComicInfo import yokai.core.metadata.ComicInfo
import yokai.core.metadata.copyFromComicInfo import yokai.core.metadata.copyFromComicInfo
@ -42,6 +41,7 @@ import yokai.domain.chapter.services.ChapterRecognition
import yokai.domain.source.SourcePreferences import yokai.domain.source.SourcePreferences
import yokai.domain.storage.StorageManager import yokai.domain.storage.StorageManager
import yokai.i18n.MR import yokai.i18n.MR
import yokai.util.fillMetadata
import yokai.util.lang.getString import yokai.util.lang.getString
class LocalSource(private val context: Context) : CatalogueSource, UnmeteredSource { class LocalSource(private val context: Context) : CatalogueSource, UnmeteredSource {
@ -410,7 +410,7 @@ class LocalSource(private val context: Context) : CatalogueSource, UnmeteredSour
} }
} }
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 { updateCover(manga, epub.getInputStream(it)!!, context) } entry?.let { updateCover(manga, epub.getInputStream(it)!!, context) }
@ -433,7 +433,7 @@ class LocalSource(private val context: Context) : CatalogueSource, UnmeteredSour
true true
} }
is Format.Epub -> { is Format.Epub -> {
EpubFile(format.file.archiveReader(context)).use { epub -> format.file.epubReader(context).use { epub ->
epub.fillMetadata(chapter, manga) epub.fillMetadata(chapter, manga)
} }
true true

View file

@ -10,7 +10,8 @@ import eu.kanade.tachiyomi.source.Source
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.util.system.withIOContext import eu.kanade.tachiyomi.util.system.withIOContext
import yokai.core.archive.archiveReader import yokai.core.archive.util.archiveReader
import yokai.core.archive.util.epubReader
import yokai.i18n.MR import yokai.i18n.MR
import yokai.util.lang.getString import yokai.util.lang.getString
@ -82,7 +83,7 @@ class ChapterLoader(
when (format) { when (format) {
is LocalSource.Format.Directory -> DirectoryPageLoader(format.file) is LocalSource.Format.Directory -> DirectoryPageLoader(format.file)
is LocalSource.Format.Archive -> ArchivePageLoader(format.file.archiveReader(context)) is LocalSource.Format.Archive -> ArchivePageLoader(format.file.archiveReader(context))
is LocalSource.Format.Epub -> EpubPageLoader(format.file.archiveReader(context)) is LocalSource.Format.Epub -> EpubPageLoader(format.file.epubReader(context))
} }
} }
else -> error(context.getString(MR.strings.source_not_installed)) else -> error(context.getString(MR.strings.source_not_installed))

View file

@ -11,7 +11,7 @@ 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 uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import yokai.core.archive.archiveReader import yokai.core.archive.util.archiveReader
/** /**
* Loader used to load a chapter from the downloaded chapters. * Loader used to load a chapter from the downloaded chapters.

View file

@ -2,21 +2,15 @@ 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 yokai.core.archive.EpubReader
import yokai.core.archive.ArchiveReader
/** /**
* Loader used to load a chapter from a .epub file. * Loader used to load a chapter from a .epub file.
*/ */
class EpubPageLoader(reader: ArchiveReader) : PageLoader() { class EpubPageLoader(private val epub: EpubReader) : PageLoader() {
override val isLocal: Boolean = true override val isLocal: Boolean = true
/**
* The epub file.
*/
private val epub = EpubFile(reader)
/** /**
* Recycles this loader and the open zip. * Recycles this loader and the open zip.
*/ */

View file

@ -1,15 +1,16 @@
package eu.kanade.tachiyomi.util.storage package yokai.util
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 java.text.ParseException import java.text.ParseException
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.Locale
import yokai.core.archive.EpubReader
/** /**
* 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(chapter: SChapter, manga: SManga) { fun EpubReader.fillMetadata(chapter: SChapter, manga: SManga) {
val ref = getPackageHref() val ref = getPackageHref()
val doc = getPackageDocument(ref) val doc = getPackageDocument(ref)

View file

@ -0,0 +1,15 @@
plugins {
id("yokai.android.library")
kotlin("android")
alias(kotlinx.plugins.serialization)
}
android {
namespace = "yokai.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,12 +1,13 @@
package yokai.core.archive package yokai.core.archive
import java.io.InputStream
import java.nio.ByteBuffer import java.nio.ByteBuffer
import kotlin.concurrent.Volatile import kotlin.concurrent.Volatile
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
class AndroidArchiveInputStream(buffer: Long, size: Long) : ArchiveInputStream() { class ArchiveInputStream(buffer: Long, size: Long) : InputStream() {
private val lock = Any() private val lock = Any()
@Volatile @Volatile
private var isClosed = false private var isClosed = false

View file

@ -1,23 +1,22 @@
package yokai.core.archive package yokai.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 java.io.Closeable
import eu.kanade.tachiyomi.util.system.openFileDescriptor
import java.io.InputStream import java.io.InputStream
import me.zhanghai.android.libarchive.ArchiveException import me.zhanghai.android.libarchive.ArchiveException
class AndroidArchiveReader(pfd: ParcelFileDescriptor) : ArchiveReader { class ArchiveReader(pfd: ParcelFileDescriptor) : Closeable {
val size = pfd.statSize val size = pfd.statSize
val address = Os.mmap(0, size, OsConstants.PROT_READ, OsConstants.MAP_PRIVATE, pfd.fileDescriptor, 0) val address = Os.mmap(0, size, OsConstants.PROT_READ, OsConstants.MAP_PRIVATE, pfd.fileDescriptor, 0)
override fun <T> useEntries(block: (Sequence<ArchiveEntry>) -> T): T = fun <T> useEntries(block: (Sequence<ArchiveEntry>) -> T): T = ArchiveInputStream(address, size).use {
AndroidArchiveInputStream(address, size).use { block(generateSequence { it.getNextEntry() }) } block(generateSequence { it.getNextEntry() })
}
override fun getInputStream(entryName: String): InputStream? { fun getInputStream(entryName: String): InputStream? {
val archive = AndroidArchiveInputStream(address, size) val archive = ArchiveInputStream(address, size)
try { try {
while (true) { while (true) {
val entry = archive.getNextEntry() ?: break val entry = archive.getNextEntry() ?: break
@ -37,6 +36,3 @@ class AndroidArchiveReader(pfd: ParcelFileDescriptor) : ArchiveReader {
Os.munmap(address, size) Os.munmap(address, size)
} }
} }
fun UniFile.archiveReader(context: Context): ArchiveReader =
openFileDescriptor(context, "r").use { AndroidArchiveReader(it) }

View file

@ -1,16 +1,15 @@
package eu.kanade.tachiyomi.util.storage package yokai.core.archive
import java.io.Closeable import java.io.Closeable
import java.io.File import java.io.File
import java.io.InputStream import java.io.InputStream
import org.jsoup.Jsoup import org.jsoup.Jsoup
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import yokai.core.archive.ArchiveReader
/** /**
* Wrapper over ZipFile to load files in epub format. * Wrapper over ZipFile 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

@ -4,12 +4,12 @@ import android.content.Context
import android.system.Os import android.system.Os
import android.system.StructStat import android.system.StructStat
import com.hippo.unifile.UniFile import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.util.system.openFileDescriptor
import java.io.Closeable import java.io.Closeable
import java.nio.ByteBuffer import java.nio.ByteBuffer
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 yokai.core.archive.util.openFileDescriptor
class ZipWriter(val context: Context, file: UniFile) : Closeable { class ZipWriter(val context: Context, file: UniFile) : Closeable {
private val pfd = file.openFileDescriptor(context, "wt") private val pfd = file.openFileDescriptor(context, "wt")

View file

@ -0,0 +1,14 @@
package yokai.core.archive.util
import android.content.Context
import android.os.ParcelFileDescriptor
import com.hippo.unifile.UniFile
import yokai.core.archive.ArchiveReader
import yokai.core.archive.EpubReader
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): ArchiveReader = openFileDescriptor(context, "r").use { ArchiveReader(it) }
fun UniFile.epubReader(context: Context): EpubReader = EpubReader(archiveReader(context))

View file

@ -60,7 +60,7 @@ kotlin {
} }
android { android {
namespace = "yokai.core" namespace = "yokai.core.main"
} }
tasks { tasks {

View file

@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.util.system
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
import android.os.FileUtils import android.os.FileUtils
import android.os.ParcelFileDescriptor
import com.hippo.unifile.UniFile import com.hippo.unifile.UniFile
import java.io.BufferedOutputStream import java.io.BufferedOutputStream
import java.io.File import java.io.File
@ -48,6 +47,3 @@ fun UniFile.writeText(string: String, onComplete: () -> Unit = {}) {
onComplete() onComplete()
} }
} }
fun UniFile.openFileDescriptor(context: Context, mode: String): ParcelFileDescriptor =
context.contentResolver.openFileDescriptor(uri, mode) ?: error("Failed to open file descriptor: $displayablePath")

View file

@ -1,6 +0,0 @@
package yokai.core.archive
import java.io.InputStream
// TODO: Use Okio's Source
abstract class ArchiveInputStream : InputStream()

View file

@ -1,9 +0,0 @@
package yokai.core.archive
import java.io.Closeable
import java.io.InputStream
interface ArchiveReader : Closeable {
fun <T> useEntries(block: (Sequence<ArchiveEntry>) -> T): T
fun getInputStream(entryName: String): InputStream?
}

View file

@ -31,6 +31,7 @@ enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
rootProject.name = "Yokai" rootProject.name = "Yokai"
include(":app") include(":app")
include(":core:archive")
include(":core:main") include(":core:main")
include(":data") include(":data")
include(":domain") include(":domain")