From 027805c4d50c8cb300693f56c6b4db6a6ab51fba Mon Sep 17 00:00:00 2001 From: schroda <50052685+schroda@users.noreply.github.com> Date: Sat, 22 Jul 2023 17:42:48 +0200 Subject: [PATCH] Preserve download queue through server restarts (#599) --- .../manga/impl/download/DownloadManager.kt | 40 ++++++++++++++++++- .../impl/download/model/DownloadStatus.kt | 6 ++- .../suwayomi/tachidesk/server/ServerSetup.kt | 4 ++ 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/DownloadManager.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/DownloadManager.kt index 19e348e0..517f4ae0 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/DownloadManager.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/DownloadManager.kt @@ -7,6 +7,8 @@ package suwayomi.tachidesk.manga.impl.download * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +import android.app.Application +import android.content.Context import io.javalin.websocket.WsContext import io.javalin.websocket.WsMessageContext import kotlinx.coroutines.CoroutineScope @@ -28,13 +30,18 @@ import suwayomi.tachidesk.graphql.subscriptions.downloadSubscriptionSource import suwayomi.tachidesk.manga.impl.download.model.DownloadChapter import suwayomi.tachidesk.manga.impl.download.model.DownloadState.Downloading import suwayomi.tachidesk.manga.impl.download.model.DownloadStatus +import suwayomi.tachidesk.manga.impl.download.model.Status import suwayomi.tachidesk.manga.model.dataclass.ChapterDataClass import suwayomi.tachidesk.manga.model.dataclass.MangaDataClass import suwayomi.tachidesk.manga.model.table.ChapterTable import suwayomi.tachidesk.manga.model.table.MangaTable import suwayomi.tachidesk.manga.model.table.toDataClass +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import java.io.IOException import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.CopyOnWriteArrayList +import kotlin.reflect.jvm.jvmName import kotlin.time.Duration.Companion.seconds private val logger = KotlinLogging.logger {} @@ -47,6 +54,29 @@ object DownloadManager { private val downloadQueue = CopyOnWriteArrayList() private val downloaders = ConcurrentHashMap() + private const val downloadQueueKey = "downloadQueueKey" + private val sharedPreferences = + Injekt.get().getSharedPreferences(DownloadManager::class.jvmName, Context.MODE_PRIVATE) + + private fun loadDownloadQueue(): List { + return sharedPreferences.getStringSet(downloadQueueKey, emptySet())?.mapNotNull { it.toInt() } ?: emptyList() + } + + private fun saveDownloadQueue() { + sharedPreferences.edit().putStringSet(downloadQueueKey, downloadQueue.map { it.chapter.id.toString() }.toSet()) + .apply() + } + + fun restoreAndResumeDownloads() { + logger.debug { "restoreAndResumeDownloads: Restore download queue..." } + enqueue(EnqueueInput(loadDownloadQueue())) + + if (downloadQueue.size > 0) { + logger.info { "restoreAndResumeDownloads: Restored download queue, starting downloads..." } + start() + } + } + fun addClient(ctx: WsContext) { clients[ctx.sessionId] = ctx } @@ -109,9 +139,9 @@ object DownloadManager { private fun getStatus(): DownloadStatus { return DownloadStatus( if (downloadQueue.none { it.state == Downloading }) { - "Stopped" + Status.Stopped } else { - "Started" + Status.Started }, downloadQueue.toList() ) @@ -144,6 +174,7 @@ object DownloadManager { private fun refreshDownloaders() { scope.launch { downloaderWatch.emit(Unit) + saveDownloadQueue() } } @@ -206,6 +237,7 @@ object DownloadManager { if (input.chapterIds.isNullOrEmpty()) return downloadQueue.removeIf { it.chapter.id in input.chapterIds } + saveDownloadQueue() notifyAllClients() } @@ -238,6 +270,7 @@ object DownloadManager { manga ) downloadQueue.add(downloadChapter) + saveDownloadQueue() downloadSubscriptionSource.publish(downloadChapter) logger.debug { "Added chapter ${chapter.id} to download queue (${manga.title} | ${chapter.name})" } return downloadChapter @@ -248,6 +281,7 @@ object DownloadManager { fun unqueue(chapterIndex: Int, mangaId: Int) { downloadQueue.removeIf { it.mangaId == mangaId && it.chapterIndex == chapterIndex } + saveDownloadQueue() notifyAllClients() } @@ -257,6 +291,7 @@ object DownloadManager { ?: return downloadQueue -= download downloadQueue.add(to, download) + saveDownloadQueue() } fun start() { @@ -279,6 +314,7 @@ object DownloadManager { suspend fun clear() { stop() downloadQueue.clear() + saveDownloadQueue() notifyAllClients() } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/model/DownloadStatus.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/model/DownloadStatus.kt index c2f748af..782109bf 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/model/DownloadStatus.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/model/DownloadStatus.kt @@ -7,7 +7,11 @@ package suwayomi.tachidesk.manga.impl.download.model * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +enum class Status { + Stopped, Started; +} + data class DownloadStatus( - val status: String, + val status: Status, val queue: List ) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/ServerSetup.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/ServerSetup.kt index a9b89c17..788340e7 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/ServerSetup.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/ServerSetup.kt @@ -20,6 +20,7 @@ import org.kodein.di.bind import org.kodein.di.conf.global import org.kodein.di.singleton import suwayomi.tachidesk.manga.impl.backup.proto.ProtoBackupExport +import suwayomi.tachidesk.manga.impl.download.DownloadManager import suwayomi.tachidesk.manga.impl.update.IUpdater import suwayomi.tachidesk.manga.impl.update.Updater import suwayomi.tachidesk.manga.impl.util.lang.renameTo @@ -180,4 +181,7 @@ fun applicationSetup() { // start automated backups ProtoBackupExport.scheduleAutomatedBackupTask() + + // start DownloadManager and restore + resume downloads + DownloadManager.restoreAndResumeDownloads() }