mirror of
https://github.com/Suwayomi/TachideskJUI.git
synced 2025-12-24 05:22:33 +01:00
Add downloading capabilities, not implemented yet
This commit is contained in:
@@ -53,6 +53,7 @@ dependencies {
|
||||
implementation("io.ktor:ktor-client-okhttp:$ktorVersion")
|
||||
implementation("io.ktor:ktor-client-serialization:$ktorVersion")
|
||||
implementation("io.ktor:ktor-client-logging:$ktorVersion")
|
||||
implementation("io.ktor:ktor-client-websockets:$ktorVersion")
|
||||
|
||||
// Logging
|
||||
val log4jVersion = "2.14.1"
|
||||
|
||||
@@ -8,6 +8,7 @@ package ca.gosyer.data
|
||||
|
||||
import ca.gosyer.core.prefs.PreferenceStoreFactory
|
||||
import ca.gosyer.data.catalog.CatalogPreferences
|
||||
import ca.gosyer.data.download.DownloadService
|
||||
import ca.gosyer.data.extension.ExtensionPreferences
|
||||
import ca.gosyer.data.library.LibraryPreferences
|
||||
import ca.gosyer.data.reader.ReaderPreferences
|
||||
@@ -73,4 +74,8 @@ val DataModule = module {
|
||||
bind<ServerService>()
|
||||
.toClass<ServerService>()
|
||||
.singleton()
|
||||
|
||||
bind<DownloadService>()
|
||||
.toClass<DownloadService>()
|
||||
.singleton()
|
||||
}
|
||||
|
||||
79
src/main/kotlin/ca/gosyer/data/download/DownloadService.kt
Normal file
79
src/main/kotlin/ca/gosyer/data/download/DownloadService.kt
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
package ca.gosyer.data.download
|
||||
|
||||
import ca.gosyer.BuildConfig
|
||||
import ca.gosyer.data.download.model.DownloadChapter
|
||||
import ca.gosyer.data.download.model.DownloadStatus
|
||||
import ca.gosyer.data.download.model.DownloaderStatus
|
||||
import ca.gosyer.data.server.Http
|
||||
import ca.gosyer.data.server.ServerPreferences
|
||||
import ca.gosyer.data.server.requests.downloadsQuery
|
||||
import ca.gosyer.util.lang.throwIfCancellation
|
||||
import io.ktor.client.features.websocket.ws
|
||||
import io.ktor.http.cio.websocket.Frame
|
||||
import io.ktor.http.cio.websocket.readText
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
import javax.inject.Inject
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
class DownloadService @Inject constructor(
|
||||
val serverPreferences: ServerPreferences,
|
||||
val client: Http
|
||||
) {
|
||||
private val json = Json {
|
||||
ignoreUnknownKeys = !BuildConfig.DEBUG
|
||||
}
|
||||
private val serverUrl = serverPreferences.serverUrl().stateIn(GlobalScope)
|
||||
private val _downloaderStatus = MutableStateFlow(DownloaderStatus.Stopped)
|
||||
val downloaderStatus = _downloaderStatus.asStateFlow()
|
||||
private val watching = mutableMapOf<Long, MutableSharedFlow<List<DownloadChapter>>>()
|
||||
|
||||
init {
|
||||
serverUrl.mapLatest { serverUrl ->
|
||||
while (true) {
|
||||
runCatching {
|
||||
client.ws(
|
||||
host = serverUrl.substringAfter("://"),
|
||||
path = downloadsQuery()
|
||||
) {
|
||||
send(Frame.Text("STATUS"))
|
||||
|
||||
while (true) {
|
||||
val frame = incoming.receive()
|
||||
runCatching {
|
||||
frame as Frame.Text
|
||||
val status = json.decodeFromString<DownloadStatus>(frame.readText())
|
||||
_downloaderStatus.value = status.status
|
||||
val queue = status.queue.groupBy { it.mangaId }
|
||||
watching.forEach { (mangaId, flow) ->
|
||||
flow.emit(queue[mangaId].orEmpty())
|
||||
}
|
||||
}.throwIfCancellation()
|
||||
}
|
||||
}
|
||||
}.throwIfCancellation()
|
||||
}
|
||||
}.launchIn(GlobalScope)
|
||||
}
|
||||
|
||||
fun registerWatch(mangaId: Long) =
|
||||
MutableSharedFlow<List<DownloadChapter>>().also { watching[mangaId] = it }.asSharedFlow()
|
||||
|
||||
fun removeWatch(mangaId: Long) {
|
||||
watching.remove(mangaId)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
package ca.gosyer.data.download.model
|
||||
|
||||
import ca.gosyer.data.models.Chapter
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class DownloadChapter(
|
||||
val chapterIndex: Int,
|
||||
val mangaId: Long,
|
||||
var state: DownloadState = DownloadState.Queued,
|
||||
var progress: Float = 0f,
|
||||
var tries: Int = 0,
|
||||
var chapter: Chapter? = null,
|
||||
)
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
package ca.gosyer.data.download.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
enum class DownloadState(val state: Int) {
|
||||
Queued(0),
|
||||
Downloading(1),
|
||||
Finished(2),
|
||||
Error(3),
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
package ca.gosyer.data.download.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class DownloadStatus(
|
||||
val status: DownloaderStatus,
|
||||
val queue: List<DownloadChapter>,
|
||||
)
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
package ca.gosyer.data.download.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
enum class DownloaderStatus {
|
||||
Started,
|
||||
Stopped
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import io.ktor.client.features.json.JsonFeature
|
||||
import io.ktor.client.features.json.serializer.KotlinxSerializer
|
||||
import io.ktor.client.features.logging.LogLevel
|
||||
import io.ktor.client.features.logging.Logging
|
||||
import io.ktor.client.features.websocket.WebSockets
|
||||
import kotlinx.serialization.json.Json
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
@@ -32,6 +33,7 @@ internal class HttpProvider @Inject constructor() : Provider<Http> {
|
||||
}
|
||||
)
|
||||
}
|
||||
install(WebSockets)
|
||||
install(Logging) {
|
||||
level = if (BuildConfig.DEBUG) {
|
||||
LogLevel.HEADERS
|
||||
|
||||
@@ -10,9 +10,11 @@ import ca.gosyer.data.models.Chapter
|
||||
import ca.gosyer.data.models.Manga
|
||||
import ca.gosyer.data.server.Http
|
||||
import ca.gosyer.data.server.ServerPreferences
|
||||
import ca.gosyer.data.server.requests.deleteDownloadChapterRequest
|
||||
import ca.gosyer.data.server.requests.getChapterQuery
|
||||
import ca.gosyer.data.server.requests.getMangaChaptersQuery
|
||||
import ca.gosyer.data.server.requests.getPageQuery
|
||||
import ca.gosyer.data.server.requests.queueDownloadChapterRequest
|
||||
import ca.gosyer.data.server.requests.updateChapterRequest
|
||||
import ca.gosyer.util.lang.withIOContext
|
||||
import io.ktor.client.request.HttpRequestBuilder
|
||||
@@ -127,4 +129,28 @@ class ChapterInteractionHandler @Inject constructor(
|
||||
suspend fun getPage(manga: Manga, chapterIndex: Int, pageNum: Int, block: HttpRequestBuilder.() -> Unit) = getPage(manga.id, chapterIndex, pageNum, block)
|
||||
|
||||
suspend fun getPage(manga: Manga, chapter: Chapter, pageNum: Int, block: HttpRequestBuilder.() -> Unit) = getPage(manga.id, chapter.index, pageNum, block)
|
||||
|
||||
suspend fun queueChapterDownload(mangaId: Long, chapterIndex: Int) = withIOContext {
|
||||
client.getRepeat<HttpResponse>(
|
||||
serverUrl + queueDownloadChapterRequest(mangaId, chapterIndex)
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun queueChapterDownload(chapter: Chapter) = queueChapterDownload(chapter.mangaId, chapter.index)
|
||||
|
||||
suspend fun queueChapterDownload(manga: Manga, chapterIndex: Int) = queueChapterDownload(manga.id, chapterIndex)
|
||||
|
||||
suspend fun queueChapterDownload(manga: Manga, chapter: Chapter) = queueChapterDownload(manga.id, chapter.index)
|
||||
|
||||
suspend fun deleteChapterDownload(mangaId: Long, chapterIndex: Int) = withIOContext {
|
||||
client.deleteRepeat<HttpResponse>(
|
||||
serverUrl + deleteDownloadChapterRequest(mangaId, chapterIndex)
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun deleteChapterDownload(chapter: Chapter) = deleteChapterDownload(chapter.mangaId, chapter.index)
|
||||
|
||||
suspend fun deleteChapterDownload(manga: Manga, chapterIndex: Int) = deleteChapterDownload(manga.id, chapterIndex)
|
||||
|
||||
suspend fun deleteChapterDownload(manga: Manga, chapter: Chapter) = deleteChapterDownload(manga.id, chapter.index)
|
||||
}
|
||||
|
||||
@@ -21,3 +21,11 @@ fun updateChapterRequest(mangaId: Long, chapterIndex: Int) =
|
||||
@Get
|
||||
fun getPageQuery(mangaId: Long, chapterIndex: Int, index: Int) =
|
||||
"/api/v1/manga/$mangaId/chapter/$chapterIndex/page/$index"
|
||||
|
||||
@Get
|
||||
fun queueDownloadChapterRequest(mangaId: Long, chapterIndex: Int) =
|
||||
"/api/v1/download/$mangaId/chapter/$chapterIndex"
|
||||
|
||||
@Delete
|
||||
fun deleteDownloadChapterRequest(mangaId: Long, chapterIndex: Int) =
|
||||
"/api/v1/download/$mangaId/chapter/$chapterIndex"
|
||||
|
||||
19
src/main/kotlin/ca/gosyer/data/server/requests/Downloads.kt
Normal file
19
src/main/kotlin/ca/gosyer/data/server/requests/Downloads.kt
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
package ca.gosyer.data.server.requests
|
||||
|
||||
fun downloadsQuery() =
|
||||
"/api/v1/downloads"
|
||||
|
||||
fun downloadsStartRequest() =
|
||||
"/api/v1/downloads/start"
|
||||
|
||||
fun downloadsStopRequest() =
|
||||
"/api/v1/downloads/stop"
|
||||
|
||||
fun downloadsClearRequest() =
|
||||
"/api/v1/downloads/clear"
|
||||
@@ -63,7 +63,9 @@ suspend fun <T> withIOContext(
|
||||
fun Throwable.throwIfCancellation() { if (this is CancellationException) throw this }
|
||||
|
||||
fun <T> Result<T>.throwIfCancellation(): Result<T> {
|
||||
if (isFailure) {
|
||||
val exception = exceptionOrNull()
|
||||
if (exception is CancellationException) throw exception
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user