Fix/downloader manager persisting queue (#639)

* Extract reorder logic into function

* Save download queue everytime a download was finished

The download queue was never saved after a download was finished.
This caused finished download to be restored on a server start, which caused unnecessary "downloads" which most of the time would just finish immediately since the pages were still in the cache

* Wait for download queue save process to be finished

Since multiple downloaders could be finished at the same time, the download queue should be saved synchronously

* Remove unnecessary download queue save trigger

This gets called everytime a downloader finished downloading all chapters of its source.
Since the queue is now saved everytime a download is finished, this is trigger is not needed anymore
This commit is contained in:
schroda
2023-08-07 05:21:21 +02:00
committed by GitHub
parent b56b4fa813
commit 2889029b70
2 changed files with 33 additions and 17 deletions

View File

@@ -20,7 +20,9 @@ import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.sample import kotlinx.coroutines.flow.sample
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -70,6 +72,10 @@ object DownloadManager {
.apply() .apply()
} }
private fun triggerSaveDownloadQueue() {
scope.launch { saveQueueFlow.emit(Unit) }
}
fun restoreAndResumeDownloads() { fun restoreAndResumeDownloads() {
scope.launch { scope.launch {
logger.debug { "restoreAndResumeDownloads: Restore download queue..." } logger.debug { "restoreAndResumeDownloads: Restore download queue..." }
@@ -126,6 +132,11 @@ object DownloadManager {
} }
} }
private val saveQueueFlow = MutableSharedFlow<Unit>(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
init {
saveQueueFlow.onEach { saveDownloadQueue() }.launchIn(scope)
}
private fun sendStatusToAllClients() { private fun sendStatusToAllClients() {
val status = getStatus() val status = getStatus()
clients.forEach { clients.forEach {
@@ -186,7 +197,6 @@ object DownloadManager {
private fun refreshDownloaders() { private fun refreshDownloaders() {
scope.launch { scope.launch {
downloaderWatch.emit(Unit) downloaderWatch.emit(Unit)
saveDownloadQueue()
} }
} }
@@ -196,7 +206,8 @@ object DownloadManager {
sourceId = sourceId, sourceId = sourceId,
downloadQueue = downloadQueue, downloadQueue = downloadQueue,
notifier = ::notifyAllClients, notifier = ::notifyAllClients,
onComplete = ::refreshDownloaders onComplete = ::refreshDownloaders,
onDownloadFinished = ::triggerSaveDownloadQueue
) )
} }
@@ -276,7 +287,7 @@ object DownloadManager {
manga manga
) )
downloadQueue.add(newDownloadChapter) downloadQueue.add(newDownloadChapter)
saveDownloadQueue() triggerSaveDownloadQueue()
logger.debug { "Added chapter ${chapter.id} to download queue ($newDownloadChapter)" } logger.debug { "Added chapter ${chapter.id} to download queue ($newDownloadChapter)" }
return newDownloadChapter return newDownloadChapter
} }
@@ -308,30 +319,33 @@ object DownloadManager {
logger.debug { "dequeue ${chapterDownloads.size} chapters [${chapterDownloads.joinToString(separator = ", ") { "$it" }}]" } logger.debug { "dequeue ${chapterDownloads.size} chapters [${chapterDownloads.joinToString(separator = ", ") { "$it" }}]" }
downloadQueue.removeAll(chapterDownloads) downloadQueue.removeAll(chapterDownloads)
saveDownloadQueue() triggerSaveDownloadQueue()
notifyAllClients() notifyAllClients()
} }
fun reorder(chapterIndex: Int, mangaId: Int, to: Int) { fun reorder(chapterIndex: Int, mangaId: Int, to: Int) {
require(to >= 0) { "'to' must be over or equal to 0" }
val download = downloadQueue.find { it.mangaId == mangaId && it.chapterIndex == chapterIndex } val download = downloadQueue.find { it.mangaId == mangaId && it.chapterIndex == chapterIndex }
?: return ?: return
reorder(download, to)
}
fun reorder(chapterId: Int, to: Int) {
val download = downloadQueue.find { it.chapter.id == chapterId }
?: return
reorder(download, to)
}
private fun reorder(download: DownloadChapter, to: Int) {
require(to >= 0) { "'to' must be over or equal to 0" }
logger.debug { "reorder download $download from ${downloadQueue.indexOf(download)} to $to" } logger.debug { "reorder download $download from ${downloadQueue.indexOf(download)} to $to" }
downloadQueue -= download downloadQueue -= download
downloadQueue.add(to, download) downloadQueue.add(to, download)
saveDownloadQueue() triggerSaveDownloadQueue()
}
fun reorder(chapterId: Int, to: Int) {
require(to >= 0) { "'to' must be over or equal to 0" }
val download = downloadQueue.find { it.chapter.id == chapterId }
?: return
downloadQueue -= download
downloadQueue.add(to, download)
saveDownloadQueue()
} }
fun start() { fun start() {
@@ -360,7 +374,7 @@ object DownloadManager {
stop() stop()
downloadQueue.clear() downloadQueue.clear()
saveDownloadQueue() triggerSaveDownloadQueue()
notifyAllClients() notifyAllClients()
} }
} }

View File

@@ -34,7 +34,8 @@ class Downloader(
val sourceId: String, val sourceId: String,
private val downloadQueue: CopyOnWriteArrayList<DownloadChapter>, private val downloadQueue: CopyOnWriteArrayList<DownloadChapter>,
private val notifier: (immediate: Boolean) -> Unit, private val notifier: (immediate: Boolean) -> Unit,
private val onComplete: () -> Unit private val onComplete: () -> Unit,
private val onDownloadFinished: () -> Unit
) { ) {
private val logger = KotlinLogging.logger("${Downloader::class.java.name} source($sourceId)") private val logger = KotlinLogging.logger("${Downloader::class.java.name} source($sourceId)")
@@ -112,6 +113,7 @@ class Downloader(
downloadQueue.removeIf { it.mangaId == download.mangaId && it.chapterIndex == download.chapterIndex } downloadQueue.removeIf { it.mangaId == download.mangaId && it.chapterIndex == download.chapterIndex }
step(null, false) step(null, false)
downloadLogger.debug { "finished" } downloadLogger.debug { "finished" }
onDownloadFinished()
} catch (e: CancellationException) { } catch (e: CancellationException) {
logger.debug("Downloader was stopped") logger.debug("Downloader was stopped")
availableSourceDownloads.filter { it.state == Downloading }.forEach { it.state = Queued } availableSourceDownloads.filter { it.state == Downloading }.forEach { it.state = Queued }