Don't copy archives to temp files when opening

Co-authored-by: FooIbar <118464521+FooIbar@users.noreply.github.com>
This commit is contained in:
Secozzi 2024-06-05 14:45:53 +02:00
parent 60c5636da2
commit 0424b60b3e
No known key found for this signature in database
GPG key ID: 71E9C97D8DDC2662
15 changed files with 49 additions and 47 deletions

View file

@ -219,7 +219,7 @@ dependencies {
// Disk // Disk
implementation(libs.disklrucache) implementation(libs.disklrucache)
implementation(libs.unifile) implementation(libs.unifile)
implementation(libs.junrar) implementation(libs.bundles.archive)
// Preferences // Preferences
implementation(libs.preferencektx) implementation(libs.preferencektx)

View file

@ -76,3 +76,6 @@
# XmlUtil # XmlUtil
-keep public enum nl.adaptivity.xmlutil.EventType { *; } -keep public enum nl.adaptivity.xmlutil.EventType { *; }
# Apache Commons Compress
-keep class * extends org.apache.commons.compress.archivers.zip.ZipExtraField { <init>(); }

View file

@ -38,7 +38,6 @@ import nl.adaptivity.xmlutil.XmlDeclMode
import nl.adaptivity.xmlutil.core.XmlVersion import nl.adaptivity.xmlutil.core.XmlVersion
import nl.adaptivity.xmlutil.serialization.XML import nl.adaptivity.xmlutil.serialization.XML
import tachiyomi.core.common.storage.AndroidStorageFolderProvider import tachiyomi.core.common.storage.AndroidStorageFolderProvider
import tachiyomi.core.common.storage.UniFileTempFileManager
import tachiyomi.data.AnimeUpdateStrategyColumnAdapter import tachiyomi.data.AnimeUpdateStrategyColumnAdapter
import tachiyomi.data.Database import tachiyomi.data.Database
import tachiyomi.data.DateColumnAdapter import tachiyomi.data.DateColumnAdapter
@ -178,8 +177,6 @@ class AppModule(val app: Application) : InjektModule {
ProtoBuf ProtoBuf
} }
addSingletonFactory { UniFileTempFileManager(app) }
addSingletonFactory { ChapterCache(app, get()) } addSingletonFactory { ChapterCache(app, get()) }
addSingletonFactory { MangaCoverCache(app) } addSingletonFactory { MangaCoverCache(app) }

View file

@ -55,7 +55,6 @@ import kotlinx.coroutines.flow.update
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import logcat.LogPriority import logcat.LogPriority
import tachiyomi.core.common.preference.toggle import tachiyomi.core.common.preference.toggle
import tachiyomi.core.common.storage.UniFileTempFileManager
import tachiyomi.core.common.util.lang.launchIO import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.core.common.util.lang.launchNonCancellable import tachiyomi.core.common.util.lang.launchNonCancellable
import tachiyomi.core.common.util.lang.withIOContext import tachiyomi.core.common.util.lang.withIOContext
@ -86,7 +85,6 @@ class ReaderViewModel @JvmOverloads constructor(
private val sourceManager: MangaSourceManager = Injekt.get(), private val sourceManager: MangaSourceManager = Injekt.get(),
private val downloadManager: MangaDownloadManager = Injekt.get(), private val downloadManager: MangaDownloadManager = Injekt.get(),
private val downloadProvider: MangaDownloadProvider = Injekt.get(), private val downloadProvider: MangaDownloadProvider = Injekt.get(),
private val tempFileManager: UniFileTempFileManager = Injekt.get(),
private val imageSaver: ImageSaver = Injekt.get(), private val imageSaver: ImageSaver = Injekt.get(),
preferences: BasePreferences = Injekt.get(), preferences: BasePreferences = Injekt.get(),
val readerPreferences: ReaderPreferences = Injekt.get(), val readerPreferences: ReaderPreferences = Injekt.get(),
@ -277,7 +275,7 @@ class ReaderViewModel @JvmOverloads constructor(
val context = Injekt.get<Application>() val context = Injekt.get<Application>()
val source = sourceManager.getOrStub(manga.source) val source = sourceManager.getOrStub(manga.source)
loader = ChapterLoader(context, downloadManager, downloadProvider, tempFileManager, manga, source) loader = ChapterLoader(context, downloadManager, downloadProvider, manga, source)
loadChapter(loader!!, chapterList.first { chapterId == it.chapter.id }) loadChapter(loader!!, chapterList.first { chapterId == it.chapter.id })
Result.success(true) Result.success(true)
@ -920,7 +918,6 @@ class ReaderViewModel @JvmOverloads constructor(
private fun deletePendingChapters() { private fun deletePendingChapters() {
viewModelScope.launchNonCancellable { viewModelScope.launchNonCancellable {
downloadManager.deletePendingChapters() downloadManager.deletePendingChapters()
tempFileManager.deleteTempFiles()
} }
} }

View file

@ -9,7 +9,7 @@ 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 tachiyomi.core.common.i18n.stringResource import tachiyomi.core.common.i18n.stringResource
import tachiyomi.core.common.storage.UniFileTempFileManager import tachiyomi.core.common.storage.openReadOnlyChannel
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
import tachiyomi.domain.entries.manga.model.Manga import tachiyomi.domain.entries.manga.model.Manga
@ -26,7 +26,6 @@ class ChapterLoader(
private val context: Context, private val context: Context,
private val downloadManager: MangaDownloadManager, private val downloadManager: MangaDownloadManager,
private val downloadProvider: MangaDownloadProvider, private val downloadProvider: MangaDownloadProvider,
private val tempFileManager: UniFileTempFileManager,
private val manga: Manga, private val manga: Manga,
private val source: MangaSource, private val source: MangaSource,
) { ) {
@ -95,18 +94,17 @@ class ChapterLoader(
source, source,
downloadManager, downloadManager,
downloadProvider, downloadProvider,
tempFileManager,
) )
source is LocalMangaSource -> source.getFormat(chapter.chapter).let { format -> source is LocalMangaSource -> source.getFormat(chapter.chapter).let { format ->
when (format) { when (format) {
is Format.Directory -> DirectoryPageLoader(format.file) is Format.Directory -> DirectoryPageLoader(format.file)
is Format.Zip -> ZipPageLoader(tempFileManager.createTempFile(format.file)) is Format.Zip -> ZipPageLoader(format.file.openReadOnlyChannel(context))
is Format.Rar -> try { is Format.Rar -> try {
RarPageLoader(tempFileManager.createTempFile(format.file)) RarPageLoader(format.file.openInputStream())
} catch (e: UnsupportedRarV5Exception) { } catch (e: UnsupportedRarV5Exception) {
error(context.stringResource(MR.strings.loader_rar5_error)) error(context.stringResource(MR.strings.loader_rar5_error))
} }
is Format.Epub -> EpubPageLoader(tempFileManager.createTempFile(format.file)) is Format.Epub -> EpubPageLoader(format.file.openReadOnlyChannel(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 tachiyomi.core.common.storage.UniFileTempFileManager import tachiyomi.core.common.storage.openReadOnlyChannel
import tachiyomi.domain.entries.manga.model.Manga import tachiyomi.domain.entries.manga.model.Manga
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
@ -23,7 +23,6 @@ internal class DownloadPageLoader(
private val source: MangaSource, private val source: MangaSource,
private val downloadManager: MangaDownloadManager, private val downloadManager: MangaDownloadManager,
private val downloadProvider: MangaDownloadProvider, private val downloadProvider: MangaDownloadProvider,
private val tempFileManager: UniFileTempFileManager,
) : PageLoader() { ) : PageLoader() {
private val context: Application by injectLazy() private val context: Application by injectLazy()
@ -53,7 +52,7 @@ internal class DownloadPageLoader(
} }
private suspend fun getPagesFromArchive(file: UniFile): List<ReaderPage> { private suspend fun getPagesFromArchive(file: UniFile): List<ReaderPage> {
val loader = ZipPageLoader(tempFileManager.createTempFile(file)).also { zipPageLoader = it } val loader = ZipPageLoader(file.openReadOnlyChannel(context)).also { zipPageLoader = it }
return loader.getPages() return loader.getPages()
} }

View file

@ -3,14 +3,14 @@ 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 eu.kanade.tachiyomi.util.storage.EpubFile
import java.io.File import java.nio.channels.SeekableByteChannel
/** /**
* Loader used to load a chapter from a .epub file. * Loader used to load a chapter from a .epub file.
*/ */
internal class EpubPageLoader(file: File) : PageLoader() { internal class EpubPageLoader(channel: SeekableByteChannel) : PageLoader() {
private val epub = EpubFile(file) private val epub = EpubFile(channel)
override var isLocal: Boolean = true override var isLocal: Boolean = true

View file

@ -6,7 +6,6 @@ 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 tachiyomi.core.common.util.system.ImageUtil import tachiyomi.core.common.util.system.ImageUtil
import java.io.File
import java.io.InputStream import java.io.InputStream
import java.io.PipedInputStream import java.io.PipedInputStream
import java.io.PipedOutputStream import java.io.PipedOutputStream
@ -15,9 +14,9 @@ import java.util.concurrent.Executors
/** /**
* Loader used to load a chapter from a .rar or .cbr file. * Loader used to load a chapter from a .rar or .cbr file.
*/ */
internal class RarPageLoader(file: File) : PageLoader() { internal class RarPageLoader(inputStream: InputStream) : PageLoader() {
private val rar = Archive(file) private val rar = Archive(inputStream)
override var isLocal: Boolean = true override var isLocal: Boolean = true

View file

@ -3,22 +3,21 @@ 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 org.apache.commons.compress.archivers.zip.ZipFile
import tachiyomi.core.common.util.system.ImageUtil import tachiyomi.core.common.util.system.ImageUtil
import java.io.File import java.nio.channels.SeekableByteChannel
import java.nio.charset.StandardCharsets
import java.util.zip.ZipFile
/** /**
* Loader used to load a chapter from a .zip or .cbz file. * Loader used to load a chapter from a .zip or .cbz file.
*/ */
internal class ZipPageLoader(file: File) : PageLoader() { internal class ZipPageLoader(channel: SeekableByteChannel) : PageLoader() {
private val zip = ZipFile(file, StandardCharsets.ISO_8859_1) private val zip = ZipFile(channel)
override var isLocal: Boolean = true override var isLocal: Boolean = true
override suspend fun getPages(): List<ReaderPage> { override suspend fun getPages(): List<ReaderPage> {
return zip.entries().asSequence() return zip.entries.asSequence()
.filter { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } } .filter { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } }
.sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) } .sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) }
.mapIndexed { i, entry -> .mapIndexed { i, entry ->

View file

@ -32,6 +32,7 @@ dependencies {
implementation(libs.image.decoder) implementation(libs.image.decoder)
implementation(libs.unifile) implementation(libs.unifile)
implementation(libs.bundles.archive)
api(kotlinx.coroutines.core) api(kotlinx.coroutines.core)
api(kotlinx.serialization.json) api(kotlinx.serialization.json)

View file

@ -1,22 +1,23 @@
package eu.kanade.tachiyomi.util.storage package eu.kanade.tachiyomi.util.storage
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry
import org.apache.commons.compress.archivers.zip.ZipFile
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
import java.io.File import java.io.File
import java.io.InputStream import java.io.InputStream
import java.util.zip.ZipEntry import java.nio.channels.SeekableByteChannel
import java.util.zip.ZipFile
/** /**
* Wrapper over ZipFile to load files in epub format. * Wrapper over ZipFile to load files in epub format.
*/ */
class EpubFile(file: File) : Closeable { class EpubFile(channel: SeekableByteChannel) : Closeable {
/** /**
* Zip file of this epub. * Zip file of this epub.
*/ */
private val zip = ZipFile(file) private val zip = ZipFile(channel)
/** /**
* Path separator used by this epub. * Path separator used by this epub.
@ -33,14 +34,14 @@ class EpubFile(file: File) : Closeable {
/** /**
* Returns an input stream for reading the contents of the specified zip file entry. * Returns an input stream for reading the contents of the specified zip file entry.
*/ */
fun getInputStream(entry: ZipEntry): InputStream { fun getInputStream(entry: ZipArchiveEntry): InputStream {
return zip.getInputStream(entry) return zip.getInputStream(entry)
} }
/** /**
* Returns the zip file entry for the specified name, or null if not found. * Returns the zip file entry for the specified name, or null if not found.
*/ */
fun getEntry(name: String): ZipEntry? { fun getEntry(name: String): ZipArchiveEntry? {
return zip.getEntry(name) return zip.getEntry(name)
} }

View file

@ -1,6 +1,9 @@
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
import java.nio.channels.FileChannel
val UniFile.extension: String? val UniFile.extension: String?
get() = name?.substringAfterLast('.') get() = name?.substringAfterLast('.')
@ -10,3 +13,7 @@ val UniFile.nameWithoutExtension: String?
val UniFile.displayablePath: String val UniFile.displayablePath: String
get() = filePath ?: uri.toString() get() = filePath ?: uri.toString()
fun UniFile.openReadOnlyChannel(context: Context): FileChannel {
return ParcelFileDescriptor.AutoCloseInputStream(context.contentResolver.openFileDescriptor(uri, "r")).channel
}

View file

@ -32,6 +32,7 @@ jsoup = "org.jsoup:jsoup:1.17.2"
disklrucache = "com.jakewharton:disklrucache:2.0.2" disklrucache = "com.jakewharton:disklrucache:2.0.2"
unifile = "com.github.tachiyomiorg:unifile:7c257e1c64" unifile = "com.github.tachiyomiorg:unifile:7c257e1c64"
common-compress = "org.apache.commons:commons-compress:1.25.0"
junrar = "com.github.junrar:junrar:7.5.5" junrar = "com.github.junrar:junrar:7.5.5"
sqlite-framework = { module = "androidx.sqlite:sqlite-framework", version.ref = "sqlite" } sqlite-framework = { module = "androidx.sqlite:sqlite-framework", version.ref = "sqlite" }
@ -110,6 +111,7 @@ seeker = "io.github.2307vivek:seeker:1.1.1"
truetypeparser = "io.github.yubyf:truetypeparser-light:2.1.4" truetypeparser = "io.github.yubyf:truetypeparser-light:2.1.4"
[bundles] [bundles]
archive = ["common-compress", "junrar"]
acra = ["acra-http", "acra-scheduler"] acra = ["acra-http", "acra-scheduler"]
okhttp = ["okhttp-core", "okhttp-logging", "okhttp-brotli", "okhttp-dnsoverhttps"] okhttp = ["okhttp-core", "okhttp-logging", "okhttp-brotli", "okhttp-dnsoverhttps"]
js-engine = ["quickjs-android"] js-engine = ["quickjs-android"]

View file

@ -12,7 +12,7 @@ kotlin {
api(projects.i18n) api(projects.i18n)
implementation(libs.unifile) implementation(libs.unifile)
implementation(libs.junrar) implementation(libs.bundles.archive)
} }
} }
val androidMain by getting { val androidMain by getting {

View file

@ -18,6 +18,7 @@ import kotlinx.serialization.json.decodeFromStream
import logcat.LogPriority import logcat.LogPriority
import nl.adaptivity.xmlutil.AndroidXmlReader import nl.adaptivity.xmlutil.AndroidXmlReader
import nl.adaptivity.xmlutil.serialization.XML import nl.adaptivity.xmlutil.serialization.XML
import org.apache.commons.compress.archivers.zip.ZipFile
import tachiyomi.core.common.i18n.stringResource import tachiyomi.core.common.i18n.stringResource
import tachiyomi.core.metadata.comicinfo.COMIC_INFO_FILE import tachiyomi.core.metadata.comicinfo.COMIC_INFO_FILE
import tachiyomi.core.metadata.comicinfo.ComicInfo import tachiyomi.core.metadata.comicinfo.ComicInfo
@ -25,9 +26,9 @@ import tachiyomi.core.metadata.comicinfo.copyFromComicInfo
import tachiyomi.core.metadata.comicinfo.getComicInfo import tachiyomi.core.metadata.comicinfo.getComicInfo
import tachiyomi.core.metadata.tachiyomi.ChapterDetails import tachiyomi.core.metadata.tachiyomi.ChapterDetails
import tachiyomi.core.metadata.tachiyomi.MangaDetails import tachiyomi.core.metadata.tachiyomi.MangaDetails
import tachiyomi.core.common.storage.UniFileTempFileManager
import tachiyomi.core.common.storage.extension import tachiyomi.core.common.storage.extension
import tachiyomi.core.common.storage.nameWithoutExtension import tachiyomi.core.common.storage.nameWithoutExtension
import tachiyomi.core.common.storage.openReadOnlyChannel
import tachiyomi.core.common.util.lang.withIOContext import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.core.common.util.system.ImageUtil import tachiyomi.core.common.util.system.ImageUtil
import tachiyomi.core.common.util.system.logcat import tachiyomi.core.common.util.system.logcat
@ -47,7 +48,6 @@ import java.nio.charset.StandardCharsets
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import java.util.zip.ZipFile
import kotlin.math.abs import kotlin.math.abs
import com.github.junrar.Archive as JunrarArchive import com.github.junrar.Archive as JunrarArchive
@ -59,7 +59,6 @@ actual class LocalMangaSource(
private val json: Json by injectLazy() private val json: Json by injectLazy()
private val xml: XML by injectLazy() private val xml: XML by injectLazy()
private val tempFileManager: UniFileTempFileManager by injectLazy()
private val POPULAR_FILTERS = FilterList(MangaOrderBy.Popular(context)) private val POPULAR_FILTERS = FilterList(MangaOrderBy.Popular(context))
private val LATEST_FILTERS = FilterList(MangaOrderBy.Latest(context)) private val LATEST_FILTERS = FilterList(MangaOrderBy.Latest(context))
@ -220,7 +219,7 @@ actual class LocalMangaSource(
for (chapter in chapterArchives) { for (chapter in chapterArchives) {
when (Format.valueOf(chapter)) { when (Format.valueOf(chapter)) {
is Format.Zip -> { is Format.Zip -> {
ZipFile(tempFileManager.createTempFile(chapter)).use { zip: ZipFile -> ZipFile(chapter.openReadOnlyChannel(context)).use { zip: ZipFile ->
zip.getEntry(COMIC_INFO_FILE)?.let { comicInfoFile -> zip.getEntry(COMIC_INFO_FILE)?.let { comicInfoFile ->
zip.getInputStream(comicInfoFile).buffered().use { stream -> zip.getInputStream(comicInfoFile).buffered().use { stream ->
return copyComicInfoFile(stream, folderPath) return copyComicInfoFile(stream, folderPath)
@ -229,7 +228,7 @@ actual class LocalMangaSource(
} }
} }
is Format.Rar -> { is Format.Rar -> {
JunrarArchive(tempFileManager.createTempFile(chapter)).use { rar -> JunrarArchive(chapter.openInputStream()).use { rar ->
rar.fileHeaders.firstOrNull { it.fileName == COMIC_INFO_FILE }?.let { comicInfoFile -> rar.fileHeaders.firstOrNull { it.fileName == COMIC_INFO_FILE }?.let { comicInfoFile ->
rar.getInputStream(comicInfoFile).buffered().use { stream -> rar.getInputStream(comicInfoFile).buffered().use { stream ->
return copyComicInfoFile(stream, folderPath) return copyComicInfoFile(stream, folderPath)
@ -290,7 +289,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(tempFileManager.createTempFile(format.file)).use { epub -> EpubFile(format.file.openReadOnlyChannel(context)).use { epub ->
epub.fillMetadata(manga, this) epub.fillMetadata(manga, this)
} }
} }
@ -368,8 +367,8 @@ actual class LocalMangaSource(
entry?.let { coverManager.update(manga, it.openInputStream()) } entry?.let { coverManager.update(manga, it.openInputStream()) }
} }
is Format.Zip -> { is Format.Zip -> {
ZipFile(tempFileManager.createTempFile(format.file)).use { zip -> ZipFile(format.file.openReadOnlyChannel(context)).use { zip ->
val entry = zip.entries().toList() val entry = zip.entries.toList()
.sortedWith { f1, f2 -> .sortedWith { f1, f2 ->
f1.name.compareToCaseInsensitiveNaturalOrder( f1.name.compareToCaseInsensitiveNaturalOrder(
f2.name, f2.name,
@ -387,7 +386,7 @@ actual class LocalMangaSource(
} }
} }
is Format.Rar -> { is Format.Rar -> {
JunrarArchive(tempFileManager.createTempFile(format.file)).use { archive -> JunrarArchive(format.file.openInputStream()).use { archive ->
val entry = archive.fileHeaders val entry = archive.fileHeaders
.sortedWith { f1, f2 -> .sortedWith { f1, f2 ->
f1.fileName.compareToCaseInsensitiveNaturalOrder( f1.fileName.compareToCaseInsensitiveNaturalOrder(
@ -406,7 +405,7 @@ actual class LocalMangaSource(
} }
} }
is Format.Epub -> { is Format.Epub -> {
EpubFile(tempFileManager.createTempFile(format.file)).use { epub -> EpubFile(format.file.openReadOnlyChannel(context)).use { epub ->
val entry = epub.getImagesFromPages() val entry = epub.getImagesFromPages()
.firstOrNull() .firstOrNull()
?.let { epub.getEntry(it) } ?.let { epub.getEntry(it) }