From 9ee3f46ff0512e5d7fb853d35c112168f3a1d839 Mon Sep 17 00:00:00 2001 From: schroda <50052685+schroda@users.noreply.github.com> Date: Mon, 28 Aug 2023 04:38:33 +0200 Subject: [PATCH] Feature/graphql chapter pages mutation handle downloaded chapters (#665) * Update chapter page refresh logic with logic from "ChapterMutation" * Rename function to "getChapterDownloadReadyByIndex" * Update "ChapterForDownload" to work with only "chapterId" being passed * Return database chapter page list in case chapter is downloaded In case the chapter is downloaded, fetching the chapter pages info should not be needed. It should also currently break reading downloaded chapters while being offline, since the page request will always fail, since there is no internet connection --- .../graphql/mutations/ChapterMutation.kt | 44 ++--------- .../manga/controller/MangaController.kt | 5 +- .../manga/impl/chapter/ChapterForDownload.kt | 78 +++++++++++-------- .../manga/impl/download/Downloader.kt | 4 +- 4 files changed, 54 insertions(+), 77 deletions(-) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/ChapterMutation.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/ChapterMutation.kt index ff66e3ef..df9bae19 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/ChapterMutation.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/ChapterMutation.kt @@ -1,9 +1,7 @@ package suwayomi.tachidesk.graphql.mutations -import eu.kanade.tachiyomi.source.model.SChapter import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.batchInsert import org.jetbrains.exposed.sql.deleteWhere import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.transactions.transaction @@ -11,11 +9,9 @@ import org.jetbrains.exposed.sql.update import suwayomi.tachidesk.graphql.types.ChapterMetaType import suwayomi.tachidesk.graphql.types.ChapterType import suwayomi.tachidesk.manga.impl.Chapter -import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrNull +import suwayomi.tachidesk.manga.impl.chapter.getChapterDownloadReadyById import suwayomi.tachidesk.manga.model.table.ChapterMetaTable import suwayomi.tachidesk.manga.model.table.ChapterTable -import suwayomi.tachidesk.manga.model.table.MangaTable -import suwayomi.tachidesk.manga.model.table.PageTable import suwayomi.tachidesk.server.JavalinSetup.future import java.time.Instant import java.util.concurrent.CompletableFuture @@ -203,43 +199,15 @@ class ChapterMutation { ): CompletableFuture { val (clientMutationId, chapterId) = input - val chapter = transaction { ChapterTable.select { ChapterTable.id eq chapterId }.first() } - val manga = transaction { MangaTable.select { MangaTable.id eq chapter[ChapterTable.manga] }.first() } - val source = getCatalogueSourceOrNull(manga[MangaTable.sourceReference])!! - return future { - source.getPageList( - SChapter.create().apply { - url = chapter[ChapterTable.url] - name = chapter[ChapterTable.name] - } - ) - }.thenApply { pageList -> - transaction { - PageTable.deleteWhere { PageTable.chapter eq chapterId } - PageTable.batchInsert(pageList) { page -> - this[PageTable.index] = page.index - this[PageTable.url] = page.url - this[PageTable.imageUrl] = page.imageUrl - this[PageTable.chapter] = chapterId - } - ChapterTable.update({ ChapterTable.id eq chapterId }) { - val pageCount = pageList.size - it[ChapterTable.pageCount] = pageCount - it[ChapterTable.lastPageRead] = chapter[ChapterTable.lastPageRead].coerceAtMost(pageCount - 1) - } - } - - val mangaId = manga[MangaTable.id].value - val chapterIndex = chapter[ChapterTable.sourceOrder] + getChapterDownloadReadyById(chapterId) + }.thenApply { chapter -> FetchChapterPagesPayload( clientMutationId = clientMutationId, - pages = List(pageList.size) { index -> - "/api/v1/manga/$mangaId/chapter/$chapterIndex/page/$index" + pages = List(chapter.pageCount) { index -> + "/api/v1/manga/${chapter.mangaId}/chapter/${chapter.index}/page/$index" }, - chapter = ChapterType( - transaction { ChapterTable.select { ChapterTable.id eq chapterId }.first() } - ) + chapter = ChapterType(chapter) ) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/MangaController.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/MangaController.kt index f9a089c6..b984c87a 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/MangaController.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/MangaController.kt @@ -8,7 +8,6 @@ package suwayomi.tachidesk.manga.controller * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import io.javalin.http.HttpCode -import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import org.kodein.di.DI import org.kodein.di.conf.global @@ -18,7 +17,7 @@ import suwayomi.tachidesk.manga.impl.Chapter import suwayomi.tachidesk.manga.impl.Library import suwayomi.tachidesk.manga.impl.Manga import suwayomi.tachidesk.manga.impl.Page -import suwayomi.tachidesk.manga.impl.chapter.getChapterDownloadReady +import suwayomi.tachidesk.manga.impl.chapter.getChapterDownloadReadyByIndex import suwayomi.tachidesk.manga.model.dataclass.CategoryDataClass import suwayomi.tachidesk.manga.model.dataclass.ChapterDataClass import suwayomi.tachidesk.manga.model.dataclass.MangaDataClass @@ -293,7 +292,7 @@ object MangaController { } }, behaviorOf = { ctx, mangaId, chapterIndex -> - ctx.future(future { getChapterDownloadReady(chapterIndex, mangaId) }) + ctx.future(future { getChapterDownloadReadyByIndex(chapterIndex, mangaId) }) }, withResults = { json(HttpCode.OK) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/chapter/ChapterForDownload.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/chapter/ChapterForDownload.kt index b184dea1..adfcfb9b 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/chapter/ChapterForDownload.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/chapter/ChapterForDownload.kt @@ -10,8 +10,10 @@ package suwayomi.tachidesk.manga.impl.chapter import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.SChapter import org.jetbrains.exposed.sql.ResultRow +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.insert +import org.jetbrains.exposed.sql.batchInsert +import org.jetbrains.exposed.sql.deleteWhere import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.update @@ -27,15 +29,24 @@ import suwayomi.tachidesk.manga.model.table.PageTable import suwayomi.tachidesk.manga.model.table.toDataClass import java.io.File -suspend fun getChapterDownloadReady(chapterIndex: Int, mangaId: Int): ChapterDataClass { - val chapter = ChapterForDownload(chapterIndex, mangaId) +suspend fun getChapterDownloadReady(chapterId: Int? = null, chapterIndex: Int? = null, mangaId: Int? = null): ChapterDataClass { + val chapter = ChapterForDownload(chapterId, chapterIndex, mangaId) return chapter.asDownloadReady() } +suspend fun getChapterDownloadReadyById(chapterId: Int): ChapterDataClass { + return getChapterDownloadReady(chapterId = chapterId) +} + +suspend fun getChapterDownloadReadyByIndex(chapterIndex: Int, mangaId: Int): ChapterDataClass { + return getChapterDownloadReady(chapterIndex = chapterIndex, mangaId = mangaId) +} + private class ChapterForDownload( - private val chapterIndex: Int, - private val mangaId: Int + optChapterId: Int? = null, + optChapterIndex: Int? = null, + optMangaId: Int? = null ) { suspend fun asDownloadReady(): ChapterDataClass { if (isNotCompletelyDownloaded()) { @@ -51,11 +62,27 @@ private class ChapterForDownload( private fun asDataClass() = ChapterTable.toDataClass(chapterEntry) - var chapterEntry: ResultRow = freshChapterEntry() + var chapterEntry: ResultRow + val chapterId: Int + val chapterIndex: Int + val mangaId: Int - private fun freshChapterEntry() = transaction { + init { + chapterEntry = freshChapterEntry(optChapterId, optChapterIndex, optMangaId) + chapterId = chapterEntry[ChapterTable.id].value + chapterIndex = chapterEntry[ChapterTable.sourceOrder] + mangaId = chapterEntry[ChapterTable.manga].value + } + + private fun freshChapterEntry(optChapterId: Int? = null, optChapterIndex: Int? = null, optMangaId: Int? = null) = transaction { ChapterTable.select { - (ChapterTable.sourceOrder eq chapterIndex) and (ChapterTable.manga eq mangaId) + if (optChapterId != null) { + ChapterTable.id eq optChapterId + } else if (optChapterIndex != null && optMangaId != null) { + (ChapterTable.sourceOrder eq optChapterIndex) and (ChapterTable.manga eq optMangaId) + } else { + throw Exception("'optChapterId' or 'optChapterIndex' and 'optMangaId' have to be passed") + } }.first() } @@ -81,48 +108,31 @@ private class ChapterForDownload( } private fun updateDatabasePages(pageList: List) { - val chapterId = chapterEntry[ChapterTable.id].value - transaction { - pageList.forEach { page -> - val pageEntry = transaction { - PageTable.select { (PageTable.chapter eq chapterId) and (PageTable.index eq page.index) } - .firstOrNull() - } - if (pageEntry == null) { - PageTable.insert { - it[index] = page.index - it[url] = page.url - it[imageUrl] = page.imageUrl - it[chapter] = chapterId - } - } else { - PageTable.update({ (PageTable.chapter eq chapterId) and (PageTable.index eq page.index) }) { - it[url] = page.url - it[imageUrl] = page.imageUrl - } - } + PageTable.deleteWhere { PageTable.chapter eq chapterId } + PageTable.batchInsert(pageList) { page -> + this[PageTable.index] = page.index + this[PageTable.url] = page.url + this[PageTable.imageUrl] = page.imageUrl + this[PageTable.chapter] = chapterId } } updatePageCount(pageList, chapterId) // chapter was updated - chapterEntry = freshChapterEntry() + chapterEntry = freshChapterEntry(chapterId, chapterIndex, mangaId) } private fun updatePageCount( pageList: List, chapterId: Int ) { - val pageCount = pageList.count() - transaction { - val lastPageRead = ChapterTable.select { ChapterTable.id eq chapterId }.firstOrNull()?.get(ChapterTable.lastPageRead) ?: 0 - ChapterTable.update({ ChapterTable.id eq chapterId }) { + val pageCount = pageList.size it[ChapterTable.pageCount] = pageCount - it[ChapterTable.lastPageRead] = lastPageRead.coerceAtMost(pageCount - 1) + it[ChapterTable.lastPageRead] = chapterEntry[ChapterTable.lastPageRead].coerceAtMost(pageCount - 1) } } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/Downloader.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/Downloader.kt index bc080fb8..f97c9250 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/Downloader.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/Downloader.kt @@ -20,7 +20,7 @@ import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.update import suwayomi.tachidesk.manga.impl.ChapterDownloadHelper -import suwayomi.tachidesk.manga.impl.chapter.getChapterDownloadReady +import suwayomi.tachidesk.manga.impl.chapter.getChapterDownloadReadyByIndex 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.Error @@ -98,7 +98,7 @@ class Downloader( download.state = Downloading step(download, true) - download.chapter = getChapterDownloadReady(download.chapterIndex, download.mangaId) + download.chapter = getChapterDownloadReadyByIndex(download.chapterIndex, download.mangaId) step(download, false) ChapterDownloadHelper.download(download.mangaId, download.chapter.id, download, scope, this::step)