diff --git a/server/src/main/kotlin/suwayomi/server/database/migration/M0004_AnimeTablesBatch1.kt b/server/src/main/kotlin/suwayomi/server/database/migration/M0004_AnimeTablesBatch1.kt index 5a6e8b1..3bca772 100644 --- a/server/src/main/kotlin/suwayomi/server/database/migration/M0004_AnimeTablesBatch1.kt +++ b/server/src/main/kotlin/suwayomi/server/database/migration/M0004_AnimeTablesBatch1.kt @@ -14,7 +14,7 @@ import suwayomi.server.database.migration.lib.Migration * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ class M0004_AnimeTablesBatch1 : Migration() { - private object AnimeExtensionTable : IntIdTable() { + private class AnimeExtensionTable : IntIdTable() { val apkName = varchar("apk_name", 1024) // default is the local source icon from tachiyomi @@ -35,7 +35,7 @@ class M0004_AnimeTablesBatch1 : Migration() { val classFQName = varchar("class_name", 1024).default("") // fully qualified name } - private object AnimeSourceTable : IdTable() { + private class AnimeSourceTable : IdTable() { override val id = long("id").entityId() val name = varchar("name", 128) val lang = varchar("lang", 10) @@ -46,8 +46,8 @@ class M0004_AnimeTablesBatch1 : Migration() { override fun run() { transaction { SchemaUtils.create( - AnimeExtensionTable, - AnimeSourceTable + AnimeExtensionTable(), + AnimeSourceTable() ) } } diff --git a/server/src/main/kotlin/suwayomi/server/database/migration/M0005_AnimeTablesBatch2.kt b/server/src/main/kotlin/suwayomi/server/database/migration/M0005_AnimeTablesBatch2.kt index 9169cf3..35bfb21 100644 --- a/server/src/main/kotlin/suwayomi/server/database/migration/M0005_AnimeTablesBatch2.kt +++ b/server/src/main/kotlin/suwayomi/server/database/migration/M0005_AnimeTablesBatch2.kt @@ -14,7 +14,7 @@ import suwayomi.server.database.migration.lib.Migration * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ class M0005_AnimeTablesBatch2 : Migration() { - private object AnimeTable : IntIdTable() { + private class AnimeTable : IntIdTable() { val url = varchar("url", 2048) val title = varchar("title", 512) val initialized = bool("initialized").default(false) @@ -38,7 +38,7 @@ class M0005_AnimeTablesBatch2 : Migration() { override fun run() { transaction { SchemaUtils.create( - AnimeTable + AnimeTable() ) } } diff --git a/server/src/main/kotlin/suwayomi/server/database/migration/M0006_AnimeTablesBatch3.kt b/server/src/main/kotlin/suwayomi/server/database/migration/M0006_AnimeTablesBatch3.kt index 891a708..5a83f08 100644 --- a/server/src/main/kotlin/suwayomi/server/database/migration/M0006_AnimeTablesBatch3.kt +++ b/server/src/main/kotlin/suwayomi/server/database/migration/M0006_AnimeTablesBatch3.kt @@ -14,7 +14,7 @@ import suwayomi.server.database.migration.lib.Migration * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ class M0006_AnimeTablesBatch3 : Migration() { - private object EpisodeTable : IntIdTable() { + private class EpisodeTable : IntIdTable() { val url = varchar("url", 2048) val name = varchar("name", 512) val date_upload = long("date_upload").default(0) @@ -34,7 +34,7 @@ class M0006_AnimeTablesBatch3 : Migration() { override fun run() { transaction { SchemaUtils.create( - EpisodeTable + EpisodeTable() ) } } diff --git a/server/src/main/kotlin/suwayomi/server/database/migration/M0010_MangaAndChapterMeta.kt b/server/src/main/kotlin/suwayomi/server/database/migration/M0010_MangaAndChapterMeta.kt new file mode 100644 index 0000000..8a8e019 --- /dev/null +++ b/server/src/main/kotlin/suwayomi/server/database/migration/M0010_MangaAndChapterMeta.kt @@ -0,0 +1,38 @@ +package suwayomi.server.database.migration + +import org.jetbrains.exposed.dao.id.IntIdTable +import org.jetbrains.exposed.sql.ReferenceOption +import org.jetbrains.exposed.sql.SchemaUtils +import org.jetbrains.exposed.sql.transactions.transaction +import suwayomi.server.database.migration.lib.Migration +import suwayomi.tachidesk.model.table.ChapterTable +import suwayomi.tachidesk.model.table.MangaTable + +/* + * 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/. */ + +class M0010_MangaAndChapterMeta : Migration() { + private class ChapterMetaTable : IntIdTable() { + val key = varchar("key", 256) + val value = varchar("value", 4096) + val ref = reference("chapter_ref", ChapterTable, ReferenceOption.CASCADE) + } + private class MangaMetaTable : IntIdTable() { + val key = varchar("key", 256) + val value = varchar("value", 4096) + val ref = reference("manga_ref", MangaTable, ReferenceOption.CASCADE) + } + + override fun run() { + transaction { + SchemaUtils.create( + ChapterMetaTable(), + MangaMetaTable() + ) + } + } +} diff --git a/server/src/main/kotlin/suwayomi/tachidesk/TachideskAPI.kt b/server/src/main/kotlin/suwayomi/tachidesk/TachideskAPI.kt index 5c7df9c..b7d32b5 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/TachideskAPI.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/TachideskAPI.kt @@ -18,11 +18,13 @@ import suwayomi.tachidesk.impl.CategoryManga.removeMangaFromCategory import suwayomi.tachidesk.impl.Chapter.getChapter import suwayomi.tachidesk.impl.Chapter.getChapterList import suwayomi.tachidesk.impl.Chapter.modifyChapter +import suwayomi.tachidesk.impl.Chapter.modifyChapterMeta import suwayomi.tachidesk.impl.Library.addMangaToLibrary import suwayomi.tachidesk.impl.Library.getLibraryMangas import suwayomi.tachidesk.impl.Library.removeMangaFromLibrary import suwayomi.tachidesk.impl.Manga.getManga import suwayomi.tachidesk.impl.Manga.getMangaThumbnail +import suwayomi.tachidesk.impl.Manga.modifyMangaMeta import suwayomi.tachidesk.impl.MangaList.getMangaList import suwayomi.tachidesk.impl.Page.getPageImage import suwayomi.tachidesk.impl.Search.sourceFilters @@ -185,6 +187,18 @@ object TachideskAPI { ctx.json(future { getChapterList(mangaId, onlineFetch) }) } + // used to modify a manga's meta paramaters + app.patch("/api/v1/manga/:mangaId/meta") { ctx -> + val mangaId = ctx.pathParam("mangaId").toInt() + + val key = ctx.formParam("key")!! + val value = ctx.formParam("value")!! + + modifyMangaMeta(mangaId, key, value) + + ctx.status(200) + } + // used to display a chapter, get a chapter in order to show it's pages app.get("/api/v1/manga/:mangaId/chapter/:chapterIndex") { ctx -> val chapterIndex = ctx.pathParam("chapterIndex").toInt() @@ -207,6 +221,19 @@ object TachideskAPI { ctx.status(200) } + // used to modify a chapter's meta paramaters + app.patch("/api/v1/manga/:mangaId/chapter/:chapterIndex/meta") { ctx -> + val chapterIndex = ctx.pathParam("chapterIndex").toInt() + val mangaId = ctx.pathParam("mangaId").toInt() + + val key = ctx.formParam("key")!! + val value = ctx.formParam("value")!! + + modifyChapterMeta(mangaId, chapterIndex, key, value) + + ctx.status(200) + } + // get page at index "index" app.get("/api/v1/manga/:mangaId/chapter/:chapterIndex/page/:index") { ctx -> val mangaId = ctx.pathParam("mangaId").toInt() diff --git a/server/src/main/kotlin/suwayomi/tachidesk/impl/Chapter.kt b/server/src/main/kotlin/suwayomi/tachidesk/impl/Chapter.kt index d062377..ea31205 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/impl/Chapter.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/impl/Chapter.kt @@ -9,6 +9,7 @@ package suwayomi.tachidesk.impl import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga +import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.sql.SortOrder.DESC import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.deleteWhere @@ -20,6 +21,7 @@ import suwayomi.tachidesk.impl.Manga.getManga import suwayomi.tachidesk.impl.util.GetHttpSource.getHttpSource import suwayomi.tachidesk.impl.util.lang.awaitSingle import suwayomi.tachidesk.model.dataclass.ChapterDataClass +import suwayomi.tachidesk.model.table.ChapterMetaTable import suwayomi.tachidesk.model.table.ChapterTable import suwayomi.tachidesk.model.table.MangaTable import suwayomi.tachidesk.model.table.PageTable @@ -131,6 +133,7 @@ object Chapter { dbChapter[ChapterTable.pageCount], chapterList.size, + meta = getChapterMetaMap(dbChapter[ChapterTable.id]) ) } } @@ -199,7 +202,8 @@ object Chapter { chapterEntry[ChapterTable.chapterIndex], chapterEntry[ChapterTable.isDownloaded], pageCount, - chapterCount.toInt() + chapterCount.toInt(), + getChapterMetaMap(chapterEntry[ChapterTable.id]) ) } else { ChapterTable.toDataClass(chapterEntry) @@ -230,4 +234,30 @@ object Chapter { } } } + + fun getChapterMetaMap(chapter: EntityID): Map { + return transaction { + ChapterMetaTable.select { ChapterMetaTable.ref eq chapter } + .associate { it[ChapterMetaTable.key] to it[ChapterMetaTable.value] } + } + } + + fun modifyChapterMeta(mangaId: Int, chapterIndex: Int, key: String, value: String) { + transaction { + val chapter = ChapterTable.select { (ChapterTable.manga eq mangaId) and (ChapterTable.chapterIndex eq chapterIndex) } + .first()[ChapterTable.id] + val meta = transaction { ChapterMetaTable.select { (ChapterMetaTable.ref eq chapter) and (ChapterMetaTable.key eq key) } }.firstOrNull() + if (meta == null) { + ChapterMetaTable.insert { + it[ChapterMetaTable.key] = key + it[ChapterMetaTable.value] = value + it[ChapterMetaTable.ref] = chapter + } + } else { + ChapterMetaTable.update { + it[ChapterMetaTable.value] = value + } + } + } + } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/impl/Manga.kt b/server/src/main/kotlin/suwayomi/tachidesk/impl/Manga.kt index 490d386..9e7c953 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/impl/Manga.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/impl/Manga.kt @@ -9,6 +9,9 @@ package suwayomi.tachidesk.impl import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.source.model.SManga +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.sql.and +import org.jetbrains.exposed.sql.insert import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.update @@ -24,6 +27,7 @@ import suwayomi.tachidesk.impl.util.network.await import suwayomi.tachidesk.impl.util.storage.CachedImageResponse.clearCachedImage import suwayomi.tachidesk.impl.util.storage.CachedImageResponse.getCachedImageResponse import suwayomi.tachidesk.model.dataclass.MangaDataClass +import suwayomi.tachidesk.model.table.MangaMetaTable import suwayomi.tachidesk.model.table.MangaStatus import suwayomi.tachidesk.model.table.MangaTable import java.io.InputStream @@ -57,6 +61,7 @@ object Manga { MangaStatus.valueOf(mangaEntry[MangaTable.status]).name, mangaEntry[MangaTable.inLibrary], getSource(mangaEntry[MangaTable.sourceReference]), + getMangaMetaMap(mangaEntry[MangaTable.id]), false ) } else { // initialize manga @@ -104,11 +109,38 @@ object Manga { MangaStatus.valueOf(fetchedManga.status).name, mangaEntry[MangaTable.inLibrary], getSource(mangaEntry[MangaTable.sourceReference]), + getMangaMetaMap(mangaEntry[MangaTable.id]), true ) } } + fun getMangaMetaMap(manga: EntityID): Map { + return transaction { + MangaMetaTable.select { MangaMetaTable.ref eq manga } + .associate { it[MangaMetaTable.key] to it[MangaMetaTable.value] } + } + } + + fun modifyMangaMeta(mangaId: Int, key: String, value: String) { + transaction { + val manga = MangaMetaTable.select { (MangaTable.id eq mangaId) } + .first()[MangaTable.id] + val meta = transaction { MangaMetaTable.select { (MangaMetaTable.ref eq manga) and (MangaMetaTable.key eq key) } }.firstOrNull() + if (meta == null) { + MangaMetaTable.insert { + it[MangaMetaTable.key] = key + it[MangaMetaTable.value] = value + it[MangaMetaTable.ref] = manga + } + } else { + MangaMetaTable.update { + it[MangaMetaTable.value] = value + } + } + } + } + private val applicationDirs by DI.global.instance() suspend fun getMangaThumbnail(mangaId: Int): Pair { val saveDir = applicationDirs.mangaThumbnailsRoot diff --git a/server/src/main/kotlin/suwayomi/tachidesk/impl/MangaList.kt b/server/src/main/kotlin/suwayomi/tachidesk/impl/MangaList.kt index b3359c0..cb8e28d 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/impl/MangaList.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/impl/MangaList.kt @@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.source.model.MangasPage import org.jetbrains.exposed.sql.insertAndGetId import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.transactions.transaction +import suwayomi.tachidesk.impl.Manga.getMangaMetaMap import suwayomi.tachidesk.impl.util.GetHttpSource.getHttpSource import suwayomi.tachidesk.impl.util.lang.awaitSingle import suwayomi.tachidesk.model.dataclass.MangaDataClass @@ -89,7 +90,8 @@ object MangaList { mangaEntry[MangaTable.description], mangaEntry[MangaTable.genre], MangaStatus.valueOf(mangaEntry[MangaTable.status]).name, - mangaEntry[MangaTable.inLibrary] + mangaEntry[MangaTable.inLibrary], + meta = getMangaMetaMap(mangaEntry[MangaTable.id]) ) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/model/dataclass/ChapterDataClass.kt b/server/src/main/kotlin/suwayomi/tachidesk/model/dataclass/ChapterDataClass.kt index 9a956f5..528effe 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/model/dataclass/ChapterDataClass.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/model/dataclass/ChapterDataClass.kt @@ -39,4 +39,6 @@ data class ChapterDataClass( /** total chapter count, used to calculate if there's a next and prev chapter */ val chapterCount: Int? = null, + /** used to store client specific values */ + val meta: Map = emptyMap(), ) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/model/dataclass/MangaDataClass.kt b/server/src/main/kotlin/suwayomi/tachidesk/model/dataclass/MangaDataClass.kt index 7ed07ff..1570a52 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/model/dataclass/MangaDataClass.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/model/dataclass/MangaDataClass.kt @@ -26,6 +26,7 @@ data class MangaDataClass( val status: String = MangaStatus.UNKNOWN.name, val inLibrary: Boolean = false, val source: SourceDataClass? = null, + val meta: Map = emptyMap(), val freshData: Boolean = false ) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/model/table/ChapterMetaTable.kt b/server/src/main/kotlin/suwayomi/tachidesk/model/table/ChapterMetaTable.kt new file mode 100644 index 0000000..c3ca6c8 --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/model/table/ChapterMetaTable.kt @@ -0,0 +1,10 @@ +package suwayomi.tachidesk.model.table + +import org.jetbrains.exposed.dao.id.IntIdTable +import org.jetbrains.exposed.sql.ReferenceOption + +object ChapterMetaTable : IntIdTable() { + val key = varchar("key", 256) + val value = varchar("value", 4096) + val ref = reference("chapter_ref", ChapterTable, ReferenceOption.CASCADE) +} diff --git a/server/src/main/kotlin/suwayomi/tachidesk/model/table/ChapterTable.kt b/server/src/main/kotlin/suwayomi/tachidesk/model/table/ChapterTable.kt index c39ecd3..ad187d4 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/model/table/ChapterTable.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/model/table/ChapterTable.kt @@ -11,6 +11,7 @@ import org.jetbrains.exposed.dao.id.IntIdTable import org.jetbrains.exposed.sql.ResultRow import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.transactions.transaction +import suwayomi.tachidesk.impl.Chapter.getChapterMetaMap import suwayomi.tachidesk.model.dataclass.ChapterDataClass object ChapterTable : IntIdTable() { @@ -51,4 +52,5 @@ fun ChapterTable.toDataClass(chapterEntry: ResultRow) = chapterEntry[isDownloaded], chapterEntry[pageCount], transaction { ChapterTable.select { ChapterTable.manga eq chapterEntry[manga].value }.count().toInt() }, + getChapterMetaMap(chapterEntry[id]), ) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/model/table/MangaMetaTable.kt b/server/src/main/kotlin/suwayomi/tachidesk/model/table/MangaMetaTable.kt new file mode 100644 index 0000000..f726997 --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/model/table/MangaMetaTable.kt @@ -0,0 +1,10 @@ +package suwayomi.tachidesk.model.table + +import org.jetbrains.exposed.dao.id.IntIdTable +import org.jetbrains.exposed.sql.ReferenceOption + +object MangaMetaTable : IntIdTable() { + val key = varchar("key", 256) + val value = varchar("value", 4096) + val ref = reference("manga_ref", MangaTable, ReferenceOption.CASCADE) +} diff --git a/server/src/main/kotlin/suwayomi/tachidesk/model/table/MangaTable.kt b/server/src/main/kotlin/suwayomi/tachidesk/model/table/MangaTable.kt index 9792ef7..d1d9039 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/model/table/MangaTable.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/model/table/MangaTable.kt @@ -10,6 +10,7 @@ package suwayomi.tachidesk.model.table import eu.kanade.tachiyomi.source.model.SManga import org.jetbrains.exposed.dao.id.IntIdTable import org.jetbrains.exposed.sql.ResultRow +import suwayomi.tachidesk.impl.Manga.getMangaMetaMap import suwayomi.tachidesk.impl.MangaList.proxyThumbnailUrl import suwayomi.tachidesk.model.dataclass.MangaDataClass import suwayomi.tachidesk.model.table.MangaStatus.Companion @@ -50,7 +51,8 @@ fun MangaTable.toDataClass(mangaEntry: ResultRow) = mangaEntry[description], mangaEntry[genre], Companion.valueOf(mangaEntry[status]).name, - mangaEntry[inLibrary] + mangaEntry[inLibrary], + meta = getMangaMetaMap(mangaEntry[id]) ) enum class MangaStatus(val status: Int) {