mirror of
https://github.com/tachiyomiorg/tachiyomi-extensions-inspector.git
synced 2024-12-26 16:51:50 +01:00
downloader backend done
This commit is contained in:
parent
e5e875c54a
commit
b56045e984
@ -33,6 +33,7 @@ import suwayomi.tachidesk.impl.Source.getSourceList
|
|||||||
import suwayomi.tachidesk.impl.backup.BackupFlags
|
import suwayomi.tachidesk.impl.backup.BackupFlags
|
||||||
import suwayomi.tachidesk.impl.backup.legacy.LegacyBackupExport.createLegacyBackup
|
import suwayomi.tachidesk.impl.backup.legacy.LegacyBackupExport.createLegacyBackup
|
||||||
import suwayomi.tachidesk.impl.backup.legacy.LegacyBackupImport.restoreLegacyBackup
|
import suwayomi.tachidesk.impl.backup.legacy.LegacyBackupImport.restoreLegacyBackup
|
||||||
|
import suwayomi.tachidesk.impl.download.DownloadManager
|
||||||
import suwayomi.tachidesk.impl.extension.Extension.getExtensionIcon
|
import suwayomi.tachidesk.impl.extension.Extension.getExtensionIcon
|
||||||
import suwayomi.tachidesk.impl.extension.Extension.installExtension
|
import suwayomi.tachidesk.impl.extension.Extension.installExtension
|
||||||
import suwayomi.tachidesk.impl.extension.Extension.uninstallExtension
|
import suwayomi.tachidesk.impl.extension.Extension.uninstallExtension
|
||||||
@ -383,15 +384,56 @@ object TachideskAPI {
|
|||||||
// Download queue stats
|
// Download queue stats
|
||||||
app.ws("/api/v1/downloads") { ws ->
|
app.ws("/api/v1/downloads") { ws ->
|
||||||
ws.onConnect { ctx ->
|
ws.onConnect { ctx ->
|
||||||
// TODO: send current stat
|
DownloadManager.addClient(ctx)
|
||||||
// TODO: add to downlad subscribers
|
DownloadManager.notifyClient(ctx)
|
||||||
}
|
}
|
||||||
ws.onMessage {
|
ws.onMessage { ctx ->
|
||||||
// TODO: send current stat
|
DownloadManager.handleRequest(ctx)
|
||||||
}
|
}
|
||||||
ws.onClose { ctx ->
|
ws.onClose { ctx ->
|
||||||
// TODO: remove from subscribers
|
DownloadManager.removeClient(ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start the downloader
|
||||||
|
app.get("/api/v1/downloads/start") { ctx ->
|
||||||
|
DownloadManager.start()
|
||||||
|
|
||||||
|
ctx.status(200)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop the downloader
|
||||||
|
app.get("/api/v1/downloads/stop") { ctx ->
|
||||||
|
DownloadManager.stop()
|
||||||
|
|
||||||
|
ctx.status(200)
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear download queue
|
||||||
|
app.get("/api/v1/downloads/clear") { ctx ->
|
||||||
|
DownloadManager.clear()
|
||||||
|
|
||||||
|
ctx.status(200)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Queue chapter for download
|
||||||
|
app.get("/api/v1/download/:mangaId/chapter/:chapterIndex") { ctx ->
|
||||||
|
val chapterIndex = ctx.pathParam("chapterIndex").toInt()
|
||||||
|
val mangaId = ctx.pathParam("mangaId").toInt()
|
||||||
|
|
||||||
|
DownloadManager.enqueue(chapterIndex, mangaId)
|
||||||
|
|
||||||
|
ctx.status(200)
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete chapter from download queue
|
||||||
|
app.delete("/api/v1/download/:mangaId/chapter/:chapterIndex") { ctx ->
|
||||||
|
val chapterIndex = ctx.pathParam("chapterIndex").toInt()
|
||||||
|
val mangaId = ctx.pathParam("mangaId").toInt()
|
||||||
|
|
||||||
|
DownloadManager.unqueue(chapterIndex, mangaId)
|
||||||
|
|
||||||
|
ctx.status(200)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,124 @@
|
|||||||
|
package suwayomi.tachidesk.impl.download
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* 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 io.javalin.websocket.WsContext
|
||||||
|
import io.javalin.websocket.WsMessageContext
|
||||||
|
import org.jetbrains.exposed.sql.and
|
||||||
|
import org.jetbrains.exposed.sql.select
|
||||||
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
|
import suwayomi.tachidesk.impl.download.model.DownloadChapter
|
||||||
|
import suwayomi.tachidesk.impl.download.model.DownloadState.Downloading
|
||||||
|
import suwayomi.tachidesk.impl.download.model.DownloadStatus
|
||||||
|
import suwayomi.tachidesk.model.table.ChapterTable
|
||||||
|
import suwayomi.tachidesk.model.table.toDataClass
|
||||||
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
|
|
||||||
|
object DownloadManager {
|
||||||
|
private val clients = ConcurrentHashMap<String, WsContext>()
|
||||||
|
private val downloadQueue = CopyOnWriteArrayList<DownloadChapter>()
|
||||||
|
private var downloader: Downloader? = null
|
||||||
|
|
||||||
|
fun addClient(ctx: WsContext) {
|
||||||
|
clients[ctx.sessionId] = ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeClient(ctx: WsContext) {
|
||||||
|
clients.remove(ctx.sessionId)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun notifyClient(ctx: WsContext) {
|
||||||
|
ctx.send(
|
||||||
|
getStatus()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun handleRequest(ctx: WsMessageContext) {
|
||||||
|
when (ctx.message()) {
|
||||||
|
"STATUS" -> notifyClient(ctx)
|
||||||
|
else -> ctx.send(
|
||||||
|
"""
|
||||||
|
|Invalid command.
|
||||||
|
|Supported commands are:
|
||||||
|
| - STATUS
|
||||||
|
| sends the current download status
|
||||||
|
|""".trimMargin()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun notifyAllClients() {
|
||||||
|
val status = getStatus()
|
||||||
|
clients.forEach {
|
||||||
|
it.value.send(status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getStatus(): DownloadStatus {
|
||||||
|
return DownloadStatus(
|
||||||
|
if (downloader == null ||
|
||||||
|
downloadQueue.none { it.state == Downloading }
|
||||||
|
) "Stopped" else "Started",
|
||||||
|
downloadQueue
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun enqueue(chapterIndex: Int, mangaId: Int) {
|
||||||
|
if (downloadQueue.none { it.mangaId == mangaId && it.chapterIndex == chapterIndex }) {
|
||||||
|
downloadQueue.add(
|
||||||
|
DownloadChapter(
|
||||||
|
chapterIndex,
|
||||||
|
mangaId,
|
||||||
|
chapter = ChapterTable.toDataClass(
|
||||||
|
transaction {
|
||||||
|
ChapterTable.select { (ChapterTable.manga eq mangaId) and (ChapterTable.chapterIndex eq chapterIndex) }
|
||||||
|
.first()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
notifyAllClients()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unqueue(chapterIndex: Int, mangaId: Int) {
|
||||||
|
downloadQueue.removeIf { it.mangaId == mangaId && it.chapterIndex == chapterIndex }
|
||||||
|
notifyAllClients()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun start() {
|
||||||
|
if (downloader == null) {
|
||||||
|
downloader = Downloader(downloadQueue) { notifyAllClients() }
|
||||||
|
downloader!!.start()
|
||||||
|
}
|
||||||
|
notifyAllClients()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stop() {
|
||||||
|
downloader?.let {
|
||||||
|
synchronized(it.shouldStop) {
|
||||||
|
it.shouldStop = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
downloader = null
|
||||||
|
notifyAllClients()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clear() {
|
||||||
|
stop()
|
||||||
|
downloadQueue.clear()
|
||||||
|
notifyAllClients()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class DownloaderState(val state: Int) {
|
||||||
|
Stopped(0),
|
||||||
|
Running(1),
|
||||||
|
Paused(2),
|
||||||
|
}
|
@ -1,28 +1,65 @@
|
|||||||
package suwayomi.tachidesk.impl.download
|
package suwayomi.tachidesk.impl.download
|
||||||
|
|
||||||
import org.jetbrains.exposed.sql.ResultRow
|
|
||||||
import java.util.concurrent.LinkedBlockingQueue
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Copyright (C) Contributors to the Suwayomi project
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
*
|
*
|
||||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
* 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/. */
|
||||||
|
|
||||||
data class Download(
|
import kotlinx.coroutines.runBlocking
|
||||||
val chapter: ResultRow,
|
import suwayomi.tachidesk.impl.Chapter.getChapter
|
||||||
)
|
import suwayomi.tachidesk.impl.Page.getPageImage
|
||||||
|
import suwayomi.tachidesk.impl.download.model.DownloadChapter
|
||||||
|
import suwayomi.tachidesk.impl.download.model.DownloadState.Downloading
|
||||||
|
import suwayomi.tachidesk.impl.download.model.DownloadState.Error
|
||||||
|
import suwayomi.tachidesk.impl.download.model.DownloadState.Finished
|
||||||
|
import suwayomi.tachidesk.impl.download.model.DownloadState.Queued
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList
|
||||||
|
|
||||||
private val downloadQueue = LinkedBlockingQueue<Download>()
|
class Downloader(private val downloadQueue: CopyOnWriteArrayList<DownloadChapter>, val notifier: () -> Unit) : Thread() {
|
||||||
|
var shouldStop: Boolean = false
|
||||||
|
|
||||||
class Downloader {
|
class DownloadShouldStopException : Exception()
|
||||||
|
|
||||||
fun start() {
|
fun step() {
|
||||||
TODO()
|
notifier()
|
||||||
|
synchronized(shouldStop) {
|
||||||
|
if (shouldStop) throw DownloadShouldStopException()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stop() {
|
override fun run() {
|
||||||
TODO()
|
do {
|
||||||
|
val download = downloadQueue.firstOrNull { it.state == Queued } ?: break
|
||||||
|
|
||||||
|
try {
|
||||||
|
download.state = Downloading
|
||||||
|
step()
|
||||||
|
|
||||||
|
download.chapter = runBlocking { getChapter(download.chapterIndex, download.mangaId) }
|
||||||
|
step()
|
||||||
|
|
||||||
|
val pageCount = download.chapter!!.pageCount!!
|
||||||
|
for (pageNum in 0 until pageCount) {
|
||||||
|
runBlocking { getPageImage(download.mangaId, download.chapterIndex, pageNum) }
|
||||||
|
// TODO: retry on error with 2,4,8 seconds of wait
|
||||||
|
// TODO: download multiple pages at once, possible solution: rx observer's strategy is used in Tachiyomi
|
||||||
|
download.progress = (pageNum + 1).toFloat() / pageCount
|
||||||
|
step()
|
||||||
|
}
|
||||||
|
download.state = Finished
|
||||||
|
step()
|
||||||
|
} catch (e: DownloadShouldStopException) {
|
||||||
|
println("Downloader was stopped")
|
||||||
|
downloadQueue.filter { it.state == Downloading }.forEach { it.state = Queued }
|
||||||
|
} catch (e: Exception) {
|
||||||
|
println("Downloader faced an exception")
|
||||||
|
downloadQueue.filter { it.state == Downloading }.forEach { it.state = Error }
|
||||||
|
e.printStackTrace()
|
||||||
|
} finally {
|
||||||
|
notifier()
|
||||||
|
}
|
||||||
|
} while (!shouldStop)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
package suwayomi.tachidesk.impl.download.model
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* 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 suwayomi.tachidesk.model.dataclass.ChapterDataClass
|
||||||
|
|
||||||
|
class DownloadChapter(
|
||||||
|
val chapterIndex: Int,
|
||||||
|
val mangaId: Int,
|
||||||
|
var state: DownloadState = DownloadState.Queued,
|
||||||
|
var progress: Float = 0f,
|
||||||
|
var chapter: ChapterDataClass? = null,
|
||||||
|
)
|
@ -0,0 +1,15 @@
|
|||||||
|
package suwayomi.tachidesk.impl.download.model
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* 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 DownloadState(val state: Int) {
|
||||||
|
Queued(0),
|
||||||
|
Downloading(1),
|
||||||
|
Finished(2),
|
||||||
|
Error(3),
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package suwayomi.tachidesk.impl.download.model
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) Contributors to the Suwayomi project
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* 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/. */
|
||||||
|
|
||||||
|
data class DownloadStatus(
|
||||||
|
val status: String,
|
||||||
|
val queue: List<DownloadChapter>,
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user