From 6490c41e63f2ad9f0d879e81077a8cacb35f2c91 Mon Sep 17 00:00:00 2001 From: AntsyLich <59261191+AntsyLich@users.noreply.github.com> Date: Sun, 4 Oct 2026 17:42:34 +0600 Subject: [PATCH] Wait for a canceled downloader job to finish before starting another Page writes block, so a canceled job could still be writing pages when the next one started on the same pages, leaving duplicate "(1)" files and failed chapters. --- .../eu/kanade/tachiyomi/data/download/Downloader.kt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt index 3c7dba520..67eb510bf 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt @@ -22,6 +22,7 @@ import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job +import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.async import kotlinx.coroutines.delay @@ -41,6 +42,7 @@ import kotlinx.coroutines.flow.transformLatest import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.supervisorScope +import kotlinx.coroutines.withContext import logcat.LogPriority import mihon.core.archive.ZipWriter import nl.adaptivity.xmlutil.serialization.XML @@ -91,6 +93,8 @@ class Downloader( val queueState = _queueState.asStateFlow() private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO) + + // Kept when canceled, so the next job can wait for it to finish private var downloaderJob: Job? = null /** @@ -183,7 +187,12 @@ class Downloader( private fun launchDownloaderJob() { if (isRunning) return + val previousJob = downloaderJob downloaderJob = scope.launch { + // Page writes block, so a canceled job can still be writing the pages this one would start on. Not + // cancelable, so a job canceled while waiting still finishes after the one before it. + withContext(NonCancellable) { previousJob?.join() } + val activeDownloadsFlow = combine( queueState, downloadPreferences.parallelSourceLimit.changes(), @@ -253,7 +262,6 @@ class Downloader( */ private fun cancelDownloaderJob() { downloaderJob?.cancel() - downloaderJob = null } /**