Disk cache for favicons

This commit is contained in:
Koitharu
2025-07-30 15:52:09 +03:00
parent dd77926dcb
commit 47f0bbee17
9 changed files with 139 additions and 46 deletions

View File

@@ -42,18 +42,21 @@ import org.koitharu.kotatsu.core.network.imageproxy.ImageProxyInterceptor
import org.koitharu.kotatsu.core.os.AppShortcutManager import org.koitharu.kotatsu.core.os.AppShortcutManager
import org.koitharu.kotatsu.core.os.NetworkState import org.koitharu.kotatsu.core.os.NetworkState
import org.koitharu.kotatsu.core.parser.MangaLoaderContextImpl import org.koitharu.kotatsu.core.parser.MangaLoaderContextImpl
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.parser.favicon.FaviconFetcher import org.koitharu.kotatsu.core.parser.favicon.FaviconFetcher
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.ui.image.CoilImageGetter import org.koitharu.kotatsu.core.ui.image.CoilImageGetter
import org.koitharu.kotatsu.core.ui.util.ActivityRecreationHandle import org.koitharu.kotatsu.core.ui.util.ActivityRecreationHandle
import org.koitharu.kotatsu.core.util.AcraScreenLogger import org.koitharu.kotatsu.core.util.AcraScreenLogger
import org.koitharu.kotatsu.core.util.FileSize
import org.koitharu.kotatsu.core.util.ext.connectivityManager import org.koitharu.kotatsu.core.util.ext.connectivityManager
import org.koitharu.kotatsu.core.util.ext.isLowRamDevice import org.koitharu.kotatsu.core.util.ext.isLowRamDevice
import org.koitharu.kotatsu.details.ui.pager.pages.MangaPageFetcher import org.koitharu.kotatsu.details.ui.pager.pages.MangaPageFetcher
import org.koitharu.kotatsu.details.ui.pager.pages.MangaPageKeyer import org.koitharu.kotatsu.details.ui.pager.pages.MangaPageKeyer
import org.koitharu.kotatsu.local.data.CacheDir import org.koitharu.kotatsu.local.data.CacheDir
import org.koitharu.kotatsu.local.data.FaviconCache
import org.koitharu.kotatsu.local.data.LocalStorageCache
import org.koitharu.kotatsu.local.data.LocalStorageChanges import org.koitharu.kotatsu.local.data.LocalStorageChanges
import org.koitharu.kotatsu.local.data.PageCache
import org.koitharu.kotatsu.local.domain.model.LocalManga import org.koitharu.kotatsu.local.domain.model.LocalManga
import org.koitharu.kotatsu.main.domain.CoverRestoreInterceptor import org.koitharu.kotatsu.main.domain.CoverRestoreInterceptor
import org.koitharu.kotatsu.main.ui.protect.AppProtectHelper import org.koitharu.kotatsu.main.ui.protect.AppProtectHelper
@@ -101,7 +104,7 @@ interface AppModule {
fun provideCoil( fun provideCoil(
@LocalizedAppContext context: Context, @LocalizedAppContext context: Context,
@MangaHttpClient okHttpClientProvider: Provider<OkHttpClient>, @MangaHttpClient okHttpClientProvider: Provider<OkHttpClient>,
mangaRepositoryFactory: MangaRepository.Factory, faviconFetcherFactory: FaviconFetcher.Factory,
imageProxyInterceptor: ImageProxyInterceptor, imageProxyInterceptor: ImageProxyInterceptor,
pageFetcherFactory: MangaPageFetcher.Factory, pageFetcherFactory: MangaPageFetcher.Factory,
coverRestoreInterceptor: CoverRestoreInterceptor, coverRestoreInterceptor: CoverRestoreInterceptor,
@@ -138,7 +141,7 @@ interface AppModule {
add(SvgDecoder.Factory()) add(SvgDecoder.Factory())
add(CbzFetcher.Factory()) add(CbzFetcher.Factory())
add(AvifImageDecoder.Factory()) add(AvifImageDecoder.Factory())
add(FaviconFetcher.Factory(mangaRepositoryFactory)) add(faviconFetcherFactory)
add(MangaPageKeyer()) add(MangaPageKeyer())
add(pageFetcherFactory) add(pageFetcherFactory)
add(imageProxyInterceptor) add(imageProxyInterceptor)
@@ -195,5 +198,29 @@ interface AppModule {
fun provideWorkManager( fun provideWorkManager(
@ApplicationContext context: Context, @ApplicationContext context: Context,
): WorkManager = WorkManager.getInstance(context) ): WorkManager = WorkManager.getInstance(context)
@Provides
@Singleton
@PageCache
fun providePageCache(
@ApplicationContext context: Context,
) = LocalStorageCache(
context = context,
dir = CacheDir.PAGES,
defaultSize = FileSize.MEGABYTES.convert(200, FileSize.BYTES),
minSize = FileSize.MEGABYTES.convert(20, FileSize.BYTES),
)
@Provides
@Singleton
@FaviconCache
fun provideFaviconCache(
@ApplicationContext context: Context,
) = LocalStorageCache(
context = context,
dir = CacheDir.FAVICONS,
defaultSize = FileSize.MEGABYTES.convert(8, FileSize.BYTES),
minSize = FileSize.MEGABYTES.convert(2, FileSize.BYTES),
)
} }
} }

View File

@@ -10,15 +10,20 @@ import coil3.ColorImage
import coil3.ImageLoader import coil3.ImageLoader
import coil3.asImage import coil3.asImage
import coil3.decode.DataSource import coil3.decode.DataSource
import coil3.decode.ImageSource
import coil3.fetch.FetchResult import coil3.fetch.FetchResult
import coil3.fetch.Fetcher import coil3.fetch.Fetcher
import coil3.fetch.ImageFetchResult import coil3.fetch.ImageFetchResult
import coil3.fetch.SourceFetchResult
import coil3.request.Options import coil3.request.Options
import coil3.size.pxOrElse import coil3.size.pxOrElse
import coil3.toAndroidUri import coil3.toAndroidUri
import coil3.toBitmap
import kotlinx.coroutines.ensureActive import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.runInterruptible import kotlinx.coroutines.runInterruptible
import okio.FileSystem
import okio.IOException import okio.IOException
import okio.Path.Companion.toOkioPath
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException import org.koitharu.kotatsu.core.exceptions.CloudFlareProtectedException
import org.koitharu.kotatsu.core.model.MangaSource import org.koitharu.kotatsu.core.model.MangaSource
@@ -26,8 +31,16 @@ import org.koitharu.kotatsu.core.parser.EmptyMangaRepository
import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.parser.ParserMangaRepository import org.koitharu.kotatsu.core.parser.ParserMangaRepository
import org.koitharu.kotatsu.core.parser.external.ExternalMangaRepository import org.koitharu.kotatsu.core.parser.external.ExternalMangaRepository
import org.koitharu.kotatsu.core.util.MimeTypes
import org.koitharu.kotatsu.core.util.ext.fetch import org.koitharu.kotatsu.core.util.ext.fetch
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import org.koitharu.kotatsu.core.util.ext.toMimeTypeOrNull
import org.koitharu.kotatsu.local.data.FaviconCache
import org.koitharu.kotatsu.local.data.LocalMangaRepository import org.koitharu.kotatsu.local.data.LocalMangaRepository
import org.koitharu.kotatsu.local.data.LocalStorageCache
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import java.io.File
import javax.inject.Inject
import kotlin.coroutines.coroutineContext import kotlin.coroutines.coroutineContext
import coil3.Uri as CoilUri import coil3.Uri as CoilUri
@@ -36,6 +49,7 @@ class FaviconFetcher(
private val options: Options, private val options: Options,
private val imageLoader: ImageLoader, private val imageLoader: ImageLoader,
private val mangaRepositoryFactory: MangaRepository.Factory, private val mangaRepositoryFactory: MangaRepository.Factory,
private val localStorageCache: LocalStorageCache,
) : Fetcher { ) : Fetcher {
override suspend fun fetch(): FetchResult? { override suspend fun fetch(): FetchResult? {
@@ -61,6 +75,16 @@ class FaviconFetcher(
options.size.width.pxOrElse { FALLBACK_SIZE }, options.size.width.pxOrElse { FALLBACK_SIZE },
options.size.height.pxOrElse { FALLBACK_SIZE }, options.size.height.pxOrElse { FALLBACK_SIZE },
) )
val cacheKey = options.diskCacheKey ?: "${repository.source.name}_$sizePx"
if (options.diskCachePolicy.readEnabled) {
localStorageCache[cacheKey]?.let { file ->
return SourceFetchResult(
source = ImageSource(file.toOkioPath(), FileSystem.SYSTEM),
mimeType = MimeTypes.probeMimeType(file)?.toString(),
dataSource = DataSource.DISK,
)
}
}
var favicons = repository.getFavicons() var favicons = repository.getFavicons()
var lastError: Exception? = null var lastError: Exception? = null
while (favicons.isNotEmpty()) { while (favicons.isNotEmpty()) {
@@ -69,7 +93,11 @@ class FaviconFetcher(
try { try {
val result = imageLoader.fetch(icon.url, options) val result = imageLoader.fetch(icon.url, options)
if (result != null) { if (result != null) {
return result return if (options.diskCachePolicy.writeEnabled) {
writeToCache(cacheKey, result)
} else {
result
}
} else { } else {
favicons -= icon favicons -= icon
} }
@@ -97,8 +125,39 @@ class FaviconFetcher(
) )
} }
class Factory( private suspend fun writeToCache(key: String, result: FetchResult): FetchResult = runCatchingCancellable {
when (result) {
is ImageFetchResult -> {
if (result.dataSource == DataSource.NETWORK) {
localStorageCache.set(key, result.image.toBitmap()).asFetchResult()
} else {
result
}
}
is SourceFetchResult -> {
if (result.dataSource == DataSource.NETWORK) {
result.source.source().use {
localStorageCache.set(key, it, result.mimeType?.toMimeTypeOrNull()).asFetchResult()
}
} else {
result
}
}
}
}.onFailure {
it.printStackTraceDebug()
}.getOrDefault(result)
private fun File.asFetchResult() = SourceFetchResult(
source = ImageSource(toOkioPath(), FileSystem.SYSTEM),
mimeType = MimeTypes.probeMimeType(this)?.toString(),
dataSource = DataSource.DISK,
)
class Factory @Inject constructor(
private val mangaRepositoryFactory: MangaRepository.Factory, private val mangaRepositoryFactory: MangaRepository.Factory,
@FaviconCache private val faviconCache: LocalStorageCache,
) : Fetcher.Factory<CoilUri> { ) : Fetcher.Factory<CoilUri> {
override fun create( override fun create(
@@ -106,7 +165,7 @@ class FaviconFetcher(
options: Options, options: Options,
imageLoader: ImageLoader imageLoader: ImageLoader
): Fetcher? = if (data.scheme == URI_SCHEME_FAVICON) { ): Fetcher? = if (data.scheme == URI_SCHEME_FAVICON) {
FaviconFetcher(data.toAndroidUri(), options, imageLoader, mangaRepositoryFactory) FaviconFetcher(data.toAndroidUri(), options, imageLoader, mangaRepositoryFactory, faviconCache)
} else { } else {
null null
} }

View File

@@ -24,7 +24,8 @@ import org.koitharu.kotatsu.core.util.MimeTypes
import org.koitharu.kotatsu.core.util.ext.fetch import org.koitharu.kotatsu.core.util.ext.fetch
import org.koitharu.kotatsu.core.util.ext.isNetworkUri import org.koitharu.kotatsu.core.util.ext.isNetworkUri
import org.koitharu.kotatsu.core.util.ext.toMimeTypeOrNull import org.koitharu.kotatsu.core.util.ext.toMimeTypeOrNull
import org.koitharu.kotatsu.local.data.PagesCache import org.koitharu.kotatsu.local.data.LocalStorageCache
import org.koitharu.kotatsu.local.data.PageCache
import org.koitharu.kotatsu.parsers.model.MangaPage import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.util.mimeType import org.koitharu.kotatsu.parsers.util.mimeType
import org.koitharu.kotatsu.parsers.util.requireBody import org.koitharu.kotatsu.parsers.util.requireBody
@@ -34,7 +35,7 @@ import javax.inject.Inject
class MangaPageFetcher( class MangaPageFetcher(
private val okHttpClient: OkHttpClient, private val okHttpClient: OkHttpClient,
private val pagesCache: PagesCache, private val pagesCache: LocalStorageCache,
private val options: Options, private val options: Options,
private val page: MangaPage, private val page: MangaPage,
private val mangaRepositoryFactory: MangaRepository.Factory, private val mangaRepositoryFactory: MangaRepository.Factory,
@@ -53,7 +54,7 @@ class MangaPageFetcher(
val repo = mangaRepositoryFactory.create(page.source) val repo = mangaRepositoryFactory.create(page.source)
val pageUrl = repo.getPageUrl(page) val pageUrl = repo.getPageUrl(page)
if (options.diskCachePolicy.readEnabled) { if (options.diskCachePolicy.readEnabled) {
pagesCache.get(pageUrl)?.let { file -> pagesCache[pageUrl]?.let { file ->
return SourceFetchResult( return SourceFetchResult(
source = ImageSource(file.toOkioPath(), options.fileSystem), source = ImageSource(file.toOkioPath(), options.fileSystem),
mimeType = MimeTypes.getMimeTypeFromExtension(file.name)?.toString(), mimeType = MimeTypes.getMimeTypeFromExtension(file.name)?.toString(),
@@ -78,7 +79,7 @@ class MangaPageFetcher(
} }
val mimeType = response.mimeType?.toMimeTypeOrNull() val mimeType = response.mimeType?.toMimeTypeOrNull()
val file = response.requireBody().use { val file = response.requireBody().use {
pagesCache.put(pageUrl, it.source(), mimeType) pagesCache.set(pageUrl, it.source(), mimeType)
} }
SourceFetchResult( SourceFetchResult(
source = ImageSource(file.toOkioPath(), FileSystem.SYSTEM), source = ImageSource(file.toOkioPath(), FileSystem.SYSTEM),
@@ -107,7 +108,7 @@ class MangaPageFetcher(
class Factory @Inject constructor( class Factory @Inject constructor(
@MangaHttpClient private val okHttpClient: OkHttpClient, @MangaHttpClient private val okHttpClient: OkHttpClient,
private val pagesCache: PagesCache, @PageCache private val pagesCache: LocalStorageCache,
private val mangaRepositoryFactory: MangaRepository.Factory, private val mangaRepositoryFactory: MangaRepository.Factory,
private val imageProxyInterceptor: ImageProxyInterceptor, private val imageProxyInterceptor: ImageProxyInterceptor,
) : Fetcher.Factory<MangaPage> { ) : Fetcher.Factory<MangaPage> {

View File

@@ -76,8 +76,9 @@ import org.koitharu.kotatsu.core.util.progress.RealtimeEtaEstimator
import org.koitharu.kotatsu.download.domain.DownloadProgress import org.koitharu.kotatsu.download.domain.DownloadProgress
import org.koitharu.kotatsu.download.domain.DownloadState import org.koitharu.kotatsu.download.domain.DownloadState
import org.koitharu.kotatsu.local.data.LocalMangaRepository import org.koitharu.kotatsu.local.data.LocalMangaRepository
import org.koitharu.kotatsu.local.data.LocalStorageCache
import org.koitharu.kotatsu.local.data.LocalStorageChanges import org.koitharu.kotatsu.local.data.LocalStorageChanges
import org.koitharu.kotatsu.local.data.PagesCache import org.koitharu.kotatsu.local.data.PageCache
import org.koitharu.kotatsu.local.data.TempFileFilter import org.koitharu.kotatsu.local.data.TempFileFilter
import org.koitharu.kotatsu.local.data.input.LocalMangaParser import org.koitharu.kotatsu.local.data.input.LocalMangaParser
import org.koitharu.kotatsu.local.data.output.LocalMangaOutput import org.koitharu.kotatsu.local.data.output.LocalMangaOutput
@@ -103,7 +104,7 @@ class DownloadWorker @AssistedInject constructor(
@Assisted appContext: Context, @Assisted appContext: Context,
@Assisted params: WorkerParameters, @Assisted params: WorkerParameters,
@MangaHttpClient private val okHttp: OkHttpClient, @MangaHttpClient private val okHttp: OkHttpClient,
private val cache: PagesCache, @PageCache private val cache: LocalStorageCache,
private val localMangaRepository: LocalMangaRepository, private val localMangaRepository: LocalMangaRepository,
private val mangaLock: MangaLock, private val mangaLock: MangaLock,
private val mangaDataRepository: MangaDataRepository, private val mangaDataRepository: MangaDataRepository,
@@ -233,7 +234,7 @@ class DownloadWorker @AssistedInject constructor(
semaphore.withPermit { semaphore.withPermit {
runFailsafe { runFailsafe {
val url = repo.getPageUrl(page) val url = repo.getPageUrl(page)
val file = cache.get(url) val file = cache[url]
?: downloadFile(url, destination, repo.source) ?: downloadFile(url, destination, repo.source)
output.addPage( output.addPage(
chapter = chapter, chapter = chapter,

View File

@@ -0,0 +1,11 @@
package org.koitharu.kotatsu.local.data
import javax.inject.Qualifier
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class PageCache
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class FaviconCache

View File

@@ -5,7 +5,6 @@ import android.graphics.Bitmap
import android.os.StatFs import android.os.StatFs
import android.webkit.MimeTypeMap import android.webkit.MimeTypeMap
import com.tomclaw.cache.DiskLruCache import com.tomclaw.cache.DiskLruCache
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runInterruptible import kotlinx.coroutines.runInterruptible
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@@ -14,7 +13,6 @@ import okio.buffer
import okio.sink import okio.sink
import okio.use import okio.use
import org.koitharu.kotatsu.core.exceptions.NoDataReceivedException import org.koitharu.kotatsu.core.exceptions.NoDataReceivedException
import org.koitharu.kotatsu.core.util.FileSize
import org.koitharu.kotatsu.core.util.MimeTypes import org.koitharu.kotatsu.core.util.MimeTypes
import org.koitharu.kotatsu.core.util.ext.MimeType import org.koitharu.kotatsu.core.util.ext.MimeType
import org.koitharu.kotatsu.core.util.ext.compressToPNG import org.koitharu.kotatsu.core.util.ext.compressToPNG
@@ -28,22 +26,24 @@ import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
import org.koitharu.kotatsu.parsers.util.suspendlazy.suspendLazy import org.koitharu.kotatsu.parsers.util.suspendlazy.suspendLazy
import java.io.File import java.io.File
import java.util.UUID import java.util.UUID
import javax.inject.Inject
import javax.inject.Singleton
@Singleton class LocalStorageCache(
class PagesCache @Inject constructor(@ApplicationContext context: Context) { context: Context,
private val dir: CacheDir,
private val defaultSize: Long,
private val minSize: Long,
) {
private val cacheDir = suspendLazy { private val cacheDir = suspendLazy {
val dirs = context.externalCacheDirs + context.cacheDir val dirs = context.externalCacheDirs + context.cacheDir
dirs.firstNotNullOf { dirs.firstNotNullOf {
it?.subdir(CacheDir.PAGES.dir)?.takeIfWriteable() it?.subdir(dir.dir)?.takeIfWriteable()
} }
} }
private val lruCache = suspendLazy { private val lruCache = suspendLazy {
val dir = cacheDir.get() val dir = cacheDir.get()
val availableSize = (getAvailableSize() * 0.8).toLong() val availableSize = (getAvailableSize() * 0.8).toLong()
val size = SIZE_DEFAULT.coerceAtMost(availableSize).coerceAtLeast(SIZE_MIN) val size = defaultSize.coerceAtMost(availableSize).coerceAtLeast(minSize)
runCatchingCancellable { runCatchingCancellable {
DiskLruCache.create(dir, size) DiskLruCache.create(dir, size)
}.recoverCatching { error -> }.recoverCatching { error ->
@@ -54,14 +54,14 @@ class PagesCache @Inject constructor(@ApplicationContext context: Context) {
}.getOrThrow() }.getOrThrow()
} }
suspend fun get(url: String): File? = withContext(Dispatchers.IO) { suspend operator fun get(url: String): File? = withContext(Dispatchers.IO) {
val cache = lruCache.get() val cache = lruCache.get()
runInterruptible { runInterruptible {
cache.get(url)?.takeIfReadable() cache.get(url)?.takeIfReadable()
} }
} }
suspend fun put(url: String, source: Source, mimeType: MimeType?): File = withContext(Dispatchers.IO) { suspend operator fun set(url: String, source: Source, mimeType: MimeType?): File = withContext(Dispatchers.IO) {
val file = createBufferFile(url, mimeType) val file = createBufferFile(url, mimeType)
try { try {
val bytes = file.sink(append = false).buffer().use { val bytes = file.sink(append = false).buffer().use {
@@ -79,7 +79,7 @@ class PagesCache @Inject constructor(@ApplicationContext context: Context) {
} }
} }
suspend fun put(url: String, bitmap: Bitmap): File = withContext(Dispatchers.IO) { suspend operator fun set(url: String, bitmap: Bitmap): File = withContext(Dispatchers.IO) {
val file = createBufferFile(url, MimeType("image/png")) val file = createBufferFile(url, MimeType("image/png"))
try { try {
bitmap.compressToPNG(file) bitmap.compressToPNG(file)
@@ -107,7 +107,7 @@ class PagesCache @Inject constructor(@ApplicationContext context: Context) {
} }
}.onFailure { }.onFailure {
it.printStackTraceDebug() it.printStackTraceDebug()
}.getOrDefault(SIZE_DEFAULT) }.getOrDefault(defaultSize)
private suspend fun createBufferFile(url: String, mimeType: MimeType?): File { private suspend fun createBufferFile(url: String, mimeType: MimeType?): File {
val ext = MimeTypes.getExtension(mimeType) ?: MimeTypeMap.getFileExtensionFromUrl(url).ifNullOrEmpty { "dat" } val ext = MimeTypes.getExtension(mimeType) ?: MimeTypeMap.getFileExtensionFromUrl(url).ifNullOrEmpty { "dat" }
@@ -116,13 +116,4 @@ class PagesCache @Inject constructor(@ApplicationContext context: Context) {
val name = UUID.randomUUID().toString() + "." + ext val name = UUID.randomUUID().toString() + "." + ext
return File(rootDir, name) return File(rootDir, name)
} }
private companion object {
val SIZE_MIN
get() = FileSize.MEGABYTES.convert(20, FileSize.BYTES)
val SIZE_DEFAULT
get() = FileSize.MEGABYTES.convert(200, FileSize.BYTES)
}
} }

View File

@@ -65,7 +65,8 @@ import org.koitharu.kotatsu.core.util.ext.use
import org.koitharu.kotatsu.core.util.ext.withProgress import org.koitharu.kotatsu.core.util.ext.withProgress
import org.koitharu.kotatsu.core.util.progress.ProgressDeferred import org.koitharu.kotatsu.core.util.progress.ProgressDeferred
import org.koitharu.kotatsu.download.ui.worker.DownloadSlowdownDispatcher import org.koitharu.kotatsu.download.ui.worker.DownloadSlowdownDispatcher
import org.koitharu.kotatsu.local.data.PagesCache import org.koitharu.kotatsu.local.data.LocalStorageCache
import org.koitharu.kotatsu.local.data.PageCache
import org.koitharu.kotatsu.parsers.model.MangaPage import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.util.requireBody import org.koitharu.kotatsu.parsers.util.requireBody
@@ -84,7 +85,7 @@ class PageLoader @Inject constructor(
@LocalizedAppContext private val context: Context, @LocalizedAppContext private val context: Context,
lifecycle: ActivityRetainedLifecycle, lifecycle: ActivityRetainedLifecycle,
@MangaHttpClient private val okHttp: OkHttpClient, @MangaHttpClient private val okHttp: OkHttpClient,
private val cache: PagesCache, @PageCache private val cache: LocalStorageCache,
private val coil: ImageLoader, private val coil: ImageLoader,
private val settings: AppSettings, private val settings: AppSettings,
private val mangaRepositoryFactory: MangaRepository.Factory, private val mangaRepositoryFactory: MangaRepository.Factory,
@@ -196,7 +197,7 @@ class PageLoader @Inject constructor(
} }
} }
}.use { image -> }.use { image ->
cache.put(uri.toString(), image).toUri() cache.set(uri.toString(), image).toUri()
} }
} else { } else {
val file = uri.toFile() val file = uri.toFile()
@@ -300,7 +301,7 @@ class PageLoader @Inject constructor(
val request = createPageRequest(pageUrl, page.source) val request = createPageRequest(pageUrl, page.source)
imageProxyInterceptor.interceptPageRequest(request, okHttp).ensureSuccess().use { response -> imageProxyInterceptor.interceptPageRequest(request, okHttp).ensureSuccess().use { response ->
response.requireBody().withProgress(progress).use { response.requireBody().withProgress(progress).use {
cache.put(pageUrl, it.source(), it.contentType()?.toMimeType()) cache.set(pageUrl, it.source(), it.contentType()?.toMimeType())
} }
}.toUri() }.toUri()
} }

View File

@@ -85,7 +85,7 @@ class StorageManageSettingsFragment : BasePreferenceFragment(R.string.storage_us
} }
AppSettings.KEY_THUMBS_CACHE_CLEAR -> { AppSettings.KEY_THUMBS_CACHE_CLEAR -> {
viewModel.clearCache(preference.key, CacheDir.THUMBS) viewModel.clearCache(preference.key, CacheDir.THUMBS, CacheDir.FAVICONS)
true true
} }

View File

@@ -82,16 +82,18 @@ class StorageManageSettingsViewModel @Inject constructor(
loadStorageUsage() loadStorageUsage()
} }
fun clearCache(key: String, cache: CacheDir) { fun clearCache(key: String, vararg caches: CacheDir) {
launchJob(Dispatchers.Default) { launchJob(Dispatchers.Default) {
try { try {
loadingKeys.update { it + key } loadingKeys.update { it + key }
storageManager.clearCache(cache) for (cache in caches) {
checkNotNull(cacheSizes[cache]).value = storageManager.computeCacheSize(cache) storageManager.clearCache(cache)
loadStorageUsage() checkNotNull(cacheSizes[cache]).value = storageManager.computeCacheSize(cache)
if (cache == CacheDir.THUMBS || cache == CacheDir.FAVICONS) { if (cache == CacheDir.THUMBS) {
coil.memoryCache?.clear() coil.memoryCache?.clear()
}
} }
loadStorageUsage()
} finally { } finally {
loadingKeys.update { it - key } loadingKeys.update { it - key }
} }