mirror of
https://github.com/mihonapp/mihon.git
synced 2026-10-16 13:48:09 -07:00
Share one cover load between rows that show it at the same time
This commit is contained in:
@@ -31,6 +31,7 @@ import eu.kanade.tachiyomi.crash.CrashActivity
|
||||
import eu.kanade.tachiyomi.crash.GlobalExceptionHandler
|
||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||
import eu.kanade.tachiyomi.data.coil.BufferedSourceFetcher
|
||||
import eu.kanade.tachiyomi.data.coil.CoverRequestInterceptor
|
||||
import eu.kanade.tachiyomi.data.coil.ImageDecoder
|
||||
import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher
|
||||
import eu.kanade.tachiyomi.data.coil.MangaCoverKeyer
|
||||
@@ -214,6 +215,8 @@ class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factor
|
||||
// Keyer
|
||||
add(MangaCoverKeyer(coverCache))
|
||||
add(MangaKeyer())
|
||||
// Interceptor
|
||||
add(CoverRequestInterceptor())
|
||||
}
|
||||
|
||||
memoryCache(
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package eu.kanade.tachiyomi.data.coil
|
||||
|
||||
import coil3.intercept.Interceptor
|
||||
import coil3.request.ImageResult
|
||||
import coil3.request.SuccessResult
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.manga.model.MangaCover
|
||||
|
||||
/**
|
||||
* De-duplicates concurrent requests for the same cover at the same size. Lists like updates show an entry's cover on
|
||||
* every row, and requests that start together would each miss the memory cache and decode the cover again, so the
|
||||
* waiting ones are served from the memory cache once the first one finishes.
|
||||
*/
|
||||
class CoverRequestInterceptor : Interceptor {
|
||||
|
||||
private val concurrentRequests = DeDupeConcurrentRequests<ImageResult> { it is SuccessResult }
|
||||
|
||||
override suspend fun intercept(chain: Interceptor.Chain): ImageResult {
|
||||
val data = chain.request.data
|
||||
if (data !is MangaCover && data !is Manga) return chain.proceed()
|
||||
|
||||
return concurrentRequests.apply(data to chain.size) { chain.proceed() }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package eu.kanade.tachiyomi.data.coil
|
||||
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
|
||||
/**
|
||||
* De-duplicates concurrent requests for the same key, like Coil's `DeDupeConcurrentRequestStrategy` but for any
|
||||
* result, so it works for decoded images as well as fetches.
|
||||
*
|
||||
* The first caller runs `block`. If it succeeds, the waiting callers run `block` too, for example to read what the
|
||||
* first one cached. If it fails or is canceled, one of them runs it instead.
|
||||
*
|
||||
* @param isSuccess whether a result counts as a success, for results that report failure without throwing.
|
||||
*/
|
||||
class DeDupeConcurrentRequests<T>(
|
||||
private val isSuccess: (T) -> Boolean = { true },
|
||||
) {
|
||||
private val requests = mutableMapOf<Any, CompletableDeferred<Boolean>>()
|
||||
|
||||
suspend fun apply(key: Any, block: suspend () -> T): T {
|
||||
while (true) {
|
||||
var isLeader = false
|
||||
val request = synchronized(requests) {
|
||||
requests.getOrPut(key) {
|
||||
isLeader = true
|
||||
CompletableDeferred()
|
||||
}
|
||||
}
|
||||
|
||||
if (!isLeader) {
|
||||
if (request.await()) return block()
|
||||
continue
|
||||
}
|
||||
|
||||
var succeeded = false
|
||||
try {
|
||||
return block().also { succeeded = isSuccess(it) }
|
||||
} finally {
|
||||
synchronized(requests) {
|
||||
requests -= key
|
||||
}
|
||||
request.complete(succeeded)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,7 +74,8 @@ class MangaCoverFetcher(
|
||||
return when (getResourceType(url)) {
|
||||
Type.File -> fileLoader(File(url.substringAfter("file://")))
|
||||
Type.URI -> fileUriLoader(url)
|
||||
Type.URL -> httpLoader()
|
||||
// Requests for the same cover at different sizes or through Manga and MangaCover would each download it
|
||||
Type.URL -> concurrentRequests.apply(diskCacheKey) { httpLoader() }
|
||||
null -> error("Invalid image")
|
||||
}
|
||||
}
|
||||
@@ -346,5 +347,7 @@ class MangaCoverFetcher(
|
||||
private val CACHE_CONTROL_NO_NETWORK_NO_CACHE = CacheControl.Builder().noCache().onlyIfCached().build()
|
||||
|
||||
private const val HTTP_NOT_MODIFIED = 304
|
||||
|
||||
private val concurrentRequests = DeDupeConcurrentRequests<FetchResult>()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user