Add meta info for clients to store custom data in (#113)

* Add meta info for clients to store custom data in

* PR comments

* Really update migration
This commit is contained in:
Syer10 2021-05-29 19:48:08 -04:00 committed by GitHub
parent e69dbbf418
commit 6c22fe193a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 167 additions and 11 deletions

View File

@ -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<Long>() {
private class AnimeSourceTable : IdTable<Long>() {
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()
)
}
}

View File

@ -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()
)
}
}

View File

@ -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()
)
}
}

View File

@ -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()
)
}
}
}

View File

@ -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()

View File

@ -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<Int>): Map<String, String> {
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
}
}
}
}
}

View File

@ -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<Int>): Map<String, String> {
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<ApplicationDirs>()
suspend fun getMangaThumbnail(mangaId: Int): Pair<InputStream, String> {
val saveDir = applicationDirs.mangaThumbnailsRoot

View File

@ -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])
)
}
}

View File

@ -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<String, String> = emptyMap(),
)

View File

@ -26,6 +26,7 @@ data class MangaDataClass(
val status: String = MangaStatus.UNKNOWN.name,
val inLibrary: Boolean = false,
val source: SourceDataClass? = null,
val meta: Map<String, String> = emptyMap(),
val freshData: Boolean = false
)

View File

@ -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)
}

View File

@ -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]),
)

View File

@ -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)
}

View File

@ -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) {