Fix downloading slowdown

master
Koitharu 2 years ago
parent 38b342b721
commit e2f8d8e022
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

@ -1,16 +1,20 @@
package org.koitharu.kotatsu.download.ui.worker package org.koitharu.kotatsu.download.ui.worker
import android.os.SystemClock
import androidx.collection.MutableObjectLongMap import androidx.collection.MutableObjectLongMap
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
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.parsers.model.MangaSource 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 mangaRepositoryFactory: MangaRepository.Factory,
private val defaultDelay: Long,
) { ) {
private val timeMap = MutableObjectLongMap<MangaSource>() private val timeMap = MutableObjectLongMap<MangaSource>()
private val defaultDelay = 1_600L
suspend fun delay(source: MangaSource) { suspend fun delay(source: MangaSource) {
val repo = mangaRepositoryFactory.create(source) as? ParserMangaRepository ?: return val repo = mangaRepositoryFactory.create(source) as? ParserMangaRepository ?: return
@ -19,11 +23,11 @@ class DownloadSlowdownDispatcher(
} }
val lastRequest = synchronized(timeMap) { val lastRequest = synchronized(timeMap) {
val res = timeMap.getOrDefault(source, 0L) val res = timeMap.getOrDefault(source, 0L)
timeMap[source] = System.currentTimeMillis() timeMap[source] = SystemClock.elapsedRealtime()
res res
} }
if (lastRequest != 0L) { if (lastRequest != 0L) {
delay(lastRequest + defaultDelay - System.currentTimeMillis()) delay(lastRequest + defaultDelay - SystemClock.elapsedRealtime())
} }
} }
} }

@ -101,6 +101,7 @@ class DownloadWorker @AssistedInject constructor(
private val mangaRepositoryFactory: MangaRepository.Factory, private val mangaRepositoryFactory: MangaRepository.Factory,
private val settings: AppSettings, private val settings: AppSettings,
@LocalStorageChanges private val localStorageChanges: MutableSharedFlow<LocalManga?>, @LocalStorageChanges private val localStorageChanges: MutableSharedFlow<LocalManga?>,
private val slowdownDispatcher: DownloadSlowdownDispatcher,
private val imageProxyInterceptor: ImageProxyInterceptor, private val imageProxyInterceptor: ImageProxyInterceptor,
notificationFactoryFactory: DownloadNotificationFactory.Factory, notificationFactoryFactory: DownloadNotificationFactory.Factory,
) : CoroutineWorker(appContext, params) { ) : CoroutineWorker(appContext, params) {
@ -110,7 +111,6 @@ class DownloadWorker @AssistedInject constructor(
isSilent = params.inputData.getBoolean(IS_SILENT, false), isSilent = params.inputData.getBoolean(IS_SILENT, false),
) )
private val notificationManager = appContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager private val notificationManager = appContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
private val slowdownDispatcher = DownloadSlowdownDispatcher(mangaRepositoryFactory, SLOWDOWN_DELAY)
@Volatile @Volatile
private var lastPublishedState: DownloadState? = null private var lastPublishedState: DownloadState? = null
@ -569,7 +569,6 @@ class DownloadWorker @AssistedInject constructor(
const val MAX_PAGES_PARALLELISM = 4 const val MAX_PAGES_PARALLELISM = 4
const val DOWNLOAD_ERROR_DELAY = 2_000L const val DOWNLOAD_ERROR_DELAY = 2_000L
const val MAX_RETRY_DELAY = 7_200_000L // 2 hours const val MAX_RETRY_DELAY = 7_200_000L // 2 hours
const val SLOWDOWN_DELAY = 200L
const val MANGA_ID = "manga_id" const val MANGA_ID = "manga_id"
const val CHAPTERS_IDS = "chapters" const val CHAPTERS_IDS = "chapters"
const val IS_SILENT = "silent" const val IS_SILENT = "silent"

@ -1,10 +1,8 @@
package org.koitharu.kotatsu.reader.domain package org.koitharu.kotatsu.reader.domain
import android.content.ContentResolver.MimeTypeInfo
import android.content.Context import android.content.Context
import android.graphics.Rect import android.graphics.Rect
import android.net.Uri import android.net.Uri
import android.webkit.MimeTypeMap
import androidx.annotation.AnyThread import androidx.annotation.AnyThread
import androidx.collection.LongSparseArray import androidx.collection.LongSparseArray
import androidx.collection.set import androidx.collection.set
@ -29,6 +27,7 @@ import kotlinx.coroutines.sync.withPermit
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okio.use import okio.use
import org.koitharu.kotatsu.core.image.BitmapDecoderCompat
import org.koitharu.kotatsu.core.network.CommonHeaders import org.koitharu.kotatsu.core.network.CommonHeaders
import org.koitharu.kotatsu.core.network.MangaHttpClient import org.koitharu.kotatsu.core.network.MangaHttpClient
import org.koitharu.kotatsu.core.network.imageproxy.ImageProxyInterceptor 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.getCompletionResultOrNull
import org.koitharu.kotatsu.core.util.ext.isPowerSaveMode import org.koitharu.kotatsu.core.util.ext.isPowerSaveMode
import org.koitharu.kotatsu.core.util.ext.isTargetNotEmpty 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.printStackTraceDebug
import org.koitharu.kotatsu.core.util.ext.ramAvailable import org.koitharu.kotatsu.core.util.ext.ramAvailable
import org.koitharu.kotatsu.core.util.ext.use 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.local.data.PagesCache import org.koitharu.kotatsu.local.data.PagesCache
import org.koitharu.kotatsu.local.data.isFileUri import org.koitharu.kotatsu.local.data.isFileUri
import org.koitharu.kotatsu.local.data.isZipUri 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.mimeType
import org.koitharu.kotatsu.parsers.util.requireBody import org.koitharu.kotatsu.parsers.util.requireBody
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable 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 org.koitharu.kotatsu.reader.ui.pager.ReaderPage
import java.util.LinkedList import java.util.LinkedList
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
@ -79,6 +78,7 @@ class PageLoader @Inject constructor(
private val settings: AppSettings, private val settings: AppSettings,
private val mangaRepositoryFactory: MangaRepository.Factory, private val mangaRepositoryFactory: MangaRepository.Factory,
private val imageProxyInterceptor: ImageProxyInterceptor, private val imageProxyInterceptor: ImageProxyInterceptor,
private val downloadSlowdownDispatcher: DownloadSlowdownDispatcher,
) { ) {
val loaderScope = RetainedLifecycleCoroutineScope(lifecycle) + InternalErrorHandler() + Dispatchers.Default val loaderScope = RetainedLifecycleCoroutineScope(lifecycle) + InternalErrorHandler() + Dispatchers.Default
@ -127,7 +127,7 @@ class PageLoader @Inject constructor(
} else if (task?.isCancelled == false) { } else if (task?.isCancelled == false) {
return task return task
} }
task = loadPageAsyncImpl(page, force) task = loadPageAsyncImpl(page, skipCache = force, isPrefetch = false)
synchronized(tasks) { synchronized(tasks) {
tasks[page.id] = task tasks[page.id] = task
} }
@ -186,7 +186,7 @@ class PageLoader @Inject constructor(
val page = prefetchQueue.pollFirst() ?: return@launch val page = prefetchQueue.pollFirst() ?: return@launch
if (cache.get(page.url) == null) { if (cache.get(page.url) == null) {
synchronized(tasks) { synchronized(tasks) {
tasks[page.id] = loadPageAsyncImpl(page, false) tasks[page.id] = loadPageAsyncImpl(page, skipCache = false, isPrefetch = true)
} }
return@launch return@launch
} }
@ -194,7 +194,11 @@ class PageLoader @Inject constructor(
} }
} }
private fun loadPageAsyncImpl(page: MangaPage, skipCache: Boolean): ProgressDeferred<Uri, Float> { private fun loadPageAsyncImpl(
page: MangaPage,
skipCache: Boolean,
isPrefetch: Boolean,
): ProgressDeferred<Uri, Float> {
val progress = MutableStateFlow(PROGRESS_UNDEFINED) val progress = MutableStateFlow(PROGRESS_UNDEFINED)
val deferred = loaderScope.async { val deferred = loaderScope.async {
if (!skipCache) { if (!skipCache) {
@ -202,7 +206,7 @@ class PageLoader @Inject constructor(
} }
counter.incrementAndGet() counter.incrementAndGet()
try { try {
loadPageImpl(page, progress) loadPageImpl(page, progress, isPrefetch)
} finally { } finally {
if (counter.decrementAndGet() == 0) { if (counter.decrementAndGet() == 0) {
onIdle() onIdle()
@ -222,7 +226,11 @@ class PageLoader @Inject constructor(
} }
} }
private suspend fun loadPageImpl(page: MangaPage, progress: MutableStateFlow<Float>): Uri = semaphore.withPermit { private suspend fun loadPageImpl(
page: MangaPage,
progress: MutableStateFlow<Float>,
isPrefetch: Boolean,
): Uri = semaphore.withPermit {
val pageUrl = getPageUrl(page) val pageUrl = getPageUrl(page)
check(pageUrl.isNotBlank()) { "Cannot obtain full image url for $page" } check(pageUrl.isNotBlank()) { "Cannot obtain full image url for $page" }
val uri = Uri.parse(pageUrl) val uri = Uri.parse(pageUrl)
@ -235,6 +243,9 @@ class PageLoader @Inject constructor(
uri.isFileUri() -> uri uri.isFileUri() -> uri
else -> { else -> {
if (isPrefetch) {
downloadSlowdownDispatcher.delay(page.source)
}
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 {

Loading…
Cancel
Save