Add option to customize concurrent downloads, increase page concurrency (mihonapp/mihon#2637)

(cherry picked from commit 643762f91325a460c74398d472a555fb00ed9f63)
This commit is contained in:
AntsyLich 2025-11-01 20:07:30 +06:00 committed by Cuong-Tran
parent 4ab421e822
commit 8ec37f3807
No known key found for this signature in database
GPG key ID: 94EFFE320FE7A47C
4 changed files with 43 additions and 19 deletions

View file

@ -39,6 +39,8 @@ object SettingsDownloadScreen : SearchableSettings {
val allCategories by getCategories.subscribe().collectAsState(initial = emptyList()) val allCategories by getCategories.subscribe().collectAsState(initial = emptyList())
val downloadPreferences = remember { Injekt.get<DownloadPreferences>() } val downloadPreferences = remember { Injekt.get<DownloadPreferences>() }
val parallelSourceLimit by downloadPreferences.parallelSourceLimit().collectAsState()
val parallelPageLimit by downloadPreferences.parallelPageLimit().collectAsState()
return listOf( return listOf(
Preference.PreferenceItem.SwitchPreference( Preference.PreferenceItem.SwitchPreference(
preference = downloadPreferences.downloadOnlyOverWifi(), preference = downloadPreferences.downloadOnlyOverWifi(),
@ -53,6 +55,19 @@ object SettingsDownloadScreen : SearchableSettings {
title = stringResource(MR.strings.split_tall_images), title = stringResource(MR.strings.split_tall_images),
subtitle = stringResource(MR.strings.split_tall_images_summary), subtitle = stringResource(MR.strings.split_tall_images_summary),
), ),
Preference.PreferenceItem.SliderPreference(
value = parallelSourceLimit,
valueRange = 1..10,
title = stringResource(MR.strings.pref_download_concurrent_sources),
onValueChanged = { downloadPreferences.parallelSourceLimit().set(it) },
),
Preference.PreferenceItem.SliderPreference(
value = parallelPageLimit,
valueRange = 1..15,
title = stringResource(MR.strings.pref_download_concurrent_pages),
subtitle = stringResource(MR.strings.pref_download_concurrent_pages_summary),
onValueChanged = { downloadPreferences.parallelPageLimit().set(it) },
),
getDeleteChaptersGroup( getDeleteChaptersGroup(
downloadPreferences = downloadPreferences, downloadPreferences = downloadPreferences,
categories = allCategories, categories = allCategories,

View file

@ -202,15 +202,17 @@ class Downloader(
if (isRunning) return if (isRunning) return
downloaderJob = scope.launch { downloaderJob = scope.launch {
val activeDownloadsFlow = queueState.transformLatest { queue -> val activeDownloadsFlow = combine(
queueState,
downloadPreferences.parallelSourceLimit().changes(),
) { a, b -> a to b }.transformLatest { (queue, parallelCount) ->
while (true) { while (true) {
val activeDownloads = queue.asSequence() val activeDownloads = queue.asSequence()
// Ignore completed downloads, leave them in the queue // Ignore completed downloads, leave them in the queue
.filter { it.status.value <= Download.State.DOWNLOADING.value } .filter { it.status.value <= Download.State.DOWNLOADING.value }
.groupBy { it.source } .groupBy { it.source }
.toList() .toList()
// Concurrently download from 5 different sources .take(parallelCount)
.take(5)
.map { (_, downloads) -> downloads.first() } .map { (_, downloads) -> downloads.first() }
emit(activeDownloads) emit(activeDownloads)
@ -222,7 +224,8 @@ class Downloader(
}.filter { it } }.filter { it }
activeDownloadsErroredFlow.first() activeDownloadsErroredFlow.first()
} }
}.distinctUntilChanged() }
.distinctUntilChanged()
// Use supervisorScope to cancel child jobs when the downloader job is cancelled // Use supervisorScope to cancel child jobs when the downloader job is cancelled
supervisorScope { supervisorScope {
@ -390,24 +393,23 @@ class Downloader(
download.status = Download.State.DOWNLOADING download.status = Download.State.DOWNLOADING
// Start downloading images, consider we can have downloaded images already // Start downloading images, consider we can have downloaded images already
// Concurrently do 2 pages at a time pageList.asFlow().flatMapMerge(concurrency = downloadPreferences.parallelPageLimit().get()) { page ->
pageList.asFlow() flow {
.flatMapMerge(concurrency = 2) { page -> // Fetch image URL if necessary
flow { if (page.imageUrl.isNullOrEmpty()) {
// Fetch image URL if necessary page.status = Page.State.LoadPage
if (page.imageUrl.isNullOrEmpty()) { try {
page.status = Page.State.LoadPage page.imageUrl = download.source.getImageUrl(page)
try { } catch (e: Throwable) {
page.imageUrl = download.source.getImageUrl(page) page.status = Page.State.Error(e)
} catch (e: Throwable) {
page.status = Page.State.Error(e)
}
} }
}
withIOContext { getOrDownloadImage(page, download, tmpDir, dataSaver) } withIOContext { getOrDownloadImage(page, download, tmpDir, dataSaver) }
emit(page) emit(page)
}.flowOn(Dispatchers.IO)
} }
.flowOn(Dispatchers.IO)
}
.collect { .collect {
// Do when page is downloaded. // Do when page is downloaded.
notifier.onProgressChange(download) notifier.onProgressChange(download)

View file

@ -37,6 +37,10 @@ class DownloadPreferences(
fun downloadNewUnreadChaptersOnly() = preferenceStore.getBoolean("download_new_unread_chapters_only", false) fun downloadNewUnreadChaptersOnly() = preferenceStore.getBoolean("download_new_unread_chapters_only", false)
fun parallelSourceLimit() = preferenceStore.getInt("download_parallel_source_limit", 5)
fun parallelPageLimit() = preferenceStore.getInt("download_parallel_page_limit", 5)
// KMK --> // KMK -->
fun downloadCacheRenewInterval() = preferenceStore.getInt("download_cache_renew_interval", 1) fun downloadCacheRenewInterval() = preferenceStore.getInt("download_cache_renew_interval", 1)
// KMK <-- // KMK <--

View file

@ -522,6 +522,9 @@
<string name="save_chapter_as_cbz">Save as CBZ archive</string> <string name="save_chapter_as_cbz">Save as CBZ archive</string>
<string name="split_tall_images">Split tall images</string> <string name="split_tall_images">Split tall images</string>
<string name="split_tall_images_summary">Improves reader performance</string> <string name="split_tall_images_summary">Improves reader performance</string>
<string name="pref_download_concurrent_sources">Concurrent source downloads</string>
<string name="pref_download_concurrent_pages">Concurrent page downloads</string>
<string name="pref_download_concurrent_pages_summary">Pages downloaded simultaneously per source</string>
<!-- Tracking section --> <!-- Tracking section -->
<string name="tracking_guide">Tracking guide</string> <string name="tracking_guide">Tracking guide</string>