mirror of
https://github.com/Suwayomi/Suwayomi-Server.git
synced 2026-07-04 03:14:40 -05:00
Preserve download queue through server restarts (#599)
This commit is contained in:
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user