Move get cache page to background thread

pull/320/head
vianh 3 years ago
parent 080c2724cd
commit 5c3662306c

@ -138,7 +138,7 @@ class DownloadManager @AssistedInject constructor(
for ((pageIndex, page) in pages.withIndex()) { for ((pageIndex, page) in pages.withIndex()) {
runFailsafe(outState, pausingHandle) { runFailsafe(outState, pausingHandle) {
val url = repo.getPageUrl(page) val url = repo.getPageUrl(page)
val file = cache[url] val file = cache.get(url)
?: downloadFile(url, page.referer, destination, tempFileName, repo.source) ?: downloadFile(url, page.referer, destination, tempFileName, repo.source)
output.addPage( output.addPage(
chapter = chapter, chapter = chapter,

@ -30,8 +30,8 @@ class PagesCache @Inject constructor(@ApplicationContext context: Context) {
size = FileSize.MEGABYTES.convert(200, FileSize.BYTES), size = FileSize.MEGABYTES.convert(200, FileSize.BYTES),
) )
operator fun get(url: String): File? { suspend fun get(url: String): File? = withContext(Dispatchers.IO) {
return lruCache.get(url)?.takeIfReadable() lruCache.get(url)?.takeIfReadable()
} }
suspend fun put(url: String, inputStream: InputStream): File = withContext(Dispatchers.IO) { suspend fun put(url: String, inputStream: InputStream): File = withContext(Dispatchers.IO) {

@ -5,7 +5,6 @@ import android.graphics.BitmapFactory
import android.net.Uri import android.net.Uri
import androidx.collection.LongSparseArray import androidx.collection.LongSparseArray
import androidx.collection.set import androidx.collection.set
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -13,7 +12,7 @@ import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch
import kotlinx.coroutines.runInterruptible import kotlinx.coroutines.runInterruptible
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
@ -54,11 +53,11 @@ class PageLoader @Inject constructor(
private val tasks = LongSparseArray<ProgressDeferred<File, Float>>() private val tasks = LongSparseArray<ProgressDeferred<File, Float>>()
private val convertLock = Mutex() private val convertLock = Mutex()
private val prefetchLock = Mutex()
private var repository: MangaRepository? = null private var repository: MangaRepository? = null
private val prefetchQueue = LinkedList<MangaPage>() private val prefetchQueue = LinkedList<MangaPage>()
private val counter = AtomicInteger(0) private val counter = AtomicInteger(0)
private var prefetchQueueLimit = PREFETCH_LIMIT_DEFAULT // TODO adaptive private var prefetchQueueLimit = PREFETCH_LIMIT_DEFAULT // TODO adaptive
private val emptyProgressFlow: StateFlow<Float> = MutableStateFlow(-1f)
override fun close() { override fun close() {
loaderScope.cancel() loaderScope.cancel()
@ -71,8 +70,8 @@ class PageLoader @Inject constructor(
return repository is RemoteMangaRepository && settings.isPagesPreloadEnabled() return repository is RemoteMangaRepository && settings.isPagesPreloadEnabled()
} }
fun prefetch(pages: List<ReaderPage>) { fun prefetch(pages: List<ReaderPage>) = loaderScope.launch {
synchronized(prefetchQueue) { prefetchLock.withLock {
for (page in pages.asReversed()) { for (page in pages.asReversed()) {
if (tasks.containsKey(page.id)) { if (tasks.containsKey(page.id)) {
continue continue
@ -89,18 +88,13 @@ class PageLoader @Inject constructor(
} }
fun loadPageAsync(page: MangaPage, force: Boolean): ProgressDeferred<File, Float> { fun loadPageAsync(page: MangaPage, force: Boolean): ProgressDeferred<File, Float> {
if (!force) {
cache[page.url]?.let {
return getCompletedTask(it)
}
}
var task = tasks[page.id] var task = tasks[page.id]
if (force) { if (force) {
task?.cancel() task?.cancel()
} else if (task?.isCancelled == false) { } else if (task?.isCancelled == false) {
return task return task
} }
task = loadPageAsyncImpl(page) task = loadPageAsyncImpl(page, force)
synchronized(tasks) { synchronized(tasks) {
tasks[page.id] = task tasks[page.id] = task
} }
@ -130,23 +124,26 @@ class PageLoader @Inject constructor(
return getRepository(page.source).getPageUrl(page) return getRepository(page.source).getPageUrl(page)
} }
private fun onIdle() { private fun onIdle() = loaderScope.launch {
synchronized(prefetchQueue) { prefetchLock.withLock {
while (prefetchQueue.isNotEmpty()) { while (prefetchQueue.isNotEmpty()) {
val page = prefetchQueue.pollFirst() ?: return val page = prefetchQueue.pollFirst() ?: return@launch
if (cache[page.url] == null) { if (cache.get(page.url) == null) {
synchronized(tasks) { synchronized(tasks) {
tasks[page.id] = loadPageAsyncImpl(page) tasks[page.id] = loadPageAsyncImpl(page, false)
} }
return return@launch
} }
} }
} }
} }
private fun loadPageAsyncImpl(page: MangaPage): ProgressDeferred<File, Float> { private fun loadPageAsyncImpl(page: MangaPage, skipCache: Boolean): ProgressDeferred<File, Float> {
val progress = MutableStateFlow(PROGRESS_UNDEFINED) val progress = MutableStateFlow(PROGRESS_UNDEFINED)
val deferred = loaderScope.async { val deferred = loaderScope.async {
if (!skipCache) {
cache.get(page.url)?.let { return@async it }
}
counter.incrementAndGet() counter.incrementAndGet()
try { try {
loadPageImpl(page, progress) loadPageImpl(page, progress)
@ -207,11 +204,6 @@ class PageLoader @Inject constructor(
} }
} }
private fun getCompletedTask(file: File): ProgressDeferred<File, Float> {
val deferred = CompletableDeferred(file)
return ProgressDeferred(deferred, emptyProgressFlow)
}
private class InternalErrorHandler : AbstractCoroutineContextElement(CoroutineExceptionHandler), private class InternalErrorHandler : AbstractCoroutineContextElement(CoroutineExceptionHandler),
CoroutineExceptionHandler { CoroutineExceptionHandler {

Loading…
Cancel
Save