mirror of
https://github.com/mihonapp/mihon.git
synced 2024-11-21 20:47:03 -05:00
Move archive related code to :core:archive
This commit is contained in:
parent
70c1a842b2
commit
bd7b354198
20 changed files with 70 additions and 52 deletions
|
@ -141,6 +141,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)
|
||||||
|
|
|
@ -38,7 +38,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 tachiyomi.core.common.i18n.stringResource
|
import tachiyomi.core.common.i18n.stringResource
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -6,7 +6,8 @@ import eu.kanade.tachiyomi.data.download.DownloadProvider
|
||||||
import eu.kanade.tachiyomi.source.Source
|
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 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
|
||||||
|
@ -95,7 +96,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)
|
||||||
|
|
|
@ -10,7 +10,7 @@ import eu.kanade.tachiyomi.source.Source
|
||||||
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.manga.model.Manga
|
import tachiyomi.domain.manga.model.Manga
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
|
|
|
@ -2,24 +2,19 @@ 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 ->
|
|
||||||
val streamFn = { epub.getInputStream(path)!! }
|
|
||||||
ReaderPage(i).apply {
|
ReaderPage(i).apply {
|
||||||
stream = streamFn
|
stream = { reader.getInputStream(path)!! }
|
||||||
status = Page.State.READY
|
status = Page.State.READY
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
1
core/archive/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/build
|
15
core/archive/build.gradle.kts
Normal file
15
core/archive/build.gradle.kts
Normal 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)
|
||||||
|
}
|
2
core/archive/src/main/AndroidManifest.xml
Normal file
2
core/archive/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest />
|
|
@ -1,4 +1,4 @@
|
||||||
package mihon.core.common.archive
|
package mihon.core.archive
|
||||||
|
|
||||||
class ArchiveEntry(
|
class ArchiveEntry(
|
||||||
val name: String,
|
val name: String,
|
|
@ -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
|
|
@ -1,21 +1,19 @@
|
||||||
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 = ArchiveInputStream(address, size)
|
||||||
|
@ -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) }
|
|
|
@ -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.
|
|
@ -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))
|
|
@ -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 }
|
|
@ -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")
|
|
||||||
|
|
|
@ -40,6 +40,7 @@ enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
|
||||||
rootProject.name = "Mihon"
|
rootProject.name = "Mihon"
|
||||||
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")
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
@ -253,7 +253,7 @@ actual class LocalSource(
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -323,7 +323,7 @@ actual class LocalSource(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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)!!) }
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue