From 7d2f985ffa95e584f4f55f3d4cd9c3f59567b44d Mon Sep 17 00:00:00 2001 From: Vladimir Dostoyevskiy <99822064+VDostoyevskiy@users.noreply.github.com> Date: Mon, 25 Dec 2023 17:27:49 +0800 Subject: [PATCH] Introduce DownloadThumbInterceptor (#538) * Introduce DownloadThumbInterceptor * Fixup * Update DownloadInfoMagics.kt --------- Co-authored-by: GG Bond --- .../com/hippo/ehviewer/EhApplication.kt | 2 + .../ehviewer/coil/DownloadThumbInterceptor.kt | 48 +++++++++++ .../com/hippo/ehviewer/coil/Extensions.kt | 4 +- .../ehviewer/spider/DownloadInfoMagics.kt | 29 +++++++ .../hippo/ehviewer/ui/main/DownloadCard.kt | 81 +------------------ .../kotlin/com/hippo/ehviewer/util/FDUtils.kt | 5 ++ 6 files changed, 88 insertions(+), 81 deletions(-) create mode 100644 app/src/main/kotlin/com/hippo/ehviewer/coil/DownloadThumbInterceptor.kt create mode 100644 app/src/main/kotlin/com/hippo/ehviewer/spider/DownloadInfoMagics.kt diff --git a/app/src/main/kotlin/com/hippo/ehviewer/EhApplication.kt b/app/src/main/kotlin/com/hippo/ehviewer/EhApplication.kt index 6e188d0e1..bf5d21795 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/EhApplication.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/EhApplication.kt @@ -34,6 +34,7 @@ import coil3.util.DebugLogger import com.hippo.ehviewer.client.EhCookieStore import com.hippo.ehviewer.client.EhTagDatabase import com.hippo.ehviewer.client.data.GalleryDetail +import com.hippo.ehviewer.coil.DownloadThumbInterceptor import com.hippo.ehviewer.coil.MergeInterceptor import com.hippo.ehviewer.coil.installKtorHttpUriFetcher import com.hippo.ehviewer.dailycheck.checkDawn @@ -185,6 +186,7 @@ class EhApplication : Application(), SingletonImageLoader.Factory { add { result, options, _ -> ImageDecoderDecoder(result.source, options, false) } } add(MergeInterceptor) + add(DownloadThumbInterceptor) } diskCache(imageCache) crossfade(300) diff --git a/app/src/main/kotlin/com/hippo/ehviewer/coil/DownloadThumbInterceptor.kt b/app/src/main/kotlin/com/hippo/ehviewer/coil/DownloadThumbInterceptor.kt new file mode 100644 index 000000000..4f9607282 --- /dev/null +++ b/app/src/main/kotlin/com/hippo/ehviewer/coil/DownloadThumbInterceptor.kt @@ -0,0 +1,48 @@ +package com.hippo.ehviewer.coil + +import coil3.intercept.Interceptor +import coil3.request.ErrorResult +import coil3.request.ImageResult +import coil3.request.SuccessResult +import com.hippo.ehviewer.EhApplication.Companion.imageCache +import com.hippo.ehviewer.download.downloadLocation +import com.hippo.ehviewer.spider.DownloadInfoMagics.decodeMagicRequestOrUrl +import com.hippo.ehviewer.util.sendTo +import com.hippo.unifile.UniFile +import eu.kanade.tachiyomi.util.lang.withIOContext + +object DownloadThumbInterceptor : Interceptor { + override suspend fun intercept(chain: Interceptor.Chain): ImageResult { + val magicOrUrl = chain.request.data as? String + if (magicOrUrl != null) { + val (url, location) = decodeMagicRequestOrUrl(magicOrUrl) + if (location != null) { + val thumb = withIOContext { downloadLocation.subFile(location)?.subFile(".thumb") } + if (thumb != null) { + if (withIOContext { thumb.isFile }) { + val new = chain.request.newBuilder().data(thumb.uri.toString()).build() + when (val result = chain.withRequest(new).proceed()) { + is SuccessResult -> return result + is ErrorResult -> withIOContext { thumb.delete() } + } + } + val new = chain.request.newBuilder().data(url).build() + return chain.withRequest(new).proceed().also { + if (it is SuccessResult) { + withIOContext { + if (thumb.ensureFile()) { + val key = requireNotNull(chain.request.memoryCacheKey) + imageCache.read(key) { + val cache = UniFile.fromFile(data.toFile()) + requireNotNull(cache) sendTo thumb + } + } + } + } + } + } + } + } + return chain.proceed() + } +} diff --git a/app/src/main/kotlin/com/hippo/ehviewer/coil/Extensions.kt b/app/src/main/kotlin/com/hippo/ehviewer/coil/Extensions.kt index aa7b2bf10..db8f31367 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/coil/Extensions.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/coil/Extensions.kt @@ -11,12 +11,12 @@ import coil3.size.Size import com.hippo.ehviewer.client.data.GalleryInfo import com.hippo.ehviewer.client.data.GalleryPreview import com.hippo.ehviewer.client.data.NormalGalleryPreview -import com.hippo.ehviewer.client.thumbUrl import com.hippo.ehviewer.client.url +import com.hippo.ehviewer.spider.DownloadInfoMagics.encodeMagicRequestOrUrl fun ImageRequest.Builder.ehUrl(info: GalleryInfo) = apply { val key = info.thumbKey!! - data(info.thumbUrl) + data(encodeMagicRequestOrUrl(info)) memoryCacheKey(key) diskCacheKey(key) } diff --git a/app/src/main/kotlin/com/hippo/ehviewer/spider/DownloadInfoMagics.kt b/app/src/main/kotlin/com/hippo/ehviewer/spider/DownloadInfoMagics.kt new file mode 100644 index 000000000..c3664a739 --- /dev/null +++ b/app/src/main/kotlin/com/hippo/ehviewer/spider/DownloadInfoMagics.kt @@ -0,0 +1,29 @@ +package com.hippo.ehviewer.spider + +import com.hippo.ehviewer.client.data.GalleryInfo +import com.hippo.ehviewer.client.thumbUrl +import com.hippo.ehviewer.dao.DownloadInfo + +object DownloadInfoMagics { + private const val DOWNLOAD_INFO_DIRNAME_URL_MAGIC = "$" + private const val DOWNLOAD_INFO_DIRNAME_URL_SEPARATOR = "|" + + fun encodeMagicRequestOrUrl(info: GalleryInfo): String { + val url = info.thumbUrl + val location = (info as? DownloadInfo)?.dirname + return if (location.isNullOrBlank()) { + url + } else { + DOWNLOAD_INFO_DIRNAME_URL_MAGIC + url + DOWNLOAD_INFO_DIRNAME_URL_SEPARATOR + location + } + } + + fun decodeMagicRequestOrUrl(encoded: String): Pair { + return if (encoded.startsWith(DOWNLOAD_INFO_DIRNAME_URL_MAGIC)) { + val (a, b) = encoded.removePrefix(DOWNLOAD_INFO_DIRNAME_URL_MAGIC).split(DOWNLOAD_INFO_DIRNAME_URL_SEPARATOR) + a to b + } else { + encoded to null + } + } +} diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/main/DownloadCard.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/main/DownloadCard.kt index f215342c3..593ac37ba 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/main/DownloadCard.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/main/DownloadCard.kt @@ -19,104 +19,27 @@ import androidx.compose.material3.ShapeDefaults import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.constraintlayout.compose.ConstraintLayout -import coil3.compose.AsyncImage -import coil3.compose.AsyncImagePainter -import com.hippo.ehviewer.EhApplication import com.hippo.ehviewer.R import com.hippo.ehviewer.Settings import com.hippo.ehviewer.client.EhUtils -import com.hippo.ehviewer.coil.read import com.hippo.ehviewer.dao.DownloadInfo import com.hippo.ehviewer.download.DownloadManager -import com.hippo.ehviewer.download.downloadDir -import com.hippo.ehviewer.ktbuilder.imageRequest import com.hippo.ehviewer.ui.tools.CrystalCard import com.hippo.ehviewer.ui.tools.DragHandle import com.hippo.ehviewer.ui.tools.GalleryListCardRating -import com.hippo.ehviewer.ui.tools.shouldCrop import com.hippo.ehviewer.util.FileUtils -import com.hippo.ehviewer.util.sendTo -import com.hippo.unifile.UniFile -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch import sh.calvin.reorderable.ReorderableItemScope -@Composable -private fun AsyncThumb( - info: DownloadInfo, - modifier: Modifier = Modifier, -) { - val path = remember(info.gid) { info.downloadDir?.subFile(".thumb") } - var contentScale by remember(info.gid) { mutableStateOf(ContentScale.Fit) } - val coroutineScope = rememberCoroutineScope { Dispatchers.IO } - val context = LocalContext.current - var localReq by remember(info.gid) { - path?.takeIf { it.isFile }?.uri?.let { - context.imageRequest { - data(it.toString()) - memoryCacheKey(info.thumbKey) - } - }.let { mutableStateOf(it) } - } - AsyncImage( - model = localReq ?: requestOf(info), - contentDescription = null, - modifier = modifier, - onState = { state -> - if (state is AsyncImagePainter.State.Success) { - if (state.result.image.shouldCrop) { - contentScale = ContentScale.Crop - } - path?.let { - coroutineScope.launch { - runCatching { - if (!path.exists() && path.ensureFile()) { - val key = info.thumbKey!! - EhApplication.imageCache.read(key) { - UniFile.fromFile(data.toFile())!!.openFileDescriptor("r").use { src -> - path.openFileDescriptor("w").use { dst -> - src sendTo dst - } - } - } - } - }.onFailure { - it.printStackTrace() - } - } - } - } - if (state is AsyncImagePainter.State.Error) { - path?.let { - coroutineScope.launch { - if (path.exists()) { - path.delete() - localReq = null - } - } - } - } - }, - contentScale = contentScale, - ) -} - @Composable fun ReorderableItemScope.DownloadCard( onClick: () -> Unit, @@ -134,8 +57,8 @@ fun ReorderableItemScope.DownloadCard( ) { Row { Card(onClick = onThumbClick) { - AsyncThumb( - info = info, + EhAsyncCropThumb( + key = info, modifier = Modifier.aspectRatio(DEFAULT_ASPECT).fillMaxSize(), ) } diff --git a/app/src/main/kotlin/com/hippo/ehviewer/util/FDUtils.kt b/app/src/main/kotlin/com/hippo/ehviewer/util/FDUtils.kt index b518971b8..bd302888f 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/util/FDUtils.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/util/FDUtils.kt @@ -3,6 +3,7 @@ package com.hippo.ehviewer.util import android.os.ParcelFileDescriptor import android.system.Int64Ref import android.system.Os +import com.hippo.unifile.UniFile import java.io.FileDescriptor import java.io.FileInputStream import java.io.FileOutputStream @@ -26,3 +27,7 @@ infix fun ParcelFileDescriptor.sendTo(fd: FileDescriptor) { infix fun ParcelFileDescriptor.sendTo(fd: ParcelFileDescriptor) { sendFileTotally(fileDescriptor, fd.fileDescriptor) } + +infix fun UniFile.sendTo(file: UniFile) = openFileDescriptor("r").use { src -> + file.openFileDescriptor("w").use { dst -> src sendTo dst } +}