From 8c27feec7aa394a4340de6e75ada03df32209bb9 Mon Sep 17 00:00:00 2001 From: FooIbar <118464521+FooIbar@users.noreply.github.com> Date: Wed, 7 Feb 2024 15:17:55 +0800 Subject: [PATCH] Refine exporting downloaded gallery as cbz archive (#698) --- .../ehviewer/gallery/ArchivePageLoader.kt | 5 ++- .../kotlin/com/hippo/ehviewer/image/Image.kt | 1 + .../com/hippo/ehviewer/spider/ComicInfo.kt | 1 + .../com/hippo/ehviewer/spider/SpiderDen.kt | 33 +++++++++++---- .../ehviewer/ui/screen/GalleryDetailScreen.kt | 40 ++++++++++--------- .../ehviewer/ui/settings/DownloadScreen.kt | 13 ++++-- .../com/hippo/ehviewer/util/Progress.kt | 2 +- 7 files changed, 61 insertions(+), 34 deletions(-) diff --git a/app/src/main/kotlin/com/hippo/ehviewer/gallery/ArchivePageLoader.kt b/app/src/main/kotlin/com/hippo/ehviewer/gallery/ArchivePageLoader.kt index 5764aa4bf..bff620ff8 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/gallery/ArchivePageLoader.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/gallery/ArchivePageLoader.kt @@ -30,6 +30,7 @@ import com.hippo.ehviewer.jni.providePassword import com.hippo.ehviewer.jni.releaseByteBuffer import com.hippo.ehviewer.util.FileUtils import com.hippo.unifile.UniFile +import com.hippo.unifile.displayPath import java.nio.ByteBuffer import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.Job @@ -52,7 +53,7 @@ class ArchivePageLoader( ) : PageLoader2(gid, startPage) { private lateinit var pfd: ParcelFileDescriptor private val hostJob = launch(start = CoroutineStart.LAZY) { - Log.d(DEBUG_TAG, "Open archive ${file.uri}") + Log.d(DEBUG_TAG, "Open archive ${file.uri.displayPath}") pfd = file.openFileDescriptor("r") size = openArchive(pfd.fd, pfd.statSize) if (size == 0) { @@ -80,7 +81,7 @@ class ArchivePageLoader( super.stop() closeArchive() pfd.close() - Log.d(DEBUG_TAG, "Close archive ${file.uri} successfully!") + Log.d(DEBUG_TAG, "Close archive ${file.uri.displayPath} successfully!") } private val mJobMap = hashMapOf() diff --git a/app/src/main/kotlin/com/hippo/ehviewer/image/Image.kt b/app/src/main/kotlin/com/hippo/ehviewer/image/Image.kt index 031e8f25b..ae75b21eb 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/image/Image.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/image/Image.kt @@ -187,6 +187,7 @@ sealed interface ImageSource : AutoCloseable interface UniFileSource : ImageSource { val source: UniFile + val type: String } interface ByteBufferSource : ImageSource { diff --git a/app/src/main/kotlin/com/hippo/ehviewer/spider/ComicInfo.kt b/app/src/main/kotlin/com/hippo/ehviewer/spider/ComicInfo.kt index f317f2df0..1fa517d19 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/spider/ComicInfo.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/spider/ComicInfo.kt @@ -25,6 +25,7 @@ private val xml = XML { } xmlDeclMode = XmlDeclMode.Charset xmlVersion = XmlVersion.XML10 + indent = 2 } fun GalleryInfo.getComicInfo(): ComicInfo { diff --git a/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderDen.kt b/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderDen.kt index a190bf1ad..baa6cbbeb 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderDen.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderDen.kt @@ -45,10 +45,16 @@ import io.ktor.client.statement.request import io.ktor.http.isSuccess import io.ktor.utils.io.jvm.nio.copyTo import kotlin.io.path.readText +import kotlinx.coroutines.CancellationException class SpiderDen(val info: GalleryInfo) { private val gid = info.gid var downloadDir: UniFile? = null + private set + + constructor(info: GalleryInfo, dirname: String) : this(info) { + downloadDir = downloadLocation / dirname + } @Volatile @SpiderQueen.Mode @@ -213,6 +219,9 @@ class SpiderDen(val info: GalleryInfo) { if (snapshot != null) { return object : UniFileSource, AutoCloseable by snapshot { override val source = snapshot.data.asUniFile() + override val type by lazy { + snapshot.metadata.toNioPath().readText() + } } } } @@ -220,22 +229,28 @@ class SpiderDen(val info: GalleryInfo) { val source = findImageFile(dir, index) ?: return null return object : UniFileSource { override val source = source + override val type by lazy { + FileUtils.getExtensionFromFilename(source.name)!! + } override fun close() {} } } suspend fun exportAsCbz(file: UniFile) = resourceScope { - val pages = info.pages val comicInfo = closeable { val f = downloadDir!! / COMIC_INFO_FILE - if (!f.exists()) writeComicInfo() + if (!f.exists()) { + writeComicInfo() + } else if (info.pages == 0) { + info.pages = readComicInfo(f)!!.pageCount + } f.openFileDescriptor("r") } + val pages = info.pages val (fdBatch, names) = (0 until pages).parMap { idx -> - val ext = requireNotNull(getExtension(idx)) - val f = autoCloseable { requireNotNull(getImageSource(idx)) } - closeable { f.source.openFileDescriptor("r") }.fd to perFilename(idx, ext) + val f = autoCloseable { getImageSource(idx) ?: throw CancellationException("Image #$idx not found") } + closeable { f.source.openFileDescriptor("r") }.fd to perFilename(idx, f.type) }.run { plus(comicInfo.fd to COMIC_INFO_FILE) }.unzip() val arcFd = closeable { file.openFileDescriptor("rw") } archiveFdBatch(fdBatch.toIntArray(), names.toTypedArray(), arcFd.fd, pages + 1) @@ -245,12 +260,14 @@ class SpiderDen(val info: GalleryInfo) { downloadDir = getGalleryDownloadDir(gid)?.takeIf { it.isDirectory } } - suspend fun writeComicInfo() { + suspend fun writeComicInfo(fetchMetadata: Boolean = true) { downloadDir?.run { createFile(COMIC_INFO_FILE)?.also { runCatching { - withNonCancellableContext { - EhEngine.fillGalleryListByApi(listOf(info)) + if (fetchMetadata) { + withNonCancellableContext { + EhEngine.fillGalleryListByApi(listOf(info)) + } } info.getComicInfo().write(it) }.onFailure { diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryDetailScreen.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryDetailScreen.kt index 4845c5817..f0be0f80f 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryDetailScreen.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryDetailScreen.kt @@ -82,7 +82,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.core.text.parseAsHtml -import androidx.lifecycle.compose.LifecycleResumeEffect import arrow.core.partially1 import coil3.imageLoader import com.hippo.ehviewer.EhApplication.Companion.galleryDetailCache @@ -908,18 +907,10 @@ fun GalleryDetailScreen(args: GalleryDetailScreenArgs, navigator: DestinationsNa val startPage by rememberInVM { EhDB.getReadProgressFlow(galleryInfo.gid) }.collectAsState(0) - var readButtonText by rememberSaveable { mutableStateOf(readText) } - LifecycleResumeEffect(Unit) { - coroutineScope.launchIO { - runSuspendCatching { - readButtonText = if (startPage == 0) { - readText - } else { - context.getString(R.string.read_from, startPage + 1) - } - } - } - onPauseOrDispose { } + val readButtonText = if (startPage == 0) { + readText + } else { + stringResource(R.string.read_from, startPage + 1) } val downloadState by EhDownloadManager.collectDownloadState(gid) val downloadButtonText = when (downloadState) { @@ -1241,15 +1232,26 @@ fun GalleryDetailScreen(args: GalleryDetailScreenArgs, navigator: DestinationsNa ) } if (uri != null) { - val success = dialogState.bgWork { - withIOContext { - SpiderDen(info).run { - initDownloadDirIfExist() - exportAsCbz(uri.asUniFile()) + val file = uri.asUniFile() + val success = runCatching { + dialogState.bgWork { + withIOContext { + SpiderDen(info).run { + initDownloadDirIfExist() + exportAsCbz(file) + } } } + }.getOrElse { + it.printStackTrace() + false + } + val msg = if (success) { + exportSuccess + } else { + file.delete() + exportFailed } - val msg = if (success) exportSuccess else exportFailed snackbarState.showSnackbar(message = msg) } } diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/settings/DownloadScreen.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/settings/DownloadScreen.kt index 3b25b7149..d2cd91cfc 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/settings/DownloadScreen.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/settings/DownloadScreen.kt @@ -27,6 +27,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource +import arrow.fx.coroutines.parMap import com.hippo.ehviewer.EhDB import com.hippo.ehviewer.R import com.hippo.ehviewer.Settings @@ -38,6 +39,7 @@ import com.hippo.ehviewer.client.parser.GalleryDetailUrlParser import com.hippo.ehviewer.download.DownloadManager import com.hippo.ehviewer.download.downloadLocation import com.hippo.ehviewer.spider.COMIC_INFO_FILE +import com.hippo.ehviewer.spider.SpiderDen import com.hippo.ehviewer.spider.SpiderQueen.Companion.SPIDER_INFO_FILENAME import com.hippo.ehviewer.spider.readComicInfo import com.hippo.ehviewer.spider.readCompatFromUniFile @@ -153,7 +155,7 @@ fun DownloadScreen(navigator: DestinationsNavigator) { return runCatching { val (gid, token) = file.findFile(SPIDER_INFO_FILENAME)?.let { readCompatFromUniFile(it)?.run { - GalleryDetailUrlParser.Result(gid, token!!) + GalleryDetailUrlParser.Result(gid, token) } } ?: file.findFile(COMIC_INFO_FILE)?.let { readComicInfo(it)?.run { @@ -186,9 +188,12 @@ fun DownloadScreen(navigator: DestinationsNavigator) { if (result.isEmpty()) { launchSnackBar(RESTORE_COUNT_MSG(restoreDirCount)) } else { - val count = result.filterNot { it.title.isNullOrBlank() }.map { - EhDB.putDownloadDirname(it.gid, it.dirname) - DownloadManager.restoreDownload(it.galleryInfo, it.dirname) + val count = result.parMap { + if (it.pages != 0) { + EhDB.putDownloadDirname(it.gid, it.dirname) + DownloadManager.restoreDownload(it.galleryInfo, it.dirname) + SpiderDen(it.galleryInfo, it.dirname).writeComicInfo(false) + } }.size launchSnackBar(RESTORE_COUNT_MSG(count + restoreDirCount)) } diff --git a/app/src/main/kotlin/com/hippo/ehviewer/util/Progress.kt b/app/src/main/kotlin/com/hippo/ehviewer/util/Progress.kt index d46cc5996..8cf10b95d 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/util/Progress.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/util/Progress.kt @@ -47,7 +47,7 @@ fun ProgressDialog() { suspend fun DialogState.bgWork(work: suspend () -> R) = dialog { cont -> ProgressDialog() LaunchedEffect(work) { - val result = runCatching { work() }.onFailure { it.printStackTrace() } + val result = runCatching { work() } cont.resumeWith(result) } }