diff --git a/app/src/main/java/org/koitharu/kotatsu/download/domain/DownloadManager.kt b/app/src/main/java/org/koitharu/kotatsu/download/domain/DownloadManager.kt index a0a2da35e..6b14c5bc0 100644 --- a/app/src/main/java/org/koitharu/kotatsu/download/domain/DownloadManager.kt +++ b/app/src/main/java/org/koitharu/kotatsu/download/domain/DownloadManager.kt @@ -138,7 +138,7 @@ class DownloadManager @AssistedInject constructor( for ((pageIndex, page) in pages.withIndex()) { runFailsafe(outState, pausingHandle) { val url = repo.getPageUrl(page) - val file = cache[url] + val file = cache.get(url) ?: downloadFile(url, page.referer, destination, tempFileName, repo.source) output.addPage( chapter = chapter, diff --git a/app/src/main/java/org/koitharu/kotatsu/local/data/PagesCache.kt b/app/src/main/java/org/koitharu/kotatsu/local/data/PagesCache.kt index 095a55b76..6b0d55a47 100644 --- a/app/src/main/java/org/koitharu/kotatsu/local/data/PagesCache.kt +++ b/app/src/main/java/org/koitharu/kotatsu/local/data/PagesCache.kt @@ -30,8 +30,8 @@ class PagesCache @Inject constructor(@ApplicationContext context: Context) { size = FileSize.MEGABYTES.convert(200, FileSize.BYTES), ) - operator fun get(url: String): File? { - return lruCache.get(url)?.takeIfReadable() + suspend fun get(url: String): File? = withContext(Dispatchers.IO) { + lruCache.get(url)?.takeIfReadable() } suspend fun put(url: String, inputStream: InputStream): File = withContext(Dispatchers.IO) { diff --git a/app/src/main/java/org/koitharu/kotatsu/reader/domain/PageLoader.kt b/app/src/main/java/org/koitharu/kotatsu/reader/domain/PageLoader.kt index 3fa7c48dd..496c0270e 100644 --- a/app/src/main/java/org/koitharu/kotatsu/reader/domain/PageLoader.kt +++ b/app/src/main/java/org/koitharu/kotatsu/reader/domain/PageLoader.kt @@ -5,7 +5,6 @@ import android.graphics.BitmapFactory import android.net.Uri import androidx.collection.LongSparseArray import androidx.collection.set -import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -13,7 +12,7 @@ import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.async import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch import kotlinx.coroutines.runInterruptible import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -54,11 +53,11 @@ class PageLoader @Inject constructor( private val tasks = LongSparseArray>() private val convertLock = Mutex() + private val prefetchLock = Mutex() private var repository: MangaRepository? = null private val prefetchQueue = LinkedList() private val counter = AtomicInteger(0) private var prefetchQueueLimit = PREFETCH_LIMIT_DEFAULT // TODO adaptive - private val emptyProgressFlow: StateFlow = MutableStateFlow(-1f) override fun close() { loaderScope.cancel() @@ -71,8 +70,8 @@ class PageLoader @Inject constructor( return repository is RemoteMangaRepository && settings.isPagesPreloadEnabled() } - fun prefetch(pages: List) { - synchronized(prefetchQueue) { + fun prefetch(pages: List) = loaderScope.launch { + prefetchLock.withLock { for (page in pages.asReversed()) { if (tasks.containsKey(page.id)) { continue @@ -89,18 +88,13 @@ class PageLoader @Inject constructor( } fun loadPageAsync(page: MangaPage, force: Boolean): ProgressDeferred { - if (!force) { - cache[page.url]?.let { - return getCompletedTask(it) - } - } var task = tasks[page.id] if (force) { task?.cancel() } else if (task?.isCancelled == false) { return task } - task = loadPageAsyncImpl(page) + task = loadPageAsyncImpl(page, force) synchronized(tasks) { tasks[page.id] = task } @@ -130,23 +124,26 @@ class PageLoader @Inject constructor( return getRepository(page.source).getPageUrl(page) } - private fun onIdle() { - synchronized(prefetchQueue) { + private fun onIdle() = loaderScope.launch { + prefetchLock.withLock { while (prefetchQueue.isNotEmpty()) { - val page = prefetchQueue.pollFirst() ?: return - if (cache[page.url] == null) { + val page = prefetchQueue.pollFirst() ?: return@launch + if (cache.get(page.url) == null) { synchronized(tasks) { - tasks[page.id] = loadPageAsyncImpl(page) + tasks[page.id] = loadPageAsyncImpl(page, false) } - return + return@launch } } } } - private fun loadPageAsyncImpl(page: MangaPage): ProgressDeferred { + private fun loadPageAsyncImpl(page: MangaPage, skipCache: Boolean): ProgressDeferred { val progress = MutableStateFlow(PROGRESS_UNDEFINED) val deferred = loaderScope.async { + if (!skipCache) { + cache.get(page.url)?.let { return@async it } + } counter.incrementAndGet() try { loadPageImpl(page, progress) @@ -207,11 +204,6 @@ class PageLoader @Inject constructor( } } - private fun getCompletedTask(file: File): ProgressDeferred { - val deferred = CompletableDeferred(file) - return ProgressDeferred(deferred, emptyProgressFlow) - } - private class InternalErrorHandler : AbstractCoroutineContextElement(CoroutineExceptionHandler), CoroutineExceptionHandler {