refactor(cover): Adjust cover (memory) cache key

Hopefully fix library image flickering on resume/bind
This commit is contained in:
Ahmad Ansori Palembani 2024-08-17 11:08:50 +07:00
parent df66327996
commit 653b2d7839
Signed by: null2264
GPG key ID: BA64F8B60AF3EFB6
10 changed files with 62 additions and 48 deletions

View file

@ -53,6 +53,7 @@ import eu.kanade.tachiyomi.util.system.AuthenticatorUtil
import eu.kanade.tachiyomi.util.system.launchIO
import eu.kanade.tachiyomi.util.system.localeContext
import eu.kanade.tachiyomi.util.system.notification
import java.security.Security
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@ -69,7 +70,6 @@ import yokai.core.migration.migrations.migrations
import yokai.domain.base.BasePreferences
import yokai.i18n.MR
import yokai.util.lang.getString
import java.security.Security
open class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factory {
@ -251,7 +251,7 @@ open class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.F
}
diskCache(diskCacheLazy::value)
// memoryCache { MemoryCache.Builder().maxSizePercent(this@App, 0.40).build() }
//memoryCache { MemoryCache.Builder().maxSizePercent(this@App, 0.40).build() }
crossfade(true)
allowRgb565(this@App.getSystemService<ActivityManager>()!!.isLowRamDevice)
allowHardware(true)

View file

@ -6,7 +6,6 @@ import co.touchlab.kermit.Logger
import coil3.imageLoader
import coil3.memory.MemoryCache
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.MangaImpl
import eu.kanade.tachiyomi.domain.manga.models.Manga
import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.system.e
@ -14,16 +13,16 @@ import eu.kanade.tachiyomi.util.system.executeOnIO
import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.system.withIOContext
import eu.kanade.tachiyomi.util.system.withUIContext
import java.io.File
import java.io.IOException
import java.io.InputStream
import java.util.concurrent.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import yokai.i18n.MR
import yokai.util.lang.getString
import java.io.File
import java.io.IOException
import java.io.InputStream
import java.util.concurrent.*
/**
* Class used to create cover cache.
@ -171,7 +170,7 @@ class CoverCache(val context: Context) {
fun setCustomCoverToCache(manga: Manga, inputStream: InputStream) {
getCustomCoverFile(manga).outputStream().use {
inputStream.copyTo(it)
context.imageLoader.memoryCache?.remove(MemoryCache.Key(manga.key()))
removeFromMemory(manga, true)
}
}
@ -185,7 +184,7 @@ class CoverCache(val context: Context) {
val result = getCustomCoverFile(manga).let {
it.exists() && it.delete()
}
context.imageLoader.memoryCache?.remove(MemoryCache.Key(manga.key()))
removeFromMemory(manga, true)
return result
}
@ -195,18 +194,28 @@ class CoverCache(val context: Context) {
* @param thumbnailUrl the thumbnail url.
* @return cover image.
*/
fun getCoverFile(manga: Manga): File {
val hashKey = DiskUtil.hashKeyForDisk((manga.thumbnail_url.orEmpty()))
return if (manga.favorite) {
File(cacheDir, hashKey)
} else {
File(onlineCoverDirectory, hashKey)
fun getCoverFile(mangaThumbnailUrl: String?, isOnline: Boolean = false): File? {
return mangaThumbnailUrl?.let {
File(if (!isOnline) cacheDir else onlineCoverDirectory, DiskUtil.hashKeyForDisk(it))
}
}
fun removeFromMemory(manga: Manga, custom: Boolean = false) {
if (custom) {
context.imageLoader.memoryCache?.remove(MemoryCache.Key(manga.key()))
return
}
manga.thumbnail_url?.let {
if (it.isEmpty()) return
context.imageLoader.memoryCache
?.remove(MemoryCache.Key(if (!manga.favorite) it else DiskUtil.hashKeyForDisk(it)))
}
}
fun deleteFromCache(name: String?) {
if (name.isNullOrEmpty()) return
val file = getCoverFile(MangaImpl().apply { thumbnail_url = name })
val file = getCoverFile(name, true) ?: return
context.imageLoader.memoryCache?.remove(MemoryCache.Key(file.name))
if (file.exists()) file.delete()
}
@ -225,12 +234,11 @@ class CoverCache(val context: Context) {
if (manga.thumbnail_url.isNullOrEmpty()) return
// Remove file
val file = getCoverFile(manga)
if (deleteCustom) deleteCustomCover(manga)
if (file.exists()) {
context.imageLoader.memoryCache?.remove(MemoryCache.Key(manga.key()))
file.delete()
getCoverFile(manga.thumbnail_url, !manga.favorite)?.let {
removeFromMemory(manga)
it.delete()
}
if (deleteCustom) deleteCustomCover(manga)
}
private fun getCacheDir(dir: String): File {

View file

@ -6,7 +6,6 @@ import androidx.palette.graphics.Palette
import coil3.Image
import coil3.ImageLoader
import coil3.imageLoader
import coil3.memory.MemoryCache
import coil3.request.Disposable
import coil3.request.ImageRequest
import coil3.target.ImageViewTarget
@ -26,15 +25,15 @@ class LibraryMangaImageTarget(
super.onError(error)
if (manga.favorite) {
launchIO {
val file = coverCache.getCoverFile(manga)
val file = coverCache.getCoverFile(manga.thumbnail_url, false)
// if the file exists and the there was still an error then the file is corrupted
if (file.exists()) {
if (file != null && file.exists()) {
val options = BitmapFactory.Options()
options.inJustDecodeBounds = true
BitmapFactory.decodeFile(file.path, options)
if (options.outWidth == -1 || options.outHeight == -1) {
coverCache.removeFromMemory(manga)
file.delete()
view.context.imageLoader.memoryCache?.remove(MemoryCache.Key(manga.key()))
}
}
}
@ -52,7 +51,6 @@ inline fun ImageView.loadManga(
.data(manga)
.target(LibraryMangaImageTarget(this, manga))
.apply(builder)
.memoryCacheKey(manga.key())
.build()
return imageLoader.enqueue(request)
}

View file

@ -87,8 +87,8 @@ class MangaCoverFetcher(
val customCoverLoader = tryCustomCover()
if (customCoverLoader != null) return customCoverLoader
}
val coverFile = coverCache.getCoverFile(manga)
if (!shouldFetchRemotely && coverFile.exists() && options.diskCachePolicy.readEnabled) {
val coverFile = coverCache.getCoverFile(manga.thumbnail_url, !manga.favorite)
if (!shouldFetchRemotely && coverFile != null && coverFile.exists() && options.diskCachePolicy.readEnabled) {
if (!manga.favorite) {
coverFile.setLastModified(Date().time)
}

View file

@ -2,12 +2,16 @@ package eu.kanade.tachiyomi.data.coil
import coil3.key.Keyer
import coil3.request.Options
import eu.kanade.tachiyomi.data.database.models.hasCustomCover
import eu.kanade.tachiyomi.domain.manga.models.Manga
import eu.kanade.tachiyomi.util.storage.DiskUtil
class MangaCoverKeyer : Keyer<Manga> {
override fun key(data: Manga, options: Options): String? {
if (data.thumbnail_url.isNullOrBlank()) return null
val hasCustomCover by lazy { data.hasCustomCover() }
if (data.thumbnail_url.isNullOrBlank() && !hasCustomCover) return null
if (hasCustomCover) return data.key()
return if (!data.favorite) {
data.thumbnail_url!!
} else {

View file

@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.data.database.models
import android.content.Context
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.domain.manga.models.Manga
import eu.kanade.tachiyomi.domain.manga.models.Manga.Companion.TYPE_COMIC
@ -14,6 +15,7 @@ import eu.kanade.tachiyomi.ui.reader.settings.OrientationType
import eu.kanade.tachiyomi.ui.reader.settings.ReadingModeType
import eu.kanade.tachiyomi.util.manga.MangaCoverMetadata
import eu.kanade.tachiyomi.util.system.withIOContext
import java.util.Locale
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
@ -21,7 +23,6 @@ import yokai.data.updateStrategyAdapter
import yokai.domain.chapter.interactor.GetChapter
import yokai.i18n.MR
import yokai.util.lang.getString
import java.util.*
fun Manga.sortDescending(preferences: PreferencesHelper): Boolean =
if (usesLocalSort) sortDescending else preferences.chaptersDescAsDefault().get()
@ -221,3 +222,7 @@ fun Manga.Companion.mapper(
this.filtered_scanlators = filteredScanlators
this.update_strategy = updateStrategy.let(updateStrategyAdapter::decode)
}
fun Manga.hasCustomCover(coverCache: CoverCache = Injekt.get()): Boolean {
return coverCache.getCustomCoverFile(this).exists()
}

View file

@ -1646,8 +1646,8 @@ class LibraryPresenter(
libraryManga.forEach { manga ->
if (manga.id == null) return@forEach
if (manga.thumbnail_url?.startsWith("custom", ignoreCase = true) == true) {
val file = cc.getCoverFile(manga)
if (file.exists()) {
val file = cc.getCoverFile(manga.thumbnail_url, !manga.favorite)
if (file != null && file.exists()) {
file.renameTo(cc.getCustomCoverFile(manga))
}
manga.thumbnail_url =

View file

@ -136,6 +136,11 @@ import eu.kanade.tachiyomi.util.view.snack
import eu.kanade.tachiyomi.util.view.toolbarHeight
import eu.kanade.tachiyomi.util.view.withFadeTransaction
import eu.kanade.tachiyomi.widget.LinearLayoutManagerAccurateOffset
import java.io.File
import java.io.IOException
import java.util.Locale
import kotlin.math.max
import kotlin.math.roundToInt
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeoutOrNull
import uy.kohesive.injekt.Injekt
@ -143,11 +148,6 @@ import uy.kohesive.injekt.api.get
import yokai.i18n.MR
import yokai.presentation.core.Constants
import yokai.util.lang.getString
import java.io.File
import java.io.IOException
import java.util.*
import kotlin.math.max
import kotlin.math.roundToInt
import android.R as AR
class MangaDetailsController :
@ -580,7 +580,6 @@ class MangaDetailsController :
val view = view ?: return
val request = ImageRequest.Builder(view.context).data(presenter.manga).allowHardware(false)
.memoryCacheKey(presenter.manga.key())
.target(
onSuccess = { image ->
val drawable = image.asDrawable(view.context.resources)
@ -619,8 +618,8 @@ class MangaDetailsController :
getHeader()?.updateCover(manga!!)
},
onError = {
val file = presenter.coverCache.getCoverFile(manga!!)
if (file.exists()) {
val file = presenter.coverCache.getCoverFile(manga!!.thumbnail_url, !manga!!.favorite)
if (file != null && file.exists()) {
file.delete()
setPaletteColor()
}

View file

@ -5,7 +5,6 @@ import android.graphics.Bitmap
import android.net.Uri
import androidx.core.net.toFile
import coil3.imageLoader
import coil3.memory.MemoryCache
import coil3.request.CachePolicy
import coil3.request.ImageRequest
import coil3.request.SuccessResult
@ -21,6 +20,7 @@ import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.database.models.bookmarkedFilter
import eu.kanade.tachiyomi.data.database.models.chapterOrder
import eu.kanade.tachiyomi.data.database.models.downloadedFilter
import eu.kanade.tachiyomi.data.database.models.hasCustomCover
import eu.kanade.tachiyomi.data.database.models.readFilter
import eu.kanade.tachiyomi.data.database.models.sortDescending
import eu.kanade.tachiyomi.data.download.DownloadManager
@ -403,7 +403,7 @@ class MangaDetailsPresenter(
.build()
if (preferences.context.imageLoader.execute(request) is SuccessResult) {
preferences.context.imageLoader.memoryCache?.remove(MemoryCache.Key(manga.key()))
coverCache.removeFromMemory(manga, manga.hasCustomCover(coverCache))
withContext(Dispatchers.Main) {
view?.setPaletteColor()
}
@ -920,8 +920,8 @@ class MangaDetailsPresenter(
}
private fun saveCover(directory: UniFile): UniFile {
val cover = coverCache.getCustomCoverFile(manga).takeIf { it.exists() } ?: coverCache.getCoverFile(manga)
val type = ImageUtil.findImageType(cover.inputStream())
val cover = coverCache.getCustomCoverFile(manga).takeIf { it.exists() } ?: coverCache.getCoverFile(manga.thumbnail_url, !manga.favorite)
val type = cover?.let { ImageUtil.findImageType(it.inputStream()) }
?: throw Exception("Not an image")
// Build destination file.

View file

@ -8,9 +8,9 @@ import eu.kanade.tachiyomi.data.coil.getBestColor
import eu.kanade.tachiyomi.data.database.models.dominantCoverColors
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.domain.manga.models.Manga
import uy.kohesive.injekt.injectLazy
import java.io.File
import java.util.concurrent.*
import java.util.concurrent.ConcurrentHashMap
import uy.kohesive.injekt.injectLazy
/** Object that holds info about a covers size ratio + dominant colors */
object MangaCoverMetadata {
@ -54,9 +54,9 @@ object MangaCoverMetadata {
remove(manga)
}
if (manga.vibrantCoverColor != null && !manga.favorite) return
val file = ogFile ?: coverCache.getCustomCoverFile(manga).takeIf { it.exists() } ?: coverCache.getCoverFile(manga)
val file = ogFile ?: coverCache.getCustomCoverFile(manga).takeIf { it.exists() } ?: coverCache.getCoverFile(manga.thumbnail_url, !manga.favorite)
// if the file exists and the there was still an error then the file is corrupted
if (file.exists()) {
if (file != null && file.exists()) {
val options = BitmapFactory.Options()
val hasVibrantColor = if (manga.favorite) manga.vibrantCoverColor != null else true
if (manga.dominantCoverColors != null && hasVibrantColor && !force) {