Preserve download queue through server restarts (#599)

This commit is contained in:
schroda
2023-07-22 17:42:48 +02:00
committed by GitHub
parent c02496c4f0
commit 027805c4d5
3 changed files with 47 additions and 3 deletions

View File

@@ -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 * 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/. */ * 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.WsContext
import io.javalin.websocket.WsMessageContext import io.javalin.websocket.WsMessageContext
import kotlinx.coroutines.CoroutineScope 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.DownloadChapter
import suwayomi.tachidesk.manga.impl.download.model.DownloadState.Downloading import suwayomi.tachidesk.manga.impl.download.model.DownloadState.Downloading
import suwayomi.tachidesk.manga.impl.download.model.DownloadStatus 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.ChapterDataClass
import suwayomi.tachidesk.manga.model.dataclass.MangaDataClass import suwayomi.tachidesk.manga.model.dataclass.MangaDataClass
import suwayomi.tachidesk.manga.model.table.ChapterTable import suwayomi.tachidesk.manga.model.table.ChapterTable
import suwayomi.tachidesk.manga.model.table.MangaTable import suwayomi.tachidesk.manga.model.table.MangaTable
import suwayomi.tachidesk.manga.model.table.toDataClass 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.ConcurrentHashMap
import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.CopyOnWriteArrayList
import kotlin.reflect.jvm.jvmName
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
private val logger = KotlinLogging.logger {} private val logger = KotlinLogging.logger {}
@@ -47,6 +54,29 @@ object DownloadManager {
private val downloadQueue = CopyOnWriteArrayList<DownloadChapter>() private val downloadQueue = CopyOnWriteArrayList<DownloadChapter>()
private val downloaders = ConcurrentHashMap<Long, Downloader>() private val downloaders = ConcurrentHashMap<Long, Downloader>()
private const val downloadQueueKey = "downloadQueueKey"
private val sharedPreferences =
Injekt.get<Application>().getSharedPreferences(DownloadManager::class.jvmName, Context.MODE_PRIVATE)
private fun loadDownloadQueue(): List<Int> {
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) { fun addClient(ctx: WsContext) {
clients[ctx.sessionId] = ctx clients[ctx.sessionId] = ctx
} }
@@ -109,9 +139,9 @@ object DownloadManager {
private fun getStatus(): DownloadStatus { private fun getStatus(): DownloadStatus {
return DownloadStatus( return DownloadStatus(
if (downloadQueue.none { it.state == Downloading }) { if (downloadQueue.none { it.state == Downloading }) {
"Stopped" Status.Stopped
} else { } else {
"Started" Status.Started
}, },
downloadQueue.toList() downloadQueue.toList()
) )
@@ -144,6 +174,7 @@ object DownloadManager {
private fun refreshDownloaders() { private fun refreshDownloaders() {
scope.launch { scope.launch {
downloaderWatch.emit(Unit) downloaderWatch.emit(Unit)
saveDownloadQueue()
} }
} }
@@ -206,6 +237,7 @@ object DownloadManager {
if (input.chapterIds.isNullOrEmpty()) return if (input.chapterIds.isNullOrEmpty()) return
downloadQueue.removeIf { it.chapter.id in input.chapterIds } downloadQueue.removeIf { it.chapter.id in input.chapterIds }
saveDownloadQueue()
notifyAllClients() notifyAllClients()
} }
@@ -238,6 +270,7 @@ object DownloadManager {
manga manga
) )
downloadQueue.add(downloadChapter) downloadQueue.add(downloadChapter)
saveDownloadQueue()
downloadSubscriptionSource.publish(downloadChapter) downloadSubscriptionSource.publish(downloadChapter)
logger.debug { "Added chapter ${chapter.id} to download queue (${manga.title} | ${chapter.name})" } logger.debug { "Added chapter ${chapter.id} to download queue (${manga.title} | ${chapter.name})" }
return downloadChapter return downloadChapter
@@ -248,6 +281,7 @@ object DownloadManager {
fun unqueue(chapterIndex: Int, mangaId: Int) { fun unqueue(chapterIndex: Int, mangaId: Int) {
downloadQueue.removeIf { it.mangaId == mangaId && it.chapterIndex == chapterIndex } downloadQueue.removeIf { it.mangaId == mangaId && it.chapterIndex == chapterIndex }
saveDownloadQueue()
notifyAllClients() notifyAllClients()
} }
@@ -257,6 +291,7 @@ object DownloadManager {
?: return ?: return
downloadQueue -= download downloadQueue -= download
downloadQueue.add(to, download) downloadQueue.add(to, download)
saveDownloadQueue()
} }
fun start() { fun start() {
@@ -279,6 +314,7 @@ object DownloadManager {
suspend fun clear() { suspend fun clear() {
stop() stop()
downloadQueue.clear() downloadQueue.clear()
saveDownloadQueue()
notifyAllClients() notifyAllClients()
} }
} }

View File

@@ -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 * 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/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
enum class Status {
Stopped, Started;
}
data class DownloadStatus( data class DownloadStatus(
val status: String, val status: Status,
val queue: List<DownloadChapter> val queue: List<DownloadChapter>
) )

View File

@@ -20,6 +20,7 @@ import org.kodein.di.bind
import org.kodein.di.conf.global import org.kodein.di.conf.global
import org.kodein.di.singleton import org.kodein.di.singleton
import suwayomi.tachidesk.manga.impl.backup.proto.ProtoBackupExport 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.IUpdater
import suwayomi.tachidesk.manga.impl.update.Updater import suwayomi.tachidesk.manga.impl.update.Updater
import suwayomi.tachidesk.manga.impl.util.lang.renameTo import suwayomi.tachidesk.manga.impl.util.lang.renameTo
@@ -180,4 +181,7 @@ fun applicationSetup() {
// start automated backups // start automated backups
ProtoBackupExport.scheduleAutomatedBackupTask() ProtoBackupExport.scheduleAutomatedBackupTask()
// start DownloadManager and restore + resume downloads
DownloadManager.restoreAndResumeDownloads()
} }