mirror of
https://github.com/null2264/yokai.git
synced 2025-06-21 10:44:42 +00:00
refactor(coil): Simplify cover fetcher code
Co-authored-by: Ivan Iskandar <12537387+ivaniskandar@users.noreply.github.com> Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com>
This commit is contained in:
parent
7fc73ddcdc
commit
103fae06d3
4 changed files with 31 additions and 80 deletions
|
@ -22,3 +22,4 @@
|
||||||
- More StorIO to SQLDelight migration effort
|
- More StorIO to SQLDelight migration effort
|
||||||
- Target Android 15
|
- Target Android 15
|
||||||
- Adjust manga cover cache key
|
- Adjust manga cover cache key
|
||||||
|
- Refactor manga cover fetcher (@ivaniskandar, @AntsyLich, @null2264)
|
||||||
|
|
|
@ -37,7 +37,6 @@ import eu.kanade.tachiyomi.appwidget.TachiyomiWidgetManager
|
||||||
import eu.kanade.tachiyomi.core.preference.Preference
|
import eu.kanade.tachiyomi.core.preference.Preference
|
||||||
import eu.kanade.tachiyomi.core.preference.PreferenceStore
|
import eu.kanade.tachiyomi.core.preference.PreferenceStore
|
||||||
import eu.kanade.tachiyomi.data.coil.BufferedSourceFetcher
|
import eu.kanade.tachiyomi.data.coil.BufferedSourceFetcher
|
||||||
import eu.kanade.tachiyomi.data.coil.CoilDiskCache
|
|
||||||
import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher
|
import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher
|
||||||
import eu.kanade.tachiyomi.data.coil.MangaCoverKeyer
|
import eu.kanade.tachiyomi.data.coil.MangaCoverKeyer
|
||||||
import eu.kanade.tachiyomi.data.coil.TachiyomiImageDecoder
|
import eu.kanade.tachiyomi.data.coil.TachiyomiImageDecoder
|
||||||
|
@ -237,7 +236,6 @@ open class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.F
|
||||||
override fun newImageLoader(context: PlatformContext): ImageLoader {
|
override fun newImageLoader(context: PlatformContext): ImageLoader {
|
||||||
return ImageLoader.Builder(this@App).apply {
|
return ImageLoader.Builder(this@App).apply {
|
||||||
val callFactoryLazy = lazy { Injekt.get<NetworkHelper>().client }
|
val callFactoryLazy = lazy { Injekt.get<NetworkHelper>().client }
|
||||||
val diskCacheLazy = lazy { CoilDiskCache.get(this@App) }
|
|
||||||
components {
|
components {
|
||||||
// NetworkFetcher.Factory
|
// NetworkFetcher.Factory
|
||||||
add(OkHttpNetworkFetcherFactory(callFactoryLazy::value))
|
add(OkHttpNetworkFetcherFactory(callFactoryLazy::value))
|
||||||
|
@ -245,13 +243,10 @@ open class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.F
|
||||||
add(TachiyomiImageDecoder.Factory())
|
add(TachiyomiImageDecoder.Factory())
|
||||||
// Fetcher.Factory
|
// Fetcher.Factory
|
||||||
add(BufferedSourceFetcher.Factory())
|
add(BufferedSourceFetcher.Factory())
|
||||||
add(MangaCoverFetcher.Factory(callFactoryLazy, diskCacheLazy))
|
add(MangaCoverFetcher.Factory(callFactoryLazy))
|
||||||
// Keyer
|
// Keyer
|
||||||
add(MangaCoverKeyer())
|
add(MangaCoverKeyer())
|
||||||
}
|
}
|
||||||
|
|
||||||
diskCache(diskCacheLazy::value)
|
|
||||||
//memoryCache { MemoryCache.Builder().maxSizePercent(this@App, 0.40).build() }
|
|
||||||
crossfade(true)
|
crossfade(true)
|
||||||
allowRgb565(this@App.getSystemService<ActivityManager>()!!.isLowRamDevice)
|
allowRgb565(this@App.getSystemService<ActivityManager>()!!.isLowRamDevice)
|
||||||
allowHardware(true)
|
allowHardware(true)
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.data.coil
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import coil3.disk.DiskCache
|
|
||||||
import coil3.disk.directory
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Direct copy of Coil's internal SingletonDiskCache so that [MangaCoverFetcher] can access it.
|
|
||||||
*/
|
|
||||||
object CoilDiskCache {
|
|
||||||
|
|
||||||
private const val FOLDER_NAME = "image_cache"
|
|
||||||
private var instance: DiskCache? = null
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
fun get(context: Context): DiskCache {
|
|
||||||
return instance ?: run {
|
|
||||||
val safeCacheDir = context.cacheDir.apply { mkdirs() }
|
|
||||||
// Create the singleton disk cache instance.
|
|
||||||
DiskCache.Builder()
|
|
||||||
.directory(safeCacheDir.resolve(FOLDER_NAME))
|
|
||||||
.build()
|
|
||||||
.also { instance = it }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -32,6 +32,7 @@ import okhttp3.Call
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import okio.FileSystem
|
import okio.FileSystem
|
||||||
|
import okio.IOException
|
||||||
import okio.Path.Companion.toOkioPath
|
import okio.Path.Companion.toOkioPath
|
||||||
import okio.Source
|
import okio.Source
|
||||||
import okio.buffer
|
import okio.buffer
|
||||||
|
@ -44,33 +45,19 @@ class MangaCoverFetcher(
|
||||||
private val sourceLazy: Lazy<HttpSource?>,
|
private val sourceLazy: Lazy<HttpSource?>,
|
||||||
private val options: Options,
|
private val options: Options,
|
||||||
private val callFactoryLazy: Lazy<Call.Factory>,
|
private val callFactoryLazy: Lazy<Call.Factory>,
|
||||||
private val diskCacheLazy: Lazy<DiskCache>,
|
|
||||||
private val diskCacheKeyLazy: Lazy<String>,
|
private val diskCacheKeyLazy: Lazy<String>,
|
||||||
private val coverFileLazy: Lazy<File?>,
|
private val coverFileLazy: Lazy<File?>,
|
||||||
private val customCoverFileLazy: Lazy<File>,
|
private val customCoverFileLazy: Lazy<File>,
|
||||||
|
private val imageLoader: ImageLoader,
|
||||||
) : Fetcher {
|
) : Fetcher {
|
||||||
|
|
||||||
private val diskCacheKey: String
|
private val diskCacheKey: String
|
||||||
get() = diskCacheKeyLazy.value
|
get() = diskCacheKeyLazy.value
|
||||||
private lateinit var url: String
|
private lateinit var url: String
|
||||||
|
|
||||||
val fileScope = CoroutineScope(Job() + Dispatchers.IO)
|
private val fileScope = CoroutineScope(Job() + Dispatchers.IO)
|
||||||
|
|
||||||
override suspend fun fetch(): FetchResult {
|
override suspend fun fetch(): FetchResult {
|
||||||
// diskCacheKey is thumbnail_url
|
|
||||||
url = manga.thumbnail_url.orEmpty()
|
|
||||||
return when (getResourceType(url)) {
|
|
||||||
Type.URL -> httpLoader()
|
|
||||||
Type.File -> {
|
|
||||||
setRatioAndColorsInScope(manga, File(url.substringAfter("file://")))
|
|
||||||
fileLoader(File(url.substringAfter("file://")))
|
|
||||||
}
|
|
||||||
Type.URI -> fileUriLoader(url)
|
|
||||||
null -> tryCustomCover() ?: error("No cover specified")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun tryCustomCover(): FetchResult? {
|
|
||||||
if (options.extras.getOrDefault(USE_CUSTOM_COVER_KEY)) {
|
if (options.extras.getOrDefault(USE_CUSTOM_COVER_KEY)) {
|
||||||
val customCoverFile = customCoverFileLazy.value
|
val customCoverFile = customCoverFileLazy.value
|
||||||
if (customCoverFile.exists()) {
|
if (customCoverFile.exists()) {
|
||||||
|
@ -78,26 +65,29 @@ class MangaCoverFetcher(
|
||||||
return fileLoader(customCoverFile)
|
return fileLoader(customCoverFile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null
|
|
||||||
|
url = manga.thumbnail_url ?: error("No cover specified")
|
||||||
|
return when (getResourceType(url)) {
|
||||||
|
Type.URL -> httpLoader()
|
||||||
|
Type.File -> {
|
||||||
|
setRatioAndColorsInScope(manga, File(url.substringAfter("file://")))
|
||||||
|
fileLoader(File(url.substringAfter("file://")))
|
||||||
|
}
|
||||||
|
Type.URI -> fileUriLoader(url)
|
||||||
|
null -> error("Invalid image")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun httpLoader(): FetchResult {
|
private suspend fun httpLoader(): FetchResult {
|
||||||
val diskRead = options.diskCachePolicy.readEnabled
|
|
||||||
val networkRead = options.networkCachePolicy.readEnabled
|
|
||||||
val onlyCache = !networkRead && diskRead
|
|
||||||
val shouldFetchRemotely = networkRead && !diskRead && !onlyCache
|
|
||||||
if (!shouldFetchRemotely) {
|
|
||||||
val customCoverLoader = tryCustomCover()
|
|
||||||
if (customCoverLoader != null) return customCoverLoader
|
|
||||||
}
|
|
||||||
val coverFile = coverFileLazy.value
|
val coverFile = coverFileLazy.value
|
||||||
if (!shouldFetchRemotely && coverFile != null && coverFile.exists() && options.diskCachePolicy.readEnabled) {
|
if (coverFile?.exists() == true && options.diskCachePolicy.readEnabled) {
|
||||||
if (!manga.favorite) {
|
if (!manga.favorite) {
|
||||||
coverFile.setLastModified(Date().time)
|
coverFile.setLastModified(Date().time)
|
||||||
}
|
}
|
||||||
setRatioAndColorsInScope(manga, coverFile)
|
setRatioAndColorsInScope(manga, coverFile)
|
||||||
return fileLoader(coverFile)
|
return fileLoader(coverFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
var snapshot = readFromDiskCache()
|
var snapshot = readFromDiskCache()
|
||||||
try {
|
try {
|
||||||
// Fetch from disk cache
|
// Fetch from disk cache
|
||||||
|
@ -130,7 +120,7 @@ class MangaCoverFetcher(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read from disk cache
|
// Read from disk cache
|
||||||
snapshot = writeToDiskCache(snapshot, response)
|
snapshot = writeToDiskCache(response)
|
||||||
if (snapshot != null) {
|
if (snapshot != null) {
|
||||||
return SourceFetchResult(
|
return SourceFetchResult(
|
||||||
source = snapshot.toImageSource(),
|
source = snapshot.toImageSource(),
|
||||||
|
@ -160,7 +150,7 @@ class MangaCoverFetcher(
|
||||||
val response = client.newCall(newRequest()).await()
|
val response = client.newCall(newRequest()).await()
|
||||||
if (!response.isSuccessful && response.code != HttpURLConnection.HTTP_NOT_MODIFIED) {
|
if (!response.isSuccessful && response.code != HttpURLConnection.HTTP_NOT_MODIFIED) {
|
||||||
response.close()
|
response.close()
|
||||||
throw Exception(response.message) // FIXME: Should probably use something else other than generic Exception
|
throw IOException(response.message)
|
||||||
}
|
}
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
@ -179,15 +169,15 @@ class MangaCoverFetcher(
|
||||||
val onlyCache = !networkRead && diskRead
|
val onlyCache = !networkRead && diskRead
|
||||||
val forceNetwork = networkRead && !diskRead
|
val forceNetwork = networkRead && !diskRead
|
||||||
when {
|
when {
|
||||||
!networkRead && diskRead -> {
|
onlyCache -> {
|
||||||
request.cacheControl(CacheControl.FORCE_CACHE)
|
request.cacheControl(CacheControl.FORCE_CACHE)
|
||||||
}
|
}
|
||||||
networkRead && !diskRead -> if (options.diskCachePolicy.writeEnabled) {
|
forceNetwork -> if (options.diskCachePolicy.writeEnabled) {
|
||||||
request.cacheControl(CacheControl.FORCE_NETWORK)
|
request.cacheControl(CacheControl.FORCE_NETWORK)
|
||||||
} else {
|
} else {
|
||||||
request.cacheControl(CACHE_CONTROL_FORCE_NETWORK_NO_CACHE)
|
request.cacheControl(CACHE_CONTROL_FORCE_NETWORK_NO_CACHE)
|
||||||
}
|
}
|
||||||
!networkRead && !diskRead -> {
|
else -> {
|
||||||
// This causes the request to fail with a 504 Unsatisfiable Request.
|
// This causes the request to fail with a 504 Unsatisfiable Request.
|
||||||
request.cacheControl(CACHE_CONTROL_NO_NETWORK_NO_CACHE)
|
request.cacheControl(CACHE_CONTROL_NO_NETWORK_NO_CACHE)
|
||||||
}
|
}
|
||||||
|
@ -199,11 +189,11 @@ class MangaCoverFetcher(
|
||||||
private fun moveSnapshotToCoverCache(snapshot: DiskCache.Snapshot, cacheFile: File?): File? {
|
private fun moveSnapshotToCoverCache(snapshot: DiskCache.Snapshot, cacheFile: File?): File? {
|
||||||
if (cacheFile == null) return null
|
if (cacheFile == null) return null
|
||||||
return try {
|
return try {
|
||||||
diskCacheLazy.value.run {
|
imageLoader.diskCache?.run {
|
||||||
fileSystem.source(snapshot.data).use { input ->
|
fileSystem.source(snapshot.data).use { input ->
|
||||||
writeSourceToCoverCache(input, cacheFile)
|
writeSourceToCoverCache(input, cacheFile)
|
||||||
}
|
}
|
||||||
remove(diskCacheKey!!)
|
remove(diskCacheKey)
|
||||||
}
|
}
|
||||||
cacheFile.takeIf { it.exists() }
|
cacheFile.takeIf { it.exists() }
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
@ -240,27 +230,19 @@ class MangaCoverFetcher(
|
||||||
|
|
||||||
private fun readFromDiskCache(): DiskCache.Snapshot? {
|
private fun readFromDiskCache(): DiskCache.Snapshot? {
|
||||||
return if (options.diskCachePolicy.readEnabled) {
|
return if (options.diskCachePolicy.readEnabled) {
|
||||||
diskCacheLazy.value.openSnapshot(diskCacheKey!!)
|
imageLoader.diskCache?.openSnapshot(diskCacheKey)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun writeToDiskCache(
|
private fun writeToDiskCache(
|
||||||
snapshot: DiskCache.Snapshot?,
|
|
||||||
response: Response,
|
response: Response,
|
||||||
): DiskCache.Snapshot? {
|
): DiskCache.Snapshot? {
|
||||||
if (!options.diskCachePolicy.writeEnabled) {
|
val diskCache = imageLoader.diskCache
|
||||||
snapshot?.close()
|
val editor = diskCache?.openEditor(diskCacheKey) ?: return null
|
||||||
return null
|
|
||||||
}
|
|
||||||
val editor = if (snapshot != null) {
|
|
||||||
snapshot.closeAndOpenEditor()
|
|
||||||
} else {
|
|
||||||
diskCacheLazy.value.openEditor(diskCacheKey!!)
|
|
||||||
} ?: return null
|
|
||||||
try {
|
try {
|
||||||
diskCacheLazy.value.fileSystem.write(editor.data) {
|
diskCache.fileSystem.write(editor.data) {
|
||||||
response.body.source().readAll(this)
|
response.body.source().readAll(this)
|
||||||
}
|
}
|
||||||
return editor.commitAndOpenSnapshot()
|
return editor.commitAndOpenSnapshot()
|
||||||
|
@ -339,7 +321,6 @@ class MangaCoverFetcher(
|
||||||
|
|
||||||
class Factory(
|
class Factory(
|
||||||
private val callFactoryLazy: Lazy<Call.Factory>,
|
private val callFactoryLazy: Lazy<Call.Factory>,
|
||||||
private val diskCacheLazy: Lazy<DiskCache>,
|
|
||||||
) : Fetcher.Factory<Manga> {
|
) : Fetcher.Factory<Manga> {
|
||||||
|
|
||||||
private val coverCache: CoverCache by injectLazy()
|
private val coverCache: CoverCache by injectLazy()
|
||||||
|
@ -351,10 +332,10 @@ class MangaCoverFetcher(
|
||||||
sourceLazy = lazy { sourceManager.get(data.source) as? HttpSource },
|
sourceLazy = lazy { sourceManager.get(data.source) as? HttpSource },
|
||||||
options = options,
|
options = options,
|
||||||
callFactoryLazy = callFactoryLazy,
|
callFactoryLazy = callFactoryLazy,
|
||||||
diskCacheLazy = diskCacheLazy,
|
diskCacheKeyLazy = lazy { imageLoader.components.key(data, options)!! },
|
||||||
diskCacheKeyLazy = lazy { MangaCoverKeyer().key(data, options) },
|
|
||||||
coverFileLazy = lazy { coverCache.getCoverFile(data.thumbnail_url, !data.favorite) },
|
coverFileLazy = lazy { coverCache.getCoverFile(data.thumbnail_url, !data.favorite) },
|
||||||
customCoverFileLazy = lazy { coverCache.getCustomCoverFile(data) },
|
customCoverFileLazy = lazy { coverCache.getCustomCoverFile(data) },
|
||||||
|
imageLoader = imageLoader,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue