diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/worker/DownloadSlowdownDispatcher.kt b/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/worker/DownloadSlowdownDispatcher.kt index 9f2f2e3b2..4df33a736 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/worker/DownloadSlowdownDispatcher.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/worker/DownloadSlowdownDispatcher.kt @@ -1,16 +1,20 @@ package org.koitharu.kotatsu.download.ui.worker +import android.os.SystemClock import androidx.collection.MutableObjectLongMap import kotlinx.coroutines.delay import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.parser.ParserMangaRepository import org.koitharu.kotatsu.parsers.model.MangaSource +import javax.inject.Inject +import javax.inject.Singleton -class DownloadSlowdownDispatcher( +@Singleton +class DownloadSlowdownDispatcher @Inject constructor( private val mangaRepositoryFactory: MangaRepository.Factory, - private val defaultDelay: Long, ) { private val timeMap = MutableObjectLongMap() + private val defaultDelay = 1_600L suspend fun delay(source: MangaSource) { val repo = mangaRepositoryFactory.create(source) as? ParserMangaRepository ?: return @@ -19,11 +23,11 @@ class DownloadSlowdownDispatcher( } val lastRequest = synchronized(timeMap) { val res = timeMap.getOrDefault(source, 0L) - timeMap[source] = System.currentTimeMillis() + timeMap[source] = SystemClock.elapsedRealtime() res } if (lastRequest != 0L) { - delay(lastRequest + defaultDelay - System.currentTimeMillis()) + delay(lastRequest + defaultDelay - SystemClock.elapsedRealtime()) } } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/worker/DownloadWorker.kt b/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/worker/DownloadWorker.kt index e29f6de14..8438dd8a9 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/worker/DownloadWorker.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/worker/DownloadWorker.kt @@ -101,6 +101,7 @@ class DownloadWorker @AssistedInject constructor( private val mangaRepositoryFactory: MangaRepository.Factory, private val settings: AppSettings, @LocalStorageChanges private val localStorageChanges: MutableSharedFlow, + private val slowdownDispatcher: DownloadSlowdownDispatcher, private val imageProxyInterceptor: ImageProxyInterceptor, notificationFactoryFactory: DownloadNotificationFactory.Factory, ) : CoroutineWorker(appContext, params) { @@ -110,7 +111,6 @@ class DownloadWorker @AssistedInject constructor( isSilent = params.inputData.getBoolean(IS_SILENT, false), ) private val notificationManager = appContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager - private val slowdownDispatcher = DownloadSlowdownDispatcher(mangaRepositoryFactory, SLOWDOWN_DELAY) @Volatile private var lastPublishedState: DownloadState? = null @@ -569,7 +569,6 @@ class DownloadWorker @AssistedInject constructor( const val MAX_PAGES_PARALLELISM = 4 const val DOWNLOAD_ERROR_DELAY = 2_000L const val MAX_RETRY_DELAY = 7_200_000L // 2 hours - const val SLOWDOWN_DELAY = 200L const val MANGA_ID = "manga_id" const val CHAPTERS_IDS = "chapters" const val IS_SILENT = "silent" diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/domain/PageLoader.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/domain/PageLoader.kt index 8406ff161..a4bd8a5fd 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/domain/PageLoader.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/domain/PageLoader.kt @@ -1,10 +1,8 @@ package org.koitharu.kotatsu.reader.domain -import android.content.ContentResolver.MimeTypeInfo import android.content.Context import android.graphics.Rect import android.net.Uri -import android.webkit.MimeTypeMap import androidx.annotation.AnyThread import androidx.collection.LongSparseArray import androidx.collection.set @@ -29,6 +27,7 @@ import kotlinx.coroutines.sync.withPermit import okhttp3.OkHttpClient import okhttp3.Request import okio.use +import org.koitharu.kotatsu.core.image.BitmapDecoderCompat import org.koitharu.kotatsu.core.network.CommonHeaders import org.koitharu.kotatsu.core.network.MangaHttpClient import org.koitharu.kotatsu.core.network.imageproxy.ImageProxyInterceptor @@ -46,11 +45,13 @@ import org.koitharu.kotatsu.core.util.ext.exists import org.koitharu.kotatsu.core.util.ext.getCompletionResultOrNull import org.koitharu.kotatsu.core.util.ext.isPowerSaveMode import org.koitharu.kotatsu.core.util.ext.isTargetNotEmpty +import org.koitharu.kotatsu.core.util.ext.mimeType import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug import org.koitharu.kotatsu.core.util.ext.ramAvailable import org.koitharu.kotatsu.core.util.ext.use import org.koitharu.kotatsu.core.util.ext.withProgress import org.koitharu.kotatsu.core.util.progress.ProgressDeferred +import org.koitharu.kotatsu.download.ui.worker.DownloadSlowdownDispatcher import org.koitharu.kotatsu.local.data.PagesCache import org.koitharu.kotatsu.local.data.isFileUri import org.koitharu.kotatsu.local.data.isZipUri @@ -59,8 +60,6 @@ import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.util.mimeType import org.koitharu.kotatsu.parsers.util.requireBody import org.koitharu.kotatsu.parsers.util.runCatchingCancellable -import org.koitharu.kotatsu.core.image.BitmapDecoderCompat -import org.koitharu.kotatsu.core.util.ext.mimeType import org.koitharu.kotatsu.reader.ui.pager.ReaderPage import java.util.LinkedList import java.util.concurrent.atomic.AtomicInteger @@ -79,6 +78,7 @@ class PageLoader @Inject constructor( private val settings: AppSettings, private val mangaRepositoryFactory: MangaRepository.Factory, private val imageProxyInterceptor: ImageProxyInterceptor, + private val downloadSlowdownDispatcher: DownloadSlowdownDispatcher, ) { val loaderScope = RetainedLifecycleCoroutineScope(lifecycle) + InternalErrorHandler() + Dispatchers.Default @@ -127,7 +127,7 @@ class PageLoader @Inject constructor( } else if (task?.isCancelled == false) { return task } - task = loadPageAsyncImpl(page, force) + task = loadPageAsyncImpl(page, skipCache = force, isPrefetch = false) synchronized(tasks) { tasks[page.id] = task } @@ -186,7 +186,7 @@ class PageLoader @Inject constructor( val page = prefetchQueue.pollFirst() ?: return@launch if (cache.get(page.url) == null) { synchronized(tasks) { - tasks[page.id] = loadPageAsyncImpl(page, false) + tasks[page.id] = loadPageAsyncImpl(page, skipCache = false, isPrefetch = true) } return@launch } @@ -194,7 +194,11 @@ class PageLoader @Inject constructor( } } - private fun loadPageAsyncImpl(page: MangaPage, skipCache: Boolean): ProgressDeferred { + private fun loadPageAsyncImpl( + page: MangaPage, + skipCache: Boolean, + isPrefetch: Boolean, + ): ProgressDeferred { val progress = MutableStateFlow(PROGRESS_UNDEFINED) val deferred = loaderScope.async { if (!skipCache) { @@ -202,7 +206,7 @@ class PageLoader @Inject constructor( } counter.incrementAndGet() try { - loadPageImpl(page, progress) + loadPageImpl(page, progress, isPrefetch) } finally { if (counter.decrementAndGet() == 0) { onIdle() @@ -222,7 +226,11 @@ class PageLoader @Inject constructor( } } - private suspend fun loadPageImpl(page: MangaPage, progress: MutableStateFlow): Uri = semaphore.withPermit { + private suspend fun loadPageImpl( + page: MangaPage, + progress: MutableStateFlow, + isPrefetch: Boolean, + ): Uri = semaphore.withPermit { val pageUrl = getPageUrl(page) check(pageUrl.isNotBlank()) { "Cannot obtain full image url for $page" } val uri = Uri.parse(pageUrl) @@ -235,6 +243,9 @@ class PageLoader @Inject constructor( uri.isFileUri() -> uri else -> { + if (isPrefetch) { + downloadSlowdownDispatcher.delay(page.source) + } val request = createPageRequest(pageUrl, page.source) imageProxyInterceptor.interceptPageRequest(request, okHttp).ensureSuccess().use { response -> response.requireBody().withProgress(progress).use {