Refine exporting downloaded gallery as cbz archive (#698)
This commit is contained in:
1 parent
37e895cdca
commit
8c27feec7a
7 files changed
+61
-34
No files matched your search
@@ -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<Int, Job>()
|
||||
|
||||
@@ -187,6 +187,7 @@ sealed interface ImageSource : AutoCloseable
|
||||
|
||||
interface UniFileSource : ImageSource {
|
||||
val source: UniFile
|
||||
val type: String
|
||||
}
|
||||
|
||||
interface ByteBufferSource : ImageSource {
|
||||
|
||||
@@ -25,6 +25,7 @@ private val xml = XML {
|
||||
}
|
||||
xmlDeclMode = XmlDeclMode.Charset
|
||||
xmlVersion = XmlVersion.XML10
|
||||
indent = 2
|
||||
}
|
||||
|
||||
fun GalleryInfo.getComicInfo(): ComicInfo {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ fun ProgressDialog() {
|
||||
suspend fun <R> DialogState.bgWork(work: suspend () -> R) = dialog { cont ->
|
||||
ProgressDialog()
|
||||
LaunchedEffect(work) {
|
||||
val result = runCatching { work() }.onFailure { it.printStackTrace() }
|
||||
val result = runCatching { work() }
|
||||
cont.resumeWith(result)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user