Introduce DownloadThumbInterceptor (#538)

* Introduce DownloadThumbInterceptor

* Fixup

* Update DownloadInfoMagics.kt

---------

Co-authored-by: GG Bond <whatever@tagkong.com>
This commit is contained in:
Vladimir DostoyevskiyandGG Bond authored and GitHub committed 2023-12-25 09:27:49 +00:00
1 parent 7e8b0a5800
commit 7d2f985ffa
6 files changed
+88 -81

No files matched your search

@@ -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)
@@ -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()
}
}
@@ -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)
}
@@ -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<String, String?> {
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
}
}
}
@@ -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(),
)
}
@@ -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 }
}