Skip to content

Commit

Permalink
Fetch Manga and Chapters in GQL (#555)
Browse files Browse the repository at this point in the history
  • Loading branch information
Syer10 authored May 24, 2023
1 parent 603105e commit ff7ac8a
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 84 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ package suwayomi.tachidesk.graphql.mutations
import com.expediagroup.graphql.server.extensions.getValueFromDataLoader
import com.expediagroup.graphql.server.extensions.getValuesFromDataLoader
import graphql.schema.DataFetchingEnvironment
import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.transactions.transaction
import org.jetbrains.exposed.sql.update
import suwayomi.tachidesk.graphql.types.ChapterType
import suwayomi.tachidesk.manga.impl.Chapter
import suwayomi.tachidesk.manga.model.table.ChapterTable
import suwayomi.tachidesk.server.JavalinSetup.future
import java.time.Instant
import java.util.concurrent.CompletableFuture

Expand Down Expand Up @@ -63,7 +66,10 @@ class ChapterMutation {
}
}

fun updateChapter(dataFetchingEnvironment: DataFetchingEnvironment, input: UpdateChapterInput): CompletableFuture<UpdateChapterPayload> {
fun updateChapter(
dataFetchingEnvironment: DataFetchingEnvironment,
input: UpdateChapterInput
): CompletableFuture<UpdateChapterPayload> {
val (clientMutationId, id, patch) = input

updateChapters(listOf(id), patch)
Expand All @@ -76,7 +82,10 @@ class ChapterMutation {
}
}

fun updateChapters(dataFetchingEnvironment: DataFetchingEnvironment, input: UpdateChaptersInput): CompletableFuture<UpdateChaptersPayload> {
fun updateChapters(
dataFetchingEnvironment: DataFetchingEnvironment,
input: UpdateChaptersInput
): CompletableFuture<UpdateChaptersPayload> {
val (clientMutationId, ids, patch) = input

updateChapters(ids, patch)
Expand All @@ -88,4 +97,31 @@ class ChapterMutation {
)
}
}

data class FetchChaptersInput(
val clientMutationId: String? = null,
val mangaId: Int
)
data class FetchChaptersPayload(
val clientMutationId: String?,
val chapters: List<ChapterType>
)

fun fetchChapters(
input: FetchChaptersInput
): CompletableFuture<FetchChaptersPayload> {
val (clientMutationId, mangaId) = input

return future {
Chapter.fetchChapterList(mangaId)
}.thenApply {
val chapters = ChapterTable.select { ChapterTable.manga eq mangaId }
.orderBy(ChapterTable.sourceOrder)
.map { ChapterType(it) }
FetchChaptersPayload(
clientMutationId = clientMutationId,
chapters = chapters
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@ package suwayomi.tachidesk.graphql.mutations
import com.expediagroup.graphql.server.extensions.getValueFromDataLoader
import com.expediagroup.graphql.server.extensions.getValuesFromDataLoader
import graphql.schema.DataFetchingEnvironment
import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.transactions.transaction
import org.jetbrains.exposed.sql.update
import suwayomi.tachidesk.graphql.queries.MangaQuery
import suwayomi.tachidesk.graphql.types.MangaType
import suwayomi.tachidesk.manga.impl.Manga
import suwayomi.tachidesk.manga.model.table.MangaTable
import suwayomi.tachidesk.server.JavalinSetup.future
import java.util.concurrent.CompletableFuture

/**
Expand Down Expand Up @@ -81,4 +85,31 @@ class MangaMutation {
)
}
}

data class FetchMangaInput(
val clientMutationId: String? = null,
val id: Int
)
data class FetchMangaPayload(
val clientMutationId: String?,
val manga: MangaType
)

fun fetchManga(
input: FetchMangaInput
): CompletableFuture<FetchMangaPayload> {
val (clientMutationId, id) = input

return future {
Manga.fetchManga(id)
}.thenApply {
val manga = transaction {
MangaTable.select { MangaTable.id eq id }.first()
}
FetchMangaPayload(
clientMutationId = clientMutationId,
manga = MangaType(manga)
)
}
}
}
88 changes: 47 additions & 41 deletions server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Chapter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package suwayomi.tachidesk.manga.impl
* 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 eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.chapter.ChapterRecognition
Expand All @@ -31,7 +32,6 @@ import suwayomi.tachidesk.manga.model.dataclass.PaginatedList
import suwayomi.tachidesk.manga.model.dataclass.paginatedFrom
import suwayomi.tachidesk.manga.model.table.ChapterMetaTable
import suwayomi.tachidesk.manga.model.table.ChapterTable
import suwayomi.tachidesk.manga.model.table.ChapterTable.scanlator
import suwayomi.tachidesk.manga.model.table.MangaTable
import suwayomi.tachidesk.manga.model.table.PageTable
import suwayomi.tachidesk.manga.model.table.toDataClass
Expand All @@ -56,6 +56,48 @@ object Chapter {
}

private suspend fun getSourceChapters(mangaId: Int): List<ChapterDataClass> {
val chapterList = fetchChapterList(mangaId)

val dbChapterMap = transaction {
ChapterTable.select { ChapterTable.manga eq mangaId }
.associateBy({ it[ChapterTable.url] }, { it })
}

val chapterIds = chapterList.map { dbChapterMap.getValue(it.url)[ChapterTable.id] }
val chapterMetas = getChaptersMetaMaps(chapterIds)

return chapterList.mapIndexed { index, it ->

val dbChapter = dbChapterMap.getValue(it.url)

ChapterDataClass(
id = dbChapter[ChapterTable.id].value,
url = it.url,
name = it.name,
uploadDate = it.date_upload,
chapterNumber = it.chapter_number,
scanlator = it.scanlator,
mangaId = mangaId,

read = dbChapter[ChapterTable.isRead],
bookmarked = dbChapter[ChapterTable.isBookmarked],
lastPageRead = dbChapter[ChapterTable.lastPageRead],
lastReadAt = dbChapter[ChapterTable.lastReadAt],

index = chapterList.size - index,
fetchedAt = dbChapter[ChapterTable.fetchedAt],
realUrl = dbChapter[ChapterTable.realUrl],
downloaded = dbChapter[ChapterTable.isDownloaded],

pageCount = dbChapter[ChapterTable.pageCount],

chapterCount = chapterList.size,
meta = chapterMetas.getValue(dbChapter[ChapterTable.id])
)
}
}

suspend fun fetchChapterList(mangaId: Int): List<SChapter> {
val manga = getManga(mangaId)
val source = getCatalogueSourceOrStub(manga.sourceId.toLong())

Expand All @@ -72,7 +114,6 @@ object Chapter {
ChapterRecognition.parseChapterNumber(it, sManga)
}

val chapterCount = chapterList.count()
var now = Instant.now().epochSecond

transaction {
Expand Down Expand Up @@ -118,9 +159,10 @@ object Chapter {

// clear any orphaned/duplicate chapters that are in the db but not in `chapterList`
val dbChapterCount = transaction { ChapterTable.select { ChapterTable.manga eq mangaId }.count() }
if (dbChapterCount > chapterCount) { // we got some clean up due
if (dbChapterCount > chapterList.size) { // we got some clean up due
val dbChapterList = transaction {
ChapterTable.select { ChapterTable.manga eq mangaId }.orderBy(ChapterTable.url to ASC).toList()
ChapterTable.select { ChapterTable.manga eq mangaId }
.orderBy(ChapterTable.url to ASC).toList()
}
val chapterUrls = chapterList.map { it.url }.toSet()

Expand All @@ -137,43 +179,7 @@ object Chapter {
}
}

val dbChapterMap = transaction {
ChapterTable.select { ChapterTable.manga eq mangaId }
.associateBy({ it[ChapterTable.url] }, { it })
}

val chapterIds = chapterList.map { dbChapterMap.getValue(it.url)[ChapterTable.id] }
val chapterMetas = getChaptersMetaMaps(chapterIds)

return chapterList.mapIndexed { index, it ->

val dbChapter = dbChapterMap.getValue(it.url)

ChapterDataClass(
id = dbChapter[ChapterTable.id].value,
url = it.url,
name = it.name,
uploadDate = it.date_upload,
chapterNumber = it.chapter_number,
scanlator = it.scanlator,
mangaId = mangaId,

read = dbChapter[ChapterTable.isRead],
bookmarked = dbChapter[ChapterTable.isBookmarked],
lastPageRead = dbChapter[ChapterTable.lastPageRead],
lastReadAt = dbChapter[ChapterTable.lastReadAt],

index = chapterCount - index,
fetchedAt = dbChapter[ChapterTable.fetchedAt],
realUrl = dbChapter[ChapterTable.realUrl],
downloaded = dbChapter[ChapterTable.isDownloaded],

pageCount = dbChapter[ChapterTable.pageCount],

chapterCount = chapterList.size,
meta = chapterMetas.getValue(dbChapter[ChapterTable.id])
)
}
return chapterList
}

fun modifyChapter(
Expand Down
90 changes: 49 additions & 41 deletions server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Manga.kt
Original file line number Diff line number Diff line change
Expand Up @@ -60,49 +60,10 @@ object Manga {
suspend fun getManga(mangaId: Int, onlineFetch: Boolean = false): MangaDataClass {
var mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() }

return if (mangaEntry[MangaTable.initialized] && !onlineFetch) {
return if (!onlineFetch && mangaEntry[MangaTable.initialized]) {
getMangaDataClass(mangaId, mangaEntry)
} else { // initialize manga
val source = getCatalogueSourceOrNull(mangaEntry[MangaTable.sourceReference])
?: return getMangaDataClass(mangaId, mangaEntry)
val sManga = SManga.create().apply {
url = mangaEntry[MangaTable.url]
title = mangaEntry[MangaTable.title]
}
val networkManga = source.fetchMangaDetails(sManga).awaitSingle()
sManga.copyFrom(networkManga)

transaction {
MangaTable.update({ MangaTable.id eq mangaId }) {
if (sManga.title != mangaEntry[MangaTable.title]) {
val canUpdateTitle = updateMangaDownloadDir(mangaId, sManga.title)

if (canUpdateTitle) {
it[MangaTable.title] = sManga.title
}
}
it[MangaTable.initialized] = true

it[MangaTable.artist] = sManga.artist
it[MangaTable.author] = sManga.author
it[MangaTable.description] = truncate(sManga.description, 4096)
it[MangaTable.genre] = sManga.genre
it[MangaTable.status] = sManga.status
if (!sManga.thumbnail_url.isNullOrEmpty() && sManga.thumbnail_url != mangaEntry[MangaTable.thumbnail_url]) {
it[MangaTable.thumbnail_url] = sManga.thumbnail_url
it[MangaTable.thumbnailUrlLastFetched] = Instant.now().epochSecond
clearMangaThumbnailCache(mangaId)
}

it[MangaTable.realUrl] = runCatching {
(source as? HttpSource)?.getMangaUrl(sManga)
}.getOrNull()

it[MangaTable.lastFetchedAt] = Instant.now().epochSecond

it[MangaTable.updateStrategy] = sManga.update_strategy.name
}
}
val sManga = fetchManga(mangaId) ?: return getMangaDataClass(mangaId, mangaEntry)

mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() }

Expand Down Expand Up @@ -135,6 +96,53 @@ object Manga {
}
}

suspend fun fetchManga(mangaId: Int): SManga? {
val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() }

val source = getCatalogueSourceOrNull(mangaEntry[MangaTable.sourceReference])
?: return null
val sManga = SManga.create().apply {
url = mangaEntry[MangaTable.url]
title = mangaEntry[MangaTable.title]
}
val networkManga = source.fetchMangaDetails(sManga).awaitSingle()
sManga.copyFrom(networkManga)

transaction {
MangaTable.update({ MangaTable.id eq mangaId }) {
if (sManga.title != mangaEntry[MangaTable.title]) {
val canUpdateTitle = updateMangaDownloadDir(mangaId, sManga.title)

if (canUpdateTitle) {
it[MangaTable.title] = sManga.title
}
}
it[MangaTable.initialized] = true

it[MangaTable.artist] = sManga.artist
it[MangaTable.author] = sManga.author
it[MangaTable.description] = truncate(sManga.description, 4096)
it[MangaTable.genre] = sManga.genre
it[MangaTable.status] = sManga.status
if (!sManga.thumbnail_url.isNullOrEmpty() && sManga.thumbnail_url != mangaEntry[MangaTable.thumbnail_url]) {
it[MangaTable.thumbnail_url] = sManga.thumbnail_url
it[MangaTable.thumbnailUrlLastFetched] = Instant.now().epochSecond
clearMangaThumbnailCache(mangaId)
}

it[MangaTable.realUrl] = runCatching {
(source as? HttpSource)?.getMangaUrl(sManga)
}.getOrNull()

it[MangaTable.lastFetchedAt] = Instant.now().epochSecond

it[MangaTable.updateStrategy] = sManga.update_strategy.name
}
}

return sManga
}

suspend fun getMangaFull(mangaId: Int, onlineFetch: Boolean = false): MangaDataClass {
val mangaDaaClass = getManga(mangaId, onlineFetch)

Expand Down

0 comments on commit ff7ac8a

Please sign in to comment.