mirror of
https://github.com/Suwayomi/Suwayomi-Server.git
synced 2026-07-09 13:54:36 -05:00
Switch to a new Ktlint Formatter (#705)
* Switch to new Ktlint plugin * Add ktlintCheck to PR builds * Run formatter * Put ktlint version in libs toml * Fix lint * Use Zip4Java from libs.toml
This commit is contained in:
@@ -15,39 +15,41 @@ import suwayomi.tachidesk.server.util.withOperation
|
||||
|
||||
object GlobalMetaController {
|
||||
/** used to modify a category's meta parameters */
|
||||
val getMeta = handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Server level meta mapping")
|
||||
description("Get a list of globally stored key-value mapping, you can set values for whatever you want inside it.")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx ->
|
||||
ctx.json(GlobalMeta.getMetaMap())
|
||||
ctx.status(200)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
}
|
||||
)
|
||||
val getMeta =
|
||||
handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Server level meta mapping")
|
||||
description("Get a list of globally stored key-value mapping, you can set values for whatever you want inside it.")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx ->
|
||||
ctx.json(GlobalMeta.getMetaMap())
|
||||
ctx.status(200)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
},
|
||||
)
|
||||
|
||||
/** used to modify global meta parameters */
|
||||
val modifyMeta = handler(
|
||||
formParam<String>("key"),
|
||||
formParam<String>("value"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Add meta data to the global meta mapping")
|
||||
description("A simple Key-Value stored at server global level, you can set values for whatever you want inside it.")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, key, value ->
|
||||
GlobalMeta.modifyMeta(key, value)
|
||||
ctx.status(200)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
httpCode(HttpCode.NOT_FOUND)
|
||||
}
|
||||
)
|
||||
val modifyMeta =
|
||||
handler(
|
||||
formParam<String>("key"),
|
||||
formParam<String>("value"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Add meta data to the global meta mapping")
|
||||
description("A simple Key-Value stored at server global level, you can set values for whatever you want inside it.")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, key, value ->
|
||||
GlobalMeta.modifyMeta(key, value)
|
||||
ctx.status(200)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
httpCode(HttpCode.NOT_FOUND)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -19,36 +19,38 @@ import suwayomi.tachidesk.server.util.withOperation
|
||||
/** Settings Page/Screen */
|
||||
object SettingsController {
|
||||
/** returns some static info about the current app build */
|
||||
val about = handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("About Tachidesk")
|
||||
description("Returns some static info about the current app build")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx ->
|
||||
ctx.json(About.getAbout())
|
||||
},
|
||||
withResults = {
|
||||
json<AboutDataClass>(HttpCode.OK)
|
||||
}
|
||||
)
|
||||
val about =
|
||||
handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("About Tachidesk")
|
||||
description("Returns some static info about the current app build")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx ->
|
||||
ctx.json(About.getAbout())
|
||||
},
|
||||
withResults = {
|
||||
json<AboutDataClass>(HttpCode.OK)
|
||||
},
|
||||
)
|
||||
|
||||
/** check for app updates */
|
||||
val checkUpdate = handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Tachidesk update check")
|
||||
description("Check for app updates")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx ->
|
||||
ctx.future(
|
||||
future { AppUpdate.checkUpdate() }
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
json<Array<UpdateDataClass>>(HttpCode.OK)
|
||||
}
|
||||
)
|
||||
val checkUpdate =
|
||||
handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Tachidesk update check")
|
||||
description("Check for app updates")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx ->
|
||||
ctx.future(
|
||||
future { AppUpdate.checkUpdate() },
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
json<Array<UpdateDataClass>>(HttpCode.OK)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ package suwayomi.tachidesk.global.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 suwayomi.tachidesk.server.BuildConfig
|
||||
import suwayomi.tachidesk.server.generated.BuildConfig
|
||||
|
||||
data class AboutDataClass(
|
||||
val name: String,
|
||||
@@ -16,7 +16,7 @@ data class AboutDataClass(
|
||||
val buildType: String,
|
||||
val buildTime: Long,
|
||||
val github: String,
|
||||
val discord: String
|
||||
val discord: String,
|
||||
)
|
||||
|
||||
object About {
|
||||
@@ -28,7 +28,7 @@ object About {
|
||||
BuildConfig.BUILD_TYPE,
|
||||
BuildConfig.BUILD_TIME,
|
||||
BuildConfig.GITHUB,
|
||||
BuildConfig.DISCORD
|
||||
BuildConfig.DISCORD,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ data class UpdateDataClass(
|
||||
/** [channel] mirrors [suwayomi.tachidesk.server.BuildConfig.BUILD_TYPE] */
|
||||
val channel: String,
|
||||
val tag: String,
|
||||
val url: String
|
||||
val url: String,
|
||||
)
|
||||
|
||||
object AppUpdate {
|
||||
@@ -30,29 +30,31 @@ object AppUpdate {
|
||||
private val network: NetworkHelper by injectLazy()
|
||||
|
||||
suspend fun checkUpdate(): List<UpdateDataClass> {
|
||||
val stableJson = json.parseToJsonElement(
|
||||
network.client.newCall(
|
||||
GET(LATEST_STABLE_CHANNEL_URL)
|
||||
).await().body.string()
|
||||
).jsonObject
|
||||
val stableJson =
|
||||
json.parseToJsonElement(
|
||||
network.client.newCall(
|
||||
GET(LATEST_STABLE_CHANNEL_URL),
|
||||
).await().body.string(),
|
||||
).jsonObject
|
||||
|
||||
val previewJson = json.parseToJsonElement(
|
||||
network.client.newCall(
|
||||
GET(LATEST_PREVIEW_CHANNEL_URL)
|
||||
).await().body.string()
|
||||
).jsonObject
|
||||
val previewJson =
|
||||
json.parseToJsonElement(
|
||||
network.client.newCall(
|
||||
GET(LATEST_PREVIEW_CHANNEL_URL),
|
||||
).await().body.string(),
|
||||
).jsonObject
|
||||
|
||||
return listOf(
|
||||
UpdateDataClass(
|
||||
"Stable",
|
||||
stableJson["tag_name"]!!.jsonPrimitive.content,
|
||||
stableJson["html_url"]!!.jsonPrimitive.content
|
||||
stableJson["html_url"]!!.jsonPrimitive.content,
|
||||
),
|
||||
UpdateDataClass(
|
||||
"Preview",
|
||||
previewJson["tag_name"]!!.jsonPrimitive.content,
|
||||
previewJson["html_url"]!!.jsonPrimitive.content
|
||||
)
|
||||
previewJson["html_url"]!!.jsonPrimitive.content,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,11 +15,15 @@ import suwayomi.tachidesk.global.model.table.GlobalMetaTable
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
object GlobalMeta {
|
||||
fun modifyMeta(key: String, value: String) {
|
||||
fun modifyMeta(
|
||||
key: String,
|
||||
value: String,
|
||||
) {
|
||||
transaction {
|
||||
val meta = transaction {
|
||||
GlobalMetaTable.select { GlobalMetaTable.key eq key }
|
||||
}.firstOrNull()
|
||||
val meta =
|
||||
transaction {
|
||||
GlobalMetaTable.select { GlobalMetaTable.key eq key }
|
||||
}.firstOrNull()
|
||||
|
||||
if (meta == null) {
|
||||
GlobalMetaTable.insert {
|
||||
|
||||
@@ -23,7 +23,7 @@ object GraphQLController {
|
||||
ctx.future(
|
||||
future {
|
||||
server.execute(ctx)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -23,48 +23,56 @@ import suwayomi.tachidesk.server.JavalinSetup.future
|
||||
|
||||
class CategoryDataLoader : KotlinDataLoader<Int, CategoryType> {
|
||||
override val dataLoaderName = "CategoryDataLoader"
|
||||
override fun getDataLoader(): DataLoader<Int, CategoryType> = DataLoaderFactory.newDataLoader { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val categories = CategoryTable.select { CategoryTable.id inList ids }
|
||||
.map { CategoryType(it) }
|
||||
.associateBy { it.id }
|
||||
ids.map { categories[it] }
|
||||
|
||||
override fun getDataLoader(): DataLoader<Int, CategoryType> =
|
||||
DataLoaderFactory.newDataLoader { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val categories =
|
||||
CategoryTable.select { CategoryTable.id inList ids }
|
||||
.map { CategoryType(it) }
|
||||
.associateBy { it.id }
|
||||
ids.map { categories[it] }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CategoryForIdsDataLoader : KotlinDataLoader<List<Int>, CategoryNodeList> {
|
||||
override val dataLoaderName = "CategoryForIdsDataLoader"
|
||||
override fun getDataLoader(): DataLoader<List<Int>, CategoryNodeList> = DataLoaderFactory.newDataLoader { categoryIds ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val ids = categoryIds.flatten().distinct()
|
||||
val categories = CategoryTable.select { CategoryTable.id inList ids }.map { CategoryType(it) }
|
||||
categoryIds.map { categoryIds ->
|
||||
categories.filter { it.id in categoryIds }.toNodeList()
|
||||
|
||||
override fun getDataLoader(): DataLoader<List<Int>, CategoryNodeList> =
|
||||
DataLoaderFactory.newDataLoader { categoryIds ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val ids = categoryIds.flatten().distinct()
|
||||
val categories = CategoryTable.select { CategoryTable.id inList ids }.map { CategoryType(it) }
|
||||
categoryIds.map { categoryIds ->
|
||||
categories.filter { it.id in categoryIds }.toNodeList()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CategoriesForMangaDataLoader : KotlinDataLoader<Int, CategoryNodeList> {
|
||||
override val dataLoaderName = "CategoriesForMangaDataLoader"
|
||||
override fun getDataLoader(): DataLoader<Int, CategoryNodeList> = DataLoaderFactory.newDataLoader<Int, CategoryNodeList> { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val itemsByRef = CategoryMangaTable.innerJoin(CategoryTable)
|
||||
.select { CategoryMangaTable.manga inList ids }
|
||||
.map { Pair(it[CategoryMangaTable.manga].value, CategoryType(it)) }
|
||||
.groupBy { it.first }
|
||||
.mapValues { it.value.map { pair -> pair.second } }
|
||||
ids.map { (itemsByRef[it] ?: emptyList()).toNodeList() }
|
||||
|
||||
override fun getDataLoader(): DataLoader<Int, CategoryNodeList> =
|
||||
DataLoaderFactory.newDataLoader<Int, CategoryNodeList> { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val itemsByRef =
|
||||
CategoryMangaTable.innerJoin(CategoryTable)
|
||||
.select { CategoryMangaTable.manga inList ids }
|
||||
.map { Pair(it[CategoryMangaTable.manga].value, CategoryType(it)) }
|
||||
.groupBy { it.first }
|
||||
.mapValues { it.value.map { pair -> pair.second } }
|
||||
ids.map { (itemsByRef[it] ?: emptyList()).toNodeList() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,82 +25,95 @@ import suwayomi.tachidesk.server.JavalinSetup.future
|
||||
|
||||
class ChapterDataLoader : KotlinDataLoader<Int, ChapterType?> {
|
||||
override val dataLoaderName = "ChapterDataLoader"
|
||||
override fun getDataLoader(): DataLoader<Int, ChapterType?> = DataLoaderFactory.newDataLoader<Int, ChapterType> { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val chapters = ChapterTable.select { ChapterTable.id inList ids }
|
||||
.map { ChapterType(it) }
|
||||
.associateBy { it.id }
|
||||
ids.map { chapters[it] }
|
||||
|
||||
override fun getDataLoader(): DataLoader<Int, ChapterType?> =
|
||||
DataLoaderFactory.newDataLoader<Int, ChapterType> { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val chapters =
|
||||
ChapterTable.select { ChapterTable.id inList ids }
|
||||
.map { ChapterType(it) }
|
||||
.associateBy { it.id }
|
||||
ids.map { chapters[it] }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ChaptersForMangaDataLoader : KotlinDataLoader<Int, ChapterNodeList> {
|
||||
override val dataLoaderName = "ChaptersForMangaDataLoader"
|
||||
override fun getDataLoader(): DataLoader<Int, ChapterNodeList> = DataLoaderFactory.newDataLoader<Int, ChapterNodeList> { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val chaptersByMangaId = ChapterTable.select { ChapterTable.manga inList ids }
|
||||
.map { ChapterType(it) }
|
||||
.groupBy { it.mangaId }
|
||||
ids.map { (chaptersByMangaId[it] ?: emptyList()).toNodeList() }
|
||||
|
||||
override fun getDataLoader(): DataLoader<Int, ChapterNodeList> =
|
||||
DataLoaderFactory.newDataLoader<Int, ChapterNodeList> { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val chaptersByMangaId =
|
||||
ChapterTable.select { ChapterTable.manga inList ids }
|
||||
.map { ChapterType(it) }
|
||||
.groupBy { it.mangaId }
|
||||
ids.map { (chaptersByMangaId[it] ?: emptyList()).toNodeList() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class DownloadedChapterCountForMangaDataLoader : KotlinDataLoader<Int, Int> {
|
||||
override val dataLoaderName = "DownloadedChapterCountForMangaDataLoader"
|
||||
override fun getDataLoader(): DataLoader<Int, Int> = DataLoaderFactory.newDataLoader<Int, Int> { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val downloadedChapterCountByMangaId =
|
||||
ChapterTable
|
||||
.slice(ChapterTable.manga, ChapterTable.isDownloaded.count())
|
||||
.select { (ChapterTable.manga inList ids) and (ChapterTable.isDownloaded eq true) }
|
||||
.groupBy(ChapterTable.manga)
|
||||
.associate { it[ChapterTable.manga].value to it[ChapterTable.isDownloaded.count()] }
|
||||
ids.map { downloadedChapterCountByMangaId[it]?.toInt() ?: 0 }
|
||||
|
||||
override fun getDataLoader(): DataLoader<Int, Int> =
|
||||
DataLoaderFactory.newDataLoader<Int, Int> { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val downloadedChapterCountByMangaId =
|
||||
ChapterTable
|
||||
.slice(ChapterTable.manga, ChapterTable.isDownloaded.count())
|
||||
.select { (ChapterTable.manga inList ids) and (ChapterTable.isDownloaded eq true) }
|
||||
.groupBy(ChapterTable.manga)
|
||||
.associate { it[ChapterTable.manga].value to it[ChapterTable.isDownloaded.count()] }
|
||||
ids.map { downloadedChapterCountByMangaId[it]?.toInt() ?: 0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class UnreadChapterCountForMangaDataLoader : KotlinDataLoader<Int, Int> {
|
||||
override val dataLoaderName = "UnreadChapterCountForMangaDataLoader"
|
||||
override fun getDataLoader(): DataLoader<Int, Int> = DataLoaderFactory.newDataLoader<Int, Int> { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val unreadChapterCountByMangaId =
|
||||
ChapterTable
|
||||
.slice(ChapterTable.manga, ChapterTable.isRead.count())
|
||||
.select { (ChapterTable.manga inList ids) and (ChapterTable.isRead eq false) }
|
||||
.groupBy(ChapterTable.manga)
|
||||
.associate { it[ChapterTable.manga].value to it[ChapterTable.isRead.count()] }
|
||||
ids.map { unreadChapterCountByMangaId[it]?.toInt() ?: 0 }
|
||||
|
||||
override fun getDataLoader(): DataLoader<Int, Int> =
|
||||
DataLoaderFactory.newDataLoader<Int, Int> { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val unreadChapterCountByMangaId =
|
||||
ChapterTable
|
||||
.slice(ChapterTable.manga, ChapterTable.isRead.count())
|
||||
.select { (ChapterTable.manga inList ids) and (ChapterTable.isRead eq false) }
|
||||
.groupBy(ChapterTable.manga)
|
||||
.associate { it[ChapterTable.manga].value to it[ChapterTable.isRead.count()] }
|
||||
ids.map { unreadChapterCountByMangaId[it]?.toInt() ?: 0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class LastReadChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType?> {
|
||||
override val dataLoaderName = "LastReadChapterForMangaDataLoader"
|
||||
override fun getDataLoader(): DataLoader<Int, ChapterType?> = DataLoaderFactory.newDataLoader<Int, ChapterType?> { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val lastReadChaptersByMangaId = ChapterTable
|
||||
.select { (ChapterTable.manga inList ids) and (ChapterTable.isRead eq true) }
|
||||
.orderBy(ChapterTable.sourceOrder to SortOrder.DESC)
|
||||
.groupBy { it[ChapterTable.manga].value }
|
||||
ids.map { id -> lastReadChaptersByMangaId[id]?.let { chapters -> ChapterType(chapters.first()) } }
|
||||
|
||||
override fun getDataLoader(): DataLoader<Int, ChapterType?> =
|
||||
DataLoaderFactory.newDataLoader<Int, ChapterType?> { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val lastReadChaptersByMangaId =
|
||||
ChapterTable
|
||||
.select { (ChapterTable.manga inList ids) and (ChapterTable.isRead eq true) }
|
||||
.orderBy(ChapterTable.sourceOrder to SortOrder.DESC)
|
||||
.groupBy { it[ChapterTable.manga].value }
|
||||
ids.map { id -> lastReadChaptersByMangaId[id]?.let { chapters -> ChapterType(chapters.first()) } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,43 +21,50 @@ import suwayomi.tachidesk.server.JavalinSetup.future
|
||||
|
||||
class ExtensionDataLoader : KotlinDataLoader<String, ExtensionType?> {
|
||||
override val dataLoaderName = "ExtensionDataLoader"
|
||||
override fun getDataLoader(): DataLoader<String, ExtensionType?> = DataLoaderFactory.newDataLoader { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val extensions = ExtensionTable.select { ExtensionTable.pkgName inList ids }
|
||||
.map { ExtensionType(it) }
|
||||
.associateBy { it.pkgName }
|
||||
ids.map { extensions[it] }
|
||||
|
||||
override fun getDataLoader(): DataLoader<String, ExtensionType?> =
|
||||
DataLoaderFactory.newDataLoader { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val extensions =
|
||||
ExtensionTable.select { ExtensionTable.pkgName inList ids }
|
||||
.map { ExtensionType(it) }
|
||||
.associateBy { it.pkgName }
|
||||
ids.map { extensions[it] }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ExtensionForSourceDataLoader : KotlinDataLoader<Long, ExtensionType?> {
|
||||
override val dataLoaderName = "ExtensionForSourceDataLoader"
|
||||
override fun getDataLoader(): DataLoader<Long, ExtensionType?> = DataLoaderFactory.newDataLoader { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val extensions = ExtensionTable.innerJoin(SourceTable)
|
||||
.select { SourceTable.id inList ids }
|
||||
.toList()
|
||||
.map { Triple(it[SourceTable.id].value, it[ExtensionTable.pkgName], it) }
|
||||
.let { triples ->
|
||||
val sources = buildMap {
|
||||
triples.forEach {
|
||||
if (!containsKey(it.second)) {
|
||||
put(it.second, ExtensionType(it.third))
|
||||
|
||||
override fun getDataLoader(): DataLoader<Long, ExtensionType?> =
|
||||
DataLoaderFactory.newDataLoader { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val extensions =
|
||||
ExtensionTable.innerJoin(SourceTable)
|
||||
.select { SourceTable.id inList ids }
|
||||
.toList()
|
||||
.map { Triple(it[SourceTable.id].value, it[ExtensionTable.pkgName], it) }
|
||||
.let { triples ->
|
||||
val sources =
|
||||
buildMap {
|
||||
triples.forEach {
|
||||
if (!containsKey(it.second)) {
|
||||
put(it.second, ExtensionType(it.third))
|
||||
}
|
||||
}
|
||||
}
|
||||
triples.associate {
|
||||
it.first to sources[it.second]
|
||||
}
|
||||
}
|
||||
}
|
||||
triples.associate {
|
||||
it.first to sources[it.second]
|
||||
}
|
||||
}
|
||||
ids.map { extensions[it] }
|
||||
ids.map { extensions[it] }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,76 +24,89 @@ import suwayomi.tachidesk.server.JavalinSetup.future
|
||||
|
||||
class MangaDataLoader : KotlinDataLoader<Int, MangaType?> {
|
||||
override val dataLoaderName = "MangaDataLoader"
|
||||
override fun getDataLoader(): DataLoader<Int, MangaType?> = DataLoaderFactory.newDataLoader { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val manga = MangaTable.select { MangaTable.id inList ids }
|
||||
.map { MangaType(it) }
|
||||
.associateBy { it.id }
|
||||
ids.map { manga[it] }
|
||||
|
||||
override fun getDataLoader(): DataLoader<Int, MangaType?> =
|
||||
DataLoaderFactory.newDataLoader { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val manga =
|
||||
MangaTable.select { MangaTable.id inList ids }
|
||||
.map { MangaType(it) }
|
||||
.associateBy { it.id }
|
||||
ids.map { manga[it] }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MangaForCategoryDataLoader : KotlinDataLoader<Int, MangaNodeList> {
|
||||
override val dataLoaderName = "MangaForCategoryDataLoader"
|
||||
override fun getDataLoader(): DataLoader<Int, MangaNodeList> = DataLoaderFactory.newDataLoader<Int, MangaNodeList> { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val itemsByRef = if (ids.contains(0)) {
|
||||
MangaTable
|
||||
.leftJoin(CategoryMangaTable)
|
||||
.select { MangaTable.inLibrary eq true }
|
||||
.andWhere { CategoryMangaTable.manga.isNull() }
|
||||
.map { MangaType(it) }
|
||||
.let {
|
||||
mapOf(0 to it)
|
||||
}
|
||||
} else {
|
||||
emptyMap()
|
||||
} + CategoryMangaTable.innerJoin(MangaTable)
|
||||
.select { CategoryMangaTable.category inList ids }
|
||||
.map { Pair(it[CategoryMangaTable.category].value, MangaType(it)) }
|
||||
.groupBy { it.first }
|
||||
.mapValues { it.value.map { pair -> pair.second } }
|
||||
|
||||
ids.map { (itemsByRef[it] ?: emptyList()).toNodeList() }
|
||||
override fun getDataLoader(): DataLoader<Int, MangaNodeList> =
|
||||
DataLoaderFactory.newDataLoader<Int, MangaNodeList> { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val itemsByRef =
|
||||
if (ids.contains(0)) {
|
||||
MangaTable
|
||||
.leftJoin(CategoryMangaTable)
|
||||
.select { MangaTable.inLibrary eq true }
|
||||
.andWhere { CategoryMangaTable.manga.isNull() }
|
||||
.map { MangaType(it) }
|
||||
.let {
|
||||
mapOf(0 to it)
|
||||
}
|
||||
} else {
|
||||
emptyMap()
|
||||
} +
|
||||
CategoryMangaTable.innerJoin(MangaTable)
|
||||
.select { CategoryMangaTable.category inList ids }
|
||||
.map { Pair(it[CategoryMangaTable.category].value, MangaType(it)) }
|
||||
.groupBy { it.first }
|
||||
.mapValues { it.value.map { pair -> pair.second } }
|
||||
|
||||
ids.map { (itemsByRef[it] ?: emptyList()).toNodeList() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MangaForSourceDataLoader : KotlinDataLoader<Long, MangaNodeList> {
|
||||
override val dataLoaderName = "MangaForSourceDataLoader"
|
||||
override fun getDataLoader(): DataLoader<Long, MangaNodeList> = DataLoaderFactory.newDataLoader<Long, MangaNodeList> { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val mangaBySourceId = MangaTable.select { MangaTable.sourceReference inList ids }
|
||||
.map { MangaType(it) }
|
||||
.groupBy { it.sourceId }
|
||||
ids.map { (mangaBySourceId[it] ?: emptyList()).toNodeList() }
|
||||
|
||||
override fun getDataLoader(): DataLoader<Long, MangaNodeList> =
|
||||
DataLoaderFactory.newDataLoader<Long, MangaNodeList> { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val mangaBySourceId =
|
||||
MangaTable.select { MangaTable.sourceReference inList ids }
|
||||
.map { MangaType(it) }
|
||||
.groupBy { it.sourceId }
|
||||
ids.map { (mangaBySourceId[it] ?: emptyList()).toNodeList() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MangaForIdsDataLoader : KotlinDataLoader<List<Int>, MangaNodeList> {
|
||||
override val dataLoaderName = "MangaForIdsDataLoader"
|
||||
override fun getDataLoader(): DataLoader<List<Int>, MangaNodeList> = DataLoaderFactory.newDataLoader { mangaIds ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val ids = mangaIds.flatten().distinct()
|
||||
val manga = MangaTable.select { MangaTable.id inList ids }
|
||||
.map { MangaType(it) }
|
||||
mangaIds.map { mangaIds ->
|
||||
manga.filter { it.id in mangaIds }.toNodeList()
|
||||
|
||||
override fun getDataLoader(): DataLoader<List<Int>, MangaNodeList> =
|
||||
DataLoaderFactory.newDataLoader { mangaIds ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val ids = mangaIds.flatten().distinct()
|
||||
val manga =
|
||||
MangaTable.select { MangaTable.id inList ids }
|
||||
.map { MangaType(it) }
|
||||
mangaIds.map { mangaIds ->
|
||||
manga.filter { it.id in mangaIds }.toNodeList()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,60 +19,72 @@ import suwayomi.tachidesk.server.JavalinSetup.future
|
||||
|
||||
class GlobalMetaDataLoader : KotlinDataLoader<String, GlobalMetaType?> {
|
||||
override val dataLoaderName = "GlobalMetaDataLoader"
|
||||
override fun getDataLoader(): DataLoader<String, GlobalMetaType?> = DataLoaderFactory.newDataLoader<String, GlobalMetaType?> { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val metasByRefId = GlobalMetaTable.select { GlobalMetaTable.key inList ids }
|
||||
.map { GlobalMetaType(it) }
|
||||
.associateBy { it.key }
|
||||
ids.map { metasByRefId[it] }
|
||||
|
||||
override fun getDataLoader(): DataLoader<String, GlobalMetaType?> =
|
||||
DataLoaderFactory.newDataLoader<String, GlobalMetaType?> { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val metasByRefId =
|
||||
GlobalMetaTable.select { GlobalMetaTable.key inList ids }
|
||||
.map { GlobalMetaType(it) }
|
||||
.associateBy { it.key }
|
||||
ids.map { metasByRefId[it] }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ChapterMetaDataLoader : KotlinDataLoader<Int, List<ChapterMetaType>> {
|
||||
override val dataLoaderName = "ChapterMetaDataLoader"
|
||||
override fun getDataLoader(): DataLoader<Int, List<ChapterMetaType>> = DataLoaderFactory.newDataLoader<Int, List<ChapterMetaType>> { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val metasByRefId = ChapterMetaTable.select { ChapterMetaTable.ref inList ids }
|
||||
.map { ChapterMetaType(it) }
|
||||
.groupBy { it.chapterId }
|
||||
ids.map { metasByRefId[it].orEmpty() }
|
||||
|
||||
override fun getDataLoader(): DataLoader<Int, List<ChapterMetaType>> =
|
||||
DataLoaderFactory.newDataLoader<Int, List<ChapterMetaType>> { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val metasByRefId =
|
||||
ChapterMetaTable.select { ChapterMetaTable.ref inList ids }
|
||||
.map { ChapterMetaType(it) }
|
||||
.groupBy { it.chapterId }
|
||||
ids.map { metasByRefId[it].orEmpty() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MangaMetaDataLoader : KotlinDataLoader<Int, List<MangaMetaType>> {
|
||||
override val dataLoaderName = "MangaMetaDataLoader"
|
||||
override fun getDataLoader(): DataLoader<Int, List<MangaMetaType>> = DataLoaderFactory.newDataLoader<Int, List<MangaMetaType>> { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val metasByRefId = MangaMetaTable.select { MangaMetaTable.ref inList ids }
|
||||
.map { MangaMetaType(it) }
|
||||
.groupBy { it.mangaId }
|
||||
ids.map { metasByRefId[it].orEmpty() }
|
||||
|
||||
override fun getDataLoader(): DataLoader<Int, List<MangaMetaType>> =
|
||||
DataLoaderFactory.newDataLoader<Int, List<MangaMetaType>> { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val metasByRefId =
|
||||
MangaMetaTable.select { MangaMetaTable.ref inList ids }
|
||||
.map { MangaMetaType(it) }
|
||||
.groupBy { it.mangaId }
|
||||
ids.map { metasByRefId[it].orEmpty() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CategoryMetaDataLoader : KotlinDataLoader<Int, List<CategoryMetaType>> {
|
||||
override val dataLoaderName = "CategoryMetaDataLoader"
|
||||
override fun getDataLoader(): DataLoader<Int, List<CategoryMetaType>> = DataLoaderFactory.newDataLoader<Int, List<CategoryMetaType>> { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val metasByRefId = CategoryMetaTable.select { CategoryMetaTable.ref inList ids }
|
||||
.map { CategoryMetaType(it) }
|
||||
.groupBy { it.categoryId }
|
||||
ids.map { metasByRefId[it].orEmpty() }
|
||||
|
||||
override fun getDataLoader(): DataLoader<Int, List<CategoryMetaType>> =
|
||||
DataLoaderFactory.newDataLoader<Int, List<CategoryMetaType>> { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val metasByRefId =
|
||||
CategoryMetaTable.select { CategoryMetaTable.ref inList ids }
|
||||
.map { CategoryMetaType(it) }
|
||||
.groupBy { it.categoryId }
|
||||
ids.map { metasByRefId[it].orEmpty() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,34 +23,40 @@ import suwayomi.tachidesk.server.JavalinSetup.future
|
||||
|
||||
class SourceDataLoader : KotlinDataLoader<Long, SourceType?> {
|
||||
override val dataLoaderName = "SourceDataLoader"
|
||||
override fun getDataLoader(): DataLoader<Long, SourceType?> = DataLoaderFactory.newDataLoader { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val source = SourceTable.select { SourceTable.id inList ids }
|
||||
.mapNotNull { SourceType(it) }
|
||||
.associateBy { it.id }
|
||||
ids.map { source[it] }
|
||||
|
||||
override fun getDataLoader(): DataLoader<Long, SourceType?> =
|
||||
DataLoaderFactory.newDataLoader { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val source =
|
||||
SourceTable.select { SourceTable.id inList ids }
|
||||
.mapNotNull { SourceType(it) }
|
||||
.associateBy { it.id }
|
||||
ids.map { source[it] }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SourcesForExtensionDataLoader : KotlinDataLoader<String, SourceNodeList> {
|
||||
override val dataLoaderName = "SourcesForExtensionDataLoader"
|
||||
override fun getDataLoader(): DataLoader<String, SourceNodeList> = DataLoaderFactory.newDataLoader { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
|
||||
val sourcesByExtensionPkg = SourceTable.innerJoin(ExtensionTable)
|
||||
.select { ExtensionTable.pkgName inList ids }
|
||||
.map { Pair(it[ExtensionTable.pkgName], SourceType(it)) }
|
||||
.groupBy { it.first }
|
||||
.mapValues { it.value.mapNotNull { pair -> pair.second } }
|
||||
override fun getDataLoader(): DataLoader<String, SourceNodeList> =
|
||||
DataLoaderFactory.newDataLoader { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
|
||||
ids.map { (sourcesByExtensionPkg[it] ?: emptyList()).toNodeList() }
|
||||
val sourcesByExtensionPkg =
|
||||
SourceTable.innerJoin(ExtensionTable)
|
||||
.select { ExtensionTable.pkgName inList ids }
|
||||
.map { Pair(it[ExtensionTable.pkgName], SourceType(it)) }
|
||||
.groupBy { it.first }
|
||||
.mapValues { it.value.mapNotNull { pair -> pair.second } }
|
||||
|
||||
ids.map { (sourcesByExtensionPkg[it] ?: emptyList()).toNodeList() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,17 +20,16 @@ import kotlin.time.Duration.Companion.seconds
|
||||
class BackupMutation {
|
||||
data class RestoreBackupInput(
|
||||
val clientMutationId: String? = null,
|
||||
val backup: UploadedFile
|
||||
val backup: UploadedFile,
|
||||
)
|
||||
|
||||
data class RestoreBackupPayload(
|
||||
val clientMutationId: String?,
|
||||
val status: BackupRestoreStatus
|
||||
val status: BackupRestoreStatus,
|
||||
)
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
fun restoreBackup(
|
||||
input: RestoreBackupInput
|
||||
): CompletableFuture<RestoreBackupPayload> {
|
||||
fun restoreBackup(input: RestoreBackupInput): CompletableFuture<RestoreBackupPayload> {
|
||||
val (clientMutationId, backup) = input
|
||||
|
||||
return future {
|
||||
@@ -38,11 +37,12 @@ class BackupMutation {
|
||||
ProtoBackupImport.performRestore(backup.content)
|
||||
}
|
||||
|
||||
val status = withTimeout(10.seconds) {
|
||||
ProtoBackupImport.backupRestoreState.first {
|
||||
it != ProtoBackupImport.BackupRestoreState.Idle
|
||||
}.toStatus()
|
||||
}
|
||||
val status =
|
||||
withTimeout(10.seconds) {
|
||||
ProtoBackupImport.backupRestoreState.first {
|
||||
it != ProtoBackupImport.BackupRestoreState.Idle
|
||||
}.toStatus()
|
||||
}
|
||||
|
||||
RestoreBackupPayload(clientMutationId, status)
|
||||
}
|
||||
@@ -51,32 +51,33 @@ class BackupMutation {
|
||||
data class CreateBackupInput(
|
||||
val clientMutationId: String? = null,
|
||||
val includeChapters: Boolean? = null,
|
||||
val includeCategories: Boolean? = null
|
||||
val includeCategories: Boolean? = null,
|
||||
)
|
||||
|
||||
data class CreateBackupPayload(
|
||||
val clientMutationId: String?,
|
||||
val url: String
|
||||
val url: String,
|
||||
)
|
||||
fun createBackup(
|
||||
input: CreateBackupInput? = null
|
||||
): CreateBackupPayload {
|
||||
|
||||
fun createBackup(input: CreateBackupInput? = null): CreateBackupPayload {
|
||||
val filename = ProtoBackupExport.getBackupFilename()
|
||||
|
||||
val backup = ProtoBackupExport.createBackup(
|
||||
BackupFlags(
|
||||
includeManga = true,
|
||||
includeCategories = input?.includeCategories ?: true,
|
||||
includeChapters = input?.includeChapters ?: true,
|
||||
includeTracking = true,
|
||||
includeHistory = true
|
||||
val backup =
|
||||
ProtoBackupExport.createBackup(
|
||||
BackupFlags(
|
||||
includeManga = true,
|
||||
includeCategories = input?.includeCategories ?: true,
|
||||
includeChapters = input?.includeChapters ?: true,
|
||||
includeTracking = true,
|
||||
includeHistory = true,
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
TemporaryFileStorage.saveFile(filename, backup)
|
||||
|
||||
return CreateBackupPayload(
|
||||
clientMutationId = input?.clientMutationId,
|
||||
url = "/api/graphql/files/backup/$filename"
|
||||
url = "/api/graphql/files/backup/$filename",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,15 +27,15 @@ import suwayomi.tachidesk.manga.model.table.MangaTable
|
||||
class CategoryMutation {
|
||||
data class SetCategoryMetaInput(
|
||||
val clientMutationId: String? = null,
|
||||
val meta: CategoryMetaType
|
||||
val meta: CategoryMetaType,
|
||||
)
|
||||
|
||||
data class SetCategoryMetaPayload(
|
||||
val clientMutationId: String?,
|
||||
val meta: CategoryMetaType
|
||||
val meta: CategoryMetaType,
|
||||
)
|
||||
fun setCategoryMeta(
|
||||
input: SetCategoryMetaInput
|
||||
): SetCategoryMetaPayload {
|
||||
|
||||
fun setCategoryMeta(input: SetCategoryMetaInput): SetCategoryMetaPayload {
|
||||
val (clientMutationId, meta) = input
|
||||
|
||||
Category.modifyMeta(meta.categoryId, meta.key, meta.value)
|
||||
@@ -46,65 +46,73 @@ class CategoryMutation {
|
||||
data class DeleteCategoryMetaInput(
|
||||
val clientMutationId: String? = null,
|
||||
val categoryId: Int,
|
||||
val key: String
|
||||
val key: String,
|
||||
)
|
||||
|
||||
data class DeleteCategoryMetaPayload(
|
||||
val clientMutationId: String?,
|
||||
val meta: CategoryMetaType?,
|
||||
val category: CategoryType
|
||||
val category: CategoryType,
|
||||
)
|
||||
fun deleteCategoryMeta(
|
||||
input: DeleteCategoryMetaInput
|
||||
): DeleteCategoryMetaPayload {
|
||||
|
||||
fun deleteCategoryMeta(input: DeleteCategoryMetaInput): DeleteCategoryMetaPayload {
|
||||
val (clientMutationId, categoryId, key) = input
|
||||
|
||||
val (meta, category) = transaction {
|
||||
val meta = CategoryMetaTable.select { (CategoryMetaTable.ref eq categoryId) and (CategoryMetaTable.key eq key) }
|
||||
.firstOrNull()
|
||||
val (meta, category) =
|
||||
transaction {
|
||||
val meta =
|
||||
CategoryMetaTable.select { (CategoryMetaTable.ref eq categoryId) and (CategoryMetaTable.key eq key) }
|
||||
.firstOrNull()
|
||||
|
||||
CategoryMetaTable.deleteWhere { (CategoryMetaTable.ref eq categoryId) and (CategoryMetaTable.key eq key) }
|
||||
CategoryMetaTable.deleteWhere { (CategoryMetaTable.ref eq categoryId) and (CategoryMetaTable.key eq key) }
|
||||
|
||||
val category = transaction {
|
||||
CategoryType(CategoryTable.select { CategoryTable.id eq categoryId }.first())
|
||||
val category =
|
||||
transaction {
|
||||
CategoryType(CategoryTable.select { CategoryTable.id eq categoryId }.first())
|
||||
}
|
||||
|
||||
if (meta != null) {
|
||||
CategoryMetaType(meta)
|
||||
} else {
|
||||
null
|
||||
} to category
|
||||
}
|
||||
|
||||
if (meta != null) {
|
||||
CategoryMetaType(meta)
|
||||
} else {
|
||||
null
|
||||
} to category
|
||||
}
|
||||
|
||||
return DeleteCategoryMetaPayload(clientMutationId, meta, category)
|
||||
}
|
||||
|
||||
data class UpdateCategoryPatch(
|
||||
val name: String? = null,
|
||||
val default: Boolean? = null,
|
||||
val includeInUpdate: IncludeInUpdate? = null
|
||||
val includeInUpdate: IncludeInUpdate? = null,
|
||||
)
|
||||
|
||||
data class UpdateCategoryPayload(
|
||||
val clientMutationId: String?,
|
||||
val category: CategoryType
|
||||
val category: CategoryType,
|
||||
)
|
||||
|
||||
data class UpdateCategoryInput(
|
||||
val clientMutationId: String? = null,
|
||||
val id: Int,
|
||||
val patch: UpdateCategoryPatch
|
||||
val patch: UpdateCategoryPatch,
|
||||
)
|
||||
|
||||
data class UpdateCategoriesPayload(
|
||||
val clientMutationId: String?,
|
||||
val categories: List<CategoryType>
|
||||
val categories: List<CategoryType>,
|
||||
)
|
||||
|
||||
data class UpdateCategoriesInput(
|
||||
val clientMutationId: String? = null,
|
||||
val ids: List<Int>,
|
||||
val patch: UpdateCategoryPatch
|
||||
val patch: UpdateCategoryPatch,
|
||||
)
|
||||
|
||||
private fun updateCategories(ids: List<Int>, patch: UpdateCategoryPatch) {
|
||||
private fun updateCategories(
|
||||
ids: List<Int>,
|
||||
patch: UpdateCategoryPatch,
|
||||
) {
|
||||
transaction {
|
||||
if (patch.name != null) {
|
||||
CategoryTable.update({ CategoryTable.id inList ids }) { update ->
|
||||
@@ -135,13 +143,14 @@ class CategoryMutation {
|
||||
|
||||
updateCategories(listOf(id), patch)
|
||||
|
||||
val category = transaction {
|
||||
CategoryType(CategoryTable.select { CategoryTable.id eq id }.first())
|
||||
}
|
||||
val category =
|
||||
transaction {
|
||||
CategoryType(CategoryTable.select { CategoryTable.id eq id }.first())
|
||||
}
|
||||
|
||||
return UpdateCategoryPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
category = category
|
||||
category = category,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -150,24 +159,26 @@ class CategoryMutation {
|
||||
|
||||
updateCategories(ids, patch)
|
||||
|
||||
val categories = transaction {
|
||||
CategoryTable.select { CategoryTable.id inList ids }.map { CategoryType(it) }
|
||||
}
|
||||
val categories =
|
||||
transaction {
|
||||
CategoryTable.select { CategoryTable.id inList ids }.map { CategoryType(it) }
|
||||
}
|
||||
|
||||
return UpdateCategoriesPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
categories = categories
|
||||
categories = categories,
|
||||
)
|
||||
}
|
||||
|
||||
data class UpdateCategoryOrderPayload(
|
||||
val clientMutationId: String?,
|
||||
val categories: List<CategoryType>
|
||||
val categories: List<CategoryType>,
|
||||
)
|
||||
|
||||
data class UpdateCategoryOrderInput(
|
||||
val clientMutationId: String? = null,
|
||||
val id: Int,
|
||||
val position: Int
|
||||
val position: Int,
|
||||
)
|
||||
|
||||
fun updateCategoryOrder(input: UpdateCategoryOrderInput): UpdateCategoryOrderPayload {
|
||||
@@ -177,9 +188,10 @@ class CategoryMutation {
|
||||
}
|
||||
|
||||
transaction {
|
||||
val currentOrder = CategoryTable
|
||||
.select { CategoryTable.id eq categoryId }
|
||||
.first()[CategoryTable.order]
|
||||
val currentOrder =
|
||||
CategoryTable
|
||||
.select { CategoryTable.id eq categoryId }
|
||||
.first()[CategoryTable.order]
|
||||
|
||||
if (currentOrder != position) {
|
||||
if (position < currentOrder) {
|
||||
@@ -200,13 +212,14 @@ class CategoryMutation {
|
||||
|
||||
Category.normalizeCategories()
|
||||
|
||||
val categories = transaction {
|
||||
CategoryTable.selectAll().orderBy(CategoryTable.order).map { CategoryType(it) }
|
||||
}
|
||||
val categories =
|
||||
transaction {
|
||||
CategoryTable.selectAll().orderBy(CategoryTable.order).map { CategoryType(it) }
|
||||
}
|
||||
|
||||
return UpdateCategoryOrderPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
categories = categories
|
||||
categories = categories,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -215,15 +228,15 @@ class CategoryMutation {
|
||||
val name: String,
|
||||
val order: Int? = null,
|
||||
val default: Boolean? = null,
|
||||
val includeInUpdate: IncludeInUpdate? = null
|
||||
val includeInUpdate: IncludeInUpdate? = null,
|
||||
)
|
||||
|
||||
data class CreateCategoryPayload(
|
||||
val clientMutationId: String?,
|
||||
val category: CategoryType
|
||||
val category: CategoryType,
|
||||
)
|
||||
fun createCategory(
|
||||
input: CreateCategoryInput
|
||||
): CreateCategoryPayload {
|
||||
|
||||
fun createCategory(input: CreateCategoryInput): CreateCategoryPayload {
|
||||
val (clientMutationId, name, order, default, includeInUpdate) = input
|
||||
transaction {
|
||||
require(CategoryTable.select { CategoryTable.name eq input.name }.isEmpty()) {
|
||||
@@ -239,104 +252,114 @@ class CategoryMutation {
|
||||
}
|
||||
}
|
||||
|
||||
val category = transaction {
|
||||
if (order != null) {
|
||||
CategoryTable.update({ CategoryTable.order greaterEq order }) {
|
||||
it[CategoryTable.order] = CategoryTable.order + 1
|
||||
val category =
|
||||
transaction {
|
||||
if (order != null) {
|
||||
CategoryTable.update({ CategoryTable.order greaterEq order }) {
|
||||
it[CategoryTable.order] = CategoryTable.order + 1
|
||||
}
|
||||
}
|
||||
|
||||
val id =
|
||||
CategoryTable.insertAndGetId {
|
||||
it[CategoryTable.name] = input.name
|
||||
it[CategoryTable.order] = order ?: Int.MAX_VALUE
|
||||
if (default != null) {
|
||||
it[CategoryTable.isDefault] = default
|
||||
}
|
||||
if (includeInUpdate != null) {
|
||||
it[CategoryTable.includeInUpdate] = includeInUpdate.value
|
||||
}
|
||||
}
|
||||
|
||||
Category.normalizeCategories()
|
||||
|
||||
CategoryType(CategoryTable.select { CategoryTable.id eq id }.first())
|
||||
}
|
||||
|
||||
val id = CategoryTable.insertAndGetId {
|
||||
it[CategoryTable.name] = input.name
|
||||
it[CategoryTable.order] = order ?: Int.MAX_VALUE
|
||||
if (default != null) {
|
||||
it[CategoryTable.isDefault] = default
|
||||
}
|
||||
if (includeInUpdate != null) {
|
||||
it[CategoryTable.includeInUpdate] = includeInUpdate.value
|
||||
}
|
||||
}
|
||||
|
||||
Category.normalizeCategories()
|
||||
|
||||
CategoryType(CategoryTable.select { CategoryTable.id eq id }.first())
|
||||
}
|
||||
|
||||
return CreateCategoryPayload(clientMutationId, category)
|
||||
}
|
||||
|
||||
data class DeleteCategoryInput(
|
||||
val clientMutationId: String? = null,
|
||||
val categoryId: Int
|
||||
val categoryId: Int,
|
||||
)
|
||||
|
||||
data class DeleteCategoryPayload(
|
||||
val clientMutationId: String?,
|
||||
val category: CategoryType?,
|
||||
val mangas: List<MangaType>
|
||||
val mangas: List<MangaType>,
|
||||
)
|
||||
fun deleteCategory(
|
||||
input: DeleteCategoryInput
|
||||
): DeleteCategoryPayload {
|
||||
|
||||
fun deleteCategory(input: DeleteCategoryInput): DeleteCategoryPayload {
|
||||
val (clientMutationId, categoryId) = input
|
||||
if (categoryId == 0) { // Don't delete default category
|
||||
return DeleteCategoryPayload(
|
||||
clientMutationId,
|
||||
null,
|
||||
emptyList()
|
||||
emptyList(),
|
||||
)
|
||||
}
|
||||
|
||||
val (category, mangas) = transaction {
|
||||
val category = CategoryTable.select { CategoryTable.id eq categoryId }
|
||||
.firstOrNull()
|
||||
val (category, mangas) =
|
||||
transaction {
|
||||
val category =
|
||||
CategoryTable.select { CategoryTable.id eq categoryId }
|
||||
.firstOrNull()
|
||||
|
||||
val mangas = transaction {
|
||||
MangaTable.innerJoin(CategoryMangaTable)
|
||||
.select { CategoryMangaTable.category eq categoryId }
|
||||
.map { MangaType(it) }
|
||||
val mangas =
|
||||
transaction {
|
||||
MangaTable.innerJoin(CategoryMangaTable)
|
||||
.select { CategoryMangaTable.category eq categoryId }
|
||||
.map { MangaType(it) }
|
||||
}
|
||||
|
||||
CategoryTable.deleteWhere { CategoryTable.id eq categoryId }
|
||||
|
||||
Category.normalizeCategories()
|
||||
|
||||
if (category != null) {
|
||||
CategoryType(category)
|
||||
} else {
|
||||
null
|
||||
} to mangas
|
||||
}
|
||||
|
||||
CategoryTable.deleteWhere { CategoryTable.id eq categoryId }
|
||||
|
||||
Category.normalizeCategories()
|
||||
|
||||
if (category != null) {
|
||||
CategoryType(category)
|
||||
} else {
|
||||
null
|
||||
} to mangas
|
||||
}
|
||||
|
||||
return DeleteCategoryPayload(clientMutationId, category, mangas)
|
||||
}
|
||||
|
||||
data class UpdateMangaCategoriesPatch(
|
||||
val clearCategories: Boolean? = null,
|
||||
val addToCategories: List<Int>? = null,
|
||||
val removeFromCategories: List<Int>? = null
|
||||
val removeFromCategories: List<Int>? = null,
|
||||
)
|
||||
|
||||
data class UpdateMangaCategoriesPayload(
|
||||
val clientMutationId: String?,
|
||||
val manga: MangaType
|
||||
val manga: MangaType,
|
||||
)
|
||||
|
||||
data class UpdateMangaCategoriesInput(
|
||||
val clientMutationId: String? = null,
|
||||
val id: Int,
|
||||
val patch: UpdateMangaCategoriesPatch
|
||||
val patch: UpdateMangaCategoriesPatch,
|
||||
)
|
||||
|
||||
data class UpdateMangasCategoriesPayload(
|
||||
val clientMutationId: String?,
|
||||
val mangas: List<MangaType>
|
||||
val mangas: List<MangaType>,
|
||||
)
|
||||
|
||||
data class UpdateMangasCategoriesInput(
|
||||
val clientMutationId: String? = null,
|
||||
val ids: List<Int>,
|
||||
val patch: UpdateMangaCategoriesPatch
|
||||
val patch: UpdateMangaCategoriesPatch,
|
||||
)
|
||||
|
||||
private fun updateMangas(ids: List<Int>, patch: UpdateMangaCategoriesPatch) {
|
||||
private fun updateMangas(
|
||||
ids: List<Int>,
|
||||
patch: UpdateMangaCategoriesPatch,
|
||||
) {
|
||||
transaction {
|
||||
if (patch.clearCategories == true) {
|
||||
CategoryMangaTable.deleteWhere { CategoryMangaTable.manga inList ids }
|
||||
@@ -346,19 +369,21 @@ class CategoryMutation {
|
||||
}
|
||||
}
|
||||
if (!patch.addToCategories.isNullOrEmpty()) {
|
||||
val newCategories = buildList {
|
||||
ids.forEach { mangaId ->
|
||||
patch.addToCategories.forEach { categoryId ->
|
||||
val existingMapping = CategoryMangaTable.select {
|
||||
(CategoryMangaTable.manga eq mangaId) and (CategoryMangaTable.category eq categoryId)
|
||||
}.isNotEmpty()
|
||||
val newCategories =
|
||||
buildList {
|
||||
ids.forEach { mangaId ->
|
||||
patch.addToCategories.forEach { categoryId ->
|
||||
val existingMapping =
|
||||
CategoryMangaTable.select {
|
||||
(CategoryMangaTable.manga eq mangaId) and (CategoryMangaTable.category eq categoryId)
|
||||
}.isNotEmpty()
|
||||
|
||||
if (!existingMapping) {
|
||||
add(mangaId to categoryId)
|
||||
if (!existingMapping) {
|
||||
add(mangaId to categoryId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CategoryMangaTable.batchInsert(newCategories) { (manga, category) ->
|
||||
this[CategoryMangaTable.manga] = manga
|
||||
@@ -373,13 +398,14 @@ class CategoryMutation {
|
||||
|
||||
updateMangas(listOf(id), patch)
|
||||
|
||||
val manga = transaction {
|
||||
MangaType(MangaTable.select { MangaTable.id eq id }.first())
|
||||
}
|
||||
val manga =
|
||||
transaction {
|
||||
MangaType(MangaTable.select { MangaTable.id eq id }.first())
|
||||
}
|
||||
|
||||
return UpdateMangaCategoriesPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
manga = manga
|
||||
manga = manga,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -388,13 +414,14 @@ class CategoryMutation {
|
||||
|
||||
updateMangas(ids, patch)
|
||||
|
||||
val mangas = transaction {
|
||||
MangaTable.select { MangaTable.id inList ids }.map { MangaType(it) }
|
||||
}
|
||||
val mangas =
|
||||
transaction {
|
||||
MangaTable.select { MangaTable.id inList ids }.map { MangaType(it) }
|
||||
}
|
||||
|
||||
return UpdateMangasCategoriesPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
mangas = mangas
|
||||
mangas = mangas,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,30 +25,35 @@ class ChapterMutation {
|
||||
data class UpdateChapterPatch(
|
||||
val isBookmarked: Boolean? = null,
|
||||
val isRead: Boolean? = null,
|
||||
val lastPageRead: Int? = null
|
||||
val lastPageRead: Int? = null,
|
||||
)
|
||||
|
||||
data class UpdateChapterPayload(
|
||||
val clientMutationId: String?,
|
||||
val chapter: ChapterType
|
||||
val chapter: ChapterType,
|
||||
)
|
||||
|
||||
data class UpdateChapterInput(
|
||||
val clientMutationId: String? = null,
|
||||
val id: Int,
|
||||
val patch: UpdateChapterPatch
|
||||
val patch: UpdateChapterPatch,
|
||||
)
|
||||
|
||||
data class UpdateChaptersPayload(
|
||||
val clientMutationId: String?,
|
||||
val chapters: List<ChapterType>
|
||||
val chapters: List<ChapterType>,
|
||||
)
|
||||
|
||||
data class UpdateChaptersInput(
|
||||
val clientMutationId: String? = null,
|
||||
val ids: List<Int>,
|
||||
val patch: UpdateChapterPatch
|
||||
val patch: UpdateChapterPatch,
|
||||
)
|
||||
|
||||
private fun updateChapters(ids: List<Int>, patch: UpdateChapterPatch) {
|
||||
private fun updateChapters(
|
||||
ids: List<Int>,
|
||||
patch: UpdateChapterPatch,
|
||||
) {
|
||||
transaction {
|
||||
if (patch.isRead != null || patch.isBookmarked != null || patch.lastPageRead != null) {
|
||||
val now = Instant.now().epochSecond
|
||||
@@ -68,81 +73,79 @@ class ChapterMutation {
|
||||
}
|
||||
}
|
||||
|
||||
fun updateChapter(
|
||||
input: UpdateChapterInput
|
||||
): UpdateChapterPayload {
|
||||
fun updateChapter(input: UpdateChapterInput): UpdateChapterPayload {
|
||||
val (clientMutationId, id, patch) = input
|
||||
|
||||
updateChapters(listOf(id), patch)
|
||||
|
||||
val chapter = transaction {
|
||||
ChapterType(ChapterTable.select { ChapterTable.id eq id }.first())
|
||||
}
|
||||
val chapter =
|
||||
transaction {
|
||||
ChapterType(ChapterTable.select { ChapterTable.id eq id }.first())
|
||||
}
|
||||
|
||||
return UpdateChapterPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
chapter = chapter
|
||||
chapter = chapter,
|
||||
)
|
||||
}
|
||||
|
||||
fun updateChapters(
|
||||
input: UpdateChaptersInput
|
||||
): UpdateChaptersPayload {
|
||||
fun updateChapters(input: UpdateChaptersInput): UpdateChaptersPayload {
|
||||
val (clientMutationId, ids, patch) = input
|
||||
|
||||
updateChapters(ids, patch)
|
||||
|
||||
val chapters = transaction {
|
||||
ChapterTable.select { ChapterTable.id inList ids }.map { ChapterType(it) }
|
||||
}
|
||||
val chapters =
|
||||
transaction {
|
||||
ChapterTable.select { ChapterTable.id inList ids }.map { ChapterType(it) }
|
||||
}
|
||||
|
||||
return UpdateChaptersPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
chapters = chapters
|
||||
chapters = chapters,
|
||||
)
|
||||
}
|
||||
|
||||
data class FetchChaptersInput(
|
||||
val clientMutationId: String? = null,
|
||||
val mangaId: Int
|
||||
)
|
||||
data class FetchChaptersPayload(
|
||||
val clientMutationId: String?,
|
||||
val chapters: List<ChapterType>
|
||||
val mangaId: Int,
|
||||
)
|
||||
|
||||
fun fetchChapters(
|
||||
input: FetchChaptersInput
|
||||
): CompletableFuture<FetchChaptersPayload> {
|
||||
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 = transaction {
|
||||
ChapterTable.select { ChapterTable.manga eq mangaId }
|
||||
.orderBy(ChapterTable.sourceOrder)
|
||||
.map { ChapterType(it) }
|
||||
}
|
||||
val chapters =
|
||||
transaction {
|
||||
ChapterTable.select { ChapterTable.manga eq mangaId }
|
||||
.orderBy(ChapterTable.sourceOrder)
|
||||
.map { ChapterType(it) }
|
||||
}
|
||||
|
||||
FetchChaptersPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
chapters = chapters
|
||||
chapters = chapters,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class SetChapterMetaInput(
|
||||
val clientMutationId: String? = null,
|
||||
val meta: ChapterMetaType
|
||||
val meta: ChapterMetaType,
|
||||
)
|
||||
|
||||
data class SetChapterMetaPayload(
|
||||
val clientMutationId: String?,
|
||||
val meta: ChapterMetaType
|
||||
val meta: ChapterMetaType,
|
||||
)
|
||||
fun setChapterMeta(
|
||||
input: SetChapterMetaInput
|
||||
): SetChapterMetaPayload {
|
||||
|
||||
fun setChapterMeta(input: SetChapterMetaInput): SetChapterMetaPayload {
|
||||
val (clientMutationId, meta) = input
|
||||
|
||||
Chapter.modifyChapterMeta(meta.chapterId, meta.key, meta.value)
|
||||
@@ -153,50 +156,53 @@ class ChapterMutation {
|
||||
data class DeleteChapterMetaInput(
|
||||
val clientMutationId: String? = null,
|
||||
val chapterId: Int,
|
||||
val key: String
|
||||
val key: String,
|
||||
)
|
||||
|
||||
data class DeleteChapterMetaPayload(
|
||||
val clientMutationId: String?,
|
||||
val meta: ChapterMetaType?,
|
||||
val chapter: ChapterType
|
||||
val chapter: ChapterType,
|
||||
)
|
||||
fun deleteChapterMeta(
|
||||
input: DeleteChapterMetaInput
|
||||
): DeleteChapterMetaPayload {
|
||||
|
||||
fun deleteChapterMeta(input: DeleteChapterMetaInput): DeleteChapterMetaPayload {
|
||||
val (clientMutationId, chapterId, key) = input
|
||||
|
||||
val (meta, chapter) = transaction {
|
||||
val meta = ChapterMetaTable.select { (ChapterMetaTable.ref eq chapterId) and (ChapterMetaTable.key eq key) }
|
||||
.firstOrNull()
|
||||
val (meta, chapter) =
|
||||
transaction {
|
||||
val meta =
|
||||
ChapterMetaTable.select { (ChapterMetaTable.ref eq chapterId) and (ChapterMetaTable.key eq key) }
|
||||
.firstOrNull()
|
||||
|
||||
ChapterMetaTable.deleteWhere { (ChapterMetaTable.ref eq chapterId) and (ChapterMetaTable.key eq key) }
|
||||
ChapterMetaTable.deleteWhere { (ChapterMetaTable.ref eq chapterId) and (ChapterMetaTable.key eq key) }
|
||||
|
||||
val chapter = transaction {
|
||||
ChapterType(ChapterTable.select { ChapterTable.id eq chapterId }.first())
|
||||
val chapter =
|
||||
transaction {
|
||||
ChapterType(ChapterTable.select { ChapterTable.id eq chapterId }.first())
|
||||
}
|
||||
|
||||
if (meta != null) {
|
||||
ChapterMetaType(meta)
|
||||
} else {
|
||||
null
|
||||
} to chapter
|
||||
}
|
||||
|
||||
if (meta != null) {
|
||||
ChapterMetaType(meta)
|
||||
} else {
|
||||
null
|
||||
} to chapter
|
||||
}
|
||||
|
||||
return DeleteChapterMetaPayload(clientMutationId, meta, chapter)
|
||||
}
|
||||
|
||||
data class FetchChapterPagesInput(
|
||||
val clientMutationId: String? = null,
|
||||
val chapterId: Int
|
||||
val chapterId: Int,
|
||||
)
|
||||
|
||||
data class FetchChapterPagesPayload(
|
||||
val clientMutationId: String?,
|
||||
val pages: List<String>,
|
||||
val chapter: ChapterType
|
||||
val chapter: ChapterType,
|
||||
)
|
||||
fun fetchChapterPages(
|
||||
input: FetchChapterPagesInput
|
||||
): CompletableFuture<FetchChapterPagesPayload> {
|
||||
|
||||
fun fetchChapterPages(input: FetchChapterPagesInput): CompletableFuture<FetchChapterPagesPayload> {
|
||||
val (clientMutationId, chapterId) = input
|
||||
|
||||
return future {
|
||||
@@ -204,10 +210,11 @@ class ChapterMutation {
|
||||
}.thenApply { chapter ->
|
||||
FetchChapterPagesPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
pages = List(chapter.pageCount) { index ->
|
||||
"/api/v1/manga/${chapter.mangaId}/chapter/${chapter.index}/page/$index"
|
||||
},
|
||||
chapter = ChapterType(chapter)
|
||||
pages =
|
||||
List(chapter.pageCount) { index ->
|
||||
"/api/v1/manga/${chapter.mangaId}/chapter/${chapter.index}/page/$index"
|
||||
},
|
||||
chapter = ChapterType(chapter),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,14 +16,14 @@ import java.util.concurrent.CompletableFuture
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
class DownloadMutation {
|
||||
|
||||
data class DeleteDownloadedChaptersInput(
|
||||
val clientMutationId: String? = null,
|
||||
val ids: List<Int>
|
||||
val ids: List<Int>,
|
||||
)
|
||||
|
||||
data class DeleteDownloadedChaptersPayload(
|
||||
val clientMutationId: String?,
|
||||
val chapters: List<ChapterType>
|
||||
val chapters: List<ChapterType>,
|
||||
)
|
||||
|
||||
fun deleteDownloadedChapters(input: DeleteDownloadedChaptersInput): DeleteDownloadedChaptersPayload {
|
||||
@@ -33,20 +33,22 @@ class DownloadMutation {
|
||||
|
||||
return DeleteDownloadedChaptersPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
chapters = transaction {
|
||||
ChapterTable.select { ChapterTable.id inList chapters }
|
||||
.map { ChapterType(it) }
|
||||
}
|
||||
chapters =
|
||||
transaction {
|
||||
ChapterTable.select { ChapterTable.id inList chapters }
|
||||
.map { ChapterType(it) }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
data class DeleteDownloadedChapterInput(
|
||||
val clientMutationId: String? = null,
|
||||
val id: Int
|
||||
val id: Int,
|
||||
)
|
||||
|
||||
data class DeleteDownloadedChapterPayload(
|
||||
val clientMutationId: String?,
|
||||
val chapters: ChapterType
|
||||
val chapters: ChapterType,
|
||||
)
|
||||
|
||||
fun deleteDownloadedChapter(input: DeleteDownloadedChapterInput): DeleteDownloadedChapterPayload {
|
||||
@@ -56,24 +58,24 @@ class DownloadMutation {
|
||||
|
||||
return DeleteDownloadedChapterPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
chapters = transaction {
|
||||
ChapterType(ChapterTable.select { ChapterTable.id eq chapter }.first())
|
||||
}
|
||||
chapters =
|
||||
transaction {
|
||||
ChapterType(ChapterTable.select { ChapterTable.id eq chapter }.first())
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
data class EnqueueChapterDownloadsInput(
|
||||
val clientMutationId: String? = null,
|
||||
val ids: List<Int>
|
||||
)
|
||||
data class EnqueueChapterDownloadsPayload(
|
||||
val clientMutationId: String?,
|
||||
val downloadStatus: DownloadStatus
|
||||
val ids: List<Int>,
|
||||
)
|
||||
|
||||
fun enqueueChapterDownloads(
|
||||
input: EnqueueChapterDownloadsInput
|
||||
): CompletableFuture<EnqueueChapterDownloadsPayload> {
|
||||
data class EnqueueChapterDownloadsPayload(
|
||||
val clientMutationId: String?,
|
||||
val downloadStatus: DownloadStatus,
|
||||
)
|
||||
|
||||
fun enqueueChapterDownloads(input: EnqueueChapterDownloadsInput): CompletableFuture<EnqueueChapterDownloadsPayload> {
|
||||
val (clientMutationId, chapters) = input
|
||||
|
||||
DownloadManager.enqueue(DownloadManager.EnqueueInput(chapters))
|
||||
@@ -81,25 +83,25 @@ class DownloadMutation {
|
||||
return future {
|
||||
EnqueueChapterDownloadsPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
downloadStatus = withTimeout(30.seconds) {
|
||||
DownloadStatus(DownloadManager.status.first { it.queue.any { it.chapter.id in chapters } })
|
||||
}
|
||||
downloadStatus =
|
||||
withTimeout(30.seconds) {
|
||||
DownloadStatus(DownloadManager.status.first { it.queue.any { it.chapter.id in chapters } })
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class EnqueueChapterDownloadInput(
|
||||
val clientMutationId: String? = null,
|
||||
val id: Int
|
||||
)
|
||||
data class EnqueueChapterDownloadPayload(
|
||||
val clientMutationId: String?,
|
||||
val downloadStatus: DownloadStatus
|
||||
val id: Int,
|
||||
)
|
||||
|
||||
fun enqueueChapterDownload(
|
||||
input: EnqueueChapterDownloadInput
|
||||
): CompletableFuture<EnqueueChapterDownloadPayload> {
|
||||
data class EnqueueChapterDownloadPayload(
|
||||
val clientMutationId: String?,
|
||||
val downloadStatus: DownloadStatus,
|
||||
)
|
||||
|
||||
fun enqueueChapterDownload(input: EnqueueChapterDownloadInput): CompletableFuture<EnqueueChapterDownloadPayload> {
|
||||
val (clientMutationId, chapter) = input
|
||||
|
||||
DownloadManager.enqueue(DownloadManager.EnqueueInput(listOf(chapter)))
|
||||
@@ -107,25 +109,25 @@ class DownloadMutation {
|
||||
return future {
|
||||
EnqueueChapterDownloadPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
downloadStatus = withTimeout(30.seconds) {
|
||||
DownloadStatus(DownloadManager.status.first { it.queue.any { it.chapter.id == chapter } })
|
||||
}
|
||||
downloadStatus =
|
||||
withTimeout(30.seconds) {
|
||||
DownloadStatus(DownloadManager.status.first { it.queue.any { it.chapter.id == chapter } })
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class DequeueChapterDownloadsInput(
|
||||
val clientMutationId: String? = null,
|
||||
val ids: List<Int>
|
||||
)
|
||||
data class DequeueChapterDownloadsPayload(
|
||||
val clientMutationId: String?,
|
||||
val downloadStatus: DownloadStatus
|
||||
val ids: List<Int>,
|
||||
)
|
||||
|
||||
fun dequeueChapterDownloads(
|
||||
input: DequeueChapterDownloadsInput
|
||||
): CompletableFuture<DequeueChapterDownloadsPayload> {
|
||||
data class DequeueChapterDownloadsPayload(
|
||||
val clientMutationId: String?,
|
||||
val downloadStatus: DownloadStatus,
|
||||
)
|
||||
|
||||
fun dequeueChapterDownloads(input: DequeueChapterDownloadsInput): CompletableFuture<DequeueChapterDownloadsPayload> {
|
||||
val (clientMutationId, chapters) = input
|
||||
|
||||
DownloadManager.dequeue(DownloadManager.EnqueueInput(chapters))
|
||||
@@ -133,25 +135,25 @@ class DownloadMutation {
|
||||
return future {
|
||||
DequeueChapterDownloadsPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
downloadStatus = withTimeout(30.seconds) {
|
||||
DownloadStatus(DownloadManager.status.first { it.queue.none { it.chapter.id in chapters } })
|
||||
}
|
||||
downloadStatus =
|
||||
withTimeout(30.seconds) {
|
||||
DownloadStatus(DownloadManager.status.first { it.queue.none { it.chapter.id in chapters } })
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class DequeueChapterDownloadInput(
|
||||
val clientMutationId: String? = null,
|
||||
val id: Int
|
||||
)
|
||||
data class DequeueChapterDownloadPayload(
|
||||
val clientMutationId: String?,
|
||||
val downloadStatus: DownloadStatus
|
||||
val id: Int,
|
||||
)
|
||||
|
||||
fun dequeueChapterDownload(
|
||||
input: DequeueChapterDownloadInput
|
||||
): CompletableFuture<DequeueChapterDownloadPayload> {
|
||||
data class DequeueChapterDownloadPayload(
|
||||
val clientMutationId: String?,
|
||||
val downloadStatus: DownloadStatus,
|
||||
)
|
||||
|
||||
fun dequeueChapterDownload(input: DequeueChapterDownloadInput): CompletableFuture<DequeueChapterDownloadPayload> {
|
||||
val (clientMutationId, chapter) = input
|
||||
|
||||
DownloadManager.dequeue(DownloadManager.EnqueueInput(listOf(chapter)))
|
||||
@@ -159,19 +161,21 @@ class DownloadMutation {
|
||||
return future {
|
||||
DequeueChapterDownloadPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
downloadStatus = withTimeout(30.seconds) {
|
||||
DownloadStatus(DownloadManager.status.first { it.queue.none { it.chapter.id == chapter } })
|
||||
}
|
||||
downloadStatus =
|
||||
withTimeout(30.seconds) {
|
||||
DownloadStatus(DownloadManager.status.first { it.queue.none { it.chapter.id == chapter } })
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class StartDownloaderInput(
|
||||
val clientMutationId: String? = null
|
||||
val clientMutationId: String? = null,
|
||||
)
|
||||
|
||||
data class StartDownloaderPayload(
|
||||
val clientMutationId: String?,
|
||||
val downloadStatus: DownloadStatus
|
||||
val downloadStatus: DownloadStatus,
|
||||
)
|
||||
|
||||
fun startDownloader(input: StartDownloaderInput): CompletableFuture<StartDownloaderPayload> {
|
||||
@@ -180,21 +184,23 @@ class DownloadMutation {
|
||||
return future {
|
||||
StartDownloaderPayload(
|
||||
input.clientMutationId,
|
||||
downloadStatus = withTimeout(30.seconds) {
|
||||
DownloadStatus(
|
||||
DownloadManager.status.first { it.status == Status.Started }
|
||||
)
|
||||
}
|
||||
downloadStatus =
|
||||
withTimeout(30.seconds) {
|
||||
DownloadStatus(
|
||||
DownloadManager.status.first { it.status == Status.Started },
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class StopDownloaderInput(
|
||||
val clientMutationId: String? = null
|
||||
val clientMutationId: String? = null,
|
||||
)
|
||||
|
||||
data class StopDownloaderPayload(
|
||||
val clientMutationId: String?,
|
||||
val downloadStatus: DownloadStatus
|
||||
val downloadStatus: DownloadStatus,
|
||||
)
|
||||
|
||||
fun stopDownloader(input: StopDownloaderInput): CompletableFuture<StopDownloaderPayload> {
|
||||
@@ -202,21 +208,23 @@ class DownloadMutation {
|
||||
DownloadManager.stop()
|
||||
StopDownloaderPayload(
|
||||
input.clientMutationId,
|
||||
downloadStatus = withTimeout(30.seconds) {
|
||||
DownloadStatus(
|
||||
DownloadManager.status.first { it.status == Status.Stopped }
|
||||
)
|
||||
}
|
||||
downloadStatus =
|
||||
withTimeout(30.seconds) {
|
||||
DownloadStatus(
|
||||
DownloadManager.status.first { it.status == Status.Stopped },
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class ClearDownloaderInput(
|
||||
val clientMutationId: String? = null
|
||||
val clientMutationId: String? = null,
|
||||
)
|
||||
|
||||
data class ClearDownloaderPayload(
|
||||
val clientMutationId: String?,
|
||||
val downloadStatus: DownloadStatus
|
||||
val downloadStatus: DownloadStatus,
|
||||
)
|
||||
|
||||
fun clearDownloader(input: ClearDownloaderInput): CompletableFuture<ClearDownloaderPayload> {
|
||||
@@ -224,11 +232,12 @@ class DownloadMutation {
|
||||
DownloadManager.clear()
|
||||
ClearDownloaderPayload(
|
||||
input.clientMutationId,
|
||||
downloadStatus = withTimeout(30.seconds) {
|
||||
DownloadStatus(
|
||||
DownloadManager.status.first { it.status == Status.Stopped && it.queue.isEmpty() }
|
||||
)
|
||||
}
|
||||
downloadStatus =
|
||||
withTimeout(30.seconds) {
|
||||
DownloadStatus(
|
||||
DownloadManager.status.first { it.status == Status.Stopped && it.queue.isEmpty() },
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -236,11 +245,12 @@ class DownloadMutation {
|
||||
data class ReorderChapterDownloadInput(
|
||||
val clientMutationId: String? = null,
|
||||
val chapterId: Int,
|
||||
val to: Int
|
||||
val to: Int,
|
||||
)
|
||||
|
||||
data class ReorderChapterDownloadPayload(
|
||||
val clientMutationId: String?,
|
||||
val downloadStatus: DownloadStatus
|
||||
val downloadStatus: DownloadStatus,
|
||||
)
|
||||
|
||||
fun reorderChapterDownload(input: ReorderChapterDownloadInput): CompletableFuture<ReorderChapterDownloadPayload> {
|
||||
@@ -250,11 +260,12 @@ class DownloadMutation {
|
||||
return future {
|
||||
ReorderChapterDownloadPayload(
|
||||
clientMutationId,
|
||||
downloadStatus = withTimeout(30.seconds) {
|
||||
DownloadStatus(
|
||||
DownloadManager.status.first { it.queue.indexOfFirst { it.chapter.id == chapter } <= to }
|
||||
)
|
||||
}
|
||||
downloadStatus =
|
||||
withTimeout(30.seconds) {
|
||||
DownloadStatus(
|
||||
DownloadManager.status.first { it.queue.indexOfFirst { it.chapter.id == chapter } <= to },
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -262,7 +273,7 @@ class DownloadMutation {
|
||||
data class DownloadAheadInput(
|
||||
val clientMutationId: String? = null,
|
||||
val mangaIds: List<Int> = emptyList(),
|
||||
val latestReadChapterIds: List<Int>? = null
|
||||
val latestReadChapterIds: List<Int>? = null,
|
||||
)
|
||||
|
||||
data class DownloadAheadPayload(val clientMutationId: String?)
|
||||
|
||||
@@ -15,34 +15,40 @@ class ExtensionMutation {
|
||||
data class UpdateExtensionPatch(
|
||||
val install: Boolean? = null,
|
||||
val update: Boolean? = null,
|
||||
val uninstall: Boolean? = null
|
||||
val uninstall: Boolean? = null,
|
||||
)
|
||||
|
||||
data class UpdateExtensionPayload(
|
||||
val clientMutationId: String?,
|
||||
val extension: ExtensionType
|
||||
val extension: ExtensionType,
|
||||
)
|
||||
|
||||
data class UpdateExtensionInput(
|
||||
val clientMutationId: String? = null,
|
||||
val id: String,
|
||||
val patch: UpdateExtensionPatch
|
||||
val patch: UpdateExtensionPatch,
|
||||
)
|
||||
|
||||
data class UpdateExtensionsPayload(
|
||||
val clientMutationId: String?,
|
||||
val extensions: List<ExtensionType>
|
||||
val extensions: List<ExtensionType>,
|
||||
)
|
||||
|
||||
data class UpdateExtensionsInput(
|
||||
val clientMutationId: String? = null,
|
||||
val ids: List<String>,
|
||||
val patch: UpdateExtensionPatch
|
||||
val patch: UpdateExtensionPatch,
|
||||
)
|
||||
|
||||
private suspend fun updateExtensions(ids: List<String>, patch: UpdateExtensionPatch) {
|
||||
val extensions = transaction {
|
||||
ExtensionTable.select { ExtensionTable.pkgName inList ids }
|
||||
.map { ExtensionType(it) }
|
||||
}
|
||||
private suspend fun updateExtensions(
|
||||
ids: List<String>,
|
||||
patch: UpdateExtensionPatch,
|
||||
) {
|
||||
val extensions =
|
||||
transaction {
|
||||
ExtensionTable.select { ExtensionTable.pkgName inList ids }
|
||||
.map { ExtensionType(it) }
|
||||
}
|
||||
|
||||
if (patch.update == true) {
|
||||
extensions.filter { it.hasUpdate }.forEach {
|
||||
@@ -69,13 +75,14 @@ class ExtensionMutation {
|
||||
return future {
|
||||
updateExtensions(listOf(id), patch)
|
||||
}.thenApply {
|
||||
val extension = transaction {
|
||||
ExtensionType(ExtensionTable.select { ExtensionTable.pkgName eq id }.first())
|
||||
}
|
||||
val extension =
|
||||
transaction {
|
||||
ExtensionType(ExtensionTable.select { ExtensionTable.pkgName eq id }.first())
|
||||
}
|
||||
|
||||
UpdateExtensionPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
extension = extension
|
||||
extension = extension,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -86,54 +93,55 @@ class ExtensionMutation {
|
||||
return future {
|
||||
updateExtensions(ids, patch)
|
||||
}.thenApply {
|
||||
val extensions = transaction {
|
||||
ExtensionTable.select { ExtensionTable.pkgName inList ids }
|
||||
.map { ExtensionType(it) }
|
||||
}
|
||||
val extensions =
|
||||
transaction {
|
||||
ExtensionTable.select { ExtensionTable.pkgName inList ids }
|
||||
.map { ExtensionType(it) }
|
||||
}
|
||||
|
||||
UpdateExtensionsPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
extensions = extensions
|
||||
extensions = extensions,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class FetchExtensionsInput(
|
||||
val clientMutationId: String? = null
|
||||
)
|
||||
data class FetchExtensionsPayload(
|
||||
val clientMutationId: String?,
|
||||
val extensions: List<ExtensionType>
|
||||
val clientMutationId: String? = null,
|
||||
)
|
||||
|
||||
fun fetchExtensions(
|
||||
input: FetchExtensionsInput
|
||||
): CompletableFuture<FetchExtensionsPayload> {
|
||||
data class FetchExtensionsPayload(
|
||||
val clientMutationId: String?,
|
||||
val extensions: List<ExtensionType>,
|
||||
)
|
||||
|
||||
fun fetchExtensions(input: FetchExtensionsInput): CompletableFuture<FetchExtensionsPayload> {
|
||||
val (clientMutationId) = input
|
||||
|
||||
return future {
|
||||
ExtensionsList.fetchExtensions()
|
||||
}.thenApply {
|
||||
val extensions = transaction {
|
||||
ExtensionTable.select { ExtensionTable.name neq LocalSource.EXTENSION_NAME }
|
||||
.map { ExtensionType(it) }
|
||||
}
|
||||
val extensions =
|
||||
transaction {
|
||||
ExtensionTable.select { ExtensionTable.name neq LocalSource.EXTENSION_NAME }
|
||||
.map { ExtensionType(it) }
|
||||
}
|
||||
|
||||
FetchExtensionsPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
extensions = extensions
|
||||
extensions = extensions,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class InstallExternalExtensionInput(
|
||||
val clientMutationId: String? = null,
|
||||
val extensionFile: UploadedFile
|
||||
val extensionFile: UploadedFile,
|
||||
)
|
||||
|
||||
data class InstallExternalExtensionPayload(
|
||||
val clientMutationId: String?,
|
||||
val extension: ExtensionType
|
||||
val extension: ExtensionType,
|
||||
)
|
||||
|
||||
fun installExternalExtension(input: InstallExternalExtensionInput): CompletableFuture<InstallExternalExtensionPayload> {
|
||||
@@ -146,7 +154,7 @@ class ExtensionMutation {
|
||||
|
||||
InstallExternalExtensionPayload(
|
||||
clientMutationId,
|
||||
extension = ExtensionType(dbExtension)
|
||||
extension = ExtensionType(dbExtension),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,12 +14,12 @@ import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
class InfoMutation {
|
||||
data class WebUIUpdateInput(
|
||||
val clientMutationId: String? = null
|
||||
val clientMutationId: String? = null,
|
||||
)
|
||||
|
||||
data class WebUIUpdatePayload(
|
||||
val clientMutationId: String?,
|
||||
val updateStatus: WebUIUpdateStatus
|
||||
val updateStatus: WebUIUpdateStatus,
|
||||
)
|
||||
|
||||
fun updateWebUI(input: WebUIUpdateInput): CompletableFuture<WebUIUpdatePayload> {
|
||||
@@ -35,14 +35,15 @@ class InfoMutation {
|
||||
return@withTimeout WebUIUpdatePayload(
|
||||
input.clientMutationId,
|
||||
WebUIUpdateStatus(
|
||||
info = WebUIUpdateInfo(
|
||||
channel = serverConfig.webUIChannel.value,
|
||||
tag = version,
|
||||
updateAvailable
|
||||
),
|
||||
info =
|
||||
WebUIUpdateInfo(
|
||||
channel = serverConfig.webUIChannel.value,
|
||||
tag = version,
|
||||
updateAvailable,
|
||||
),
|
||||
state = STOPPED,
|
||||
progress = 0
|
||||
)
|
||||
progress = 0,
|
||||
),
|
||||
)
|
||||
}
|
||||
try {
|
||||
@@ -53,7 +54,7 @@ class InfoMutation {
|
||||
|
||||
WebUIUpdatePayload(
|
||||
input.clientMutationId,
|
||||
updateStatus = WebInterfaceManager.status.first { it.state == DOWNLOADING }
|
||||
updateStatus = WebInterfaceManager.status.first { it.state == DOWNLOADING },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,30 +22,35 @@ import java.util.concurrent.CompletableFuture
|
||||
*/
|
||||
class MangaMutation {
|
||||
data class UpdateMangaPatch(
|
||||
val inLibrary: Boolean? = null
|
||||
val inLibrary: Boolean? = null,
|
||||
)
|
||||
|
||||
data class UpdateMangaPayload(
|
||||
val clientMutationId: String?,
|
||||
val manga: MangaType
|
||||
val manga: MangaType,
|
||||
)
|
||||
|
||||
data class UpdateMangaInput(
|
||||
val clientMutationId: String? = null,
|
||||
val id: Int,
|
||||
val patch: UpdateMangaPatch
|
||||
val patch: UpdateMangaPatch,
|
||||
)
|
||||
|
||||
data class UpdateMangasPayload(
|
||||
val clientMutationId: String?,
|
||||
val mangas: List<MangaType>
|
||||
val mangas: List<MangaType>,
|
||||
)
|
||||
|
||||
data class UpdateMangasInput(
|
||||
val clientMutationId: String? = null,
|
||||
val ids: List<Int>,
|
||||
val patch: UpdateMangaPatch
|
||||
val patch: UpdateMangaPatch,
|
||||
)
|
||||
|
||||
private suspend fun updateMangas(ids: List<Int>, patch: UpdateMangaPatch) {
|
||||
private suspend fun updateMangas(
|
||||
ids: List<Int>,
|
||||
patch: UpdateMangaPatch,
|
||||
) {
|
||||
transaction {
|
||||
if (patch.inLibrary != null) {
|
||||
MangaTable.update({ MangaTable.id inList ids }) { update ->
|
||||
@@ -69,13 +74,14 @@ class MangaMutation {
|
||||
return future {
|
||||
updateMangas(listOf(id), patch)
|
||||
}.thenApply {
|
||||
val manga = transaction {
|
||||
MangaType(MangaTable.select { MangaTable.id eq id }.first())
|
||||
}
|
||||
val manga =
|
||||
transaction {
|
||||
MangaType(MangaTable.select { MangaTable.id eq id }.first())
|
||||
}
|
||||
|
||||
UpdateMangaPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
manga = manga
|
||||
manga = manga,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -86,55 +92,56 @@ class MangaMutation {
|
||||
return future {
|
||||
updateMangas(ids, patch)
|
||||
}.thenApply {
|
||||
val mangas = transaction {
|
||||
MangaTable.select { MangaTable.id inList ids }.map { MangaType(it) }
|
||||
}
|
||||
val mangas =
|
||||
transaction {
|
||||
MangaTable.select { MangaTable.id inList ids }.map { MangaType(it) }
|
||||
}
|
||||
|
||||
UpdateMangasPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
mangas = mangas
|
||||
mangas = mangas,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class FetchMangaInput(
|
||||
val clientMutationId: String? = null,
|
||||
val id: Int
|
||||
)
|
||||
data class FetchMangaPayload(
|
||||
val clientMutationId: String?,
|
||||
val manga: MangaType
|
||||
val id: Int,
|
||||
)
|
||||
|
||||
fun fetchManga(
|
||||
input: FetchMangaInput
|
||||
): CompletableFuture<FetchMangaPayload> {
|
||||
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()
|
||||
}
|
||||
val manga =
|
||||
transaction {
|
||||
MangaTable.select { MangaTable.id eq id }.first()
|
||||
}
|
||||
FetchMangaPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
manga = MangaType(manga)
|
||||
manga = MangaType(manga),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class SetMangaMetaInput(
|
||||
val clientMutationId: String? = null,
|
||||
val meta: MangaMetaType
|
||||
val meta: MangaMetaType,
|
||||
)
|
||||
|
||||
data class SetMangaMetaPayload(
|
||||
val clientMutationId: String?,
|
||||
val meta: MangaMetaType
|
||||
val meta: MangaMetaType,
|
||||
)
|
||||
fun setMangaMeta(
|
||||
input: SetMangaMetaInput
|
||||
): SetMangaMetaPayload {
|
||||
|
||||
fun setMangaMeta(input: SetMangaMetaInput): SetMangaMetaPayload {
|
||||
val (clientMutationId, meta) = input
|
||||
|
||||
Manga.modifyMangaMeta(meta.mangaId, meta.key, meta.value)
|
||||
@@ -145,35 +152,38 @@ class MangaMutation {
|
||||
data class DeleteMangaMetaInput(
|
||||
val clientMutationId: String? = null,
|
||||
val mangaId: Int,
|
||||
val key: String
|
||||
val key: String,
|
||||
)
|
||||
|
||||
data class DeleteMangaMetaPayload(
|
||||
val clientMutationId: String?,
|
||||
val meta: MangaMetaType?,
|
||||
val manga: MangaType
|
||||
val manga: MangaType,
|
||||
)
|
||||
fun deleteMangaMeta(
|
||||
input: DeleteMangaMetaInput
|
||||
): DeleteMangaMetaPayload {
|
||||
|
||||
fun deleteMangaMeta(input: DeleteMangaMetaInput): DeleteMangaMetaPayload {
|
||||
val (clientMutationId, mangaId, key) = input
|
||||
|
||||
val (meta, manga) = transaction {
|
||||
val meta = MangaMetaTable.select { (MangaMetaTable.ref eq mangaId) and (MangaMetaTable.key eq key) }
|
||||
.firstOrNull()
|
||||
val (meta, manga) =
|
||||
transaction {
|
||||
val meta =
|
||||
MangaMetaTable.select { (MangaMetaTable.ref eq mangaId) and (MangaMetaTable.key eq key) }
|
||||
.firstOrNull()
|
||||
|
||||
MangaMetaTable.deleteWhere { (MangaMetaTable.ref eq mangaId) and (MangaMetaTable.key eq key) }
|
||||
MangaMetaTable.deleteWhere { (MangaMetaTable.ref eq mangaId) and (MangaMetaTable.key eq key) }
|
||||
|
||||
val manga = transaction {
|
||||
MangaType(MangaTable.select { MangaTable.id eq mangaId }.first())
|
||||
val manga =
|
||||
transaction {
|
||||
MangaType(MangaTable.select { MangaTable.id eq mangaId }.first())
|
||||
}
|
||||
|
||||
if (meta != null) {
|
||||
MangaMetaType(meta)
|
||||
} else {
|
||||
null
|
||||
} to manga
|
||||
}
|
||||
|
||||
if (meta != null) {
|
||||
MangaMetaType(meta)
|
||||
} else {
|
||||
null
|
||||
} to manga
|
||||
}
|
||||
|
||||
return DeleteMangaMetaPayload(clientMutationId, meta, manga)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,18 +9,17 @@ import suwayomi.tachidesk.global.model.table.GlobalMetaTable
|
||||
import suwayomi.tachidesk.graphql.types.GlobalMetaType
|
||||
|
||||
class MetaMutation {
|
||||
|
||||
data class SetGlobalMetaInput(
|
||||
val clientMutationId: String? = null,
|
||||
val meta: GlobalMetaType
|
||||
val meta: GlobalMetaType,
|
||||
)
|
||||
|
||||
data class SetGlobalMetaPayload(
|
||||
val clientMutationId: String?,
|
||||
val meta: GlobalMetaType
|
||||
val meta: GlobalMetaType,
|
||||
)
|
||||
fun setGlobalMeta(
|
||||
input: SetGlobalMetaInput
|
||||
): SetGlobalMetaPayload {
|
||||
|
||||
fun setGlobalMeta(input: SetGlobalMetaInput): SetGlobalMetaPayload {
|
||||
val (clientMutationId, meta) = input
|
||||
|
||||
GlobalMeta.modifyMeta(meta.key, meta.value)
|
||||
@@ -30,29 +29,31 @@ class MetaMutation {
|
||||
|
||||
data class DeleteGlobalMetaInput(
|
||||
val clientMutationId: String? = null,
|
||||
val key: String
|
||||
val key: String,
|
||||
)
|
||||
|
||||
data class DeleteGlobalMetaPayload(
|
||||
val clientMutationId: String?,
|
||||
val meta: GlobalMetaType?
|
||||
val meta: GlobalMetaType?,
|
||||
)
|
||||
fun deleteGlobalMeta(
|
||||
input: DeleteGlobalMetaInput
|
||||
): DeleteGlobalMetaPayload {
|
||||
|
||||
fun deleteGlobalMeta(input: DeleteGlobalMetaInput): DeleteGlobalMetaPayload {
|
||||
val (clientMutationId, key) = input
|
||||
|
||||
val meta = transaction {
|
||||
val meta = GlobalMetaTable.select { GlobalMetaTable.key eq key }
|
||||
.firstOrNull()
|
||||
val meta =
|
||||
transaction {
|
||||
val meta =
|
||||
GlobalMetaTable.select { GlobalMetaTable.key eq key }
|
||||
.firstOrNull()
|
||||
|
||||
GlobalMetaTable.deleteWhere { GlobalMetaTable.key eq key }
|
||||
GlobalMetaTable.deleteWhere { GlobalMetaTable.key eq key }
|
||||
|
||||
if (meta != null) {
|
||||
GlobalMetaType(meta)
|
||||
} else {
|
||||
null
|
||||
if (meta != null) {
|
||||
GlobalMetaType(meta)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return DeleteGlobalMetaPayload(clientMutationId, meta)
|
||||
}
|
||||
|
||||
@@ -11,53 +11,107 @@ import xyz.nulldev.ts.config.GlobalConfigManager
|
||||
class SettingsMutation {
|
||||
data class SetSettingsInput(
|
||||
val clientMutationId: String? = null,
|
||||
val settings: PartialSettingsType
|
||||
val settings: PartialSettingsType,
|
||||
)
|
||||
|
||||
data class SetSettingsPayload(
|
||||
val clientMutationId: String?,
|
||||
val settings: SettingsType
|
||||
val settings: SettingsType,
|
||||
)
|
||||
|
||||
private fun updateSettings(settings: Settings) {
|
||||
if (settings.ip != null) serverConfig.ip.value = settings.ip!!
|
||||
if (settings.port != null) serverConfig.port.value = settings.port!!
|
||||
|
||||
if (settings.socksProxyEnabled != null) serverConfig.socksProxyEnabled.value = settings.socksProxyEnabled!!
|
||||
if (settings.socksProxyHost != null) serverConfig.socksProxyHost.value = settings.socksProxyHost!!
|
||||
if (settings.socksProxyPort != null) serverConfig.socksProxyPort.value = settings.socksProxyPort!!
|
||||
if (settings.socksProxyEnabled != null) {
|
||||
serverConfig.socksProxyEnabled.value = settings.socksProxyEnabled!!
|
||||
}
|
||||
if (settings.socksProxyHost != null) {
|
||||
serverConfig.socksProxyHost.value = settings.socksProxyHost!!
|
||||
}
|
||||
if (settings.socksProxyPort != null) {
|
||||
serverConfig.socksProxyPort.value = settings.socksProxyPort!!
|
||||
}
|
||||
|
||||
if (settings.webUIFlavor != null) serverConfig.webUIFlavor.value = settings.webUIFlavor!!.uiName
|
||||
if (settings.initialOpenInBrowserEnabled != null) serverConfig.initialOpenInBrowserEnabled.value = settings.initialOpenInBrowserEnabled!!
|
||||
if (settings.webUIInterface != null) serverConfig.webUIInterface.value = settings.webUIInterface!!.name.lowercase()
|
||||
if (settings.electronPath != null) serverConfig.electronPath.value = settings.electronPath!!
|
||||
if (settings.webUIChannel != null) serverConfig.webUIChannel.value = settings.webUIChannel!!.name.lowercase()
|
||||
if (settings.webUIUpdateCheckInterval != null) serverConfig.webUIUpdateCheckInterval.value = settings.webUIUpdateCheckInterval!!
|
||||
if (settings.webUIFlavor != null) {
|
||||
serverConfig.webUIFlavor.value = settings.webUIFlavor!!.uiName
|
||||
}
|
||||
if (settings.initialOpenInBrowserEnabled != null) {
|
||||
serverConfig.initialOpenInBrowserEnabled.value = settings.initialOpenInBrowserEnabled!!
|
||||
}
|
||||
if (settings.webUIInterface != null) {
|
||||
serverConfig.webUIInterface.value = settings.webUIInterface!!.name.lowercase()
|
||||
}
|
||||
if (settings.electronPath != null) {
|
||||
serverConfig.electronPath.value = settings.electronPath!!
|
||||
}
|
||||
if (settings.webUIChannel != null) {
|
||||
serverConfig.webUIChannel.value = settings.webUIChannel!!.name.lowercase()
|
||||
}
|
||||
if (settings.webUIUpdateCheckInterval != null) {
|
||||
serverConfig.webUIUpdateCheckInterval.value = settings.webUIUpdateCheckInterval!!
|
||||
}
|
||||
|
||||
if (settings.downloadAsCbz != null) serverConfig.downloadAsCbz.value = settings.downloadAsCbz!!
|
||||
if (settings.downloadsPath != null) serverConfig.downloadsPath.value = settings.downloadsPath!!
|
||||
if (settings.autoDownloadNewChapters != null) serverConfig.autoDownloadNewChapters.value = settings.autoDownloadNewChapters!!
|
||||
if (settings.downloadAsCbz != null) {
|
||||
serverConfig.downloadAsCbz.value = settings.downloadAsCbz!!
|
||||
}
|
||||
if (settings.downloadsPath != null) {
|
||||
serverConfig.downloadsPath.value = settings.downloadsPath!!
|
||||
}
|
||||
if (settings.autoDownloadNewChapters != null) {
|
||||
serverConfig.autoDownloadNewChapters.value = settings.autoDownloadNewChapters!!
|
||||
}
|
||||
|
||||
if (settings.maxSourcesInParallel != null) serverConfig.maxSourcesInParallel.value = settings.maxSourcesInParallel!!
|
||||
if (settings.maxSourcesInParallel != null) {
|
||||
serverConfig.maxSourcesInParallel.value = settings.maxSourcesInParallel!!
|
||||
}
|
||||
|
||||
if (settings.excludeUnreadChapters != null) serverConfig.excludeUnreadChapters.value = settings.excludeUnreadChapters!!
|
||||
if (settings.excludeNotStarted != null) serverConfig.excludeNotStarted.value = settings.excludeNotStarted!!
|
||||
if (settings.excludeCompleted != null) serverConfig.excludeCompleted.value = settings.excludeCompleted!!
|
||||
if (settings.globalUpdateInterval != null) serverConfig.globalUpdateInterval.value = settings.globalUpdateInterval!!
|
||||
if (settings.excludeUnreadChapters != null) {
|
||||
serverConfig.excludeUnreadChapters.value = settings.excludeUnreadChapters!!
|
||||
}
|
||||
if (settings.excludeNotStarted != null) {
|
||||
serverConfig.excludeNotStarted.value = settings.excludeNotStarted!!
|
||||
}
|
||||
if (settings.excludeCompleted != null) {
|
||||
serverConfig.excludeCompleted.value = settings.excludeCompleted!!
|
||||
}
|
||||
if (settings.globalUpdateInterval != null) {
|
||||
serverConfig.globalUpdateInterval.value = settings.globalUpdateInterval!!
|
||||
}
|
||||
|
||||
if (settings.basicAuthEnabled != null) serverConfig.basicAuthEnabled.value = settings.basicAuthEnabled!!
|
||||
if (settings.basicAuthUsername != null) serverConfig.basicAuthUsername.value = settings.basicAuthUsername!!
|
||||
if (settings.basicAuthPassword != null) serverConfig.basicAuthPassword.value = settings.basicAuthPassword!!
|
||||
if (settings.basicAuthEnabled != null) {
|
||||
serverConfig.basicAuthEnabled.value = settings.basicAuthEnabled!!
|
||||
}
|
||||
if (settings.basicAuthUsername != null) {
|
||||
serverConfig.basicAuthUsername.value = settings.basicAuthUsername!!
|
||||
}
|
||||
if (settings.basicAuthPassword != null) {
|
||||
serverConfig.basicAuthPassword.value = settings.basicAuthPassword!!
|
||||
}
|
||||
|
||||
if (settings.debugLogsEnabled != null) serverConfig.debugLogsEnabled.value = settings.debugLogsEnabled!!
|
||||
if (settings.systemTrayEnabled != null) serverConfig.systemTrayEnabled.value = settings.systemTrayEnabled!!
|
||||
if (settings.debugLogsEnabled != null) {
|
||||
serverConfig.debugLogsEnabled.value = settings.debugLogsEnabled!!
|
||||
}
|
||||
if (settings.systemTrayEnabled != null) {
|
||||
serverConfig.systemTrayEnabled.value = settings.systemTrayEnabled!!
|
||||
}
|
||||
|
||||
if (settings.backupPath != null) serverConfig.backupPath.value = settings.backupPath!!
|
||||
if (settings.backupTime != null) serverConfig.backupTime.value = settings.backupTime!!
|
||||
if (settings.backupInterval != null) serverConfig.backupInterval.value = settings.backupInterval!!
|
||||
if (settings.backupTTL != null) serverConfig.backupTTL.value = settings.backupTTL!!
|
||||
if (settings.backupPath != null) {
|
||||
serverConfig.backupPath.value = settings.backupPath!!
|
||||
}
|
||||
if (settings.backupTime != null) {
|
||||
serverConfig.backupTime.value = settings.backupTime!!
|
||||
}
|
||||
if (settings.backupInterval != null) {
|
||||
serverConfig.backupInterval.value = settings.backupInterval!!
|
||||
}
|
||||
if (settings.backupTTL != null) {
|
||||
serverConfig.backupTTL.value = settings.backupTTL!!
|
||||
}
|
||||
|
||||
if (settings.localSourcePath != null) serverConfig.localSourcePath.value = settings.localSourcePath!!
|
||||
if (settings.localSourcePath != null) {
|
||||
serverConfig.localSourcePath.value = settings.localSourcePath!!
|
||||
}
|
||||
}
|
||||
|
||||
fun setSettings(input: SetSettingsInput): SetSettingsPayload {
|
||||
@@ -72,7 +126,7 @@ class SettingsMutation {
|
||||
|
||||
data class ResetSettingsPayload(
|
||||
val clientMutationId: String?,
|
||||
val settings: SettingsType
|
||||
val settings: SettingsType,
|
||||
)
|
||||
|
||||
fun resetSettings(input: ResetSettingsInput): ResetSettingsPayload {
|
||||
|
||||
@@ -20,63 +20,64 @@ import suwayomi.tachidesk.server.JavalinSetup.future
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class SourceMutation {
|
||||
|
||||
enum class FetchSourceMangaType {
|
||||
SEARCH,
|
||||
POPULAR,
|
||||
LATEST
|
||||
LATEST,
|
||||
}
|
||||
|
||||
data class FetchSourceMangaInput(
|
||||
val clientMutationId: String? = null,
|
||||
val source: Long,
|
||||
val type: FetchSourceMangaType,
|
||||
val page: Int,
|
||||
val query: String? = null,
|
||||
val filters: List<FilterChange>? = null
|
||||
val filters: List<FilterChange>? = null,
|
||||
)
|
||||
|
||||
data class FetchSourceMangaPayload(
|
||||
val clientMutationId: String?,
|
||||
val mangas: List<MangaType>,
|
||||
val hasNextPage: Boolean
|
||||
val hasNextPage: Boolean,
|
||||
)
|
||||
|
||||
fun fetchSourceManga(
|
||||
input: FetchSourceMangaInput
|
||||
): CompletableFuture<FetchSourceMangaPayload> {
|
||||
fun fetchSourceManga(input: FetchSourceMangaInput): CompletableFuture<FetchSourceMangaPayload> {
|
||||
val (clientMutationId, sourceId, type, page, query, filters) = input
|
||||
|
||||
return future {
|
||||
val source = GetCatalogueSource.getCatalogueSourceOrNull(sourceId)!!
|
||||
val mangasPage = when (type) {
|
||||
FetchSourceMangaType.SEARCH -> {
|
||||
source.getSearchManga(
|
||||
page = page,
|
||||
query = query.orEmpty(),
|
||||
filters = updateFilterList(source, filters)
|
||||
)
|
||||
val mangasPage =
|
||||
when (type) {
|
||||
FetchSourceMangaType.SEARCH -> {
|
||||
source.getSearchManga(
|
||||
page = page,
|
||||
query = query.orEmpty(),
|
||||
filters = updateFilterList(source, filters),
|
||||
)
|
||||
}
|
||||
FetchSourceMangaType.POPULAR -> {
|
||||
source.getPopularManga(page)
|
||||
}
|
||||
FetchSourceMangaType.LATEST -> {
|
||||
if (!source.supportsLatest) throw Exception("Source does not support latest")
|
||||
source.getLatestUpdates(page)
|
||||
}
|
||||
}
|
||||
FetchSourceMangaType.POPULAR -> {
|
||||
source.getPopularManga(page)
|
||||
}
|
||||
FetchSourceMangaType.LATEST -> {
|
||||
if (!source.supportsLatest) throw Exception("Source does not support latest")
|
||||
source.getLatestUpdates(page)
|
||||
}
|
||||
}
|
||||
|
||||
val mangaIds = mangasPage.insertOrGet(sourceId)
|
||||
|
||||
val mangas = transaction {
|
||||
MangaTable.select { MangaTable.id inList mangaIds }
|
||||
.map { MangaType(it) }
|
||||
}.sortedBy {
|
||||
mangaIds.indexOf(it.id)
|
||||
}
|
||||
val mangas =
|
||||
transaction {
|
||||
MangaTable.select { MangaTable.id inList mangaIds }
|
||||
.map { MangaType(it) }
|
||||
}.sortedBy {
|
||||
mangaIds.indexOf(it.id)
|
||||
}
|
||||
|
||||
FetchSourceMangaPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
mangas = mangas,
|
||||
hasNextPage = mangasPage.hasNextPage
|
||||
hasNextPage = mangasPage.hasNextPage,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -87,21 +88,21 @@ class SourceMutation {
|
||||
val checkBoxState: Boolean? = null,
|
||||
val editTextState: String? = null,
|
||||
val listState: String? = null,
|
||||
val multiSelectState: List<String>? = null
|
||||
val multiSelectState: List<String>? = null,
|
||||
)
|
||||
|
||||
data class UpdateSourcePreferenceInput(
|
||||
val clientMutationId: String? = null,
|
||||
val source: Long,
|
||||
val change: SourcePreferenceChange
|
||||
)
|
||||
data class UpdateSourcePreferencePayload(
|
||||
val clientMutationId: String?,
|
||||
val preferences: List<Preference>
|
||||
val change: SourcePreferenceChange,
|
||||
)
|
||||
|
||||
fun updateSourcePreference(
|
||||
input: UpdateSourcePreferenceInput
|
||||
): UpdateSourcePreferencePayload {
|
||||
data class UpdateSourcePreferencePayload(
|
||||
val clientMutationId: String?,
|
||||
val preferences: List<Preference>,
|
||||
)
|
||||
|
||||
fun updateSourcePreference(input: UpdateSourcePreferenceInput): UpdateSourcePreferencePayload {
|
||||
val (clientMutationId, sourceId, change) = input
|
||||
|
||||
Source.setSourcePreference(sourceId, change.position, "") { preference ->
|
||||
@@ -117,7 +118,7 @@ class SourceMutation {
|
||||
|
||||
return UpdateSourcePreferencePayload(
|
||||
clientMutationId = clientMutationId,
|
||||
preferences = Source.getSourcePreferencesRaw(sourceId).map { preferenceOf(it) }
|
||||
preferences = Source.getSourcePreferencesRaw(sourceId).map { preferenceOf(it) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,18 +15,19 @@ class UpdateMutation {
|
||||
private val updater by DI.global.instance<IUpdater>()
|
||||
|
||||
data class UpdateLibraryMangaInput(
|
||||
val clientMutationId: String? = null
|
||||
val clientMutationId: String? = null,
|
||||
)
|
||||
|
||||
data class UpdateLibraryMangaPayload(
|
||||
val clientMutationId: String?,
|
||||
val updateStatus: UpdateStatus
|
||||
val updateStatus: UpdateStatus,
|
||||
)
|
||||
|
||||
fun updateLibraryManga(input: UpdateLibraryMangaInput): UpdateLibraryMangaPayload {
|
||||
updater.addCategoriesToUpdateQueue(
|
||||
Category.getCategoryList(),
|
||||
clear = true,
|
||||
forceAll = false
|
||||
forceAll = false,
|
||||
)
|
||||
|
||||
return UpdateLibraryMangaPayload(input.clientMutationId, UpdateStatus(updater.status.value))
|
||||
@@ -34,32 +35,35 @@ class UpdateMutation {
|
||||
|
||||
data class UpdateCategoryMangaInput(
|
||||
val clientMutationId: String? = null,
|
||||
val categories: List<Int>
|
||||
val categories: List<Int>,
|
||||
)
|
||||
|
||||
data class UpdateCategoryMangaPayload(
|
||||
val clientMutationId: String?,
|
||||
val updateStatus: UpdateStatus
|
||||
val updateStatus: UpdateStatus,
|
||||
)
|
||||
|
||||
fun updateCategoryManga(input: UpdateCategoryMangaInput): UpdateCategoryMangaPayload {
|
||||
val categories = transaction {
|
||||
CategoryTable.select { CategoryTable.id inList input.categories }.map {
|
||||
CategoryTable.toDataClass(it)
|
||||
val categories =
|
||||
transaction {
|
||||
CategoryTable.select { CategoryTable.id inList input.categories }.map {
|
||||
CategoryTable.toDataClass(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
updater.addCategoriesToUpdateQueue(categories, clear = true, forceAll = true)
|
||||
|
||||
return UpdateCategoryMangaPayload(
|
||||
clientMutationId = input.clientMutationId,
|
||||
updateStatus = UpdateStatus(updater.status.value)
|
||||
updateStatus = UpdateStatus(updater.status.value),
|
||||
)
|
||||
}
|
||||
|
||||
data class UpdateStopInput(
|
||||
val clientMutationId: String? = null
|
||||
val clientMutationId: String? = null,
|
||||
)
|
||||
|
||||
data class UpdateStopPayload(
|
||||
val clientMutationId: String?
|
||||
val clientMutationId: String?,
|
||||
)
|
||||
|
||||
fun updateStop(input: UpdateStopInput): UpdateStopPayload {
|
||||
|
||||
@@ -8,21 +8,22 @@ import suwayomi.tachidesk.manga.impl.backup.proto.ProtoBackupValidator
|
||||
|
||||
class BackupQuery {
|
||||
data class ValidateBackupInput(
|
||||
val backup: UploadedFile
|
||||
val backup: UploadedFile,
|
||||
)
|
||||
|
||||
data class ValidateBackupSource(
|
||||
val id: Long,
|
||||
val name: String
|
||||
val name: String,
|
||||
)
|
||||
|
||||
data class ValidateBackupResult(
|
||||
val missingSources: List<ValidateBackupSource>
|
||||
val missingSources: List<ValidateBackupSource>,
|
||||
)
|
||||
fun validateBackup(
|
||||
input: ValidateBackupInput
|
||||
): ValidateBackupResult {
|
||||
|
||||
fun validateBackup(input: ValidateBackupInput): ValidateBackupResult {
|
||||
val result = ProtoBackupValidator.validate(input.backup.content)
|
||||
return ValidateBackupResult(
|
||||
result.missingSourceIds.map { ValidateBackupSource(it.first, it.second) }
|
||||
result.missingSourceIds.map { ValidateBackupSource(it.first, it.second) },
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -46,14 +46,18 @@ import suwayomi.tachidesk.manga.model.table.CategoryTable
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class CategoryQuery {
|
||||
fun category(dataFetchingEnvironment: DataFetchingEnvironment, id: Int): CompletableFuture<CategoryType> {
|
||||
fun category(
|
||||
dataFetchingEnvironment: DataFetchingEnvironment,
|
||||
id: Int,
|
||||
): CompletableFuture<CategoryType> {
|
||||
return dataFetchingEnvironment.getValueFromDataLoader("CategoryDataLoader", id)
|
||||
}
|
||||
|
||||
enum class CategoryOrderBy(override val column: Column<out Comparable<*>>) : OrderBy<CategoryType> {
|
||||
ID(CategoryTable.id),
|
||||
NAME(CategoryTable.name),
|
||||
ORDER(CategoryTable.order);
|
||||
ORDER(CategoryTable.order),
|
||||
;
|
||||
|
||||
override fun greater(cursor: Cursor): Op<Boolean> {
|
||||
return when (this) {
|
||||
@@ -72,11 +76,12 @@ class CategoryQuery {
|
||||
}
|
||||
|
||||
override fun asCursor(type: CategoryType): Cursor {
|
||||
val value = when (this) {
|
||||
ID -> type.id.toString()
|
||||
NAME -> type.id.toString() + "-" + type.name
|
||||
ORDER -> type.id.toString() + "-" + type.order
|
||||
}
|
||||
val value =
|
||||
when (this) {
|
||||
ID -> type.id.toString()
|
||||
NAME -> type.id.toString() + "-" + type.name
|
||||
ORDER -> type.id.toString() + "-" + type.order
|
||||
}
|
||||
return Cursor(value)
|
||||
}
|
||||
}
|
||||
@@ -85,7 +90,7 @@ class CategoryQuery {
|
||||
val id: Int? = null,
|
||||
val order: Int? = null,
|
||||
val name: String? = null,
|
||||
val default: Boolean? = null
|
||||
val default: Boolean? = null,
|
||||
) : HasGetOp {
|
||||
override fun getOp(): Op<Boolean>? {
|
||||
val opAnd = OpAnd()
|
||||
@@ -105,14 +110,14 @@ class CategoryQuery {
|
||||
val default: BooleanFilter? = null,
|
||||
override val and: List<CategoryFilter>? = null,
|
||||
override val or: List<CategoryFilter>? = null,
|
||||
override val not: CategoryFilter? = null
|
||||
override val not: CategoryFilter? = null,
|
||||
) : Filter<CategoryFilter> {
|
||||
override fun getOpList(): List<Op<Boolean>> {
|
||||
return listOfNotNull(
|
||||
andFilterWithCompareEntity(CategoryTable.id, id),
|
||||
andFilterWithCompare(CategoryTable.order, order),
|
||||
andFilterWithCompareString(CategoryTable.name, name),
|
||||
andFilterWithCompare(CategoryTable.isDefault, default)
|
||||
andFilterWithCompare(CategoryTable.isDefault, default),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -126,55 +131,56 @@ class CategoryQuery {
|
||||
after: Cursor? = null,
|
||||
first: Int? = null,
|
||||
last: Int? = null,
|
||||
offset: Int? = null
|
||||
offset: Int? = null,
|
||||
): CategoryNodeList {
|
||||
val queryResults = transaction {
|
||||
val res = CategoryTable.selectAll()
|
||||
val queryResults =
|
||||
transaction {
|
||||
val res = CategoryTable.selectAll()
|
||||
|
||||
res.applyOps(condition, filter)
|
||||
res.applyOps(condition, filter)
|
||||
|
||||
if (orderBy != null || (last != null || before != null)) {
|
||||
val orderByColumn = orderBy?.column ?: CategoryTable.id
|
||||
val orderType = orderByType.maybeSwap(last ?: before)
|
||||
if (orderBy != null || (last != null || before != null)) {
|
||||
val orderByColumn = orderBy?.column ?: CategoryTable.id
|
||||
val orderType = orderByType.maybeSwap(last ?: before)
|
||||
|
||||
if (orderBy == CategoryOrderBy.ID || orderBy == null) {
|
||||
res.orderBy(orderByColumn to orderType)
|
||||
} else {
|
||||
res.orderBy(
|
||||
orderByColumn to orderType,
|
||||
CategoryTable.id to SortOrder.ASC
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val total = res.count()
|
||||
val firstResult = res.firstOrNull()?.get(CategoryTable.id)?.value
|
||||
val lastResult = res.lastOrNull()?.get(CategoryTable.id)?.value
|
||||
|
||||
if (after != null) {
|
||||
res.andWhere {
|
||||
when (orderByType) {
|
||||
DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: CategoryOrderBy.ID).less(after)
|
||||
null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: CategoryOrderBy.ID).greater(after)
|
||||
if (orderBy == CategoryOrderBy.ID || orderBy == null) {
|
||||
res.orderBy(orderByColumn to orderType)
|
||||
} else {
|
||||
res.orderBy(
|
||||
orderByColumn to orderType,
|
||||
CategoryTable.id to SortOrder.ASC,
|
||||
)
|
||||
}
|
||||
}
|
||||
} else if (before != null) {
|
||||
res.andWhere {
|
||||
when (orderByType) {
|
||||
DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: CategoryOrderBy.ID).greater(before)
|
||||
null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: CategoryOrderBy.ID).less(before)
|
||||
|
||||
val total = res.count()
|
||||
val firstResult = res.firstOrNull()?.get(CategoryTable.id)?.value
|
||||
val lastResult = res.lastOrNull()?.get(CategoryTable.id)?.value
|
||||
|
||||
if (after != null) {
|
||||
res.andWhere {
|
||||
when (orderByType) {
|
||||
DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: CategoryOrderBy.ID).less(after)
|
||||
null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: CategoryOrderBy.ID).greater(after)
|
||||
}
|
||||
}
|
||||
} else if (before != null) {
|
||||
res.andWhere {
|
||||
when (orderByType) {
|
||||
DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: CategoryOrderBy.ID).greater(before)
|
||||
null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: CategoryOrderBy.ID).less(before)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (first != null) {
|
||||
res.limit(first, offset?.toLong() ?: 0)
|
||||
} else if (last != null) {
|
||||
res.limit(last)
|
||||
}
|
||||
if (first != null) {
|
||||
res.limit(first, offset?.toLong() ?: 0)
|
||||
} else if (last != null) {
|
||||
res.limit(last)
|
||||
}
|
||||
|
||||
QueryResults(total, firstResult, lastResult, res.toList())
|
||||
}
|
||||
QueryResults(total, firstResult, lastResult, res.toList())
|
||||
}
|
||||
|
||||
val getAsCursor: (CategoryType) -> Cursor = (orderBy ?: CategoryOrderBy.ID)::asCursor
|
||||
|
||||
@@ -189,24 +195,25 @@ class CategoryQuery {
|
||||
resultsAsType.firstOrNull()?.let {
|
||||
CategoryNodeList.CategoryEdge(
|
||||
getAsCursor(it),
|
||||
it
|
||||
it,
|
||||
)
|
||||
},
|
||||
resultsAsType.lastOrNull()?.let {
|
||||
CategoryNodeList.CategoryEdge(
|
||||
getAsCursor(it),
|
||||
it
|
||||
it,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
pageInfo = PageInfo(
|
||||
hasNextPage = queryResults.lastKey != resultsAsType.lastOrNull()?.id,
|
||||
hasPreviousPage = queryResults.firstKey != resultsAsType.firstOrNull()?.id,
|
||||
startCursor = resultsAsType.firstOrNull()?.let { getAsCursor(it) },
|
||||
endCursor = resultsAsType.lastOrNull()?.let { getAsCursor(it) }
|
||||
),
|
||||
totalCount = queryResults.total.toInt()
|
||||
pageInfo =
|
||||
PageInfo(
|
||||
hasNextPage = queryResults.lastKey != resultsAsType.lastOrNull()?.id,
|
||||
hasPreviousPage = queryResults.firstKey != resultsAsType.firstOrNull()?.id,
|
||||
startCursor = resultsAsType.firstOrNull()?.let { getAsCursor(it) },
|
||||
endCursor = resultsAsType.lastOrNull()?.let { getAsCursor(it) },
|
||||
),
|
||||
totalCount = queryResults.total.toInt(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,10 @@ import java.util.concurrent.CompletableFuture
|
||||
* - Get page list?
|
||||
*/
|
||||
class ChapterQuery {
|
||||
fun chapter(dataFetchingEnvironment: DataFetchingEnvironment, id: Int): CompletableFuture<ChapterType> {
|
||||
fun chapter(
|
||||
dataFetchingEnvironment: DataFetchingEnvironment,
|
||||
id: Int,
|
||||
): CompletableFuture<ChapterType> {
|
||||
return dataFetchingEnvironment.getValueFromDataLoader("ChapterDataLoader", id)
|
||||
}
|
||||
|
||||
@@ -66,7 +69,8 @@ class ChapterQuery {
|
||||
UPLOAD_DATE(ChapterTable.date_upload),
|
||||
CHAPTER_NUMBER(ChapterTable.chapter_number),
|
||||
LAST_READ_AT(ChapterTable.lastReadAt),
|
||||
FETCHED_AT(ChapterTable.fetchedAt);
|
||||
FETCHED_AT(ChapterTable.fetchedAt),
|
||||
;
|
||||
|
||||
override fun greater(cursor: Cursor): Op<Boolean> {
|
||||
return when (this) {
|
||||
@@ -93,15 +97,16 @@ class ChapterQuery {
|
||||
}
|
||||
|
||||
override fun asCursor(type: ChapterType): Cursor {
|
||||
val value = when (this) {
|
||||
ID -> type.id.toString()
|
||||
SOURCE_ORDER -> type.id.toString() + "-" + type.sourceOrder
|
||||
NAME -> type.id.toString() + "-" + type.name
|
||||
UPLOAD_DATE -> type.id.toString() + "-" + type.uploadDate
|
||||
CHAPTER_NUMBER -> type.id.toString() + "-" + type.chapterNumber
|
||||
LAST_READ_AT -> type.id.toString() + "-" + type.lastReadAt
|
||||
FETCHED_AT -> type.id.toString() + "-" + type.fetchedAt
|
||||
}
|
||||
val value =
|
||||
when (this) {
|
||||
ID -> type.id.toString()
|
||||
SOURCE_ORDER -> type.id.toString() + "-" + type.sourceOrder
|
||||
NAME -> type.id.toString() + "-" + type.name
|
||||
UPLOAD_DATE -> type.id.toString() + "-" + type.uploadDate
|
||||
CHAPTER_NUMBER -> type.id.toString() + "-" + type.chapterNumber
|
||||
LAST_READ_AT -> type.id.toString() + "-" + type.lastReadAt
|
||||
FETCHED_AT -> type.id.toString() + "-" + type.fetchedAt
|
||||
}
|
||||
return Cursor(value)
|
||||
}
|
||||
}
|
||||
@@ -122,7 +127,7 @@ class ChapterQuery {
|
||||
val realUrl: String? = null,
|
||||
val fetchedAt: Long? = null,
|
||||
val isDownloaded: Boolean? = null,
|
||||
val pageCount: Int? = null
|
||||
val pageCount: Int? = null,
|
||||
) : HasGetOp {
|
||||
override fun getOp(): Op<Boolean>? {
|
||||
val opAnd = OpAnd()
|
||||
@@ -167,7 +172,7 @@ class ChapterQuery {
|
||||
val inLibrary: BooleanFilter? = null,
|
||||
override val and: List<ChapterFilter>? = null,
|
||||
override val or: List<ChapterFilter>? = null,
|
||||
override val not: ChapterFilter? = null
|
||||
override val not: ChapterFilter? = null,
|
||||
) : Filter<ChapterFilter> {
|
||||
override fun getOpList(): List<Op<Boolean>> {
|
||||
return listOfNotNull(
|
||||
@@ -186,7 +191,7 @@ class ChapterQuery {
|
||||
andFilterWithCompareString(ChapterTable.realUrl, realUrl),
|
||||
andFilterWithCompare(ChapterTable.fetchedAt, fetchedAt),
|
||||
andFilterWithCompare(ChapterTable.isDownloaded, isDownloaded),
|
||||
andFilterWithCompare(ChapterTable.pageCount, pageCount)
|
||||
andFilterWithCompare(ChapterTable.pageCount, pageCount),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -202,63 +207,64 @@ class ChapterQuery {
|
||||
after: Cursor? = null,
|
||||
first: Int? = null,
|
||||
last: Int? = null,
|
||||
offset: Int? = null
|
||||
offset: Int? = null,
|
||||
): ChapterNodeList {
|
||||
val queryResults = transaction {
|
||||
val res = ChapterTable.selectAll()
|
||||
val queryResults =
|
||||
transaction {
|
||||
val res = ChapterTable.selectAll()
|
||||
|
||||
val libraryOp = filter?.getLibraryOp()
|
||||
if (libraryOp != null) {
|
||||
res.adjustColumnSet {
|
||||
innerJoin(MangaTable)
|
||||
val libraryOp = filter?.getLibraryOp()
|
||||
if (libraryOp != null) {
|
||||
res.adjustColumnSet {
|
||||
innerJoin(MangaTable)
|
||||
}
|
||||
res.andWhere { libraryOp }
|
||||
}
|
||||
res.andWhere { libraryOp }
|
||||
}
|
||||
|
||||
res.applyOps(condition, filter)
|
||||
res.applyOps(condition, filter)
|
||||
|
||||
if (orderBy != null || (last != null || before != null)) {
|
||||
val orderByColumn = orderBy?.column ?: ChapterTable.id
|
||||
val orderType = orderByType.maybeSwap(last ?: before)
|
||||
if (orderBy != null || (last != null || before != null)) {
|
||||
val orderByColumn = orderBy?.column ?: ChapterTable.id
|
||||
val orderType = orderByType.maybeSwap(last ?: before)
|
||||
|
||||
if (orderBy == ChapterOrderBy.ID || orderBy == null) {
|
||||
res.orderBy(orderByColumn to orderType)
|
||||
} else {
|
||||
res.orderBy(
|
||||
orderByColumn to orderType,
|
||||
ChapterTable.id to SortOrder.ASC
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val total = res.count()
|
||||
val firstResult = res.firstOrNull()?.get(ChapterTable.id)?.value
|
||||
val lastResult = res.lastOrNull()?.get(ChapterTable.id)?.value
|
||||
|
||||
if (after != null) {
|
||||
res.andWhere {
|
||||
when (orderByType) {
|
||||
DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: ID).less(after)
|
||||
null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: ID).greater(after)
|
||||
if (orderBy == ChapterOrderBy.ID || orderBy == null) {
|
||||
res.orderBy(orderByColumn to orderType)
|
||||
} else {
|
||||
res.orderBy(
|
||||
orderByColumn to orderType,
|
||||
ChapterTable.id to SortOrder.ASC,
|
||||
)
|
||||
}
|
||||
}
|
||||
} else if (before != null) {
|
||||
res.andWhere {
|
||||
when (orderByType) {
|
||||
DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: ID).greater(before)
|
||||
null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: ID).less(before)
|
||||
|
||||
val total = res.count()
|
||||
val firstResult = res.firstOrNull()?.get(ChapterTable.id)?.value
|
||||
val lastResult = res.lastOrNull()?.get(ChapterTable.id)?.value
|
||||
|
||||
if (after != null) {
|
||||
res.andWhere {
|
||||
when (orderByType) {
|
||||
DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: ID).less(after)
|
||||
null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: ID).greater(after)
|
||||
}
|
||||
}
|
||||
} else if (before != null) {
|
||||
res.andWhere {
|
||||
when (orderByType) {
|
||||
DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: ID).greater(before)
|
||||
null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: ID).less(before)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (first != null) {
|
||||
res.limit(first, offset?.toLong() ?: 0)
|
||||
} else if (last != null) {
|
||||
res.limit(last)
|
||||
}
|
||||
if (first != null) {
|
||||
res.limit(first, offset?.toLong() ?: 0)
|
||||
} else if (last != null) {
|
||||
res.limit(last)
|
||||
}
|
||||
|
||||
QueryResults(total, firstResult, lastResult, res.toList())
|
||||
}
|
||||
QueryResults(total, firstResult, lastResult, res.toList())
|
||||
}
|
||||
|
||||
val getAsCursor: (ChapterType) -> Cursor = (orderBy ?: ChapterOrderBy.ID)::asCursor
|
||||
|
||||
@@ -273,24 +279,25 @@ class ChapterQuery {
|
||||
resultsAsType.firstOrNull()?.let {
|
||||
ChapterNodeList.ChapterEdge(
|
||||
getAsCursor(it),
|
||||
it
|
||||
it,
|
||||
)
|
||||
},
|
||||
resultsAsType.lastOrNull()?.let {
|
||||
ChapterNodeList.ChapterEdge(
|
||||
getAsCursor(it),
|
||||
it
|
||||
it,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
pageInfo = PageInfo(
|
||||
hasNextPage = queryResults.lastKey != resultsAsType.lastOrNull()?.id,
|
||||
hasPreviousPage = queryResults.firstKey != resultsAsType.firstOrNull()?.id,
|
||||
startCursor = resultsAsType.firstOrNull()?.let { getAsCursor(it) },
|
||||
endCursor = resultsAsType.lastOrNull()?.let { getAsCursor(it) }
|
||||
),
|
||||
totalCount = queryResults.total.toInt()
|
||||
pageInfo =
|
||||
PageInfo(
|
||||
hasNextPage = queryResults.lastKey != resultsAsType.lastOrNull()?.id,
|
||||
hasPreviousPage = queryResults.firstKey != resultsAsType.firstOrNull()?.id,
|
||||
startCursor = resultsAsType.firstOrNull()?.let { getAsCursor(it) },
|
||||
endCursor = resultsAsType.lastOrNull()?.let { getAsCursor(it) },
|
||||
),
|
||||
totalCount = queryResults.total.toInt(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import suwayomi.tachidesk.server.JavalinSetup.future
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class DownloadQuery {
|
||||
|
||||
fun downloadStatus(): CompletableFuture<DownloadStatus> {
|
||||
return future {
|
||||
DownloadStatus(DownloadManager.status.first())
|
||||
|
||||
@@ -47,14 +47,18 @@ import suwayomi.tachidesk.manga.model.table.ExtensionTable
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class ExtensionQuery {
|
||||
fun extension(dataFetchingEnvironment: DataFetchingEnvironment, pkgName: String): CompletableFuture<ExtensionType> {
|
||||
fun extension(
|
||||
dataFetchingEnvironment: DataFetchingEnvironment,
|
||||
pkgName: String,
|
||||
): CompletableFuture<ExtensionType> {
|
||||
return dataFetchingEnvironment.getValueFromDataLoader("ExtensionDataLoader", pkgName)
|
||||
}
|
||||
|
||||
enum class ExtensionOrderBy(override val column: Column<out Comparable<*>>) : OrderBy<ExtensionType> {
|
||||
PKG_NAME(ExtensionTable.pkgName),
|
||||
NAME(ExtensionTable.name),
|
||||
APK_NAME(ExtensionTable.apkName);
|
||||
APK_NAME(ExtensionTable.apkName),
|
||||
;
|
||||
|
||||
override fun greater(cursor: Cursor): Op<Boolean> {
|
||||
return when (this) {
|
||||
@@ -73,11 +77,12 @@ class ExtensionQuery {
|
||||
}
|
||||
|
||||
override fun asCursor(type: ExtensionType): Cursor {
|
||||
val value = when (this) {
|
||||
PKG_NAME -> type.pkgName
|
||||
NAME -> type.pkgName + "\\-" + type.name
|
||||
APK_NAME -> type.pkgName + "\\-" + type.apkName
|
||||
}
|
||||
val value =
|
||||
when (this) {
|
||||
PKG_NAME -> type.pkgName
|
||||
NAME -> type.pkgName + "\\-" + type.name
|
||||
APK_NAME -> type.pkgName + "\\-" + type.apkName
|
||||
}
|
||||
return Cursor(value)
|
||||
}
|
||||
}
|
||||
@@ -93,7 +98,7 @@ class ExtensionQuery {
|
||||
val isNsfw: Boolean? = null,
|
||||
val isInstalled: Boolean? = null,
|
||||
val hasUpdate: Boolean? = null,
|
||||
val isObsolete: Boolean? = null
|
||||
val isObsolete: Boolean? = null,
|
||||
) : HasGetOp {
|
||||
override fun getOp(): Op<Boolean>? {
|
||||
val opAnd = OpAnd()
|
||||
@@ -126,7 +131,7 @@ class ExtensionQuery {
|
||||
val isObsolete: BooleanFilter? = null,
|
||||
override val and: List<ExtensionFilter>? = null,
|
||||
override val or: List<ExtensionFilter>? = null,
|
||||
override val not: ExtensionFilter? = null
|
||||
override val not: ExtensionFilter? = null,
|
||||
) : Filter<ExtensionFilter> {
|
||||
override fun getOpList(): List<Op<Boolean>> {
|
||||
return listOfNotNull(
|
||||
@@ -140,7 +145,7 @@ class ExtensionQuery {
|
||||
andFilterWithCompare(ExtensionTable.isNsfw, isNsfw),
|
||||
andFilterWithCompare(ExtensionTable.isInstalled, isInstalled),
|
||||
andFilterWithCompare(ExtensionTable.hasUpdate, hasUpdate),
|
||||
andFilterWithCompare(ExtensionTable.isObsolete, isObsolete)
|
||||
andFilterWithCompare(ExtensionTable.isObsolete, isObsolete),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -154,57 +159,58 @@ class ExtensionQuery {
|
||||
after: Cursor? = null,
|
||||
first: Int? = null,
|
||||
last: Int? = null,
|
||||
offset: Int? = null
|
||||
offset: Int? = null,
|
||||
): ExtensionNodeList {
|
||||
val queryResults = transaction {
|
||||
val res = ExtensionTable.selectAll()
|
||||
val queryResults =
|
||||
transaction {
|
||||
val res = ExtensionTable.selectAll()
|
||||
|
||||
res.adjustWhere { ExtensionTable.name neq LocalSource.EXTENSION_NAME }
|
||||
res.adjustWhere { ExtensionTable.name neq LocalSource.EXTENSION_NAME }
|
||||
|
||||
res.applyOps(condition, filter)
|
||||
res.applyOps(condition, filter)
|
||||
|
||||
if (orderBy != null || (last != null || before != null)) {
|
||||
val orderByColumn = orderBy?.column ?: ExtensionTable.pkgName
|
||||
val orderType = orderByType.maybeSwap(last ?: before)
|
||||
if (orderBy != null || (last != null || before != null)) {
|
||||
val orderByColumn = orderBy?.column ?: ExtensionTable.pkgName
|
||||
val orderType = orderByType.maybeSwap(last ?: before)
|
||||
|
||||
if (orderBy == ExtensionOrderBy.PKG_NAME || orderBy == null) {
|
||||
res.orderBy(orderByColumn to orderType)
|
||||
} else {
|
||||
res.orderBy(
|
||||
orderByColumn to orderType,
|
||||
ExtensionTable.pkgName to SortOrder.ASC
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val total = res.count()
|
||||
val firstResult = res.firstOrNull()?.get(ExtensionTable.pkgName)
|
||||
val lastResult = res.lastOrNull()?.get(ExtensionTable.pkgName)
|
||||
|
||||
if (after != null) {
|
||||
res.andWhere {
|
||||
when (orderByType) {
|
||||
DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: ExtensionOrderBy.PKG_NAME).less(after)
|
||||
null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: ExtensionOrderBy.PKG_NAME).greater(after)
|
||||
if (orderBy == ExtensionOrderBy.PKG_NAME || orderBy == null) {
|
||||
res.orderBy(orderByColumn to orderType)
|
||||
} else {
|
||||
res.orderBy(
|
||||
orderByColumn to orderType,
|
||||
ExtensionTable.pkgName to SortOrder.ASC,
|
||||
)
|
||||
}
|
||||
}
|
||||
} else if (before != null) {
|
||||
res.andWhere {
|
||||
when (orderByType) {
|
||||
DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: ExtensionOrderBy.PKG_NAME).greater(before)
|
||||
null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: ExtensionOrderBy.PKG_NAME).less(before)
|
||||
|
||||
val total = res.count()
|
||||
val firstResult = res.firstOrNull()?.get(ExtensionTable.pkgName)
|
||||
val lastResult = res.lastOrNull()?.get(ExtensionTable.pkgName)
|
||||
|
||||
if (after != null) {
|
||||
res.andWhere {
|
||||
when (orderByType) {
|
||||
DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: ExtensionOrderBy.PKG_NAME).less(after)
|
||||
null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: ExtensionOrderBy.PKG_NAME).greater(after)
|
||||
}
|
||||
}
|
||||
} else if (before != null) {
|
||||
res.andWhere {
|
||||
when (orderByType) {
|
||||
DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: ExtensionOrderBy.PKG_NAME).greater(before)
|
||||
null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: ExtensionOrderBy.PKG_NAME).less(before)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (first != null) {
|
||||
res.limit(first, offset?.toLong() ?: 0)
|
||||
} else if (last != null) {
|
||||
res.limit(last)
|
||||
}
|
||||
if (first != null) {
|
||||
res.limit(first, offset?.toLong() ?: 0)
|
||||
} else if (last != null) {
|
||||
res.limit(last)
|
||||
}
|
||||
|
||||
QueryResults(total, firstResult, lastResult, res.toList())
|
||||
}
|
||||
QueryResults(total, firstResult, lastResult, res.toList())
|
||||
}
|
||||
|
||||
val getAsCursor: (ExtensionType) -> Cursor = (orderBy ?: ExtensionOrderBy.PKG_NAME)::asCursor
|
||||
|
||||
@@ -219,24 +225,25 @@ class ExtensionQuery {
|
||||
resultsAsType.firstOrNull()?.let {
|
||||
ExtensionNodeList.ExtensionEdge(
|
||||
getAsCursor(it),
|
||||
it
|
||||
it,
|
||||
)
|
||||
},
|
||||
resultsAsType.lastOrNull()?.let {
|
||||
ExtensionNodeList.ExtensionEdge(
|
||||
getAsCursor(it),
|
||||
it
|
||||
it,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
pageInfo = PageInfo(
|
||||
hasNextPage = queryResults.lastKey != resultsAsType.lastOrNull()?.pkgName,
|
||||
hasPreviousPage = queryResults.firstKey != resultsAsType.firstOrNull()?.pkgName,
|
||||
startCursor = resultsAsType.firstOrNull()?.let { getAsCursor(it) },
|
||||
endCursor = resultsAsType.lastOrNull()?.let { getAsCursor(it) }
|
||||
),
|
||||
totalCount = queryResults.total.toInt()
|
||||
pageInfo =
|
||||
PageInfo(
|
||||
hasNextPage = queryResults.lastKey != resultsAsType.lastOrNull()?.pkgName,
|
||||
hasPreviousPage = queryResults.firstKey != resultsAsType.firstOrNull()?.pkgName,
|
||||
startCursor = resultsAsType.firstOrNull()?.let { getAsCursor(it) },
|
||||
endCursor = resultsAsType.lastOrNull()?.let { getAsCursor(it) },
|
||||
),
|
||||
totalCount = queryResults.total.toInt(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ package suwayomi.tachidesk.graphql.queries
|
||||
import suwayomi.tachidesk.global.impl.AppUpdate
|
||||
import suwayomi.tachidesk.graphql.types.WebUIUpdateInfo
|
||||
import suwayomi.tachidesk.graphql.types.WebUIUpdateStatus
|
||||
import suwayomi.tachidesk.server.BuildConfig
|
||||
import suwayomi.tachidesk.server.JavalinSetup.future
|
||||
import suwayomi.tachidesk.server.generated.BuildConfig
|
||||
import suwayomi.tachidesk.server.serverConfig
|
||||
import suwayomi.tachidesk.server.util.WebInterfaceManager
|
||||
import java.util.concurrent.CompletableFuture
|
||||
@@ -17,7 +17,7 @@ class InfoQuery {
|
||||
val buildType: String,
|
||||
val buildTime: Long,
|
||||
val github: String,
|
||||
val discord: String
|
||||
val discord: String,
|
||||
)
|
||||
|
||||
fun about(): AboutPayload {
|
||||
@@ -28,7 +28,7 @@ class InfoQuery {
|
||||
BuildConfig.BUILD_TYPE,
|
||||
BuildConfig.BUILD_TIME,
|
||||
BuildConfig.GITHUB,
|
||||
BuildConfig.DISCORD
|
||||
BuildConfig.DISCORD,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ class InfoQuery {
|
||||
/** [channel] mirrors [suwayomi.tachidesk.server.BuildConfig.BUILD_TYPE] */
|
||||
val channel: String,
|
||||
val tag: String,
|
||||
val url: String
|
||||
val url: String,
|
||||
)
|
||||
|
||||
fun checkForServerUpdates(): CompletableFuture<List<CheckForServerUpdatesPayload>> {
|
||||
@@ -45,7 +45,7 @@ class InfoQuery {
|
||||
CheckForServerUpdatesPayload(
|
||||
channel = it.channel,
|
||||
tag = it.tag,
|
||||
url = it.url
|
||||
url = it.url,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -57,7 +57,7 @@ class InfoQuery {
|
||||
WebUIUpdateInfo(
|
||||
channel = serverConfig.webUIChannel.value,
|
||||
tag = version,
|
||||
updateAvailable
|
||||
updateAvailable,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,10 @@ import suwayomi.tachidesk.manga.model.table.MangaTable
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class MangaQuery {
|
||||
fun manga(dataFetchingEnvironment: DataFetchingEnvironment, id: Int): CompletableFuture<MangaType> {
|
||||
fun manga(
|
||||
dataFetchingEnvironment: DataFetchingEnvironment,
|
||||
id: Int,
|
||||
): CompletableFuture<MangaType> {
|
||||
return dataFetchingEnvironment.getValueFromDataLoader("MangaDataLoader", id)
|
||||
}
|
||||
|
||||
@@ -57,7 +60,8 @@ class MangaQuery {
|
||||
ID(MangaTable.id),
|
||||
TITLE(MangaTable.title),
|
||||
IN_LIBRARY_AT(MangaTable.inLibraryAt),
|
||||
LAST_FETCHED_AT(MangaTable.lastFetchedAt);
|
||||
LAST_FETCHED_AT(MangaTable.lastFetchedAt),
|
||||
;
|
||||
|
||||
override fun greater(cursor: Cursor): Op<Boolean> {
|
||||
return when (this) {
|
||||
@@ -78,12 +82,13 @@ class MangaQuery {
|
||||
}
|
||||
|
||||
override fun asCursor(type: MangaType): Cursor {
|
||||
val value = when (this) {
|
||||
ID -> type.id.toString()
|
||||
TITLE -> type.id.toString() + "-" + type.title
|
||||
IN_LIBRARY_AT -> type.id.toString() + "-" + type.inLibraryAt.toString()
|
||||
LAST_FETCHED_AT -> type.id.toString() + "-" + type.lastFetchedAt.toString()
|
||||
}
|
||||
val value =
|
||||
when (this) {
|
||||
ID -> type.id.toString()
|
||||
TITLE -> type.id.toString() + "-" + type.title
|
||||
IN_LIBRARY_AT -> type.id.toString() + "-" + type.inLibraryAt.toString()
|
||||
LAST_FETCHED_AT -> type.id.toString() + "-" + type.lastFetchedAt.toString()
|
||||
}
|
||||
return Cursor(value)
|
||||
}
|
||||
}
|
||||
@@ -104,7 +109,7 @@ class MangaQuery {
|
||||
val inLibraryAt: Long? = null,
|
||||
val realUrl: String? = null,
|
||||
val lastFetchedAt: Long? = null,
|
||||
val chaptersLastFetchedAt: Long? = null
|
||||
val chaptersLastFetchedAt: Long? = null,
|
||||
) : HasGetOp {
|
||||
override fun getOp(): Op<Boolean>? {
|
||||
val opAnd = OpAnd()
|
||||
@@ -140,21 +145,21 @@ class MangaQuery {
|
||||
override val lessThan: MangaStatus? = null,
|
||||
override val lessThanOrEqualTo: MangaStatus? = null,
|
||||
override val greaterThan: MangaStatus? = null,
|
||||
override val greaterThanOrEqualTo: MangaStatus? = null
|
||||
override val greaterThanOrEqualTo: MangaStatus? = null,
|
||||
) : ComparableScalarFilter<MangaStatus> {
|
||||
fun asIntFilter() = IntFilter(
|
||||
equalTo = equalTo?.value,
|
||||
notEqualTo = notEqualTo?.value,
|
||||
distinctFrom = distinctFrom?.value,
|
||||
notDistinctFrom = notDistinctFrom?.value,
|
||||
`in` = `in`?.map { it.value },
|
||||
notIn = notIn?.map { it.value },
|
||||
lessThan = lessThan?.value,
|
||||
lessThanOrEqualTo = lessThanOrEqualTo?.value,
|
||||
greaterThan = greaterThan?.value,
|
||||
greaterThanOrEqualTo = greaterThanOrEqualTo?.value
|
||||
|
||||
)
|
||||
fun asIntFilter() =
|
||||
IntFilter(
|
||||
equalTo = equalTo?.value,
|
||||
notEqualTo = notEqualTo?.value,
|
||||
distinctFrom = distinctFrom?.value,
|
||||
notDistinctFrom = notDistinctFrom?.value,
|
||||
`in` = `in`?.map { it.value },
|
||||
notIn = notIn?.map { it.value },
|
||||
lessThan = lessThan?.value,
|
||||
lessThanOrEqualTo = lessThanOrEqualTo?.value,
|
||||
greaterThan = greaterThan?.value,
|
||||
greaterThanOrEqualTo = greaterThanOrEqualTo?.value,
|
||||
)
|
||||
}
|
||||
|
||||
data class MangaFilter(
|
||||
@@ -176,7 +181,7 @@ class MangaQuery {
|
||||
val chaptersLastFetchedAt: LongFilter? = null,
|
||||
override val and: List<MangaFilter>? = null,
|
||||
override val or: List<MangaFilter>? = null,
|
||||
override val not: MangaFilter? = null
|
||||
override val not: MangaFilter? = null,
|
||||
) : Filter<MangaFilter> {
|
||||
override fun getOpList(): List<Op<Boolean>> {
|
||||
return listOfNotNull(
|
||||
@@ -194,7 +199,7 @@ class MangaQuery {
|
||||
andFilterWithCompare(MangaTable.inLibraryAt, inLibraryAt),
|
||||
andFilterWithCompareString(MangaTable.realUrl, realUrl),
|
||||
andFilterWithCompare(MangaTable.lastFetchedAt, lastFetchedAt),
|
||||
andFilterWithCompare(MangaTable.chaptersLastFetchedAt, chaptersLastFetchedAt)
|
||||
andFilterWithCompare(MangaTable.chaptersLastFetchedAt, chaptersLastFetchedAt),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -208,55 +213,56 @@ class MangaQuery {
|
||||
after: Cursor? = null,
|
||||
first: Int? = null,
|
||||
last: Int? = null,
|
||||
offset: Int? = null
|
||||
offset: Int? = null,
|
||||
): MangaNodeList {
|
||||
val queryResults = transaction {
|
||||
val res = MangaTable.selectAll()
|
||||
val queryResults =
|
||||
transaction {
|
||||
val res = MangaTable.selectAll()
|
||||
|
||||
res.applyOps(condition, filter)
|
||||
res.applyOps(condition, filter)
|
||||
|
||||
if (orderBy != null || (last != null || before != null)) {
|
||||
val orderByColumn = orderBy?.column ?: MangaTable.id
|
||||
val orderType = orderByType.maybeSwap(last ?: before)
|
||||
if (orderBy != null || (last != null || before != null)) {
|
||||
val orderByColumn = orderBy?.column ?: MangaTable.id
|
||||
val orderType = orderByType.maybeSwap(last ?: before)
|
||||
|
||||
if (orderBy == MangaOrderBy.ID || orderBy == null) {
|
||||
res.orderBy(orderByColumn to orderType)
|
||||
} else {
|
||||
res.orderBy(
|
||||
orderByColumn to orderType,
|
||||
MangaTable.id to SortOrder.ASC
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val total = res.count()
|
||||
val firstResult = res.firstOrNull()?.get(MangaTable.id)?.value
|
||||
val lastResult = res.lastOrNull()?.get(MangaTable.id)?.value
|
||||
|
||||
if (after != null) {
|
||||
res.andWhere {
|
||||
when (orderByType) {
|
||||
DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: MangaOrderBy.ID).less(after)
|
||||
null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: MangaOrderBy.ID).greater(after)
|
||||
if (orderBy == MangaOrderBy.ID || orderBy == null) {
|
||||
res.orderBy(orderByColumn to orderType)
|
||||
} else {
|
||||
res.orderBy(
|
||||
orderByColumn to orderType,
|
||||
MangaTable.id to SortOrder.ASC,
|
||||
)
|
||||
}
|
||||
}
|
||||
} else if (before != null) {
|
||||
res.andWhere {
|
||||
when (orderByType) {
|
||||
DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: MangaOrderBy.ID).greater(before)
|
||||
null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: MangaOrderBy.ID).less(before)
|
||||
|
||||
val total = res.count()
|
||||
val firstResult = res.firstOrNull()?.get(MangaTable.id)?.value
|
||||
val lastResult = res.lastOrNull()?.get(MangaTable.id)?.value
|
||||
|
||||
if (after != null) {
|
||||
res.andWhere {
|
||||
when (orderByType) {
|
||||
DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: MangaOrderBy.ID).less(after)
|
||||
null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: MangaOrderBy.ID).greater(after)
|
||||
}
|
||||
}
|
||||
} else if (before != null) {
|
||||
res.andWhere {
|
||||
when (orderByType) {
|
||||
DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: MangaOrderBy.ID).greater(before)
|
||||
null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: MangaOrderBy.ID).less(before)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (first != null) {
|
||||
res.limit(first, offset?.toLong() ?: 0)
|
||||
} else if (last != null) {
|
||||
res.limit(last)
|
||||
}
|
||||
if (first != null) {
|
||||
res.limit(first, offset?.toLong() ?: 0)
|
||||
} else if (last != null) {
|
||||
res.limit(last)
|
||||
}
|
||||
|
||||
QueryResults(total, firstResult, lastResult, res.toList())
|
||||
}
|
||||
QueryResults(total, firstResult, lastResult, res.toList())
|
||||
}
|
||||
|
||||
val getAsCursor: (MangaType) -> Cursor = (orderBy ?: MangaOrderBy.ID)::asCursor
|
||||
|
||||
@@ -271,24 +277,25 @@ class MangaQuery {
|
||||
resultsAsType.firstOrNull()?.let {
|
||||
MangaNodeList.MangaEdge(
|
||||
getAsCursor(it),
|
||||
it
|
||||
it,
|
||||
)
|
||||
},
|
||||
resultsAsType.lastOrNull()?.let {
|
||||
MangaNodeList.MangaEdge(
|
||||
getAsCursor(it),
|
||||
it
|
||||
it,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
pageInfo = PageInfo(
|
||||
hasNextPage = queryResults.lastKey != resultsAsType.lastOrNull()?.id,
|
||||
hasPreviousPage = queryResults.firstKey != resultsAsType.firstOrNull()?.id,
|
||||
startCursor = resultsAsType.firstOrNull()?.let { getAsCursor(it) },
|
||||
endCursor = resultsAsType.lastOrNull()?.let { getAsCursor(it) }
|
||||
),
|
||||
totalCount = queryResults.total.toInt()
|
||||
pageInfo =
|
||||
PageInfo(
|
||||
hasNextPage = queryResults.lastKey != resultsAsType.lastOrNull()?.id,
|
||||
hasPreviousPage = queryResults.firstKey != resultsAsType.firstOrNull()?.id,
|
||||
startCursor = resultsAsType.firstOrNull()?.let { getAsCursor(it) },
|
||||
endCursor = resultsAsType.lastOrNull()?.let { getAsCursor(it) },
|
||||
),
|
||||
totalCount = queryResults.total.toInt(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,13 +42,17 @@ import suwayomi.tachidesk.graphql.types.GlobalMetaType
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class MetaQuery {
|
||||
fun meta(dataFetchingEnvironment: DataFetchingEnvironment, key: String): CompletableFuture<GlobalMetaType> {
|
||||
fun meta(
|
||||
dataFetchingEnvironment: DataFetchingEnvironment,
|
||||
key: String,
|
||||
): CompletableFuture<GlobalMetaType> {
|
||||
return dataFetchingEnvironment.getValueFromDataLoader("GlobalMetaDataLoader", key)
|
||||
}
|
||||
|
||||
enum class MetaOrderBy(override val column: Column<out Comparable<*>>) : OrderBy<GlobalMetaType> {
|
||||
KEY(GlobalMetaTable.key),
|
||||
VALUE(GlobalMetaTable.value);
|
||||
VALUE(GlobalMetaTable.value),
|
||||
;
|
||||
|
||||
override fun greater(cursor: Cursor): Op<Boolean> {
|
||||
return when (this) {
|
||||
@@ -65,17 +69,18 @@ class MetaQuery {
|
||||
}
|
||||
|
||||
override fun asCursor(type: GlobalMetaType): Cursor {
|
||||
val value = when (this) {
|
||||
KEY -> type.key
|
||||
VALUE -> type.key + "\\-" + type.value
|
||||
}
|
||||
val value =
|
||||
when (this) {
|
||||
KEY -> type.key
|
||||
VALUE -> type.key + "\\-" + type.value
|
||||
}
|
||||
return Cursor(value)
|
||||
}
|
||||
}
|
||||
|
||||
data class MetaCondition(
|
||||
val key: String? = null,
|
||||
val value: String? = null
|
||||
val value: String? = null,
|
||||
) : HasGetOp {
|
||||
override fun getOp(): Op<Boolean>? {
|
||||
val opAnd = OpAnd()
|
||||
@@ -91,12 +96,12 @@ class MetaQuery {
|
||||
val value: StringFilter? = null,
|
||||
override val and: List<MetaFilter>? = null,
|
||||
override val or: List<MetaFilter>? = null,
|
||||
override val not: MetaFilter? = null
|
||||
override val not: MetaFilter? = null,
|
||||
) : Filter<MetaFilter> {
|
||||
override fun getOpList(): List<Op<Boolean>> {
|
||||
return listOfNotNull(
|
||||
andFilterWithCompareString(GlobalMetaTable.key, key),
|
||||
andFilterWithCompareString(GlobalMetaTable.value, value)
|
||||
andFilterWithCompareString(GlobalMetaTable.value, value),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -110,55 +115,56 @@ class MetaQuery {
|
||||
after: Cursor? = null,
|
||||
first: Int? = null,
|
||||
last: Int? = null,
|
||||
offset: Int? = null
|
||||
offset: Int? = null,
|
||||
): GlobalMetaNodeList {
|
||||
val queryResults = transaction {
|
||||
val res = GlobalMetaTable.selectAll()
|
||||
val queryResults =
|
||||
transaction {
|
||||
val res = GlobalMetaTable.selectAll()
|
||||
|
||||
res.applyOps(condition, filter)
|
||||
res.applyOps(condition, filter)
|
||||
|
||||
if (orderBy != null || (last != null || before != null)) {
|
||||
val orderByColumn = orderBy?.column ?: GlobalMetaTable.key
|
||||
val orderType = orderByType.maybeSwap(last ?: before)
|
||||
if (orderBy != null || (last != null || before != null)) {
|
||||
val orderByColumn = orderBy?.column ?: GlobalMetaTable.key
|
||||
val orderType = orderByType.maybeSwap(last ?: before)
|
||||
|
||||
if (orderBy == MetaOrderBy.KEY || orderBy == null) {
|
||||
res.orderBy(orderByColumn to orderType)
|
||||
} else {
|
||||
res.orderBy(
|
||||
orderByColumn to orderType,
|
||||
GlobalMetaTable.key to SortOrder.ASC
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val total = res.count()
|
||||
val firstResult = res.firstOrNull()?.get(GlobalMetaTable.key)
|
||||
val lastResult = res.lastOrNull()?.get(GlobalMetaTable.key)
|
||||
|
||||
if (after != null) {
|
||||
res.andWhere {
|
||||
when (orderByType) {
|
||||
DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: MetaOrderBy.KEY).less(after)
|
||||
null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: MetaOrderBy.KEY).greater(after)
|
||||
if (orderBy == MetaOrderBy.KEY || orderBy == null) {
|
||||
res.orderBy(orderByColumn to orderType)
|
||||
} else {
|
||||
res.orderBy(
|
||||
orderByColumn to orderType,
|
||||
GlobalMetaTable.key to SortOrder.ASC,
|
||||
)
|
||||
}
|
||||
}
|
||||
} else if (before != null) {
|
||||
res.andWhere {
|
||||
when (orderByType) {
|
||||
DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: MetaOrderBy.KEY).greater(before)
|
||||
null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: MetaOrderBy.KEY).less(before)
|
||||
|
||||
val total = res.count()
|
||||
val firstResult = res.firstOrNull()?.get(GlobalMetaTable.key)
|
||||
val lastResult = res.lastOrNull()?.get(GlobalMetaTable.key)
|
||||
|
||||
if (after != null) {
|
||||
res.andWhere {
|
||||
when (orderByType) {
|
||||
DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: MetaOrderBy.KEY).less(after)
|
||||
null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: MetaOrderBy.KEY).greater(after)
|
||||
}
|
||||
}
|
||||
} else if (before != null) {
|
||||
res.andWhere {
|
||||
when (orderByType) {
|
||||
DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: MetaOrderBy.KEY).greater(before)
|
||||
null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: MetaOrderBy.KEY).less(before)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (first != null) {
|
||||
res.limit(first, offset?.toLong() ?: 0)
|
||||
} else if (last != null) {
|
||||
res.limit(last)
|
||||
}
|
||||
if (first != null) {
|
||||
res.limit(first, offset?.toLong() ?: 0)
|
||||
} else if (last != null) {
|
||||
res.limit(last)
|
||||
}
|
||||
|
||||
QueryResults(total, firstResult, lastResult, res.toList())
|
||||
}
|
||||
QueryResults(total, firstResult, lastResult, res.toList())
|
||||
}
|
||||
|
||||
val getAsCursor: (GlobalMetaType) -> Cursor = (orderBy ?: MetaOrderBy.KEY)::asCursor
|
||||
|
||||
@@ -173,24 +179,25 @@ class MetaQuery {
|
||||
resultsAsType.firstOrNull()?.let {
|
||||
GlobalMetaNodeList.MetaEdge(
|
||||
getAsCursor(it),
|
||||
it
|
||||
it,
|
||||
)
|
||||
},
|
||||
resultsAsType.lastOrNull()?.let {
|
||||
GlobalMetaNodeList.MetaEdge(
|
||||
getAsCursor(it),
|
||||
it
|
||||
it,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
pageInfo = PageInfo(
|
||||
hasNextPage = queryResults.lastKey != resultsAsType.lastOrNull()?.key,
|
||||
hasPreviousPage = queryResults.firstKey != resultsAsType.firstOrNull()?.key,
|
||||
startCursor = resultsAsType.firstOrNull()?.let { getAsCursor(it) },
|
||||
endCursor = resultsAsType.lastOrNull()?.let { getAsCursor(it) }
|
||||
),
|
||||
totalCount = queryResults.total.toInt()
|
||||
pageInfo =
|
||||
PageInfo(
|
||||
hasNextPage = queryResults.lastKey != resultsAsType.lastOrNull()?.key,
|
||||
hasPreviousPage = queryResults.firstKey != resultsAsType.firstOrNull()?.key,
|
||||
startCursor = resultsAsType.firstOrNull()?.let { getAsCursor(it) },
|
||||
endCursor = resultsAsType.lastOrNull()?.let { getAsCursor(it) },
|
||||
),
|
||||
totalCount = queryResults.total.toInt(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,14 +46,18 @@ import suwayomi.tachidesk.manga.model.table.SourceTable
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class SourceQuery {
|
||||
fun source(dataFetchingEnvironment: DataFetchingEnvironment, id: Long): CompletableFuture<SourceType> {
|
||||
fun source(
|
||||
dataFetchingEnvironment: DataFetchingEnvironment,
|
||||
id: Long,
|
||||
): CompletableFuture<SourceType> {
|
||||
return dataFetchingEnvironment.getValueFromDataLoader("SourceDataLoader", id)
|
||||
}
|
||||
|
||||
enum class SourceOrderBy(override val column: Column<out Comparable<*>>) : OrderBy<SourceType> {
|
||||
ID(SourceTable.id),
|
||||
NAME(SourceTable.name),
|
||||
LANG(SourceTable.lang);
|
||||
LANG(SourceTable.lang),
|
||||
;
|
||||
|
||||
override fun greater(cursor: Cursor): Op<Boolean> {
|
||||
return when (this) {
|
||||
@@ -72,11 +76,12 @@ class SourceQuery {
|
||||
}
|
||||
|
||||
override fun asCursor(type: SourceType): Cursor {
|
||||
val value = when (this) {
|
||||
ID -> type.id.toString()
|
||||
NAME -> type.id.toString() + "-" + type.name
|
||||
LANG -> type.id.toString() + "-" + type.lang
|
||||
}
|
||||
val value =
|
||||
when (this) {
|
||||
ID -> type.id.toString()
|
||||
NAME -> type.id.toString() + "-" + type.name
|
||||
LANG -> type.id.toString() + "-" + type.lang
|
||||
}
|
||||
return Cursor(value)
|
||||
}
|
||||
}
|
||||
@@ -85,7 +90,7 @@ class SourceQuery {
|
||||
val id: Long? = null,
|
||||
val name: String? = null,
|
||||
val lang: String? = null,
|
||||
val isNsfw: Boolean? = null
|
||||
val isNsfw: Boolean? = null,
|
||||
) : HasGetOp {
|
||||
override fun getOp(): Op<Boolean>? {
|
||||
val opAnd = OpAnd()
|
||||
@@ -105,14 +110,14 @@ class SourceQuery {
|
||||
val isNsfw: BooleanFilter? = null,
|
||||
override val and: List<SourceFilter>? = null,
|
||||
override val or: List<SourceFilter>? = null,
|
||||
override val not: SourceFilter? = null
|
||||
override val not: SourceFilter? = null,
|
||||
) : Filter<SourceFilter> {
|
||||
override fun getOpList(): List<Op<Boolean>> {
|
||||
return listOfNotNull(
|
||||
andFilterWithCompareEntity(SourceTable.id, id),
|
||||
andFilterWithCompareString(SourceTable.name, name),
|
||||
andFilterWithCompareString(SourceTable.lang, lang),
|
||||
andFilterWithCompare(SourceTable.isNsfw, isNsfw)
|
||||
andFilterWithCompare(SourceTable.isNsfw, isNsfw),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -126,57 +131,58 @@ class SourceQuery {
|
||||
after: Cursor? = null,
|
||||
first: Int? = null,
|
||||
last: Int? = null,
|
||||
offset: Int? = null
|
||||
offset: Int? = null,
|
||||
): SourceNodeList {
|
||||
val (queryResults, resultsAsType) = transaction {
|
||||
val res = SourceTable.selectAll()
|
||||
val (queryResults, resultsAsType) =
|
||||
transaction {
|
||||
val res = SourceTable.selectAll()
|
||||
|
||||
res.applyOps(condition, filter)
|
||||
res.applyOps(condition, filter)
|
||||
|
||||
if (orderBy != null || (last != null || before != null)) {
|
||||
val orderByColumn = orderBy?.column ?: SourceTable.id
|
||||
val orderType = orderByType.maybeSwap(last ?: before)
|
||||
if (orderBy != null || (last != null || before != null)) {
|
||||
val orderByColumn = orderBy?.column ?: SourceTable.id
|
||||
val orderType = orderByType.maybeSwap(last ?: before)
|
||||
|
||||
if (orderBy == SourceOrderBy.ID || orderBy == null) {
|
||||
res.orderBy(orderByColumn to orderType)
|
||||
} else {
|
||||
res.orderBy(
|
||||
orderByColumn to orderType,
|
||||
SourceTable.id to SortOrder.ASC
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val total = res.count()
|
||||
val firstResult = res.firstOrNull()?.get(SourceTable.id)?.value
|
||||
val lastResult = res.lastOrNull()?.get(SourceTable.id)?.value
|
||||
|
||||
if (after != null) {
|
||||
res.andWhere {
|
||||
when (orderByType) {
|
||||
DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: SourceOrderBy.ID).less(after)
|
||||
null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: SourceOrderBy.ID).greater(after)
|
||||
if (orderBy == SourceOrderBy.ID || orderBy == null) {
|
||||
res.orderBy(orderByColumn to orderType)
|
||||
} else {
|
||||
res.orderBy(
|
||||
orderByColumn to orderType,
|
||||
SourceTable.id to SortOrder.ASC,
|
||||
)
|
||||
}
|
||||
}
|
||||
} else if (before != null) {
|
||||
res.andWhere {
|
||||
when (orderByType) {
|
||||
DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: SourceOrderBy.ID).greater(before)
|
||||
null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: SourceOrderBy.ID).less(before)
|
||||
|
||||
val total = res.count()
|
||||
val firstResult = res.firstOrNull()?.get(SourceTable.id)?.value
|
||||
val lastResult = res.lastOrNull()?.get(SourceTable.id)?.value
|
||||
|
||||
if (after != null) {
|
||||
res.andWhere {
|
||||
when (orderByType) {
|
||||
DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: SourceOrderBy.ID).less(after)
|
||||
null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: SourceOrderBy.ID).greater(after)
|
||||
}
|
||||
}
|
||||
} else if (before != null) {
|
||||
res.andWhere {
|
||||
when (orderByType) {
|
||||
DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: SourceOrderBy.ID).greater(before)
|
||||
null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: SourceOrderBy.ID).less(before)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (first != null) {
|
||||
res.limit(first, offset?.toLong() ?: 0)
|
||||
} else if (last != null) {
|
||||
res.limit(last)
|
||||
}
|
||||
if (first != null) {
|
||||
res.limit(first, offset?.toLong() ?: 0)
|
||||
} else if (last != null) {
|
||||
res.limit(last)
|
||||
}
|
||||
|
||||
QueryResults(total, firstResult, lastResult, res.toList()).let {
|
||||
it to it.results.mapNotNull { SourceType(it) }
|
||||
QueryResults(total, firstResult, lastResult, res.toList()).let {
|
||||
it to it.results.mapNotNull { SourceType(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val getAsCursor: (SourceType) -> Cursor = (orderBy ?: SourceOrderBy.ID)::asCursor
|
||||
|
||||
@@ -189,24 +195,25 @@ class SourceQuery {
|
||||
resultsAsType.firstOrNull()?.let {
|
||||
SourceNodeList.SourceEdge(
|
||||
getAsCursor(it),
|
||||
it
|
||||
it,
|
||||
)
|
||||
},
|
||||
resultsAsType.lastOrNull()?.let {
|
||||
SourceNodeList.SourceEdge(
|
||||
getAsCursor(it),
|
||||
it
|
||||
it,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
pageInfo = PageInfo(
|
||||
hasNextPage = queryResults.lastKey != resultsAsType.lastOrNull()?.id,
|
||||
hasPreviousPage = queryResults.firstKey != resultsAsType.firstOrNull()?.id,
|
||||
startCursor = resultsAsType.firstOrNull()?.let { getAsCursor(it) },
|
||||
endCursor = resultsAsType.lastOrNull()?.let { getAsCursor(it) }
|
||||
),
|
||||
totalCount = queryResults.total.toInt()
|
||||
pageInfo =
|
||||
PageInfo(
|
||||
hasNextPage = queryResults.lastKey != resultsAsType.lastOrNull()?.id,
|
||||
hasPreviousPage = queryResults.firstKey != resultsAsType.firstOrNull()?.id,
|
||||
startCursor = resultsAsType.firstOrNull()?.let { getAsCursor(it) },
|
||||
endCursor = resultsAsType.lastOrNull()?.let { getAsCursor(it) },
|
||||
),
|
||||
totalCount = queryResults.total.toInt(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,11 @@ import org.jetbrains.exposed.sql.or
|
||||
import org.jetbrains.exposed.sql.stringParam
|
||||
import org.jetbrains.exposed.sql.upperCase
|
||||
|
||||
class ILikeEscapeOp(expr1: Expression<*>, expr2: Expression<*>, like: Boolean, val escapeChar: Char?) : ComparisonOp(expr1, expr2, if (like) "ILIKE" else "NOT ILIKE") {
|
||||
class ILikeEscapeOp(expr1: Expression<*>, expr2: Expression<*>, like: Boolean, val escapeChar: Char?) : ComparisonOp(
|
||||
expr1,
|
||||
expr2,
|
||||
if (like) "ILIKE" else "NOT ILIKE",
|
||||
) {
|
||||
override fun toQueryBuilder(queryBuilder: QueryBuilder) {
|
||||
super.toQueryBuilder(queryBuilder)
|
||||
if (escapeChar != null) {
|
||||
@@ -29,43 +33,93 @@ class ILikeEscapeOp(expr1: Expression<*>, expr2: Expression<*>, like: Boolean, v
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun <T : String?> iLike(expression: Expression<T>, pattern: String): ILikeEscapeOp = iLike(expression, LikePattern(pattern))
|
||||
fun <T : String?> iNotLike(expression: Expression<T>, pattern: String): ILikeEscapeOp = iNotLike(expression, LikePattern(pattern))
|
||||
fun <T : String?> iLike(expression: Expression<T>, pattern: LikePattern): ILikeEscapeOp = ILikeEscapeOp(expression, stringParam(pattern.pattern), true, pattern.escapeChar)
|
||||
fun <T : String?> iNotLike(expression: Expression<T>, pattern: LikePattern): ILikeEscapeOp = ILikeEscapeOp(expression, stringParam(pattern.pattern), false, pattern.escapeChar)
|
||||
fun <T : String?> iLike(
|
||||
expression: Expression<T>,
|
||||
pattern: String,
|
||||
): ILikeEscapeOp = iLike(expression, LikePattern(pattern))
|
||||
|
||||
fun <T : String?> iNotLike(
|
||||
expression: Expression<T>,
|
||||
pattern: String,
|
||||
): ILikeEscapeOp = iNotLike(expression, LikePattern(pattern))
|
||||
|
||||
fun <T : String?> iLike(
|
||||
expression: Expression<T>,
|
||||
pattern: LikePattern,
|
||||
): ILikeEscapeOp =
|
||||
ILikeEscapeOp(
|
||||
expression,
|
||||
stringParam(pattern.pattern),
|
||||
true,
|
||||
pattern.escapeChar,
|
||||
)
|
||||
|
||||
fun <T : String?> iNotLike(
|
||||
expression: Expression<T>,
|
||||
pattern: LikePattern,
|
||||
): ILikeEscapeOp =
|
||||
ILikeEscapeOp(
|
||||
expression,
|
||||
stringParam(pattern.pattern),
|
||||
false,
|
||||
pattern.escapeChar,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class DistinctFromOp(expr1: Expression<*>, expr2: Expression<*>, not: Boolean) : ComparisonOp(expr1, expr2, if (not) "IS NOT DISTINCT FROM" else "IS DISTINCT FROM") {
|
||||
class DistinctFromOp(expr1: Expression<*>, expr2: Expression<*>, not: Boolean) : ComparisonOp(
|
||||
expr1,
|
||||
expr2,
|
||||
if (not) "IS NOT DISTINCT FROM" else "IS DISTINCT FROM",
|
||||
) {
|
||||
companion object {
|
||||
fun <T> distinctFrom(expression: ExpressionWithColumnType<T>, t: T): DistinctFromOp = DistinctFromOp(
|
||||
expression,
|
||||
with(SqlExpressionBuilder) {
|
||||
expression.wrap(t)
|
||||
},
|
||||
false
|
||||
)
|
||||
fun <T> notDistinctFrom(expression: ExpressionWithColumnType<T>, t: T): DistinctFromOp = DistinctFromOp(
|
||||
expression,
|
||||
with(SqlExpressionBuilder) {
|
||||
expression.wrap(t)
|
||||
},
|
||||
true
|
||||
)
|
||||
fun <T : Comparable<T>> distinctFrom(expression: ExpressionWithColumnType<EntityID<T>>, t: T): DistinctFromOp = DistinctFromOp(
|
||||
expression,
|
||||
with(SqlExpressionBuilder) {
|
||||
expression.wrap(t)
|
||||
},
|
||||
false
|
||||
)
|
||||
fun <T : Comparable<T>> notDistinctFrom(expression: ExpressionWithColumnType<EntityID<T>>, t: T): DistinctFromOp = DistinctFromOp(
|
||||
expression,
|
||||
with(SqlExpressionBuilder) {
|
||||
expression.wrap(t)
|
||||
},
|
||||
true
|
||||
)
|
||||
fun <T> distinctFrom(
|
||||
expression: ExpressionWithColumnType<T>,
|
||||
t: T,
|
||||
): DistinctFromOp =
|
||||
DistinctFromOp(
|
||||
expression,
|
||||
with(SqlExpressionBuilder) {
|
||||
expression.wrap(t)
|
||||
},
|
||||
false,
|
||||
)
|
||||
|
||||
fun <T> notDistinctFrom(
|
||||
expression: ExpressionWithColumnType<T>,
|
||||
t: T,
|
||||
): DistinctFromOp =
|
||||
DistinctFromOp(
|
||||
expression,
|
||||
with(SqlExpressionBuilder) {
|
||||
expression.wrap(t)
|
||||
},
|
||||
true,
|
||||
)
|
||||
|
||||
fun <T : Comparable<T>> distinctFrom(
|
||||
expression: ExpressionWithColumnType<EntityID<T>>,
|
||||
t: T,
|
||||
): DistinctFromOp =
|
||||
DistinctFromOp(
|
||||
expression,
|
||||
with(SqlExpressionBuilder) {
|
||||
expression.wrap(t)
|
||||
},
|
||||
false,
|
||||
)
|
||||
|
||||
fun <T : Comparable<T>> notDistinctFrom(
|
||||
expression: ExpressionWithColumnType<EntityID<T>>,
|
||||
t: T,
|
||||
): DistinctFromOp =
|
||||
DistinctFromOp(
|
||||
expression,
|
||||
with(SqlExpressionBuilder) {
|
||||
expression.wrap(t)
|
||||
},
|
||||
true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,9 +142,10 @@ interface Filter<T : Filter<T>> : HasGetOp {
|
||||
|
||||
override fun getOp(): Op<Boolean>? {
|
||||
var op: Op<Boolean>? = null
|
||||
|
||||
fun newOp(
|
||||
otherOp: Op<Boolean>?,
|
||||
operator: (Op<Boolean>, Op<Boolean>) -> Op<Boolean>
|
||||
operator: (Op<Boolean>, Op<Boolean>) -> Op<Boolean>,
|
||||
) {
|
||||
when {
|
||||
op == null && otherOp == null -> Unit
|
||||
@@ -99,9 +154,11 @@ interface Filter<T : Filter<T>> : HasGetOp {
|
||||
op != null && otherOp != null -> op = operator(op!!, otherOp)
|
||||
}
|
||||
}
|
||||
|
||||
fun andOp(andOp: Op<Boolean>?) {
|
||||
newOp(andOp, Op<Boolean>::and)
|
||||
}
|
||||
|
||||
fun orOp(orOp: Op<Boolean>?) {
|
||||
newOp(orOp, Op<Boolean>::or)
|
||||
}
|
||||
@@ -127,6 +184,8 @@ interface ScalarFilter<T> {
|
||||
val notEqualTo: T?
|
||||
val distinctFrom: T?
|
||||
val notDistinctFrom: T?
|
||||
|
||||
@Suppress("ktlint:standard:property-naming")
|
||||
val `in`: List<T>?
|
||||
val notIn: List<T>?
|
||||
}
|
||||
@@ -155,7 +214,7 @@ data class LongFilter(
|
||||
override val lessThan: Long? = null,
|
||||
override val lessThanOrEqualTo: Long? = null,
|
||||
override val greaterThan: Long? = null,
|
||||
override val greaterThanOrEqualTo: Long? = null
|
||||
override val greaterThanOrEqualTo: Long? = null,
|
||||
) : ComparableScalarFilter<Long>
|
||||
|
||||
data class BooleanFilter(
|
||||
@@ -169,7 +228,7 @@ data class BooleanFilter(
|
||||
override val lessThan: Boolean? = null,
|
||||
override val lessThanOrEqualTo: Boolean? = null,
|
||||
override val greaterThan: Boolean? = null,
|
||||
override val greaterThanOrEqualTo: Boolean? = null
|
||||
override val greaterThanOrEqualTo: Boolean? = null,
|
||||
) : ComparableScalarFilter<Boolean>
|
||||
|
||||
data class IntFilter(
|
||||
@@ -183,7 +242,7 @@ data class IntFilter(
|
||||
override val lessThan: Int? = null,
|
||||
override val lessThanOrEqualTo: Int? = null,
|
||||
override val greaterThan: Int? = null,
|
||||
override val greaterThanOrEqualTo: Int? = null
|
||||
override val greaterThanOrEqualTo: Int? = null,
|
||||
) : ComparableScalarFilter<Int>
|
||||
|
||||
data class FloatFilter(
|
||||
@@ -197,7 +256,7 @@ data class FloatFilter(
|
||||
override val lessThan: Float? = null,
|
||||
override val lessThanOrEqualTo: Float? = null,
|
||||
override val greaterThan: Float? = null,
|
||||
override val greaterThanOrEqualTo: Float? = null
|
||||
override val greaterThanOrEqualTo: Float? = null,
|
||||
) : ComparableScalarFilter<Float>
|
||||
|
||||
data class StringFilter(
|
||||
@@ -235,7 +294,7 @@ data class StringFilter(
|
||||
val lessThanInsensitive: String? = null,
|
||||
val lessThanOrEqualToInsensitive: String? = null,
|
||||
val greaterThanInsensitive: String? = null,
|
||||
val greaterThanOrEqualToInsensitive: String? = null
|
||||
val greaterThanOrEqualToInsensitive: String? = null,
|
||||
) : ComparableScalarFilter<String>
|
||||
|
||||
data class StringListFilter(
|
||||
@@ -251,13 +310,13 @@ data class StringListFilter(
|
||||
override val hasNone: List<String>? = null,
|
||||
val hasAnyInsensitive: List<String>? = null,
|
||||
val hasAllInsensitive: List<String>? = null,
|
||||
val hasNoneInsensitive: List<String>? = null
|
||||
val hasNoneInsensitive: List<String>? = null,
|
||||
) : ListScalarFilter<String, List<String>>
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T : String, S : T?> andFilterWithCompareString(
|
||||
column: Column<S>,
|
||||
filter: StringFilter?
|
||||
filter: StringFilter?,
|
||||
): Op<Boolean>? {
|
||||
filter ?: return null
|
||||
val opAnd = OpAnd()
|
||||
@@ -314,19 +373,29 @@ fun <T : String, S : T?> andFilterWithCompareString(
|
||||
}
|
||||
|
||||
class OpAnd(var op: Op<Boolean>? = null) {
|
||||
fun <T> andWhere(value: T?, andPart: SqlExpressionBuilder.(T & Any) -> Op<Boolean>) {
|
||||
fun <T> andWhere(
|
||||
value: T?,
|
||||
andPart: SqlExpressionBuilder.(T & Any) -> Op<Boolean>,
|
||||
) {
|
||||
value ?: return
|
||||
val expr = Op.build { andPart(value) }
|
||||
op = if (op == null) expr else (op!! and expr)
|
||||
}
|
||||
|
||||
fun <T> eq(value: T?, column: Column<T>) = andWhere(value) { column eq it }
|
||||
fun <T : Comparable<T>> eq(value: T?, column: Column<EntityID<T>>) = andWhere(value) { column eq it }
|
||||
fun <T> eq(
|
||||
value: T?,
|
||||
column: Column<T>,
|
||||
) = andWhere(value) { column eq it }
|
||||
|
||||
fun <T : Comparable<T>> eq(
|
||||
value: T?,
|
||||
column: Column<EntityID<T>>,
|
||||
) = andWhere(value) { column eq it }
|
||||
}
|
||||
|
||||
fun <T : Comparable<T>> andFilterWithCompare(
|
||||
column: Column<T>,
|
||||
filter: ComparableScalarFilter<T>?
|
||||
filter: ComparableScalarFilter<T>?,
|
||||
): Op<Boolean>? {
|
||||
filter ?: return null
|
||||
val opAnd = OpAnd(andFilter(column, filter))
|
||||
@@ -341,7 +410,7 @@ fun <T : Comparable<T>> andFilterWithCompare(
|
||||
|
||||
fun <T : Comparable<T>> andFilterWithCompareEntity(
|
||||
column: Column<EntityID<T>>,
|
||||
filter: ComparableScalarFilter<T>?
|
||||
filter: ComparableScalarFilter<T>?,
|
||||
): Op<Boolean>? {
|
||||
filter ?: return null
|
||||
val opAnd = OpAnd(andFilterEntity(column, filter))
|
||||
@@ -356,7 +425,7 @@ fun <T : Comparable<T>> andFilterWithCompareEntity(
|
||||
|
||||
fun <T : Comparable<T>> andFilter(
|
||||
column: Column<T>,
|
||||
filter: ScalarFilter<T>?
|
||||
filter: ScalarFilter<T>?,
|
||||
): Op<Boolean>? {
|
||||
filter ?: return null
|
||||
val opAnd = OpAnd()
|
||||
@@ -377,7 +446,7 @@ fun <T : Comparable<T>> andFilter(
|
||||
|
||||
fun <T : Comparable<T>> andFilterEntity(
|
||||
column: Column<EntityID<T>>,
|
||||
filter: ScalarFilter<T>?
|
||||
filter: ScalarFilter<T>?,
|
||||
): Op<Boolean>? {
|
||||
filter ?: return null
|
||||
val opAnd = OpAnd()
|
||||
|
||||
@@ -17,36 +17,39 @@ import io.javalin.plugin.json.jsonMapper
|
||||
import java.io.IOException
|
||||
|
||||
class JavalinGraphQLRequestParser : GraphQLRequestParser<Context> {
|
||||
|
||||
@Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE", "UNCHECKED_CAST")
|
||||
override suspend fun parseRequest(context: Context): GraphQLServerRequest? {
|
||||
return try {
|
||||
val formParam = context.formParam("operations")
|
||||
?: return context.bodyAsClass(GraphQLServerRequest::class.java)
|
||||
val formParam =
|
||||
context.formParam("operations")
|
||||
?: return context.bodyAsClass(GraphQLServerRequest::class.java)
|
||||
|
||||
val request = context.jsonMapper().fromJsonString(
|
||||
formParam,
|
||||
GraphQLServerRequest::class.java
|
||||
)
|
||||
val map = context.formParam("map")?.let {
|
||||
val request =
|
||||
context.jsonMapper().fromJsonString(
|
||||
it,
|
||||
Map::class.java as Class<Map<String, List<String>>>
|
||||
formParam,
|
||||
GraphQLServerRequest::class.java,
|
||||
)
|
||||
}.orEmpty()
|
||||
|
||||
val mapItems = map.flatMap { (key, variables) ->
|
||||
val file = context.uploadedFile(key)
|
||||
variables.map { fullVariable ->
|
||||
val variable = fullVariable.removePrefix("variables.").substringBefore('.')
|
||||
val listIndex = fullVariable.substringAfterLast('.').toIntOrNull()
|
||||
MapItem(
|
||||
variable,
|
||||
listIndex,
|
||||
file
|
||||
val map =
|
||||
context.formParam("map")?.let {
|
||||
context.jsonMapper().fromJsonString(
|
||||
it,
|
||||
Map::class.java as Class<Map<String, List<String>>>,
|
||||
)
|
||||
}
|
||||
}.groupBy { it.variable }
|
||||
}.orEmpty()
|
||||
|
||||
val mapItems =
|
||||
map.flatMap { (key, variables) ->
|
||||
val file = context.uploadedFile(key)
|
||||
variables.map { fullVariable ->
|
||||
val variable = fullVariable.removePrefix("variables.").substringBefore('.')
|
||||
val listIndex = fullVariable.substringAfterLast('.').toIntOrNull()
|
||||
MapItem(
|
||||
variable,
|
||||
listIndex,
|
||||
file,
|
||||
)
|
||||
}
|
||||
}.groupBy { it.variable }
|
||||
|
||||
when (request) {
|
||||
is GraphQLRequest -> {
|
||||
@@ -54,11 +57,12 @@ class JavalinGraphQLRequestParser : GraphQLRequestParser<Context> {
|
||||
}
|
||||
is GraphQLBatchRequest -> {
|
||||
request.copy(
|
||||
requests = request.requests.map {
|
||||
it.copy(
|
||||
variables = it.variables?.modifyFiles(mapItems)
|
||||
)
|
||||
}
|
||||
requests =
|
||||
request.requests.map {
|
||||
it.copy(
|
||||
variables = it.variables?.modifyFiles(mapItems),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -70,7 +74,7 @@ class JavalinGraphQLRequestParser : GraphQLRequestParser<Context> {
|
||||
data class MapItem(
|
||||
val variable: String,
|
||||
val listIndex: Int?,
|
||||
val file: UploadedFile?
|
||||
val file: UploadedFile?,
|
||||
)
|
||||
|
||||
/**
|
||||
|
||||
@@ -52,7 +52,7 @@ class TachideskDataLoaderRegistryFactory {
|
||||
SourceDataLoader(),
|
||||
SourcesForExtensionDataLoader(),
|
||||
ExtensionDataLoader(),
|
||||
ExtensionForSourceDataLoader()
|
||||
ExtensionForSourceDataLoader(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,5 +37,4 @@ class TachideskGraphQLContextFactory : GraphQLContextFactory<GraphQLContext, Con
|
||||
* Create a [GraphQLContext] from [this] map
|
||||
* @return a new [GraphQLContext]
|
||||
*/
|
||||
fun Map<*, Any?>.toGraphQLContext(): graphql.GraphQLContext =
|
||||
graphql.GraphQLContext.of(this)
|
||||
fun Map<*, Any?>.toGraphQLContext(): graphql.GraphQLContext = graphql.GraphQLContext.of(this)
|
||||
|
||||
@@ -46,49 +46,55 @@ import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KType
|
||||
|
||||
class CustomSchemaGeneratorHooks : FlowSubscriptionSchemaGeneratorHooks() {
|
||||
override fun willGenerateGraphQLType(type: KType): GraphQLType? = when (type.classifier as? KClass<*>) {
|
||||
Long::class -> GraphQLLongAsString // encode to string for JS
|
||||
Cursor::class -> GraphQLCursor
|
||||
UploadedFile::class -> GraphQLUpload
|
||||
else -> super.willGenerateGraphQLType(type)
|
||||
}
|
||||
override fun willGenerateGraphQLType(type: KType): GraphQLType? =
|
||||
when (type.classifier as? KClass<*>) {
|
||||
Long::class -> GraphQLLongAsString // encode to string for JS
|
||||
Cursor::class -> GraphQLCursor
|
||||
UploadedFile::class -> GraphQLUpload
|
||||
else -> super.willGenerateGraphQLType(type)
|
||||
}
|
||||
}
|
||||
|
||||
val schema = toSchema(
|
||||
config = SchemaGeneratorConfig(
|
||||
supportedPackages = listOf("suwayomi.tachidesk.graphql"),
|
||||
introspectionEnabled = true,
|
||||
hooks = CustomSchemaGeneratorHooks()
|
||||
),
|
||||
queries = listOf(
|
||||
TopLevelObject(BackupQuery()),
|
||||
TopLevelObject(CategoryQuery()),
|
||||
TopLevelObject(ChapterQuery()),
|
||||
TopLevelObject(DownloadQuery()),
|
||||
TopLevelObject(ExtensionQuery()),
|
||||
TopLevelObject(InfoQuery()),
|
||||
TopLevelObject(MangaQuery()),
|
||||
TopLevelObject(MetaQuery()),
|
||||
TopLevelObject(SettingsQuery()),
|
||||
TopLevelObject(SourceQuery()),
|
||||
TopLevelObject(UpdateQuery())
|
||||
),
|
||||
mutations = listOf(
|
||||
TopLevelObject(BackupMutation()),
|
||||
TopLevelObject(CategoryMutation()),
|
||||
TopLevelObject(ChapterMutation()),
|
||||
TopLevelObject(DownloadMutation()),
|
||||
TopLevelObject(ExtensionMutation()),
|
||||
TopLevelObject(InfoMutation()),
|
||||
TopLevelObject(MangaMutation()),
|
||||
TopLevelObject(MetaMutation()),
|
||||
TopLevelObject(SettingsMutation()),
|
||||
TopLevelObject(SourceMutation()),
|
||||
TopLevelObject(UpdateMutation())
|
||||
),
|
||||
subscriptions = listOf(
|
||||
TopLevelObject(DownloadSubscription()),
|
||||
TopLevelObject(InfoSubscription()),
|
||||
TopLevelObject(UpdateSubscription())
|
||||
val schema =
|
||||
toSchema(
|
||||
config =
|
||||
SchemaGeneratorConfig(
|
||||
supportedPackages = listOf("suwayomi.tachidesk.graphql"),
|
||||
introspectionEnabled = true,
|
||||
hooks = CustomSchemaGeneratorHooks(),
|
||||
),
|
||||
queries =
|
||||
listOf(
|
||||
TopLevelObject(BackupQuery()),
|
||||
TopLevelObject(CategoryQuery()),
|
||||
TopLevelObject(ChapterQuery()),
|
||||
TopLevelObject(DownloadQuery()),
|
||||
TopLevelObject(ExtensionQuery()),
|
||||
TopLevelObject(InfoQuery()),
|
||||
TopLevelObject(MangaQuery()),
|
||||
TopLevelObject(MetaQuery()),
|
||||
TopLevelObject(SettingsQuery()),
|
||||
TopLevelObject(SourceQuery()),
|
||||
TopLevelObject(UpdateQuery()),
|
||||
),
|
||||
mutations =
|
||||
listOf(
|
||||
TopLevelObject(BackupMutation()),
|
||||
TopLevelObject(CategoryMutation()),
|
||||
TopLevelObject(ChapterMutation()),
|
||||
TopLevelObject(DownloadMutation()),
|
||||
TopLevelObject(ExtensionMutation()),
|
||||
TopLevelObject(InfoMutation()),
|
||||
TopLevelObject(MangaMutation()),
|
||||
TopLevelObject(MetaMutation()),
|
||||
TopLevelObject(SettingsMutation()),
|
||||
TopLevelObject(SourceMutation()),
|
||||
TopLevelObject(UpdateMutation()),
|
||||
),
|
||||
subscriptions =
|
||||
listOf(
|
||||
TopLevelObject(DownloadSubscription()),
|
||||
TopLevelObject(InfoSubscription()),
|
||||
TopLevelObject(UpdateSubscription()),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -25,7 +25,7 @@ class TachideskGraphQLServer(
|
||||
requestParser: JavalinGraphQLRequestParser,
|
||||
contextFactory: TachideskGraphQLContextFactory,
|
||||
requestHandler: GraphQLRequestHandler,
|
||||
subscriptionHandler: GraphQLSubscriptionHandler
|
||||
subscriptionHandler: GraphQLSubscriptionHandler,
|
||||
) : GraphQLServer<Context>(requestParser, contextFactory, requestHandler) {
|
||||
private val objectMapper = jacksonObjectMapper()
|
||||
private val subscriptionProtocolHandler = ApolloSubscriptionProtocolHandler(contextFactory, subscriptionHandler, objectMapper)
|
||||
@@ -42,9 +42,10 @@ class TachideskGraphQLServer(
|
||||
}
|
||||
|
||||
companion object {
|
||||
private fun getGraphQLObject(): GraphQL = GraphQL.newGraphQL(schema)
|
||||
.subscriptionExecutionStrategy(FlowSubscriptionExecutionStrategy())
|
||||
.build()
|
||||
private fun getGraphQLObject(): GraphQL =
|
||||
GraphQL.newGraphQL(schema)
|
||||
.subscriptionExecutionStrategy(FlowSubscriptionExecutionStrategy())
|
||||
.build()
|
||||
|
||||
fun create(): TachideskGraphQLServer {
|
||||
val graphQL = getGraphQLObject()
|
||||
|
||||
@@ -22,12 +22,15 @@ object TemporaryFileStorage {
|
||||
Runtime.getRuntime().addShutdownHook(
|
||||
thread(start = false) {
|
||||
folder.deleteRecursively()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
fun saveFile(name: String, content: InputStream) {
|
||||
fun saveFile(
|
||||
name: String,
|
||||
content: InputStream,
|
||||
) {
|
||||
val file = folder.resolve(name)
|
||||
content.use { inStream ->
|
||||
file.outputStream().use {
|
||||
|
||||
@@ -14,36 +14,45 @@ import java.util.Locale
|
||||
|
||||
data class Cursor(val value: String)
|
||||
|
||||
val GraphQLCursor: GraphQLScalarType = GraphQLScalarType.newScalar()
|
||||
.name("Cursor").description("A location in a connection that can be used for resuming pagination.").coercing(GraphqlCursorCoercing()).build()
|
||||
val GraphQLCursor: GraphQLScalarType =
|
||||
GraphQLScalarType.newScalar()
|
||||
.name(
|
||||
"Cursor",
|
||||
).description("A location in a connection that can be used for resuming pagination.").coercing(GraphqlCursorCoercing()).build()
|
||||
|
||||
private class GraphqlCursorCoercing : Coercing<Cursor, String> {
|
||||
private fun toStringImpl(input: Any): String? {
|
||||
return (input as? Cursor)?.value
|
||||
}
|
||||
|
||||
private fun parseValueImpl(input: Any, locale: Locale): Cursor {
|
||||
private fun parseValueImpl(
|
||||
input: Any,
|
||||
locale: Locale,
|
||||
): Cursor {
|
||||
if (input !is String) {
|
||||
throw CoercingParseValueException(
|
||||
CoercingUtil.i18nMsg(
|
||||
locale,
|
||||
"String.unexpectedRawValueType",
|
||||
CoercingUtil.typeName(input)
|
||||
)
|
||||
CoercingUtil.typeName(input),
|
||||
),
|
||||
)
|
||||
}
|
||||
return Cursor(input)
|
||||
}
|
||||
|
||||
private fun parseLiteralImpl(input: Any, locale: Locale): Cursor {
|
||||
private fun parseLiteralImpl(
|
||||
input: Any,
|
||||
locale: Locale,
|
||||
): Cursor {
|
||||
if (input !is StringValue) {
|
||||
throw CoercingParseLiteralException(
|
||||
CoercingUtil.i18nMsg(
|
||||
locale,
|
||||
"Scalar.unexpectedAstType",
|
||||
"StringValue",
|
||||
CoercingUtil.typeName(input)
|
||||
)
|
||||
CoercingUtil.typeName(input),
|
||||
),
|
||||
)
|
||||
}
|
||||
return Cursor(input.value)
|
||||
@@ -59,8 +68,8 @@ private class GraphqlCursorCoercing : Coercing<Cursor, String> {
|
||||
CoercingUtil.i18nMsg(
|
||||
Locale.getDefault(),
|
||||
"String.unexpectedRawValueType",
|
||||
CoercingUtil.typeName(dataFetcherResult)
|
||||
)
|
||||
CoercingUtil.typeName(dataFetcherResult),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -68,14 +77,14 @@ private class GraphqlCursorCoercing : Coercing<Cursor, String> {
|
||||
override fun serialize(
|
||||
dataFetcherResult: Any,
|
||||
graphQLContext: GraphQLContext,
|
||||
locale: Locale
|
||||
locale: Locale,
|
||||
): String {
|
||||
return toStringImpl(dataFetcherResult) ?: throw CoercingSerializeException(
|
||||
CoercingUtil.i18nMsg(
|
||||
locale,
|
||||
"String.unexpectedRawValueType",
|
||||
CoercingUtil.typeName(dataFetcherResult)
|
||||
)
|
||||
CoercingUtil.typeName(dataFetcherResult),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -85,7 +94,11 @@ private class GraphqlCursorCoercing : Coercing<Cursor, String> {
|
||||
}
|
||||
|
||||
@Throws(CoercingParseValueException::class)
|
||||
override fun parseValue(input: Any, graphQLContext: GraphQLContext, locale: Locale): Cursor {
|
||||
override fun parseValue(
|
||||
input: Any,
|
||||
graphQLContext: GraphQLContext,
|
||||
locale: Locale,
|
||||
): Cursor {
|
||||
return parseValueImpl(input, locale)
|
||||
}
|
||||
|
||||
@@ -99,7 +112,7 @@ private class GraphqlCursorCoercing : Coercing<Cursor, String> {
|
||||
input: Value<*>,
|
||||
variables: CoercedVariables,
|
||||
graphQLContext: GraphQLContext,
|
||||
locale: Locale
|
||||
locale: Locale,
|
||||
): Cursor {
|
||||
return parseLiteralImpl(input, locale)
|
||||
}
|
||||
@@ -112,7 +125,7 @@ private class GraphqlCursorCoercing : Coercing<Cursor, String> {
|
||||
override fun valueToLiteral(
|
||||
input: Any,
|
||||
graphQLContext: GraphQLContext,
|
||||
locale: Locale
|
||||
locale: Locale,
|
||||
): Value<*> {
|
||||
return valueToLiteralImpl(input)
|
||||
}
|
||||
|
||||
@@ -12,36 +12,43 @@ import graphql.schema.CoercingSerializeException
|
||||
import graphql.schema.GraphQLScalarType
|
||||
import java.util.Locale
|
||||
|
||||
val GraphQLLongAsString: GraphQLScalarType = GraphQLScalarType.newScalar()
|
||||
.name("LongString").description("A 64-bit signed integer as a String").coercing(GraphqlLongAsStringCoercing()).build()
|
||||
val GraphQLLongAsString: GraphQLScalarType =
|
||||
GraphQLScalarType.newScalar()
|
||||
.name("LongString").description("A 64-bit signed integer as a String").coercing(GraphqlLongAsStringCoercing()).build()
|
||||
|
||||
private class GraphqlLongAsStringCoercing : Coercing<Long, String> {
|
||||
private fun toStringImpl(input: Any): String {
|
||||
return input.toString()
|
||||
}
|
||||
|
||||
private fun parseValueImpl(input: Any, locale: Locale): Long {
|
||||
private fun parseValueImpl(
|
||||
input: Any,
|
||||
locale: Locale,
|
||||
): Long {
|
||||
if (input !is String) {
|
||||
throw CoercingParseValueException(
|
||||
CoercingUtil.i18nMsg(
|
||||
locale,
|
||||
"String.unexpectedRawValueType",
|
||||
CoercingUtil.typeName(input)
|
||||
)
|
||||
CoercingUtil.typeName(input),
|
||||
),
|
||||
)
|
||||
}
|
||||
return input.toLong()
|
||||
}
|
||||
|
||||
private fun parseLiteralImpl(input: Any, locale: Locale): Long {
|
||||
private fun parseLiteralImpl(
|
||||
input: Any,
|
||||
locale: Locale,
|
||||
): Long {
|
||||
if (input !is StringValue) {
|
||||
throw CoercingParseLiteralException(
|
||||
CoercingUtil.i18nMsg(
|
||||
locale,
|
||||
"Scalar.unexpectedAstType",
|
||||
"StringValue",
|
||||
CoercingUtil.typeName(input)
|
||||
)
|
||||
CoercingUtil.typeName(input),
|
||||
),
|
||||
)
|
||||
}
|
||||
return input.value.toLong()
|
||||
@@ -60,7 +67,7 @@ private class GraphqlLongAsStringCoercing : Coercing<Long, String> {
|
||||
override fun serialize(
|
||||
dataFetcherResult: Any,
|
||||
graphQLContext: GraphQLContext,
|
||||
locale: Locale
|
||||
locale: Locale,
|
||||
): String {
|
||||
return toStringImpl(dataFetcherResult)
|
||||
}
|
||||
@@ -71,7 +78,11 @@ private class GraphqlLongAsStringCoercing : Coercing<Long, String> {
|
||||
}
|
||||
|
||||
@Throws(CoercingParseValueException::class)
|
||||
override fun parseValue(input: Any, graphQLContext: GraphQLContext, locale: Locale): Long {
|
||||
override fun parseValue(
|
||||
input: Any,
|
||||
graphQLContext: GraphQLContext,
|
||||
locale: Locale,
|
||||
): Long {
|
||||
return parseValueImpl(input, locale)
|
||||
}
|
||||
|
||||
@@ -85,7 +96,7 @@ private class GraphqlLongAsStringCoercing : Coercing<Long, String> {
|
||||
input: Value<*>,
|
||||
variables: CoercedVariables,
|
||||
graphQLContext: GraphQLContext,
|
||||
locale: Locale
|
||||
locale: Locale,
|
||||
): Long {
|
||||
return parseLiteralImpl(input, locale)
|
||||
}
|
||||
@@ -98,7 +109,7 @@ private class GraphqlLongAsStringCoercing : Coercing<Long, String> {
|
||||
override fun valueToLiteral(
|
||||
input: Any,
|
||||
graphQLContext: GraphQLContext,
|
||||
locale: Locale
|
||||
locale: Locale,
|
||||
): Value<*> {
|
||||
return valueToLiteralImpl(input)
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ data class PageInfo(
|
||||
@GraphQLDescription("When paginating backwards, the cursor to continue.")
|
||||
val startCursor: Cursor?,
|
||||
@GraphQLDescription("When paginating forwards, the cursor to continue.")
|
||||
val endCursor: Cursor?
|
||||
val endCursor: Cursor?,
|
||||
)
|
||||
|
||||
abstract class Edge {
|
||||
|
||||
@@ -41,7 +41,7 @@ fun <T : Comparable<T>> greaterNotUnique(
|
||||
column: Column<T>,
|
||||
idColumn: Column<EntityID<Int>>,
|
||||
cursor: Cursor,
|
||||
toValue: (String) -> T
|
||||
toValue: (String) -> T,
|
||||
): Op<Boolean> {
|
||||
return greaterNotUniqueImpl(column, idColumn, cursor, String::toInt, toValue)
|
||||
}
|
||||
@@ -51,7 +51,7 @@ fun <T : Comparable<T>> greaterNotUnique(
|
||||
column: Column<T>,
|
||||
idColumn: Column<EntityID<Long>>,
|
||||
cursor: Cursor,
|
||||
toValue: (String) -> T
|
||||
toValue: (String) -> T,
|
||||
): Op<Boolean> {
|
||||
return greaterNotUniqueImpl(column, idColumn, cursor, String::toLong, toValue)
|
||||
}
|
||||
@@ -61,7 +61,7 @@ private fun <K : Comparable<K>, V : Comparable<V>> greaterNotUniqueImpl(
|
||||
idColumn: Column<EntityID<K>>,
|
||||
cursor: Cursor,
|
||||
toKey: (String) -> K,
|
||||
toValue: (String) -> V
|
||||
toValue: (String) -> V,
|
||||
): Op<Boolean> {
|
||||
val id = toKey(cursor.value.substringBefore('-'))
|
||||
val value = toValue(cursor.value.substringAfter('-'))
|
||||
@@ -73,7 +73,7 @@ fun <T : Comparable<T>> greaterNotUnique(
|
||||
column: Column<T>,
|
||||
idColumn: Column<String>,
|
||||
cursor: Cursor,
|
||||
toValue: (String) -> T
|
||||
toValue: (String) -> T,
|
||||
): Op<Boolean> {
|
||||
val id = cursor.value.substringBefore("\\-")
|
||||
val value = toValue(cursor.value.substringAfter("\\-"))
|
||||
@@ -85,7 +85,7 @@ fun <T : Comparable<T>> lessNotUnique(
|
||||
column: Column<T>,
|
||||
idColumn: Column<EntityID<Int>>,
|
||||
cursor: Cursor,
|
||||
toValue: (String) -> T
|
||||
toValue: (String) -> T,
|
||||
): Op<Boolean> {
|
||||
return lessNotUniqueImpl(column, idColumn, cursor, String::toInt, toValue)
|
||||
}
|
||||
@@ -95,7 +95,7 @@ fun <T : Comparable<T>> lessNotUnique(
|
||||
column: Column<T>,
|
||||
idColumn: Column<EntityID<Long>>,
|
||||
cursor: Cursor,
|
||||
toValue: (String) -> T
|
||||
toValue: (String) -> T,
|
||||
): Op<Boolean> {
|
||||
return lessNotUniqueImpl(column, idColumn, cursor, String::toLong, toValue)
|
||||
}
|
||||
@@ -105,7 +105,7 @@ private fun <K : Comparable<K>, V : Comparable<V>> lessNotUniqueImpl(
|
||||
idColumn: Column<EntityID<K>>,
|
||||
cursor: Cursor,
|
||||
toKey: (String) -> K,
|
||||
toValue: (String) -> V
|
||||
toValue: (String) -> V,
|
||||
): Op<Boolean> {
|
||||
val id = toKey(cursor.value.substringBefore('-'))
|
||||
val value = toValue(cursor.value.substringAfter('-'))
|
||||
@@ -117,7 +117,7 @@ fun <T : Comparable<T>> lessNotUnique(
|
||||
column: Column<T>,
|
||||
idColumn: Column<String>,
|
||||
cursor: Cursor,
|
||||
toValue: (String) -> T
|
||||
toValue: (String) -> T,
|
||||
): Op<Boolean> {
|
||||
val id = cursor.value.substringBefore("\\-")
|
||||
val value = toValue(cursor.value.substringAfter("\\-"))
|
||||
|
||||
@@ -9,21 +9,25 @@ import graphql.schema.GraphQLScalarType
|
||||
import io.javalin.http.UploadedFile
|
||||
import java.util.Locale
|
||||
|
||||
val GraphQLUpload = GraphQLScalarType.newScalar()
|
||||
.name("Upload")
|
||||
.description("A file part in a multipart request")
|
||||
.coercing(GraphqlUploadCoercing())
|
||||
.build()
|
||||
val GraphQLUpload =
|
||||
GraphQLScalarType.newScalar()
|
||||
.name("Upload")
|
||||
.description("A file part in a multipart request")
|
||||
.coercing(GraphqlUploadCoercing())
|
||||
.build()
|
||||
|
||||
private class GraphqlUploadCoercing : Coercing<UploadedFile, Void?> {
|
||||
private fun parseValueImpl(input: Any, locale: Locale): UploadedFile {
|
||||
private fun parseValueImpl(
|
||||
input: Any,
|
||||
locale: Locale,
|
||||
): UploadedFile {
|
||||
if (input !is UploadedFile) {
|
||||
throw CoercingParseValueException(
|
||||
CoercingUtil.i18nMsg(
|
||||
locale,
|
||||
"String.unexpectedRawValueType",
|
||||
CoercingUtil.typeName(input)
|
||||
)
|
||||
CoercingUtil.typeName(input),
|
||||
),
|
||||
)
|
||||
}
|
||||
return input
|
||||
@@ -38,7 +42,7 @@ private class GraphqlUploadCoercing : Coercing<UploadedFile, Void?> {
|
||||
override fun serialize(
|
||||
dataFetcherResult: Any,
|
||||
graphQLContext: GraphQLContext,
|
||||
locale: Locale
|
||||
locale: Locale,
|
||||
): Void? {
|
||||
throw CoercingSerializeException("Upload is an input-only type")
|
||||
}
|
||||
@@ -49,7 +53,11 @@ private class GraphqlUploadCoercing : Coercing<UploadedFile, Void?> {
|
||||
}
|
||||
|
||||
@Throws(CoercingParseValueException::class)
|
||||
override fun parseValue(input: Any, graphQLContext: GraphQLContext, locale: Locale): UploadedFile {
|
||||
override fun parseValue(
|
||||
input: Any,
|
||||
graphQLContext: GraphQLContext,
|
||||
locale: Locale,
|
||||
): UploadedFile {
|
||||
return parseValueImpl(input, locale)
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ import suwayomi.tachidesk.server.serverConfig
|
||||
class ApolloSubscriptionProtocolHandler(
|
||||
private val contextFactory: TachideskGraphQLContextFactory,
|
||||
private val subscriptionHandler: GraphQLSubscriptionHandler,
|
||||
private val objectMapper: ObjectMapper
|
||||
private val objectMapper: ObjectMapper,
|
||||
) {
|
||||
private val sessionState = ApolloSubscriptionSessionState()
|
||||
private val logger = KotlinLogging.logger {}
|
||||
@@ -68,13 +68,13 @@ class ApolloSubscriptionProtocolHandler(
|
||||
val operationMessage = convertToMessageOrNull(context.message()) ?: return flowOf(basicConnectionErrorMessage)
|
||||
logger.debug {
|
||||
"GraphQL subscription client message, sessionId=${context.sessionId} type=${operationMessage.type} operationName=${
|
||||
getOperationName(operationMessage.payload)
|
||||
getOperationName(operationMessage.payload)
|
||||
} ${
|
||||
if (serverConfig.gqlDebugLogsEnabled.value) {
|
||||
"operationMessage=$operationMessage"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
if (serverConfig.gqlDebugLogsEnabled.value) {
|
||||
"operationMessage=$operationMessage"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}"
|
||||
}
|
||||
|
||||
@@ -108,7 +108,7 @@ class ApolloSubscriptionProtocolHandler(
|
||||
@Suppress("Detekt.TooGenericExceptionCaught")
|
||||
private fun startSubscription(
|
||||
operationMessage: SubscriptionOperationMessage,
|
||||
context: WsContext
|
||||
context: WsContext,
|
||||
): Flow<SubscriptionOperationMessage> {
|
||||
if (operationMessage.id == null) {
|
||||
logger.error("GraphQL subscription operation id is required")
|
||||
@@ -149,7 +149,10 @@ class ApolloSubscriptionProtocolHandler(
|
||||
}
|
||||
}
|
||||
|
||||
private fun onInit(operationMessage: SubscriptionOperationMessage, context: WsContext): Flow<SubscriptionOperationMessage> {
|
||||
private fun onInit(
|
||||
operationMessage: SubscriptionOperationMessage,
|
||||
context: WsContext,
|
||||
): Flow<SubscriptionOperationMessage> {
|
||||
saveContext(operationMessage, context)
|
||||
return flowOf(acknowledgeMessage)
|
||||
}
|
||||
@@ -157,7 +160,10 @@ class ApolloSubscriptionProtocolHandler(
|
||||
/**
|
||||
* Generate the context and save it for all future messages.
|
||||
*/
|
||||
private fun saveContext(operationMessage: SubscriptionOperationMessage, context: WsContext) {
|
||||
private fun saveContext(
|
||||
operationMessage: SubscriptionOperationMessage,
|
||||
context: WsContext,
|
||||
) {
|
||||
runBlocking {
|
||||
val graphQLContext = contextFactory.generateContextMap(context).toGraphQLContext()
|
||||
sessionState.saveContext(context, graphQLContext)
|
||||
@@ -169,7 +175,7 @@ class ApolloSubscriptionProtocolHandler(
|
||||
*/
|
||||
private fun onComplete(
|
||||
operationMessage: SubscriptionOperationMessage,
|
||||
context: WsContext
|
||||
context: WsContext,
|
||||
): Flow<SubscriptionOperationMessage> {
|
||||
return sessionState.completeOperation(operationMessage)
|
||||
}
|
||||
@@ -183,7 +189,10 @@ class ApolloSubscriptionProtocolHandler(
|
||||
return emptyFlow()
|
||||
}
|
||||
|
||||
private fun onUnknownOperation(operationMessage: SubscriptionOperationMessage, context: WsContext): Flow<SubscriptionOperationMessage> {
|
||||
private fun onUnknownOperation(
|
||||
operationMessage: SubscriptionOperationMessage,
|
||||
context: WsContext,
|
||||
): Flow<SubscriptionOperationMessage> {
|
||||
logger.error("Unknown subscription operation $operationMessage")
|
||||
sessionState.completeOperation(operationMessage)
|
||||
return emptyFlow()
|
||||
|
||||
@@ -19,7 +19,6 @@ import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
|
||||
internal class ApolloSubscriptionSessionState {
|
||||
|
||||
// Operations are saved by web socket session id, then operation id
|
||||
internal val activeOperations = ConcurrentHashMap<String, Job>()
|
||||
|
||||
@@ -33,21 +32,29 @@ internal class ApolloSubscriptionSessionState {
|
||||
* This allows us to include some initial state to be used when handling all the messages.
|
||||
* This will be removed in [terminateSession].
|
||||
*/
|
||||
fun saveContext(context: WsContext, graphQLContext: GraphQLContext) {
|
||||
fun saveContext(
|
||||
context: WsContext,
|
||||
graphQLContext: GraphQLContext,
|
||||
) {
|
||||
cachedGraphQLContext[context.sessionId] = graphQLContext
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the graphQL context for this session.
|
||||
*/
|
||||
fun getGraphQLContext(context: WsContext): GraphQLContext = cachedGraphQLContext[context.sessionId] ?: emptyMap<Any, Any>().toGraphQLContext()
|
||||
fun getGraphQLContext(context: WsContext): GraphQLContext =
|
||||
cachedGraphQLContext[context.sessionId] ?: emptyMap<Any, Any>().toGraphQLContext()
|
||||
|
||||
/**
|
||||
* Save the operation that is sending data to the client.
|
||||
* This will override values without cancelling the subscription so it is the responsibility of the consumer to cancel.
|
||||
* These messages will be stopped on [stopOperation].
|
||||
*/
|
||||
fun saveOperation(context: WsContext, operationMessage: SubscriptionOperationMessage, subscription: Job) {
|
||||
fun saveOperation(
|
||||
context: WsContext,
|
||||
operationMessage: SubscriptionOperationMessage,
|
||||
subscription: Job,
|
||||
) {
|
||||
val id = operationMessage.id
|
||||
if (id != null) {
|
||||
activeOperations[id] = subscription
|
||||
@@ -78,7 +85,10 @@ internal class ApolloSubscriptionSessionState {
|
||||
/**
|
||||
* Terminate the session, cancelling the keep alive messages and all operations active for this session.
|
||||
*/
|
||||
fun terminateSession(context: WsContext, code: CloseStatus) {
|
||||
fun terminateSession(
|
||||
context: WsContext,
|
||||
code: CloseStatus,
|
||||
) {
|
||||
sessionToOperationId.remove(context.sessionId)?.forEach {
|
||||
activeOperations[it]?.cancel()
|
||||
}
|
||||
@@ -89,6 +99,5 @@ internal class ApolloSubscriptionSessionState {
|
||||
/**
|
||||
* Looks up the operation for the client, to check if it already exists
|
||||
*/
|
||||
fun doesOperationExist(operationMessage: SubscriptionOperationMessage): Boolean =
|
||||
activeOperations.containsKey(operationMessage.id)
|
||||
fun doesOperationExist(operationMessage: SubscriptionOperationMessage): Boolean = activeOperations.containsKey(operationMessage.id)
|
||||
}
|
||||
|
||||
@@ -23,11 +23,11 @@ import kotlinx.coroutines.flow.map
|
||||
|
||||
open class GraphQLSubscriptionHandler(
|
||||
private val graphQL: GraphQL,
|
||||
private val dataLoaderRegistryFactory: KotlinDataLoaderRegistryFactory? = null
|
||||
private val dataLoaderRegistryFactory: KotlinDataLoaderRegistryFactory? = null,
|
||||
) {
|
||||
open fun executeSubscription(
|
||||
graphQLRequest: GraphQLRequest,
|
||||
graphQLContext: GraphQLContext = GraphQLContext.of(emptyMap<Any, Any>())
|
||||
graphQLContext: GraphQLContext = GraphQLContext.of(emptyMap<Any, Any>()),
|
||||
): Flow<GraphQLResponse<*>> {
|
||||
val dataLoaderRegistry = dataLoaderRegistryFactory?.generate()
|
||||
val input = graphQLRequest.toExecutionInput(dataLoaderRegistry, graphQLContext)
|
||||
|
||||
@@ -21,22 +21,22 @@ import com.fasterxml.jackson.annotation.JsonInclude
|
||||
data class SubscriptionOperationMessage(
|
||||
val type: String,
|
||||
val id: String? = null,
|
||||
val payload: Any? = null
|
||||
val payload: Any? = null,
|
||||
) {
|
||||
enum class CommonMessages(val type: String) {
|
||||
GQL_PING("ping"),
|
||||
GQL_PONG("pong"),
|
||||
GQL_COMPLETE("complete")
|
||||
GQL_COMPLETE("complete"),
|
||||
}
|
||||
|
||||
enum class ClientMessages(val type: String) {
|
||||
GQL_CONNECTION_INIT("connection_init"),
|
||||
GQL_SUBSCRIBE("subscribe")
|
||||
GQL_SUBSCRIBE("subscribe"),
|
||||
}
|
||||
|
||||
enum class ServerMessages(val type: String) {
|
||||
GQL_CONNECTION_ACK("connection_ack"),
|
||||
GQL_NEXT("next"),
|
||||
GQL_ERROR("error")
|
||||
GQL_ERROR("error"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import suwayomi.tachidesk.graphql.types.DownloadStatus
|
||||
import suwayomi.tachidesk.manga.impl.download.DownloadManager
|
||||
|
||||
class DownloadSubscription {
|
||||
|
||||
fun downloadChanged(): Flow<DownloadStatus> {
|
||||
return DownloadManager.status.map { downloadStatus ->
|
||||
DownloadStatus(downloadStatus)
|
||||
|
||||
@@ -5,31 +5,34 @@ import suwayomi.tachidesk.manga.impl.backup.proto.ProtoBackupImport
|
||||
enum class BackupRestoreState {
|
||||
IDLE,
|
||||
RESTORING_CATEGORIES,
|
||||
RESTORING_MANGA
|
||||
RESTORING_MANGA,
|
||||
}
|
||||
|
||||
data class BackupRestoreStatus(
|
||||
val state: BackupRestoreState,
|
||||
val totalManga: Int,
|
||||
val mangaProgress: Int
|
||||
val mangaProgress: Int,
|
||||
)
|
||||
|
||||
fun ProtoBackupImport.BackupRestoreState.toStatus(): BackupRestoreStatus {
|
||||
return when (this) {
|
||||
ProtoBackupImport.BackupRestoreState.Idle -> BackupRestoreStatus(
|
||||
state = BackupRestoreState.IDLE,
|
||||
totalManga = 0,
|
||||
mangaProgress = 0
|
||||
)
|
||||
is ProtoBackupImport.BackupRestoreState.RestoringCategories -> BackupRestoreStatus(
|
||||
state = BackupRestoreState.RESTORING_CATEGORIES,
|
||||
totalManga = totalManga,
|
||||
mangaProgress = 0
|
||||
)
|
||||
is ProtoBackupImport.BackupRestoreState.RestoringManga -> BackupRestoreStatus(
|
||||
state = BackupRestoreState.RESTORING_MANGA,
|
||||
totalManga = totalManga,
|
||||
mangaProgress = current
|
||||
)
|
||||
ProtoBackupImport.BackupRestoreState.Idle ->
|
||||
BackupRestoreStatus(
|
||||
state = BackupRestoreState.IDLE,
|
||||
totalManga = 0,
|
||||
mangaProgress = 0,
|
||||
)
|
||||
is ProtoBackupImport.BackupRestoreState.RestoringCategories ->
|
||||
BackupRestoreStatus(
|
||||
state = BackupRestoreState.RESTORING_CATEGORIES,
|
||||
totalManga = totalManga,
|
||||
mangaProgress = 0,
|
||||
)
|
||||
is ProtoBackupImport.BackupRestoreState.RestoringManga ->
|
||||
BackupRestoreStatus(
|
||||
state = BackupRestoreState.RESTORING_MANGA,
|
||||
totalManga = totalManga,
|
||||
mangaProgress = current,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,14 +24,14 @@ class CategoryType(
|
||||
val order: Int,
|
||||
val name: String,
|
||||
val default: Boolean,
|
||||
val includeInUpdate: IncludeInUpdate
|
||||
val includeInUpdate: IncludeInUpdate,
|
||||
) : Node {
|
||||
constructor(row: ResultRow) : this(
|
||||
row[CategoryTable.id].value,
|
||||
row[CategoryTable.order],
|
||||
row[CategoryTable.name],
|
||||
row[CategoryTable.isDefault],
|
||||
IncludeInUpdate.fromValue(row[CategoryTable.includeInUpdate])
|
||||
IncludeInUpdate.fromValue(row[CategoryTable.includeInUpdate]),
|
||||
)
|
||||
|
||||
fun mangas(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture<MangaNodeList> {
|
||||
@@ -47,11 +47,11 @@ data class CategoryNodeList(
|
||||
override val nodes: List<CategoryType>,
|
||||
override val edges: List<CategoryEdge>,
|
||||
override val pageInfo: PageInfo,
|
||||
override val totalCount: Int
|
||||
override val totalCount: Int,
|
||||
) : NodeList() {
|
||||
data class CategoryEdge(
|
||||
override val cursor: Cursor,
|
||||
override val node: CategoryType
|
||||
override val node: CategoryType,
|
||||
) : Edge()
|
||||
|
||||
companion object {
|
||||
@@ -59,13 +59,14 @@ data class CategoryNodeList(
|
||||
return CategoryNodeList(
|
||||
nodes = this,
|
||||
edges = getEdges(),
|
||||
pageInfo = PageInfo(
|
||||
hasNextPage = false,
|
||||
hasPreviousPage = false,
|
||||
startCursor = Cursor(0.toString()),
|
||||
endCursor = Cursor(lastIndex.toString())
|
||||
),
|
||||
totalCount = size
|
||||
pageInfo =
|
||||
PageInfo(
|
||||
hasNextPage = false,
|
||||
hasPreviousPage = false,
|
||||
startCursor = Cursor(0.toString()),
|
||||
endCursor = Cursor(lastIndex.toString()),
|
||||
),
|
||||
totalCount = size,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -74,12 +75,12 @@ data class CategoryNodeList(
|
||||
return listOf(
|
||||
CategoryEdge(
|
||||
cursor = Cursor("0"),
|
||||
node = first()
|
||||
node = first(),
|
||||
),
|
||||
CategoryEdge(
|
||||
cursor = Cursor(lastIndex.toString()),
|
||||
node = last()
|
||||
)
|
||||
node = last(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ class ChapterType(
|
||||
val realUrl: String?,
|
||||
val fetchedAt: Long,
|
||||
val isDownloaded: Boolean,
|
||||
val pageCount: Int
|
||||
val pageCount: Int,
|
||||
// val chapterCount: Int?,
|
||||
) : Node {
|
||||
constructor(row: ResultRow) : this(
|
||||
@@ -54,7 +54,7 @@ class ChapterType(
|
||||
row[ChapterTable.realUrl],
|
||||
row[ChapterTable.fetchedAt],
|
||||
row[ChapterTable.isDownloaded],
|
||||
row[ChapterTable.pageCount]
|
||||
row[ChapterTable.pageCount],
|
||||
// transaction { ChapterTable.select { manga eq chapterEntry[manga].value }.count().toInt() },
|
||||
)
|
||||
|
||||
@@ -74,7 +74,7 @@ class ChapterType(
|
||||
dataClass.realUrl,
|
||||
dataClass.fetchedAt,
|
||||
dataClass.downloaded,
|
||||
dataClass.pageCount
|
||||
dataClass.pageCount,
|
||||
)
|
||||
|
||||
fun manga(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture<MangaType> {
|
||||
@@ -90,11 +90,11 @@ data class ChapterNodeList(
|
||||
override val nodes: List<ChapterType>,
|
||||
override val edges: List<ChapterEdge>,
|
||||
override val pageInfo: PageInfo,
|
||||
override val totalCount: Int
|
||||
override val totalCount: Int,
|
||||
) : NodeList() {
|
||||
data class ChapterEdge(
|
||||
override val cursor: Cursor,
|
||||
override val node: ChapterType
|
||||
override val node: ChapterType,
|
||||
) : Edge()
|
||||
|
||||
companion object {
|
||||
@@ -102,13 +102,14 @@ data class ChapterNodeList(
|
||||
return ChapterNodeList(
|
||||
nodes = this,
|
||||
edges = getEdges(),
|
||||
pageInfo = PageInfo(
|
||||
hasNextPage = false,
|
||||
hasPreviousPage = false,
|
||||
startCursor = Cursor(0.toString()),
|
||||
endCursor = Cursor(lastIndex.toString())
|
||||
),
|
||||
totalCount = size
|
||||
pageInfo =
|
||||
PageInfo(
|
||||
hasNextPage = false,
|
||||
hasPreviousPage = false,
|
||||
startCursor = Cursor(0.toString()),
|
||||
endCursor = Cursor(lastIndex.toString()),
|
||||
),
|
||||
totalCount = size,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -117,12 +118,12 @@ data class ChapterNodeList(
|
||||
return listOf(
|
||||
ChapterEdge(
|
||||
cursor = Cursor("0"),
|
||||
node = first()
|
||||
node = first(),
|
||||
),
|
||||
ChapterEdge(
|
||||
cursor = Cursor(lastIndex.toString()),
|
||||
node = last()
|
||||
)
|
||||
node = last(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,14 +23,14 @@ import suwayomi.tachidesk.manga.impl.download.model.DownloadState as OtherDownlo
|
||||
|
||||
data class DownloadStatus(
|
||||
val state: DownloaderState,
|
||||
val queue: List<DownloadType>
|
||||
val queue: List<DownloadType>,
|
||||
) {
|
||||
constructor(downloadStatus: DownloadStatus) : this(
|
||||
when (downloadStatus.status) {
|
||||
Status.Stopped -> DownloaderState.STOPPED
|
||||
Status.Started -> DownloaderState.STARTED
|
||||
},
|
||||
downloadStatus.queue.map { DownloadType(it) }
|
||||
downloadStatus.queue.map { DownloadType(it) },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ class DownloadType(
|
||||
val mangaId: Int,
|
||||
val state: DownloadState,
|
||||
val progress: Float,
|
||||
val tries: Int
|
||||
val tries: Int,
|
||||
) : Node {
|
||||
constructor(downloadChapter: DownloadChapter) : this(
|
||||
downloadChapter.chapter.id,
|
||||
@@ -53,7 +53,7 @@ class DownloadType(
|
||||
OtherDownloadState.Error -> DownloadState.ERROR
|
||||
},
|
||||
downloadChapter.progress,
|
||||
downloadChapter.tries
|
||||
downloadChapter.tries,
|
||||
)
|
||||
|
||||
fun manga(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture<MangaType> {
|
||||
@@ -69,23 +69,23 @@ enum class DownloadState {
|
||||
QUEUED,
|
||||
DOWNLOADING,
|
||||
FINISHED,
|
||||
ERROR
|
||||
ERROR,
|
||||
}
|
||||
|
||||
enum class DownloaderState {
|
||||
STARTED,
|
||||
STOPPED
|
||||
STOPPED,
|
||||
}
|
||||
|
||||
data class DownloadNodeList(
|
||||
override val nodes: List<DownloadType>,
|
||||
override val edges: List<DownloadEdge>,
|
||||
override val pageInfo: PageInfo,
|
||||
override val totalCount: Int
|
||||
override val totalCount: Int,
|
||||
) : NodeList() {
|
||||
data class DownloadEdge(
|
||||
override val cursor: Cursor,
|
||||
override val node: DownloadType
|
||||
override val node: DownloadType,
|
||||
) : Edge()
|
||||
|
||||
companion object {
|
||||
@@ -93,13 +93,14 @@ data class DownloadNodeList(
|
||||
return DownloadNodeList(
|
||||
nodes = this,
|
||||
edges = getEdges(),
|
||||
pageInfo = PageInfo(
|
||||
hasNextPage = false,
|
||||
hasPreviousPage = false,
|
||||
startCursor = Cursor(0.toString()),
|
||||
endCursor = Cursor(lastIndex.toString())
|
||||
),
|
||||
totalCount = size
|
||||
pageInfo =
|
||||
PageInfo(
|
||||
hasNextPage = false,
|
||||
hasPreviousPage = false,
|
||||
startCursor = Cursor(0.toString()),
|
||||
endCursor = Cursor(lastIndex.toString()),
|
||||
),
|
||||
totalCount = size,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -108,12 +109,12 @@ data class DownloadNodeList(
|
||||
return listOf(
|
||||
DownloadEdge(
|
||||
cursor = Cursor("0"),
|
||||
node = first()
|
||||
node = first(),
|
||||
),
|
||||
DownloadEdge(
|
||||
cursor = Cursor(lastIndex.toString()),
|
||||
node = last()
|
||||
)
|
||||
node = last(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,17 +22,15 @@ import java.util.concurrent.CompletableFuture
|
||||
class ExtensionType(
|
||||
val apkName: String,
|
||||
val iconUrl: String,
|
||||
|
||||
val name: String,
|
||||
val pkgName: String,
|
||||
val versionName: String,
|
||||
val versionCode: Int,
|
||||
val lang: String,
|
||||
val isNsfw: Boolean,
|
||||
|
||||
val isInstalled: Boolean,
|
||||
val hasUpdate: Boolean,
|
||||
val isObsolete: Boolean
|
||||
val isObsolete: Boolean,
|
||||
) : Node {
|
||||
constructor(row: ResultRow) : this(
|
||||
apkName = row[ExtensionTable.apkName],
|
||||
@@ -45,7 +43,7 @@ class ExtensionType(
|
||||
isNsfw = row[ExtensionTable.isNsfw],
|
||||
isInstalled = row[ExtensionTable.isInstalled],
|
||||
hasUpdate = row[ExtensionTable.hasUpdate],
|
||||
isObsolete = row[ExtensionTable.isObsolete]
|
||||
isObsolete = row[ExtensionTable.isObsolete],
|
||||
)
|
||||
|
||||
fun source(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture<SourceNodeList> {
|
||||
@@ -57,11 +55,11 @@ data class ExtensionNodeList(
|
||||
override val nodes: List<ExtensionType>,
|
||||
override val edges: List<ExtensionEdge>,
|
||||
override val pageInfo: PageInfo,
|
||||
override val totalCount: Int
|
||||
override val totalCount: Int,
|
||||
) : NodeList() {
|
||||
data class ExtensionEdge(
|
||||
override val cursor: Cursor,
|
||||
override val node: ExtensionType
|
||||
override val node: ExtensionType,
|
||||
) : Edge()
|
||||
|
||||
companion object {
|
||||
@@ -69,13 +67,14 @@ data class ExtensionNodeList(
|
||||
return ExtensionNodeList(
|
||||
nodes = this,
|
||||
edges = getEdges(),
|
||||
pageInfo = PageInfo(
|
||||
hasNextPage = false,
|
||||
hasPreviousPage = false,
|
||||
startCursor = Cursor(0.toString()),
|
||||
endCursor = Cursor(lastIndex.toString())
|
||||
),
|
||||
totalCount = size
|
||||
pageInfo =
|
||||
PageInfo(
|
||||
hasNextPage = false,
|
||||
hasPreviousPage = false,
|
||||
startCursor = Cursor(0.toString()),
|
||||
endCursor = Cursor(lastIndex.toString()),
|
||||
),
|
||||
totalCount = size,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -84,12 +83,12 @@ data class ExtensionNodeList(
|
||||
return listOf(
|
||||
ExtensionEdge(
|
||||
cursor = Cursor("0"),
|
||||
node = first()
|
||||
node = first(),
|
||||
),
|
||||
ExtensionEdge(
|
||||
cursor = Cursor(lastIndex.toString()),
|
||||
node = last()
|
||||
)
|
||||
node = last(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ class MangaType(
|
||||
val inLibraryAt: Long,
|
||||
val realUrl: String?,
|
||||
var lastFetchedAt: Long?, // todo
|
||||
var chaptersLastFetchedAt: Long? // todo
|
||||
var chaptersLastFetchedAt: Long?, // todo
|
||||
) : Node {
|
||||
constructor(row: ResultRow) : this(
|
||||
row[MangaTable.id].value,
|
||||
@@ -57,7 +57,7 @@ class MangaType(
|
||||
row[MangaTable.inLibraryAt],
|
||||
row[MangaTable.realUrl],
|
||||
row[MangaTable.lastFetchedAt],
|
||||
row[MangaTable.chaptersLastFetchedAt]
|
||||
row[MangaTable.chaptersLastFetchedAt],
|
||||
)
|
||||
|
||||
constructor(dataClass: MangaDataClass) : this(
|
||||
@@ -76,7 +76,7 @@ class MangaType(
|
||||
dataClass.inLibraryAt,
|
||||
dataClass.realUrl,
|
||||
dataClass.lastFetchedAt,
|
||||
dataClass.chaptersLastFetchedAt
|
||||
dataClass.chaptersLastFetchedAt,
|
||||
)
|
||||
|
||||
fun downloadCount(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture<Int> {
|
||||
@@ -123,11 +123,11 @@ data class MangaNodeList(
|
||||
override val nodes: List<MangaType>,
|
||||
override val edges: List<MangaEdge>,
|
||||
override val pageInfo: PageInfo,
|
||||
override val totalCount: Int
|
||||
override val totalCount: Int,
|
||||
) : NodeList() {
|
||||
data class MangaEdge(
|
||||
override val cursor: Cursor,
|
||||
override val node: MangaType
|
||||
override val node: MangaType,
|
||||
) : Edge()
|
||||
|
||||
companion object {
|
||||
@@ -135,13 +135,14 @@ data class MangaNodeList(
|
||||
return MangaNodeList(
|
||||
nodes = this,
|
||||
edges = getEdges(),
|
||||
pageInfo = PageInfo(
|
||||
hasNextPage = false,
|
||||
hasPreviousPage = false,
|
||||
startCursor = Cursor(0.toString()),
|
||||
endCursor = Cursor(lastIndex.toString())
|
||||
),
|
||||
totalCount = size
|
||||
pageInfo =
|
||||
PageInfo(
|
||||
hasNextPage = false,
|
||||
hasPreviousPage = false,
|
||||
startCursor = Cursor(0.toString()),
|
||||
endCursor = Cursor(lastIndex.toString()),
|
||||
),
|
||||
totalCount = size,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -150,12 +151,12 @@ data class MangaNodeList(
|
||||
return listOf(
|
||||
MangaEdge(
|
||||
cursor = Cursor("0"),
|
||||
node = first()
|
||||
node = first(),
|
||||
),
|
||||
MangaEdge(
|
||||
cursor = Cursor(lastIndex.toString()),
|
||||
node = last()
|
||||
)
|
||||
node = last(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,12 +22,12 @@ interface MetaType : Node {
|
||||
class ChapterMetaType(
|
||||
override val key: String,
|
||||
override val value: String,
|
||||
val chapterId: Int
|
||||
val chapterId: Int,
|
||||
) : MetaType {
|
||||
constructor(row: ResultRow) : this(
|
||||
key = row[ChapterMetaTable.key],
|
||||
value = row[ChapterMetaTable.value],
|
||||
chapterId = row[ChapterMetaTable.ref].value
|
||||
chapterId = row[ChapterMetaTable.ref].value,
|
||||
)
|
||||
|
||||
fun chapter(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture<ChapterType> {
|
||||
@@ -38,12 +38,12 @@ class ChapterMetaType(
|
||||
class MangaMetaType(
|
||||
override val key: String,
|
||||
override val value: String,
|
||||
val mangaId: Int
|
||||
val mangaId: Int,
|
||||
) : MetaType {
|
||||
constructor(row: ResultRow) : this(
|
||||
key = row[MangaMetaTable.key],
|
||||
value = row[MangaMetaTable.value],
|
||||
mangaId = row[MangaMetaTable.ref].value
|
||||
mangaId = row[MangaMetaTable.ref].value,
|
||||
)
|
||||
|
||||
fun manga(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture<MangaType> {
|
||||
@@ -54,12 +54,12 @@ class MangaMetaType(
|
||||
class CategoryMetaType(
|
||||
override val key: String,
|
||||
override val value: String,
|
||||
val categoryId: Int
|
||||
val categoryId: Int,
|
||||
) : MetaType {
|
||||
constructor(row: ResultRow) : this(
|
||||
key = row[CategoryMetaTable.key],
|
||||
value = row[CategoryMetaTable.value],
|
||||
categoryId = row[CategoryMetaTable.ref].value
|
||||
categoryId = row[CategoryMetaTable.ref].value,
|
||||
)
|
||||
|
||||
fun category(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture<CategoryType> {
|
||||
@@ -69,11 +69,11 @@ class CategoryMetaType(
|
||||
|
||||
class GlobalMetaType(
|
||||
override val key: String,
|
||||
override val value: String
|
||||
override val value: String,
|
||||
) : MetaType {
|
||||
constructor(row: ResultRow) : this(
|
||||
key = row[GlobalMetaTable.key],
|
||||
value = row[GlobalMetaTable.value]
|
||||
value = row[GlobalMetaTable.value],
|
||||
)
|
||||
}
|
||||
|
||||
@@ -81,11 +81,11 @@ data class GlobalMetaNodeList(
|
||||
override val nodes: List<GlobalMetaType>,
|
||||
override val edges: List<MetaEdge>,
|
||||
override val pageInfo: PageInfo,
|
||||
override val totalCount: Int
|
||||
override val totalCount: Int,
|
||||
) : NodeList() {
|
||||
data class MetaEdge(
|
||||
override val cursor: Cursor,
|
||||
override val node: GlobalMetaType
|
||||
override val node: GlobalMetaType,
|
||||
) : Edge()
|
||||
|
||||
companion object {
|
||||
@@ -93,13 +93,14 @@ data class GlobalMetaNodeList(
|
||||
return GlobalMetaNodeList(
|
||||
nodes = this,
|
||||
edges = getEdges(),
|
||||
pageInfo = PageInfo(
|
||||
hasNextPage = false,
|
||||
hasPreviousPage = false,
|
||||
startCursor = Cursor(0.toString()),
|
||||
endCursor = Cursor(lastIndex.toString())
|
||||
),
|
||||
totalCount = size
|
||||
pageInfo =
|
||||
PageInfo(
|
||||
hasNextPage = false,
|
||||
hasPreviousPage = false,
|
||||
startCursor = Cursor(0.toString()),
|
||||
endCursor = Cursor(lastIndex.toString()),
|
||||
),
|
||||
totalCount = size,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -108,12 +109,12 @@ data class GlobalMetaNodeList(
|
||||
return listOf(
|
||||
MetaEdge(
|
||||
cursor = Cursor("0"),
|
||||
node = first()
|
||||
node = first(),
|
||||
),
|
||||
MetaEdge(
|
||||
cursor = Cursor(lastIndex.toString()),
|
||||
node = last()
|
||||
)
|
||||
node = last(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,12 +69,10 @@ interface Settings : Node {
|
||||
data class PartialSettingsType(
|
||||
override val ip: String?,
|
||||
override val port: Int?,
|
||||
|
||||
// proxy
|
||||
override val socksProxyEnabled: Boolean?,
|
||||
override val socksProxyHost: String?,
|
||||
override val socksProxyPort: String?,
|
||||
|
||||
// webUI
|
||||
override val webUIFlavor: WebUIFlavor?,
|
||||
override val initialOpenInBrowserEnabled: Boolean?,
|
||||
@@ -82,49 +80,40 @@ data class PartialSettingsType(
|
||||
override val electronPath: String?,
|
||||
override val webUIChannel: WebUIChannel?,
|
||||
override val webUIUpdateCheckInterval: Double?,
|
||||
|
||||
// downloader
|
||||
override val downloadAsCbz: Boolean?,
|
||||
override val downloadsPath: String?,
|
||||
override val autoDownloadNewChapters: Boolean?,
|
||||
|
||||
// requests
|
||||
override val maxSourcesInParallel: Int?,
|
||||
|
||||
// updater
|
||||
override val excludeUnreadChapters: Boolean?,
|
||||
override val excludeNotStarted: Boolean?,
|
||||
override val excludeCompleted: Boolean?,
|
||||
override val globalUpdateInterval: Double?,
|
||||
|
||||
// Authentication
|
||||
override val basicAuthEnabled: Boolean?,
|
||||
override val basicAuthUsername: String?,
|
||||
override val basicAuthPassword: String?,
|
||||
|
||||
// misc
|
||||
override val debugLogsEnabled: Boolean?,
|
||||
override val systemTrayEnabled: Boolean?,
|
||||
|
||||
// backup
|
||||
override val backupPath: String?,
|
||||
override val backupTime: String?,
|
||||
override val backupInterval: Int?,
|
||||
override val backupTTL: Int?,
|
||||
|
||||
// local source
|
||||
override val localSourcePath: String?
|
||||
override val localSourcePath: String?,
|
||||
) : Settings
|
||||
|
||||
class SettingsType(
|
||||
override val ip: String,
|
||||
override val port: Int,
|
||||
|
||||
// proxy
|
||||
override val socksProxyEnabled: Boolean,
|
||||
override val socksProxyHost: String,
|
||||
override val socksProxyPort: String,
|
||||
|
||||
// webUI
|
||||
override val webUIFlavor: WebUIFlavor,
|
||||
override val initialOpenInBrowserEnabled: Boolean,
|
||||
@@ -132,77 +121,61 @@ class SettingsType(
|
||||
override val electronPath: String,
|
||||
override val webUIChannel: WebUIChannel,
|
||||
override val webUIUpdateCheckInterval: Double,
|
||||
|
||||
// downloader
|
||||
override val downloadAsCbz: Boolean,
|
||||
override val downloadsPath: String,
|
||||
override val autoDownloadNewChapters: Boolean,
|
||||
|
||||
// requests
|
||||
override val maxSourcesInParallel: Int,
|
||||
|
||||
// updater
|
||||
override val excludeUnreadChapters: Boolean,
|
||||
override val excludeNotStarted: Boolean,
|
||||
override val excludeCompleted: Boolean,
|
||||
override val globalUpdateInterval: Double,
|
||||
|
||||
// Authentication
|
||||
override val basicAuthEnabled: Boolean,
|
||||
override val basicAuthUsername: String,
|
||||
override val basicAuthPassword: String,
|
||||
|
||||
// misc
|
||||
override val debugLogsEnabled: Boolean,
|
||||
override val systemTrayEnabled: Boolean,
|
||||
|
||||
// backup
|
||||
override val backupPath: String,
|
||||
override val backupTime: String,
|
||||
override val backupInterval: Int,
|
||||
override val backupTTL: Int,
|
||||
|
||||
// local source
|
||||
override val localSourcePath: String
|
||||
override val localSourcePath: String,
|
||||
) : Settings {
|
||||
constructor(config: ServerConfig = serverConfig) : this(
|
||||
config.ip.value,
|
||||
config.port.value,
|
||||
|
||||
config.socksProxyEnabled.value,
|
||||
config.socksProxyHost.value,
|
||||
config.socksProxyPort.value,
|
||||
|
||||
WebUIFlavor.from(config.webUIFlavor.value),
|
||||
config.initialOpenInBrowserEnabled.value,
|
||||
WebUIInterface.from(config.webUIInterface.value),
|
||||
config.electronPath.value,
|
||||
WebUIChannel.from(config.webUIChannel.value),
|
||||
config.webUIUpdateCheckInterval.value,
|
||||
|
||||
config.downloadAsCbz.value,
|
||||
config.downloadsPath.value,
|
||||
config.autoDownloadNewChapters.value,
|
||||
|
||||
config.maxSourcesInParallel.value,
|
||||
|
||||
config.excludeUnreadChapters.value,
|
||||
config.excludeNotStarted.value,
|
||||
config.excludeCompleted.value,
|
||||
config.globalUpdateInterval.value,
|
||||
|
||||
config.basicAuthEnabled.value,
|
||||
config.basicAuthUsername.value,
|
||||
config.basicAuthPassword.value,
|
||||
|
||||
config.debugLogsEnabled.value,
|
||||
config.systemTrayEnabled.value,
|
||||
|
||||
config.backupPath.value,
|
||||
config.backupTime.value,
|
||||
config.backupInterval.value,
|
||||
config.backupTTL.value,
|
||||
|
||||
config.localSourcePath.value
|
||||
config.localSourcePath.value,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ class SourceType(
|
||||
val supportsLatest: Boolean,
|
||||
val isConfigurable: Boolean,
|
||||
val isNsfw: Boolean,
|
||||
val displayName: String
|
||||
val displayName: String,
|
||||
) : Node {
|
||||
constructor(source: SourceDataClass) : this(
|
||||
id = source.id.toLong(),
|
||||
@@ -53,7 +53,7 @@ class SourceType(
|
||||
supportsLatest = source.supportsLatest,
|
||||
isConfigurable = source.isConfigurable,
|
||||
isNsfw = source.isNsfw,
|
||||
displayName = source.displayName
|
||||
displayName = source.displayName,
|
||||
)
|
||||
|
||||
constructor(row: ResultRow, sourceExtension: ResultRow, catalogueSource: CatalogueSource) : this(
|
||||
@@ -64,7 +64,7 @@ class SourceType(
|
||||
supportsLatest = catalogueSource.supportsLatest,
|
||||
isConfigurable = catalogueSource is ConfigurableSource,
|
||||
isNsfw = row[SourceTable.isNsfw],
|
||||
displayName = catalogueSource.toString()
|
||||
displayName = catalogueSource.toString(),
|
||||
)
|
||||
|
||||
fun manga(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture<MangaNodeList> {
|
||||
@@ -84,17 +84,20 @@ class SourceType(
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("ktlint:standard:function-naming")
|
||||
fun SourceType(row: ResultRow): SourceType? {
|
||||
val catalogueSource = GetCatalogueSource
|
||||
.getCatalogueSourceOrNull(row[SourceTable.id].value)
|
||||
?: return null
|
||||
val sourceExtension = if (row.hasValue(ExtensionTable.id)) {
|
||||
row
|
||||
} else {
|
||||
ExtensionTable
|
||||
.select { ExtensionTable.id eq row[SourceTable.extension] }
|
||||
.first()
|
||||
}
|
||||
val catalogueSource =
|
||||
GetCatalogueSource
|
||||
.getCatalogueSourceOrNull(row[SourceTable.id].value)
|
||||
?: return null
|
||||
val sourceExtension =
|
||||
if (row.hasValue(ExtensionTable.id)) {
|
||||
row
|
||||
} else {
|
||||
ExtensionTable
|
||||
.select { ExtensionTable.id eq row[SourceTable.extension] }
|
||||
.first()
|
||||
}
|
||||
|
||||
return SourceType(row, sourceExtension, catalogueSource)
|
||||
}
|
||||
@@ -103,11 +106,11 @@ data class SourceNodeList(
|
||||
override val nodes: List<SourceType>,
|
||||
override val edges: List<SourceEdge>,
|
||||
override val pageInfo: PageInfo,
|
||||
override val totalCount: Int
|
||||
override val totalCount: Int,
|
||||
) : NodeList() {
|
||||
data class SourceEdge(
|
||||
override val cursor: Cursor,
|
||||
override val node: SourceType
|
||||
override val node: SourceType,
|
||||
) : Edge()
|
||||
|
||||
companion object {
|
||||
@@ -115,13 +118,14 @@ data class SourceNodeList(
|
||||
return SourceNodeList(
|
||||
nodes = this,
|
||||
edges = getEdges(),
|
||||
pageInfo = PageInfo(
|
||||
hasNextPage = false,
|
||||
hasPreviousPage = false,
|
||||
startCursor = Cursor(0.toString()),
|
||||
endCursor = Cursor(lastIndex.toString())
|
||||
),
|
||||
totalCount = size
|
||||
pageInfo =
|
||||
PageInfo(
|
||||
hasNextPage = false,
|
||||
hasPreviousPage = false,
|
||||
startCursor = Cursor(0.toString()),
|
||||
endCursor = Cursor(lastIndex.toString()),
|
||||
),
|
||||
totalCount = size,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -130,12 +134,12 @@ data class SourceNodeList(
|
||||
return listOf(
|
||||
SourceEdge(
|
||||
cursor = Cursor("0"),
|
||||
node = first()
|
||||
node = first(),
|
||||
),
|
||||
SourceEdge(
|
||||
cursor = Cursor(lastIndex.toString()),
|
||||
node = last()
|
||||
)
|
||||
node = last(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -156,7 +160,7 @@ data class CheckBoxFilter(val name: String, val default: Boolean) : Filter
|
||||
enum class TriState {
|
||||
IGNORE,
|
||||
INCLUDE,
|
||||
EXCLUDE
|
||||
EXCLUDE,
|
||||
}
|
||||
|
||||
data class TriStateFilter(val name: String, val default: TriState) : Filter
|
||||
@@ -177,18 +181,20 @@ fun filterOf(filter: SourceFilter<*>): Filter {
|
||||
is SourceFilter.Select<*> -> SelectFilter(filter.name, filter.displayValues, filter.state)
|
||||
is SourceFilter.Text -> TextFilter(filter.name, filter.state)
|
||||
is SourceFilter.CheckBox -> CheckBoxFilter(filter.name, filter.state)
|
||||
is SourceFilter.TriState -> TriStateFilter(
|
||||
filter.name,
|
||||
when (filter.state) {
|
||||
SourceFilter.TriState.STATE_INCLUDE -> TriState.INCLUDE
|
||||
SourceFilter.TriState.STATE_EXCLUDE -> TriState.EXCLUDE
|
||||
else -> TriState.IGNORE
|
||||
}
|
||||
)
|
||||
is SourceFilter.Group<*> -> GroupFilter(
|
||||
filter.name,
|
||||
filter.state.map { filterOf(it as SourceFilter<*>) }
|
||||
)
|
||||
is SourceFilter.TriState ->
|
||||
TriStateFilter(
|
||||
filter.name,
|
||||
when (filter.state) {
|
||||
SourceFilter.TriState.STATE_INCLUDE -> TriState.INCLUDE
|
||||
SourceFilter.TriState.STATE_EXCLUDE -> TriState.EXCLUDE
|
||||
else -> TriState.IGNORE
|
||||
},
|
||||
)
|
||||
is SourceFilter.Group<*> ->
|
||||
GroupFilter(
|
||||
filter.name,
|
||||
filter.state.map { filterOf(it as SourceFilter<*>) },
|
||||
)
|
||||
is SourceFilter.Sort -> SortFilter(filter.name, filter.values.asList(), filter.state?.let(SortFilter::SortSelection))
|
||||
else -> throw RuntimeException("sealed class cannot have more subtypes!")
|
||||
}
|
||||
@@ -239,10 +245,13 @@ data class FilterChange(
|
||||
val checkBoxState: Boolean? = null,
|
||||
val triState: TriState? = null,
|
||||
val sortState: SortFilter.SortSelection? = null,
|
||||
val groupChange: FilterChange? = null
|
||||
val groupChange: FilterChange? = null,
|
||||
)
|
||||
|
||||
fun updateFilterList(source: CatalogueSource, changes: List<FilterChange>?): FilterList {
|
||||
fun updateFilterList(
|
||||
source: CatalogueSource,
|
||||
changes: List<FilterChange>?,
|
||||
): FilterList {
|
||||
val filterList = source.getFilterList()
|
||||
|
||||
changes?.forEach { change ->
|
||||
@@ -254,32 +263,42 @@ fun updateFilterList(source: CatalogueSource, changes: List<FilterChange>?): Fil
|
||||
// NOOP
|
||||
}
|
||||
is SourceFilter.Select<*> -> {
|
||||
filter.state = change.selectState ?: throw Exception("Expected select state change at position ${change.position}")
|
||||
filter.state = change.selectState
|
||||
?: throw Exception("Expected select state change at position ${change.position}")
|
||||
}
|
||||
is SourceFilter.Text -> {
|
||||
filter.state = change.textState ?: throw Exception("Expected text state change at position ${change.position}")
|
||||
filter.state = change.textState
|
||||
?: throw Exception("Expected text state change at position ${change.position}")
|
||||
}
|
||||
is SourceFilter.CheckBox -> {
|
||||
filter.state = change.checkBoxState ?: throw Exception("Expected checkbox state change at position ${change.position}")
|
||||
filter.state = change.checkBoxState
|
||||
?: throw Exception("Expected checkbox state change at position ${change.position}")
|
||||
}
|
||||
is SourceFilter.TriState -> {
|
||||
filter.state = change.triState?.ordinal ?: throw Exception("Expected tri state change at position ${change.position}")
|
||||
filter.state = change.triState?.ordinal
|
||||
?: throw Exception("Expected tri state change at position ${change.position}")
|
||||
}
|
||||
is SourceFilter.Group<*> -> {
|
||||
val groupChange = change.groupChange ?: throw Exception("Expected group change at position ${change.position}")
|
||||
val groupChange =
|
||||
change.groupChange
|
||||
?: throw Exception("Expected group change at position ${change.position}")
|
||||
|
||||
when (val groupFilter = filter.state[groupChange.position]) {
|
||||
is SourceFilter.CheckBox -> {
|
||||
groupFilter.state = groupChange.checkBoxState ?: throw Exception("Expected checkbox state change at position ${change.position}")
|
||||
groupFilter.state = groupChange.checkBoxState
|
||||
?: throw Exception("Expected checkbox state change at position ${change.position}")
|
||||
}
|
||||
is SourceFilter.TriState -> {
|
||||
groupFilter.state = groupChange.triState?.ordinal ?: throw Exception("Expected tri state change at position ${change.position}")
|
||||
groupFilter.state = groupChange.triState?.ordinal
|
||||
?: throw Exception("Expected tri state change at position ${change.position}")
|
||||
}
|
||||
is SourceFilter.Text -> {
|
||||
groupFilter.state = groupChange.textState ?: throw Exception("Expected text state change at position ${change.position}")
|
||||
groupFilter.state = groupChange.textState
|
||||
?: throw Exception("Expected text state change at position ${change.position}")
|
||||
}
|
||||
is SourceFilter.Select<*> -> {
|
||||
groupFilter.state = groupChange.selectState ?: throw Exception("Expected select state change at position ${change.position}")
|
||||
groupFilter.state = groupChange.selectState
|
||||
?: throw Exception("Expected select state change at position ${change.position}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -301,7 +320,7 @@ data class SwitchPreference(
|
||||
val summary: String?,
|
||||
val visible: Boolean,
|
||||
val currentValue: Boolean?,
|
||||
val default: Boolean
|
||||
val default: Boolean,
|
||||
) : Preference
|
||||
|
||||
data class CheckBoxPreference(
|
||||
@@ -310,7 +329,7 @@ data class CheckBoxPreference(
|
||||
val summary: String?,
|
||||
val visible: Boolean,
|
||||
val currentValue: Boolean?,
|
||||
val default: Boolean
|
||||
val default: Boolean,
|
||||
) : Preference
|
||||
|
||||
data class EditTextPreference(
|
||||
@@ -322,7 +341,7 @@ data class EditTextPreference(
|
||||
val default: String?,
|
||||
val dialogTitle: String?,
|
||||
val dialogMessage: String?,
|
||||
val text: String?
|
||||
val text: String?,
|
||||
) : Preference
|
||||
|
||||
data class ListPreference(
|
||||
@@ -333,7 +352,7 @@ data class ListPreference(
|
||||
val currentValue: String?,
|
||||
val default: String?,
|
||||
val entries: List<String>,
|
||||
val entryValues: List<String>
|
||||
val entryValues: List<String>,
|
||||
) : Preference
|
||||
|
||||
data class MultiSelectListPreference(
|
||||
@@ -346,60 +365,65 @@ data class MultiSelectListPreference(
|
||||
val dialogTitle: String?,
|
||||
val dialogMessage: String?,
|
||||
val entries: List<String>,
|
||||
val entryValues: List<String>
|
||||
val entryValues: List<String>,
|
||||
) : Preference
|
||||
|
||||
fun preferenceOf(preference: SourcePreference): Preference {
|
||||
return when (preference) {
|
||||
is SourceSwitchPreference -> SwitchPreference(
|
||||
preference.key,
|
||||
preference.title.toString(),
|
||||
preference.summary?.toString(),
|
||||
preference.visible,
|
||||
preference.currentValue as Boolean,
|
||||
preference.defaultValue as Boolean,
|
||||
)
|
||||
is SourceCheckBoxPreference -> CheckBoxPreference(
|
||||
preference.key,
|
||||
preference.title.toString(),
|
||||
preference.summary?.toString(),
|
||||
preference.visible,
|
||||
preference.currentValue as Boolean,
|
||||
preference.defaultValue as Boolean
|
||||
)
|
||||
is SourceEditTextPreference -> EditTextPreference(
|
||||
preference.key,
|
||||
preference.title?.toString(),
|
||||
preference.summary?.toString(),
|
||||
preference.visible,
|
||||
(preference.currentValue as CharSequence?)?.toString(),
|
||||
(preference.defaultValue as CharSequence?)?.toString(),
|
||||
preference.dialogTitle?.toString(),
|
||||
preference.dialogMessage?.toString(),
|
||||
preference.text
|
||||
)
|
||||
is SourceListPreference -> ListPreference(
|
||||
preference.key,
|
||||
preference.title?.toString(),
|
||||
preference.summary?.toString(),
|
||||
preference.visible,
|
||||
(preference.currentValue as CharSequence?)?.toString(),
|
||||
(preference.defaultValue as CharSequence?)?.toString(),
|
||||
preference.entries.map { it.toString() },
|
||||
preference.entryValues.map { it.toString() }
|
||||
)
|
||||
is SourceMultiSelectListPreference -> MultiSelectListPreference(
|
||||
preference.key,
|
||||
preference.title?.toString(),
|
||||
preference.summary?.toString(),
|
||||
preference.visible,
|
||||
(preference.currentValue as Collection<*>?)?.map { it.toString() },
|
||||
(preference.defaultValue as Collection<*>?)?.map { it.toString() },
|
||||
preference.dialogTitle?.toString(),
|
||||
preference.dialogMessage?.toString(),
|
||||
preference.entries.map { it.toString() },
|
||||
preference.entryValues.map { it.toString() }
|
||||
)
|
||||
is SourceSwitchPreference ->
|
||||
SwitchPreference(
|
||||
preference.key,
|
||||
preference.title.toString(),
|
||||
preference.summary?.toString(),
|
||||
preference.visible,
|
||||
preference.currentValue as Boolean,
|
||||
preference.defaultValue as Boolean,
|
||||
)
|
||||
is SourceCheckBoxPreference ->
|
||||
CheckBoxPreference(
|
||||
preference.key,
|
||||
preference.title.toString(),
|
||||
preference.summary?.toString(),
|
||||
preference.visible,
|
||||
preference.currentValue as Boolean,
|
||||
preference.defaultValue as Boolean,
|
||||
)
|
||||
is SourceEditTextPreference ->
|
||||
EditTextPreference(
|
||||
preference.key,
|
||||
preference.title?.toString(),
|
||||
preference.summary?.toString(),
|
||||
preference.visible,
|
||||
(preference.currentValue as CharSequence?)?.toString(),
|
||||
(preference.defaultValue as CharSequence?)?.toString(),
|
||||
preference.dialogTitle?.toString(),
|
||||
preference.dialogMessage?.toString(),
|
||||
preference.text,
|
||||
)
|
||||
is SourceListPreference ->
|
||||
ListPreference(
|
||||
preference.key,
|
||||
preference.title?.toString(),
|
||||
preference.summary?.toString(),
|
||||
preference.visible,
|
||||
(preference.currentValue as CharSequence?)?.toString(),
|
||||
(preference.defaultValue as CharSequence?)?.toString(),
|
||||
preference.entries.map { it.toString() },
|
||||
preference.entryValues.map { it.toString() },
|
||||
)
|
||||
is SourceMultiSelectListPreference ->
|
||||
MultiSelectListPreference(
|
||||
preference.key,
|
||||
preference.title?.toString(),
|
||||
preference.summary?.toString(),
|
||||
preference.visible,
|
||||
(preference.currentValue as Collection<*>?)?.map { it.toString() },
|
||||
(preference.defaultValue as Collection<*>?)?.map { it.toString() },
|
||||
preference.dialogTitle?.toString(),
|
||||
preference.dialogMessage?.toString(),
|
||||
preference.entries.map { it.toString() },
|
||||
preference.entryValues.map { it.toString() },
|
||||
)
|
||||
else -> throw RuntimeException("sealed class cannot have more subtypes!")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ class UpdateStatus(
|
||||
val runningJobs: UpdateStatusType,
|
||||
val completeJobs: UpdateStatusType,
|
||||
val failedJobs: UpdateStatusType,
|
||||
val skippedJobs: UpdateStatusType
|
||||
val skippedJobs: UpdateStatusType,
|
||||
) {
|
||||
constructor(status: UpdateStatus) : this(
|
||||
isRunning = status.running,
|
||||
@@ -26,13 +26,13 @@ class UpdateStatus(
|
||||
runningJobs = UpdateStatusType(status.mangaStatusMap[JobStatus.RUNNING]?.map { it.id }.orEmpty()),
|
||||
completeJobs = UpdateStatusType(status.mangaStatusMap[JobStatus.COMPLETE]?.map { it.id }.orEmpty()),
|
||||
failedJobs = UpdateStatusType(status.mangaStatusMap[JobStatus.FAILED]?.map { it.id }.orEmpty()),
|
||||
skippedJobs = UpdateStatusType(status.mangaStatusMap[JobStatus.SKIPPED]?.map { it.id }.orEmpty())
|
||||
skippedJobs = UpdateStatusType(status.mangaStatusMap[JobStatus.SKIPPED]?.map { it.id }.orEmpty()),
|
||||
)
|
||||
}
|
||||
|
||||
class UpdateStatusCategoryType(
|
||||
@get:GraphQLIgnore
|
||||
val categoryIds: List<Int>
|
||||
val categoryIds: List<Int>,
|
||||
) {
|
||||
fun categories(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture<CategoryNodeList> {
|
||||
return dataFetchingEnvironment.getValueFromDataLoader("CategoryForIdsDataLoader", categoryIds)
|
||||
@@ -41,7 +41,7 @@ class UpdateStatusCategoryType(
|
||||
|
||||
class UpdateStatusType(
|
||||
@get:GraphQLIgnore
|
||||
val mangaIds: List<Int>
|
||||
val mangaIds: List<Int>,
|
||||
) {
|
||||
fun mangas(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture<MangaNodeList> {
|
||||
return dataFetchingEnvironment.getValueFromDataLoader<List<Int>, MangaNodeList>("MangaForIdsDataLoader", mangaIds)
|
||||
|
||||
@@ -3,18 +3,18 @@ package suwayomi.tachidesk.graphql.types
|
||||
data class WebUIUpdateInfo(
|
||||
val channel: String,
|
||||
val tag: String,
|
||||
val updateAvailable: Boolean
|
||||
val updateAvailable: Boolean,
|
||||
)
|
||||
|
||||
enum class UpdateState {
|
||||
STOPPED,
|
||||
DOWNLOADING,
|
||||
FINISHED,
|
||||
ERROR
|
||||
ERROR,
|
||||
}
|
||||
|
||||
data class WebUIUpdateStatus(
|
||||
val info: WebUIUpdateInfo,
|
||||
val state: UpdateState,
|
||||
val progress: Int
|
||||
val progress: Int,
|
||||
)
|
||||
|
||||
@@ -17,154 +17,163 @@ import suwayomi.tachidesk.server.util.withOperation
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
object BackupController {
|
||||
|
||||
/** expects a Tachiyomi protobuf backup in the body */
|
||||
val protobufImport = handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Restore a backup")
|
||||
description("Expects a Tachiyomi protobuf backup in the body")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx ->
|
||||
ctx.future(
|
||||
future {
|
||||
ProtoBackupImport.performRestore(ctx.bodyAsInputStream())
|
||||
val protobufImport =
|
||||
handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Restore a backup")
|
||||
description("Expects a Tachiyomi protobuf backup in the body")
|
||||
}
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
}
|
||||
)
|
||||
},
|
||||
behaviorOf = { ctx ->
|
||||
ctx.future(
|
||||
future {
|
||||
ProtoBackupImport.performRestore(ctx.bodyAsInputStream())
|
||||
},
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
},
|
||||
)
|
||||
|
||||
/** expects a Tachiyomi protobuf backup as a file upload, the file must be named "backup.proto.gz" */
|
||||
val protobufImportFile = handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Restore a backup file")
|
||||
description("Expects a Tachiyomi protobuf backup as a file upload, the file must be named \"backup.proto.gz\"")
|
||||
}
|
||||
uploadedFile("backup.proto.gz") {
|
||||
it.description("Protobuf backup")
|
||||
it.required(true)
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx ->
|
||||
// TODO: rewrite this with ctx.uploadedFiles(), don't call the multipart field "backup.proto.gz"
|
||||
ctx.future(
|
||||
future {
|
||||
ProtoBackupImport.performRestore(ctx.uploadedFile("backup.proto.gz")!!.content)
|
||||
val protobufImportFile =
|
||||
handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Restore a backup file")
|
||||
description("Expects a Tachiyomi protobuf backup as a file upload, the file must be named \"backup.proto.gz\"")
|
||||
}
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
httpCode(HttpCode.NOT_FOUND)
|
||||
}
|
||||
)
|
||||
uploadedFile("backup.proto.gz") {
|
||||
it.description("Protobuf backup")
|
||||
it.required(true)
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx ->
|
||||
// TODO: rewrite this with ctx.uploadedFiles(), don't call the multipart field "backup.proto.gz"
|
||||
ctx.future(
|
||||
future {
|
||||
ProtoBackupImport.performRestore(ctx.uploadedFile("backup.proto.gz")!!.content)
|
||||
},
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
httpCode(HttpCode.NOT_FOUND)
|
||||
},
|
||||
)
|
||||
|
||||
/** returns a Tachiyomi protobuf backup created from the current database as a body */
|
||||
val protobufExport = handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Create a backup")
|
||||
description("Returns a Tachiyomi protobuf backup created from the current database as a body")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx ->
|
||||
ctx.contentType("application/octet-stream")
|
||||
ctx.future(
|
||||
future {
|
||||
ProtoBackupExport.createBackup(
|
||||
BackupFlags(
|
||||
includeManga = true,
|
||||
includeCategories = true,
|
||||
includeChapters = true,
|
||||
includeTracking = true,
|
||||
includeHistory = true
|
||||
)
|
||||
)
|
||||
val protobufExport =
|
||||
handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Create a backup")
|
||||
description("Returns a Tachiyomi protobuf backup created from the current database as a body")
|
||||
}
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
stream(HttpCode.OK)
|
||||
}
|
||||
)
|
||||
},
|
||||
behaviorOf = { ctx ->
|
||||
ctx.contentType("application/octet-stream")
|
||||
ctx.future(
|
||||
future {
|
||||
ProtoBackupExport.createBackup(
|
||||
BackupFlags(
|
||||
includeManga = true,
|
||||
includeCategories = true,
|
||||
includeChapters = true,
|
||||
includeTracking = true,
|
||||
includeHistory = true,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
stream(HttpCode.OK)
|
||||
},
|
||||
)
|
||||
|
||||
/** returns a Tachiyomi protobuf backup created from the current database as a file */
|
||||
val protobufExportFile = handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Create a backup file")
|
||||
description("Returns a Tachiyomi protobuf backup created from the current database as a file")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx ->
|
||||
ctx.contentType("application/octet-stream")
|
||||
|
||||
ctx.header("Content-Disposition", """attachment; filename="${ProtoBackupExport.getBackupFilename()}"""")
|
||||
ctx.future(
|
||||
future {
|
||||
ProtoBackupExport.createBackup(
|
||||
BackupFlags(
|
||||
includeManga = true,
|
||||
includeCategories = true,
|
||||
includeChapters = true,
|
||||
includeTracking = true,
|
||||
includeHistory = true
|
||||
)
|
||||
)
|
||||
val protobufExportFile =
|
||||
handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Create a backup file")
|
||||
description("Returns a Tachiyomi protobuf backup created from the current database as a file")
|
||||
}
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
stream(HttpCode.OK)
|
||||
}
|
||||
)
|
||||
},
|
||||
behaviorOf = { ctx ->
|
||||
ctx.contentType("application/octet-stream")
|
||||
|
||||
ctx.header("Content-Disposition", """attachment; filename="${ProtoBackupExport.getBackupFilename()}"""")
|
||||
ctx.future(
|
||||
future {
|
||||
ProtoBackupExport.createBackup(
|
||||
BackupFlags(
|
||||
includeManga = true,
|
||||
includeCategories = true,
|
||||
includeChapters = true,
|
||||
includeTracking = true,
|
||||
includeHistory = true,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
stream(HttpCode.OK)
|
||||
},
|
||||
)
|
||||
|
||||
/** Reports missing sources and trackers, expects a Tachiyomi protobuf backup in the body */
|
||||
val protobufValidate = handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Validate a backup")
|
||||
description("Reports missing sources and trackers, expects a Tachiyomi protobuf backup in the body")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx ->
|
||||
ctx.future(
|
||||
future {
|
||||
ProtoBackupValidator.validate(ctx.bodyAsInputStream())
|
||||
val protobufValidate =
|
||||
handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Validate a backup")
|
||||
description("Reports missing sources and trackers, expects a Tachiyomi protobuf backup in the body")
|
||||
}
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
json<ProtoBackupValidator.ValidationResult>(HttpCode.OK)
|
||||
}
|
||||
)
|
||||
},
|
||||
behaviorOf = { ctx ->
|
||||
ctx.future(
|
||||
future {
|
||||
ProtoBackupValidator.validate(ctx.bodyAsInputStream())
|
||||
},
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
json<ProtoBackupValidator.ValidationResult>(HttpCode.OK)
|
||||
},
|
||||
)
|
||||
|
||||
/** Reports missing sources and trackers, expects a Tachiyomi protobuf backup as a file upload, the file must be named "backup.proto.gz" */
|
||||
val protobufValidateFile = handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Validate a backup file")
|
||||
description("Reports missing sources and trackers, expects a Tachiyomi protobuf backup as a file upload, the file must be named \"backup.proto.gz\"")
|
||||
}
|
||||
uploadedFile("backup.proto.gz") {
|
||||
it.description("Protobuf backup")
|
||||
it.required(true)
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx ->
|
||||
ctx.future(
|
||||
future {
|
||||
ProtoBackupValidator.validate(ctx.uploadedFile("backup.proto.gz")!!.content)
|
||||
val protobufValidateFile =
|
||||
handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Validate a backup file")
|
||||
description(
|
||||
"Reports missing sources and trackers, " +
|
||||
"expects a Tachiyomi protobuf backup as a file upload, " +
|
||||
"the file must be named \"backup.proto.gz\"",
|
||||
)
|
||||
}
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
json<ProtoBackupValidator.ValidationResult>(HttpCode.OK)
|
||||
}
|
||||
)
|
||||
uploadedFile("backup.proto.gz") {
|
||||
it.description("Protobuf backup")
|
||||
it.required(true)
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx ->
|
||||
ctx.future(
|
||||
future {
|
||||
ProtoBackupValidator.validate(ctx.uploadedFile("backup.proto.gz")!!.content)
|
||||
},
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
json<ProtoBackupValidator.ValidationResult>(HttpCode.OK)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -19,136 +19,143 @@ import suwayomi.tachidesk.server.util.withOperation
|
||||
|
||||
object CategoryController {
|
||||
/** category list */
|
||||
val categoryList = handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Category list")
|
||||
description("get a list of categories")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx ->
|
||||
ctx.json(Category.getCategoryList())
|
||||
},
|
||||
withResults = {
|
||||
json<Array<CategoryDataClass>>(HttpCode.OK)
|
||||
}
|
||||
)
|
||||
val categoryList =
|
||||
handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Category list")
|
||||
description("get a list of categories")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx ->
|
||||
ctx.json(Category.getCategoryList())
|
||||
},
|
||||
withResults = {
|
||||
json<Array<CategoryDataClass>>(HttpCode.OK)
|
||||
},
|
||||
)
|
||||
|
||||
/** category create */
|
||||
val categoryCreate = handler(
|
||||
formParam<String>("name"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Category create")
|
||||
description("Create a category")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, name ->
|
||||
if (Category.createCategory(name) != -1) {
|
||||
ctx.status(200)
|
||||
} else {
|
||||
ctx.status(HttpCode.BAD_REQUEST)
|
||||
}
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
httpCode(HttpCode.BAD_REQUEST)
|
||||
}
|
||||
)
|
||||
val categoryCreate =
|
||||
handler(
|
||||
formParam<String>("name"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Category create")
|
||||
description("Create a category")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, name ->
|
||||
if (Category.createCategory(name) != -1) {
|
||||
ctx.status(200)
|
||||
} else {
|
||||
ctx.status(HttpCode.BAD_REQUEST)
|
||||
}
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
httpCode(HttpCode.BAD_REQUEST)
|
||||
},
|
||||
)
|
||||
|
||||
/** category modification */
|
||||
val categoryModify = handler(
|
||||
pathParam<Int>("categoryId"),
|
||||
formParam<String?>("name"),
|
||||
formParam<Boolean?>("default"),
|
||||
formParam<Int?>("includeInUpdate"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Category modify")
|
||||
description("Modify a category")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, categoryId, name, isDefault, includeInUpdate ->
|
||||
Category.updateCategory(categoryId, name, isDefault, includeInUpdate)
|
||||
ctx.status(200)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
}
|
||||
)
|
||||
val categoryModify =
|
||||
handler(
|
||||
pathParam<Int>("categoryId"),
|
||||
formParam<String?>("name"),
|
||||
formParam<Boolean?>("default"),
|
||||
formParam<Int?>("includeInUpdate"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Category modify")
|
||||
description("Modify a category")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, categoryId, name, isDefault, includeInUpdate ->
|
||||
Category.updateCategory(categoryId, name, isDefault, includeInUpdate)
|
||||
ctx.status(200)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
},
|
||||
)
|
||||
|
||||
/** category delete */
|
||||
val categoryDelete = handler(
|
||||
pathParam<Int>("categoryId"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Category delete")
|
||||
description("Delete a category")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, categoryId ->
|
||||
Category.removeCategory(categoryId)
|
||||
ctx.status(200)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
}
|
||||
)
|
||||
val categoryDelete =
|
||||
handler(
|
||||
pathParam<Int>("categoryId"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Category delete")
|
||||
description("Delete a category")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, categoryId ->
|
||||
Category.removeCategory(categoryId)
|
||||
ctx.status(200)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
},
|
||||
)
|
||||
|
||||
/** returns the manga list associated with a category */
|
||||
val categoryMangas = handler(
|
||||
pathParam<Int>("categoryId"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Category manga")
|
||||
description("Returns the manga list associated with a category")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, categoryId ->
|
||||
ctx.json(CategoryManga.getCategoryMangaList(categoryId))
|
||||
},
|
||||
withResults = {
|
||||
json<Array<MangaDataClass>>(HttpCode.OK)
|
||||
}
|
||||
)
|
||||
val categoryMangas =
|
||||
handler(
|
||||
pathParam<Int>("categoryId"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Category manga")
|
||||
description("Returns the manga list associated with a category")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, categoryId ->
|
||||
ctx.json(CategoryManga.getCategoryMangaList(categoryId))
|
||||
},
|
||||
withResults = {
|
||||
json<Array<MangaDataClass>>(HttpCode.OK)
|
||||
},
|
||||
)
|
||||
|
||||
/** category re-ordering */
|
||||
val categoryReorder = handler(
|
||||
formParam<Int>("from"),
|
||||
formParam<Int>("to"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Category re-ordering")
|
||||
description("Re-order a category")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, from, to ->
|
||||
Category.reorderCategory(from, to)
|
||||
ctx.status(200)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
}
|
||||
)
|
||||
val categoryReorder =
|
||||
handler(
|
||||
formParam<Int>("from"),
|
||||
formParam<Int>("to"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Category re-ordering")
|
||||
description("Re-order a category")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, from, to ->
|
||||
Category.reorderCategory(from, to)
|
||||
ctx.status(200)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
},
|
||||
)
|
||||
|
||||
/** used to modify a category's meta parameters */
|
||||
val meta = handler(
|
||||
pathParam<Int>("categoryId"),
|
||||
formParam<String>("key"),
|
||||
formParam<String>("value"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Add meta data to category")
|
||||
description("A simple Key-Value storage in the manga object, you can set values for whatever you want inside it.")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, categoryId, key, value ->
|
||||
Category.modifyMeta(categoryId, key, value)
|
||||
ctx.status(200)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
httpCode(HttpCode.NOT_FOUND)
|
||||
}
|
||||
)
|
||||
val meta =
|
||||
handler(
|
||||
pathParam<Int>("categoryId"),
|
||||
formParam<String>("key"),
|
||||
formParam<String>("value"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Add meta data to category")
|
||||
description("A simple Key-Value storage in the manga object, you can set values for whatever you want inside it.")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, categoryId, key, value ->
|
||||
Category.modifyMeta(categoryId, key, value)
|
||||
ctx.status(200)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
httpCode(HttpCode.NOT_FOUND)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -39,159 +39,167 @@ object DownloadController {
|
||||
}
|
||||
|
||||
/** Start the downloader */
|
||||
val start = handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Downloader start")
|
||||
description("Start the downloader")
|
||||
}
|
||||
},
|
||||
behaviorOf = {
|
||||
DownloadManager.start()
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
}
|
||||
)
|
||||
val start =
|
||||
handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Downloader start")
|
||||
description("Start the downloader")
|
||||
}
|
||||
},
|
||||
behaviorOf = {
|
||||
DownloadManager.start()
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
},
|
||||
)
|
||||
|
||||
/** Stop the downloader */
|
||||
val stop = handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Downloader stop")
|
||||
description("Stop the downloader")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx ->
|
||||
ctx.future(
|
||||
future { DownloadManager.stop() }
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
}
|
||||
)
|
||||
val stop =
|
||||
handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Downloader stop")
|
||||
description("Stop the downloader")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx ->
|
||||
ctx.future(
|
||||
future { DownloadManager.stop() },
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
},
|
||||
)
|
||||
|
||||
/** clear download queue */
|
||||
val clear = handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Downloader clear")
|
||||
description("Clear download queue")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx ->
|
||||
ctx.future(
|
||||
future { DownloadManager.clear() }
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
}
|
||||
)
|
||||
val clear =
|
||||
handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Downloader clear")
|
||||
description("Clear download queue")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx ->
|
||||
ctx.future(
|
||||
future { DownloadManager.clear() },
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
},
|
||||
)
|
||||
|
||||
/** Queue single chapter for download */
|
||||
val queueChapter = handler(
|
||||
pathParam<Int>("chapterIndex"),
|
||||
pathParam<Int>("mangaId"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Downloader add single chapter")
|
||||
description("Queue single chapter for download")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, chapterIndex, mangaId ->
|
||||
ctx.future(
|
||||
future {
|
||||
DownloadManager.enqueueWithChapterIndex(mangaId, chapterIndex)
|
||||
val queueChapter =
|
||||
handler(
|
||||
pathParam<Int>("chapterIndex"),
|
||||
pathParam<Int>("mangaId"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Downloader add single chapter")
|
||||
description("Queue single chapter for download")
|
||||
}
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
httpCode(HttpCode.NOT_FOUND)
|
||||
}
|
||||
)
|
||||
},
|
||||
behaviorOf = { ctx, chapterIndex, mangaId ->
|
||||
ctx.future(
|
||||
future {
|
||||
DownloadManager.enqueueWithChapterIndex(mangaId, chapterIndex)
|
||||
},
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
httpCode(HttpCode.NOT_FOUND)
|
||||
},
|
||||
)
|
||||
|
||||
val queueChapters = handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Downloader add multiple chapters")
|
||||
description("Queue multiple chapters for download")
|
||||
}
|
||||
body<EnqueueInput>()
|
||||
},
|
||||
behaviorOf = { ctx ->
|
||||
val inputs = json.decodeFromString<EnqueueInput>(ctx.body())
|
||||
ctx.future(
|
||||
future {
|
||||
DownloadManager.enqueue(inputs)
|
||||
val queueChapters =
|
||||
handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Downloader add multiple chapters")
|
||||
description("Queue multiple chapters for download")
|
||||
}
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
}
|
||||
)
|
||||
body<EnqueueInput>()
|
||||
},
|
||||
behaviorOf = { ctx ->
|
||||
val inputs = json.decodeFromString<EnqueueInput>(ctx.body())
|
||||
ctx.future(
|
||||
future {
|
||||
DownloadManager.enqueue(inputs)
|
||||
},
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
},
|
||||
)
|
||||
|
||||
/** delete multiple chapters from download queue */
|
||||
val unqueueChapters = handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Downloader remove multiple downloads")
|
||||
description("Remove multiple chapters downloads from queue")
|
||||
}
|
||||
body<EnqueueInput>()
|
||||
},
|
||||
behaviorOf = { ctx ->
|
||||
val input = json.decodeFromString<EnqueueInput>(ctx.body())
|
||||
ctx.future(
|
||||
future {
|
||||
DownloadManager.dequeue(input)
|
||||
val unqueueChapters =
|
||||
handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Downloader remove multiple downloads")
|
||||
description("Remove multiple chapters downloads from queue")
|
||||
}
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
}
|
||||
)
|
||||
body<EnqueueInput>()
|
||||
},
|
||||
behaviorOf = { ctx ->
|
||||
val input = json.decodeFromString<EnqueueInput>(ctx.body())
|
||||
ctx.future(
|
||||
future {
|
||||
DownloadManager.dequeue(input)
|
||||
},
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
},
|
||||
)
|
||||
|
||||
/** delete chapter from download queue */
|
||||
val unqueueChapter = handler(
|
||||
pathParam<Int>("chapterIndex"),
|
||||
pathParam<Int>("mangaId"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Downloader remove chapter")
|
||||
description("Delete chapter from download queue")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, chapterIndex, mangaId ->
|
||||
DownloadManager.dequeue(chapterIndex, mangaId)
|
||||
val unqueueChapter =
|
||||
handler(
|
||||
pathParam<Int>("chapterIndex"),
|
||||
pathParam<Int>("mangaId"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Downloader remove chapter")
|
||||
description("Delete chapter from download queue")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, chapterIndex, mangaId ->
|
||||
DownloadManager.dequeue(chapterIndex, mangaId)
|
||||
|
||||
ctx.status(200)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
}
|
||||
)
|
||||
ctx.status(200)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
},
|
||||
)
|
||||
|
||||
/** clear download queue */
|
||||
val reorderChapter = handler(
|
||||
pathParam<Int>("chapterIndex"),
|
||||
pathParam<Int>("mangaId"),
|
||||
pathParam<Int>("to"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Downloader reorder chapter")
|
||||
description("Reorder chapter in download queue")
|
||||
}
|
||||
},
|
||||
behaviorOf = { _, chapterIndex, mangaId, to ->
|
||||
DownloadManager.reorder(chapterIndex, mangaId, to)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
}
|
||||
)
|
||||
val reorderChapter =
|
||||
handler(
|
||||
pathParam<Int>("chapterIndex"),
|
||||
pathParam<Int>("mangaId"),
|
||||
pathParam<Int>("to"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Downloader reorder chapter")
|
||||
description("Reorder chapter in download queue")
|
||||
}
|
||||
},
|
||||
behaviorOf = { _, chapterIndex, mangaId, to ->
|
||||
DownloadManager.reorder(chapterIndex, mangaId, to)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -21,143 +21,149 @@ object ExtensionController {
|
||||
private val logger = KotlinLogging.logger {}
|
||||
|
||||
/** list all extensions */
|
||||
val list = handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Extension list")
|
||||
description("List all extensions")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx ->
|
||||
ctx.future(
|
||||
future {
|
||||
ExtensionsList.getExtensionList()
|
||||
val list =
|
||||
handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Extension list")
|
||||
description("List all extensions")
|
||||
}
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
json<Array<ExtensionDataClass>>(HttpCode.OK)
|
||||
}
|
||||
)
|
||||
},
|
||||
behaviorOf = { ctx ->
|
||||
ctx.future(
|
||||
future {
|
||||
ExtensionsList.getExtensionList()
|
||||
},
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
json<Array<ExtensionDataClass>>(HttpCode.OK)
|
||||
},
|
||||
)
|
||||
|
||||
/** install extension identified with "pkgName" */
|
||||
val install = handler(
|
||||
pathParam<String>("pkgName"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Extension install")
|
||||
description("install extension identified with \"pkgName\"")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, pkgName ->
|
||||
ctx.future(
|
||||
future {
|
||||
Extension.installExtension(pkgName)
|
||||
val install =
|
||||
handler(
|
||||
pathParam<String>("pkgName"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Extension install")
|
||||
description("install extension identified with \"pkgName\"")
|
||||
}
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.CREATED)
|
||||
httpCode(HttpCode.FOUND)
|
||||
httpCode(HttpCode.INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
)
|
||||
},
|
||||
behaviorOf = { ctx, pkgName ->
|
||||
ctx.future(
|
||||
future {
|
||||
Extension.installExtension(pkgName)
|
||||
},
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.CREATED)
|
||||
httpCode(HttpCode.FOUND)
|
||||
httpCode(HttpCode.INTERNAL_SERVER_ERROR)
|
||||
},
|
||||
)
|
||||
|
||||
/** install the uploaded apk file */
|
||||
val installFile = handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Extension install apk")
|
||||
description("Install the uploaded apk file")
|
||||
}
|
||||
uploadedFile("file") {
|
||||
it.description("Extension apk")
|
||||
it.required(true)
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx ->
|
||||
val uploadedFile = ctx.uploadedFile("file")!!
|
||||
logger.debug { "Uploaded extension file name: " + uploadedFile.filename }
|
||||
|
||||
ctx.future(
|
||||
future {
|
||||
Extension.installExternalExtension(uploadedFile.content, uploadedFile.filename)
|
||||
val installFile =
|
||||
handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Extension install apk")
|
||||
description("Install the uploaded apk file")
|
||||
}
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.CREATED)
|
||||
httpCode(HttpCode.FOUND)
|
||||
httpCode(HttpCode.INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
)
|
||||
uploadedFile("file") {
|
||||
it.description("Extension apk")
|
||||
it.required(true)
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx ->
|
||||
val uploadedFile = ctx.uploadedFile("file")!!
|
||||
logger.debug { "Uploaded extension file name: " + uploadedFile.filename }
|
||||
|
||||
ctx.future(
|
||||
future {
|
||||
Extension.installExternalExtension(uploadedFile.content, uploadedFile.filename)
|
||||
},
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.CREATED)
|
||||
httpCode(HttpCode.FOUND)
|
||||
httpCode(HttpCode.INTERNAL_SERVER_ERROR)
|
||||
},
|
||||
)
|
||||
|
||||
/** update extension identified with "pkgName" */
|
||||
val update = handler(
|
||||
pathParam<String>("pkgName"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Extension update")
|
||||
description("Update extension identified with \"pkgName\"")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, pkgName ->
|
||||
ctx.future(
|
||||
future {
|
||||
Extension.updateExtension(pkgName)
|
||||
val update =
|
||||
handler(
|
||||
pathParam<String>("pkgName"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Extension update")
|
||||
description("Update extension identified with \"pkgName\"")
|
||||
}
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.CREATED)
|
||||
httpCode(HttpCode.FOUND)
|
||||
httpCode(HttpCode.NOT_FOUND)
|
||||
httpCode(HttpCode.INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
)
|
||||
},
|
||||
behaviorOf = { ctx, pkgName ->
|
||||
ctx.future(
|
||||
future {
|
||||
Extension.updateExtension(pkgName)
|
||||
},
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.CREATED)
|
||||
httpCode(HttpCode.FOUND)
|
||||
httpCode(HttpCode.NOT_FOUND)
|
||||
httpCode(HttpCode.INTERNAL_SERVER_ERROR)
|
||||
},
|
||||
)
|
||||
|
||||
/** uninstall extension identified with "pkgName" */
|
||||
val uninstall = handler(
|
||||
pathParam<String>("pkgName"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Extension uninstall")
|
||||
description("Uninstall extension identified with \"pkgName\"")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, pkgName ->
|
||||
Extension.uninstallExtension(pkgName)
|
||||
ctx.status(200)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.CREATED)
|
||||
httpCode(HttpCode.FOUND)
|
||||
httpCode(HttpCode.NOT_FOUND)
|
||||
httpCode(HttpCode.INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
)
|
||||
val uninstall =
|
||||
handler(
|
||||
pathParam<String>("pkgName"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Extension uninstall")
|
||||
description("Uninstall extension identified with \"pkgName\"")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, pkgName ->
|
||||
Extension.uninstallExtension(pkgName)
|
||||
ctx.status(200)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.CREATED)
|
||||
httpCode(HttpCode.FOUND)
|
||||
httpCode(HttpCode.NOT_FOUND)
|
||||
httpCode(HttpCode.INTERNAL_SERVER_ERROR)
|
||||
},
|
||||
)
|
||||
|
||||
/** icon for extension named `apkName` */
|
||||
val icon = handler(
|
||||
pathParam<String>("apkName"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Extension icon")
|
||||
description("Icon for extension named `apkName`")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, apkName ->
|
||||
ctx.future(
|
||||
future { Extension.getExtensionIcon(apkName) }
|
||||
.thenApply {
|
||||
ctx.header("content-type", it.second)
|
||||
it.first
|
||||
}
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
image(HttpCode.OK)
|
||||
httpCode(HttpCode.NOT_FOUND)
|
||||
}
|
||||
)
|
||||
val icon =
|
||||
handler(
|
||||
pathParam<String>("apkName"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Extension icon")
|
||||
description("Icon for extension named `apkName`")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, apkName ->
|
||||
ctx.future(
|
||||
future { Extension.getExtensionIcon(apkName) }
|
||||
.thenApply {
|
||||
ctx.header("content-type", it.second)
|
||||
it.first
|
||||
},
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
image(HttpCode.OK)
|
||||
httpCode(HttpCode.NOT_FOUND)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -32,367 +32,390 @@ import kotlin.time.Duration.Companion.days
|
||||
object MangaController {
|
||||
private val json by DI.global.instance<Json>()
|
||||
|
||||
val retrieve = handler(
|
||||
pathParam<Int>("mangaId"),
|
||||
queryParam("onlineFetch", false),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Get manga info")
|
||||
description("Get a manga from the database using a specific id.")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, mangaId, onlineFetch ->
|
||||
ctx.future(
|
||||
future {
|
||||
Manga.getManga(mangaId, onlineFetch)
|
||||
val retrieve =
|
||||
handler(
|
||||
pathParam<Int>("mangaId"),
|
||||
queryParam("onlineFetch", false),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Get manga info")
|
||||
description("Get a manga from the database using a specific id.")
|
||||
}
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
json<MangaDataClass>(HttpCode.OK)
|
||||
httpCode(HttpCode.NOT_FOUND)
|
||||
}
|
||||
)
|
||||
},
|
||||
behaviorOf = { ctx, mangaId, onlineFetch ->
|
||||
ctx.future(
|
||||
future {
|
||||
Manga.getManga(mangaId, onlineFetch)
|
||||
},
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
json<MangaDataClass>(HttpCode.OK)
|
||||
httpCode(HttpCode.NOT_FOUND)
|
||||
},
|
||||
)
|
||||
|
||||
/** get manga info with all data filled in */
|
||||
val retrieveFull = handler(
|
||||
pathParam<Int>("mangaId"),
|
||||
queryParam("onlineFetch", false),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Get manga info with all data filled in")
|
||||
description("Get a manga from the database using a specific id.")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, mangaId, onlineFetch ->
|
||||
ctx.future(
|
||||
future {
|
||||
Manga.getMangaFull(mangaId, onlineFetch)
|
||||
val retrieveFull =
|
||||
handler(
|
||||
pathParam<Int>("mangaId"),
|
||||
queryParam("onlineFetch", false),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Get manga info with all data filled in")
|
||||
description("Get a manga from the database using a specific id.")
|
||||
}
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
json<MangaDataClass>(HttpCode.OK)
|
||||
httpCode(HttpCode.NOT_FOUND)
|
||||
}
|
||||
)
|
||||
},
|
||||
behaviorOf = { ctx, mangaId, onlineFetch ->
|
||||
ctx.future(
|
||||
future {
|
||||
Manga.getMangaFull(mangaId, onlineFetch)
|
||||
},
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
json<MangaDataClass>(HttpCode.OK)
|
||||
httpCode(HttpCode.NOT_FOUND)
|
||||
},
|
||||
)
|
||||
|
||||
/** manga thumbnail */
|
||||
val thumbnail = handler(
|
||||
pathParam<Int>("mangaId"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Get a manga thumbnail")
|
||||
description("Get a manga thumbnail from the source or the cache.")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, mangaId ->
|
||||
ctx.future(
|
||||
future { Manga.getMangaThumbnail(mangaId) }
|
||||
.thenApply {
|
||||
ctx.header("content-type", it.second)
|
||||
val httpCacheSeconds = 1.days.inWholeSeconds
|
||||
ctx.header("cache-control", "max-age=$httpCacheSeconds")
|
||||
it.first
|
||||
}
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
image(HttpCode.OK)
|
||||
httpCode(HttpCode.NOT_FOUND)
|
||||
}
|
||||
)
|
||||
val thumbnail =
|
||||
handler(
|
||||
pathParam<Int>("mangaId"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Get a manga thumbnail")
|
||||
description("Get a manga thumbnail from the source or the cache.")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, mangaId ->
|
||||
ctx.future(
|
||||
future { Manga.getMangaThumbnail(mangaId) }
|
||||
.thenApply {
|
||||
ctx.header("content-type", it.second)
|
||||
val httpCacheSeconds = 1.days.inWholeSeconds
|
||||
ctx.header("cache-control", "max-age=$httpCacheSeconds")
|
||||
it.first
|
||||
},
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
image(HttpCode.OK)
|
||||
httpCode(HttpCode.NOT_FOUND)
|
||||
},
|
||||
)
|
||||
|
||||
/** adds the manga to library */
|
||||
val addToLibrary = handler(
|
||||
pathParam<Int>("mangaId"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Add manga to library")
|
||||
description("Use a manga id to add the manga to your library.\nWill do nothing if manga is already in your library.")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, mangaId ->
|
||||
ctx.future(
|
||||
future { Library.addMangaToLibrary(mangaId) }
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
httpCode(HttpCode.NOT_FOUND)
|
||||
}
|
||||
)
|
||||
val addToLibrary =
|
||||
handler(
|
||||
pathParam<Int>("mangaId"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Add manga to library")
|
||||
description("Use a manga id to add the manga to your library.\nWill do nothing if manga is already in your library.")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, mangaId ->
|
||||
ctx.future(
|
||||
future { Library.addMangaToLibrary(mangaId) },
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
httpCode(HttpCode.NOT_FOUND)
|
||||
},
|
||||
)
|
||||
|
||||
/** removes the manga from the library */
|
||||
val removeFromLibrary = handler(
|
||||
pathParam<Int>("mangaId"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Remove manga to library")
|
||||
description("Use a manga id to remove the manga to your library.\nWill do nothing if manga not in your library.")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, mangaId ->
|
||||
ctx.future(
|
||||
future { Library.removeMangaFromLibrary(mangaId) }
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
httpCode(HttpCode.NOT_FOUND)
|
||||
}
|
||||
)
|
||||
val removeFromLibrary =
|
||||
handler(
|
||||
pathParam<Int>("mangaId"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Remove manga to library")
|
||||
description("Use a manga id to remove the manga to your library.\nWill do nothing if manga not in your library.")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, mangaId ->
|
||||
ctx.future(
|
||||
future { Library.removeMangaFromLibrary(mangaId) },
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
httpCode(HttpCode.NOT_FOUND)
|
||||
},
|
||||
)
|
||||
|
||||
/** list manga's categories */
|
||||
val categoryList = handler(
|
||||
pathParam<Int>("mangaId"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Get a manga's categories")
|
||||
description("Get the list of categories for this manga")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, mangaId ->
|
||||
ctx.json(CategoryManga.getMangaCategories(mangaId))
|
||||
},
|
||||
withResults = {
|
||||
json<Array<CategoryDataClass>>(HttpCode.OK)
|
||||
}
|
||||
)
|
||||
val categoryList =
|
||||
handler(
|
||||
pathParam<Int>("mangaId"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Get a manga's categories")
|
||||
description("Get the list of categories for this manga")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, mangaId ->
|
||||
ctx.json(CategoryManga.getMangaCategories(mangaId))
|
||||
},
|
||||
withResults = {
|
||||
json<Array<CategoryDataClass>>(HttpCode.OK)
|
||||
},
|
||||
)
|
||||
|
||||
/** adds the manga to category */
|
||||
val addToCategory = handler(
|
||||
pathParam<Int>("mangaId"),
|
||||
pathParam<Int>("categoryId"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Add manga to category")
|
||||
description("Add a manga to a category using their ids.")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, mangaId, categoryId ->
|
||||
CategoryManga.addMangaToCategory(mangaId, categoryId)
|
||||
ctx.status(200)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
}
|
||||
)
|
||||
val addToCategory =
|
||||
handler(
|
||||
pathParam<Int>("mangaId"),
|
||||
pathParam<Int>("categoryId"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Add manga to category")
|
||||
description("Add a manga to a category using their ids.")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, mangaId, categoryId ->
|
||||
CategoryManga.addMangaToCategory(mangaId, categoryId)
|
||||
ctx.status(200)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
},
|
||||
)
|
||||
|
||||
/** removes the manga from the category */
|
||||
val removeFromCategory = handler(
|
||||
pathParam<Int>("mangaId"),
|
||||
pathParam<Int>("categoryId"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Remove manga from category")
|
||||
description("Remove a manga from a category using their ids.")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, mangaId, categoryId ->
|
||||
CategoryManga.removeMangaFromCategory(mangaId, categoryId)
|
||||
ctx.status(200)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
}
|
||||
)
|
||||
val removeFromCategory =
|
||||
handler(
|
||||
pathParam<Int>("mangaId"),
|
||||
pathParam<Int>("categoryId"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Remove manga from category")
|
||||
description("Remove a manga from a category using their ids.")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, mangaId, categoryId ->
|
||||
CategoryManga.removeMangaFromCategory(mangaId, categoryId)
|
||||
ctx.status(200)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
},
|
||||
)
|
||||
|
||||
/** used to modify a manga's meta parameters */
|
||||
val meta = handler(
|
||||
pathParam<Int>("mangaId"),
|
||||
formParam<String>("key"),
|
||||
formParam<String>("value"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Add data to manga")
|
||||
description("A simple Key-Value storage in the manga object, you can set values for whatever you want inside it.")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, mangaId, key, value ->
|
||||
Manga.modifyMangaMeta(mangaId, key, value)
|
||||
ctx.status(200)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
httpCode(HttpCode.NOT_FOUND)
|
||||
}
|
||||
)
|
||||
val meta =
|
||||
handler(
|
||||
pathParam<Int>("mangaId"),
|
||||
formParam<String>("key"),
|
||||
formParam<String>("value"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Add data to manga")
|
||||
description("A simple Key-Value storage in the manga object, you can set values for whatever you want inside it.")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, mangaId, key, value ->
|
||||
Manga.modifyMangaMeta(mangaId, key, value)
|
||||
ctx.status(200)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
httpCode(HttpCode.NOT_FOUND)
|
||||
},
|
||||
)
|
||||
|
||||
/** get chapter list when showing a manga */
|
||||
val chapterList = handler(
|
||||
pathParam<Int>("mangaId"),
|
||||
queryParam("onlineFetch", false),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Get manga chapter list")
|
||||
description("Get the manga chapter list from the database or online. If there is no chapters in the database it fetches the chapters online. Use onlineFetch to update chapter list.")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, mangaId, onlineFetch ->
|
||||
ctx.future(future { Chapter.getChapterList(mangaId, onlineFetch) })
|
||||
},
|
||||
withResults = {
|
||||
json<Array<ChapterDataClass>>(HttpCode.OK)
|
||||
httpCode(HttpCode.NOT_FOUND)
|
||||
}
|
||||
)
|
||||
val chapterList =
|
||||
handler(
|
||||
pathParam<Int>("mangaId"),
|
||||
queryParam("onlineFetch", false),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Get manga chapter list")
|
||||
description(
|
||||
"Get the manga chapter list from the database or online. " +
|
||||
"If there is no chapters in the database it fetches the chapters online. " +
|
||||
"Use onlineFetch to update chapter list.",
|
||||
)
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, mangaId, onlineFetch ->
|
||||
ctx.future(future { Chapter.getChapterList(mangaId, onlineFetch) })
|
||||
},
|
||||
withResults = {
|
||||
json<Array<ChapterDataClass>>(HttpCode.OK)
|
||||
httpCode(HttpCode.NOT_FOUND)
|
||||
},
|
||||
)
|
||||
|
||||
/** batch edit chapters of single manga */
|
||||
val chapterBatch = handler(
|
||||
pathParam<Int>("mangaId"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Chapters update multiple")
|
||||
description("Update multiple chapters of single manga. For batch marking as read, or bookmarking")
|
||||
}
|
||||
body<Chapter.MangaChapterBatchEditInput>()
|
||||
},
|
||||
behaviorOf = { ctx, mangaId ->
|
||||
val input = json.decodeFromString<Chapter.MangaChapterBatchEditInput>(ctx.body())
|
||||
Chapter.modifyChapters(input, mangaId)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
}
|
||||
)
|
||||
val chapterBatch =
|
||||
handler(
|
||||
pathParam<Int>("mangaId"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Chapters update multiple")
|
||||
description("Update multiple chapters of single manga. For batch marking as read, or bookmarking")
|
||||
}
|
||||
body<Chapter.MangaChapterBatchEditInput>()
|
||||
},
|
||||
behaviorOf = { ctx, mangaId ->
|
||||
val input = json.decodeFromString<Chapter.MangaChapterBatchEditInput>(ctx.body())
|
||||
Chapter.modifyChapters(input, mangaId)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
},
|
||||
)
|
||||
|
||||
/** batch edit chapters from multiple manga */
|
||||
val anyChapterBatch = handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Chapters update multiple")
|
||||
description("Update multiple chapters on any manga. For batch marking as read, or bookmarking")
|
||||
}
|
||||
body<Chapter.ChapterBatchEditInput>()
|
||||
},
|
||||
behaviorOf = { ctx ->
|
||||
val input = json.decodeFromString<Chapter.ChapterBatchEditInput>(ctx.body())
|
||||
Chapter.modifyChapters(
|
||||
Chapter.MangaChapterBatchEditInput(
|
||||
input.chapterIds,
|
||||
null,
|
||||
input.change
|
||||
val anyChapterBatch =
|
||||
handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Chapters update multiple")
|
||||
description("Update multiple chapters on any manga. For batch marking as read, or bookmarking")
|
||||
}
|
||||
body<Chapter.ChapterBatchEditInput>()
|
||||
},
|
||||
behaviorOf = { ctx ->
|
||||
val input = json.decodeFromString<Chapter.ChapterBatchEditInput>(ctx.body())
|
||||
Chapter.modifyChapters(
|
||||
Chapter.MangaChapterBatchEditInput(
|
||||
input.chapterIds,
|
||||
null,
|
||||
input.change,
|
||||
),
|
||||
)
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
}
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
},
|
||||
)
|
||||
|
||||
/** used to display a chapter, get a chapter in order to show its pages */
|
||||
val chapterRetrieve = handler(
|
||||
pathParam<Int>("mangaId"),
|
||||
pathParam<Int>("chapterIndex"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Get a chapter")
|
||||
description("Get the chapter from the manga id and chapter index. It will also retrieve the pages for this chapter.")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, mangaId, chapterIndex ->
|
||||
ctx.future(future { getChapterDownloadReadyByIndex(chapterIndex, mangaId) })
|
||||
},
|
||||
withResults = {
|
||||
json<ChapterDataClass>(HttpCode.OK)
|
||||
httpCode(HttpCode.NOT_FOUND)
|
||||
}
|
||||
)
|
||||
val chapterRetrieve =
|
||||
handler(
|
||||
pathParam<Int>("mangaId"),
|
||||
pathParam<Int>("chapterIndex"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Get a chapter")
|
||||
description("Get the chapter from the manga id and chapter index. It will also retrieve the pages for this chapter.")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, mangaId, chapterIndex ->
|
||||
ctx.future(future { getChapterDownloadReadyByIndex(chapterIndex, mangaId) })
|
||||
},
|
||||
withResults = {
|
||||
json<ChapterDataClass>(HttpCode.OK)
|
||||
httpCode(HttpCode.NOT_FOUND)
|
||||
},
|
||||
)
|
||||
|
||||
/** used to modify a chapter's parameters */
|
||||
val chapterModify = handler(
|
||||
pathParam<Int>("mangaId"),
|
||||
pathParam<Int>("chapterIndex"),
|
||||
formParam<Boolean?>("read"),
|
||||
formParam<Boolean?>("bookmarked"),
|
||||
formParam<Boolean?>("markPrevRead"),
|
||||
formParam<Int?>("lastPageRead"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Modify a chapter")
|
||||
description("Update user info for a given chapter, such as read status, bookmarked, and more.")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, mangaId, chapterIndex, read, bookmarked, markPrevRead, lastPageRead ->
|
||||
Chapter.modifyChapter(mangaId, chapterIndex, read, bookmarked, markPrevRead, lastPageRead)
|
||||
val chapterModify =
|
||||
handler(
|
||||
pathParam<Int>("mangaId"),
|
||||
pathParam<Int>("chapterIndex"),
|
||||
formParam<Boolean?>("read"),
|
||||
formParam<Boolean?>("bookmarked"),
|
||||
formParam<Boolean?>("markPrevRead"),
|
||||
formParam<Int?>("lastPageRead"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Modify a chapter")
|
||||
description("Update user info for a given chapter, such as read status, bookmarked, and more.")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, mangaId, chapterIndex, read, bookmarked, markPrevRead, lastPageRead ->
|
||||
Chapter.modifyChapter(mangaId, chapterIndex, read, bookmarked, markPrevRead, lastPageRead)
|
||||
|
||||
ctx.status(200)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
}
|
||||
)
|
||||
ctx.status(200)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
},
|
||||
)
|
||||
|
||||
/** delete a downloaded chapter */
|
||||
val chapterDelete = handler(
|
||||
pathParam<Int>("mangaId"),
|
||||
pathParam<Int>("chapterIndex"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Delete a chapter download")
|
||||
description("Delete the downloaded chapter and its files.")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, mangaId, chapterIndex ->
|
||||
Chapter.deleteChapter(mangaId, chapterIndex)
|
||||
val chapterDelete =
|
||||
handler(
|
||||
pathParam<Int>("mangaId"),
|
||||
pathParam<Int>("chapterIndex"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Delete a chapter download")
|
||||
description("Delete the downloaded chapter and its files.")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, mangaId, chapterIndex ->
|
||||
Chapter.deleteChapter(mangaId, chapterIndex)
|
||||
|
||||
ctx.status(200)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
httpCode(HttpCode.NOT_FOUND)
|
||||
}
|
||||
)
|
||||
ctx.status(200)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
httpCode(HttpCode.NOT_FOUND)
|
||||
},
|
||||
)
|
||||
|
||||
/** used to modify a chapter's meta parameters */
|
||||
val chapterMeta = handler(
|
||||
pathParam<Int>("mangaId"),
|
||||
pathParam<Int>("chapterIndex"),
|
||||
formParam<String>("key"),
|
||||
formParam<String>("value"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Add data to chapter")
|
||||
description("A simple Key-Value storage in the chapter object, you can set values for whatever you want inside it.")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, mangaId, chapterIndex, key, value ->
|
||||
Chapter.modifyChapterMeta(mangaId, chapterIndex, key, value)
|
||||
val chapterMeta =
|
||||
handler(
|
||||
pathParam<Int>("mangaId"),
|
||||
pathParam<Int>("chapterIndex"),
|
||||
formParam<String>("key"),
|
||||
formParam<String>("value"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Add data to chapter")
|
||||
description("A simple Key-Value storage in the chapter object, you can set values for whatever you want inside it.")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, mangaId, chapterIndex, key, value ->
|
||||
Chapter.modifyChapterMeta(mangaId, chapterIndex, key, value)
|
||||
|
||||
ctx.status(200)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
httpCode(HttpCode.NOT_FOUND)
|
||||
}
|
||||
)
|
||||
ctx.status(200)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
httpCode(HttpCode.NOT_FOUND)
|
||||
},
|
||||
)
|
||||
|
||||
/** get page at index "index" */
|
||||
val pageRetrieve = handler(
|
||||
pathParam<Int>("mangaId"),
|
||||
pathParam<Int>("chapterIndex"),
|
||||
pathParam<Int>("index"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Get a chapter page")
|
||||
description("Get a chapter page for a given index. Cache use can be disabled so it only retrieves it directly from the source.")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, mangaId, chapterIndex, index ->
|
||||
ctx.future(
|
||||
future { Page.getPageImage(mangaId, chapterIndex, index) }
|
||||
.thenApply {
|
||||
ctx.header("content-type", it.second)
|
||||
val httpCacheSeconds = 1.days.inWholeSeconds
|
||||
ctx.header("cache-control", "max-age=$httpCacheSeconds")
|
||||
it.first
|
||||
}
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
image(HttpCode.OK)
|
||||
httpCode(HttpCode.NOT_FOUND)
|
||||
}
|
||||
)
|
||||
val pageRetrieve =
|
||||
handler(
|
||||
pathParam<Int>("mangaId"),
|
||||
pathParam<Int>("chapterIndex"),
|
||||
pathParam<Int>("index"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Get a chapter page")
|
||||
description(
|
||||
"Get a chapter page for a given index. Cache use can be disabled so it only retrieves it directly from the source.",
|
||||
)
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, mangaId, chapterIndex, index ->
|
||||
ctx.future(
|
||||
future { Page.getPageImage(mangaId, chapterIndex, index) }
|
||||
.thenApply {
|
||||
ctx.header("content-type", it.second)
|
||||
val httpCacheSeconds = 1.days.inWholeSeconds
|
||||
ctx.header("cache-control", "max-age=$httpCacheSeconds")
|
||||
it.first
|
||||
},
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
image(HttpCode.OK)
|
||||
httpCode(HttpCode.NOT_FOUND)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -29,217 +29,229 @@ import suwayomi.tachidesk.server.util.withOperation
|
||||
|
||||
object SourceController {
|
||||
/** list of sources */
|
||||
val list = handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Sources list")
|
||||
description("List of sources")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx ->
|
||||
ctx.json(Source.getSourceList())
|
||||
},
|
||||
withResults = {
|
||||
json<Array<SourceDataClass>>(HttpCode.OK)
|
||||
}
|
||||
)
|
||||
val list =
|
||||
handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Sources list")
|
||||
description("List of sources")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx ->
|
||||
ctx.json(Source.getSourceList())
|
||||
},
|
||||
withResults = {
|
||||
json<Array<SourceDataClass>>(HttpCode.OK)
|
||||
},
|
||||
)
|
||||
|
||||
/** fetch source with id `sourceId` */
|
||||
val retrieve = handler(
|
||||
pathParam<Long>("sourceId"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Source fetch")
|
||||
description("Fetch source with id `sourceId`")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, sourceId ->
|
||||
ctx.json(Source.getSource(sourceId)!!)
|
||||
},
|
||||
withResults = {
|
||||
json<SourceDataClass>(HttpCode.OK)
|
||||
httpCode(HttpCode.NOT_FOUND)
|
||||
}
|
||||
)
|
||||
val retrieve =
|
||||
handler(
|
||||
pathParam<Long>("sourceId"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Source fetch")
|
||||
description("Fetch source with id `sourceId`")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, sourceId ->
|
||||
ctx.json(Source.getSource(sourceId)!!)
|
||||
},
|
||||
withResults = {
|
||||
json<SourceDataClass>(HttpCode.OK)
|
||||
httpCode(HttpCode.NOT_FOUND)
|
||||
},
|
||||
)
|
||||
|
||||
/** popular mangas from source with id `sourceId` */
|
||||
val popular = handler(
|
||||
pathParam<Long>("sourceId"),
|
||||
pathParam<Int>("pageNum"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Source popular manga")
|
||||
description("Popular mangas from source with id `sourceId`")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, sourceId, pageNum ->
|
||||
ctx.future(
|
||||
future {
|
||||
MangaList.getMangaList(sourceId, pageNum, popular = true)
|
||||
val popular =
|
||||
handler(
|
||||
pathParam<Long>("sourceId"),
|
||||
pathParam<Int>("pageNum"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Source popular manga")
|
||||
description("Popular mangas from source with id `sourceId`")
|
||||
}
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
json<PagedMangaListDataClass>(HttpCode.OK)
|
||||
}
|
||||
)
|
||||
},
|
||||
behaviorOf = { ctx, sourceId, pageNum ->
|
||||
ctx.future(
|
||||
future {
|
||||
MangaList.getMangaList(sourceId, pageNum, popular = true)
|
||||
},
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
json<PagedMangaListDataClass>(HttpCode.OK)
|
||||
},
|
||||
)
|
||||
|
||||
/** latest mangas from source with id `sourceId` */
|
||||
val latest = handler(
|
||||
pathParam<Long>("sourceId"),
|
||||
pathParam<Int>("pageNum"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Source latest manga")
|
||||
description("Latest mangas from source with id `sourceId`")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, sourceId, pageNum ->
|
||||
ctx.future(
|
||||
future {
|
||||
MangaList.getMangaList(sourceId, pageNum, popular = false)
|
||||
val latest =
|
||||
handler(
|
||||
pathParam<Long>("sourceId"),
|
||||
pathParam<Int>("pageNum"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Source latest manga")
|
||||
description("Latest mangas from source with id `sourceId`")
|
||||
}
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
json<PagedMangaListDataClass>(HttpCode.OK)
|
||||
}
|
||||
)
|
||||
},
|
||||
behaviorOf = { ctx, sourceId, pageNum ->
|
||||
ctx.future(
|
||||
future {
|
||||
MangaList.getMangaList(sourceId, pageNum, popular = false)
|
||||
},
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
json<PagedMangaListDataClass>(HttpCode.OK)
|
||||
},
|
||||
)
|
||||
|
||||
/** fetch preferences of source with id `sourceId` */
|
||||
val getPreferences = handler(
|
||||
pathParam<Long>("sourceId"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Source preferences")
|
||||
description("Fetch preferences of source with id `sourceId`")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, sourceId ->
|
||||
ctx.json(Source.getSourcePreferences(sourceId))
|
||||
},
|
||||
withResults = {
|
||||
json<Array<Source.PreferenceObject>>(HttpCode.OK)
|
||||
}
|
||||
)
|
||||
val getPreferences =
|
||||
handler(
|
||||
pathParam<Long>("sourceId"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Source preferences")
|
||||
description("Fetch preferences of source with id `sourceId`")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, sourceId ->
|
||||
ctx.json(Source.getSourcePreferences(sourceId))
|
||||
},
|
||||
withResults = {
|
||||
json<Array<Source.PreferenceObject>>(HttpCode.OK)
|
||||
},
|
||||
)
|
||||
|
||||
/** set one preference of source with id `sourceId` */
|
||||
val setPreference = handler(
|
||||
pathParam<Long>("sourceId"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Source preference set")
|
||||
description("Set one preference of source with id `sourceId`")
|
||||
}
|
||||
body<SourcePreferenceChange>()
|
||||
},
|
||||
behaviorOf = { ctx, sourceId ->
|
||||
val preferenceChange = ctx.bodyAsClass(SourcePreferenceChange::class.java)
|
||||
ctx.json(Source.setSourcePreference(sourceId, preferenceChange.position, preferenceChange.value))
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
}
|
||||
)
|
||||
val setPreference =
|
||||
handler(
|
||||
pathParam<Long>("sourceId"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Source preference set")
|
||||
description("Set one preference of source with id `sourceId`")
|
||||
}
|
||||
body<SourcePreferenceChange>()
|
||||
},
|
||||
behaviorOf = { ctx, sourceId ->
|
||||
val preferenceChange = ctx.bodyAsClass(SourcePreferenceChange::class.java)
|
||||
ctx.json(Source.setSourcePreference(sourceId, preferenceChange.position, preferenceChange.value))
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
},
|
||||
)
|
||||
|
||||
/** fetch filters of source with id `sourceId` */
|
||||
val getFilters = handler(
|
||||
pathParam<Long>("sourceId"),
|
||||
queryParam("reset", false),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Source filters")
|
||||
description("Fetch filters of source with id `sourceId`")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, sourceId, reset ->
|
||||
ctx.json(Search.getFilterList(sourceId, reset))
|
||||
},
|
||||
withResults = {
|
||||
json<Array<Search.FilterObject>>(HttpCode.OK)
|
||||
}
|
||||
)
|
||||
val getFilters =
|
||||
handler(
|
||||
pathParam<Long>("sourceId"),
|
||||
queryParam("reset", false),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Source filters")
|
||||
description("Fetch filters of source with id `sourceId`")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, sourceId, reset ->
|
||||
ctx.json(Search.getFilterList(sourceId, reset))
|
||||
},
|
||||
withResults = {
|
||||
json<Array<Search.FilterObject>>(HttpCode.OK)
|
||||
},
|
||||
)
|
||||
|
||||
private val json by DI.global.instance<Json>()
|
||||
|
||||
/** change filters of source with id `sourceId` */
|
||||
val setFilters = handler(
|
||||
pathParam<Long>("sourceId"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Source filters set")
|
||||
description("Change filters of source with id `sourceId`")
|
||||
}
|
||||
body<FilterChange>()
|
||||
body<Array<FilterChange>>()
|
||||
},
|
||||
behaviorOf = { ctx, sourceId ->
|
||||
val filterChange = try {
|
||||
json.decodeFromString<List<FilterChange>>(ctx.body())
|
||||
} catch (e: Exception) {
|
||||
listOf(json.decodeFromString<FilterChange>(ctx.body()))
|
||||
}
|
||||
val setFilters =
|
||||
handler(
|
||||
pathParam<Long>("sourceId"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Source filters set")
|
||||
description("Change filters of source with id `sourceId`")
|
||||
}
|
||||
body<FilterChange>()
|
||||
body<Array<FilterChange>>()
|
||||
},
|
||||
behaviorOf = { ctx, sourceId ->
|
||||
val filterChange =
|
||||
try {
|
||||
json.decodeFromString<List<FilterChange>>(ctx.body())
|
||||
} catch (e: Exception) {
|
||||
listOf(json.decodeFromString<FilterChange>(ctx.body()))
|
||||
}
|
||||
|
||||
ctx.json(Search.setFilter(sourceId, filterChange))
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
}
|
||||
)
|
||||
ctx.json(Search.setFilter(sourceId, filterChange))
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
},
|
||||
)
|
||||
|
||||
/** single source search */
|
||||
val searchSingle = handler(
|
||||
pathParam<Long>("sourceId"),
|
||||
queryParam("searchTerm", ""),
|
||||
queryParam("pageNum", 1),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Source search")
|
||||
description("Single source search")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, sourceId, searchTerm, pageNum ->
|
||||
ctx.future(future { Search.sourceSearch(sourceId, searchTerm, pageNum) })
|
||||
},
|
||||
withResults = {
|
||||
json<PagedMangaListDataClass>(HttpCode.OK)
|
||||
}
|
||||
)
|
||||
val searchSingle =
|
||||
handler(
|
||||
pathParam<Long>("sourceId"),
|
||||
queryParam("searchTerm", ""),
|
||||
queryParam("pageNum", 1),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Source search")
|
||||
description("Single source search")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, sourceId, searchTerm, pageNum ->
|
||||
ctx.future(future { Search.sourceSearch(sourceId, searchTerm, pageNum) })
|
||||
},
|
||||
withResults = {
|
||||
json<PagedMangaListDataClass>(HttpCode.OK)
|
||||
},
|
||||
)
|
||||
|
||||
/** quick search single source filter */
|
||||
val quickSearchSingle = handler(
|
||||
pathParam<Long>("sourceId"),
|
||||
queryParam("pageNum", 1),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Source manga quick search")
|
||||
description("Returns list of manga from source matching posted searchTerm and filter")
|
||||
}
|
||||
body<FilterData>()
|
||||
},
|
||||
behaviorOf = { ctx, sourceId, pageNum ->
|
||||
val filter = json.decodeFromString<FilterData>(ctx.body())
|
||||
ctx.future(future { Search.sourceFilter(sourceId, pageNum, filter) })
|
||||
},
|
||||
withResults = {
|
||||
json<PagedMangaListDataClass>(HttpCode.OK)
|
||||
}
|
||||
)
|
||||
val quickSearchSingle =
|
||||
handler(
|
||||
pathParam<Long>("sourceId"),
|
||||
queryParam("pageNum", 1),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Source manga quick search")
|
||||
description("Returns list of manga from source matching posted searchTerm and filter")
|
||||
}
|
||||
body<FilterData>()
|
||||
},
|
||||
behaviorOf = { ctx, sourceId, pageNum ->
|
||||
val filter = json.decodeFromString<FilterData>(ctx.body())
|
||||
ctx.future(future { Search.sourceFilter(sourceId, pageNum, filter) })
|
||||
},
|
||||
withResults = {
|
||||
json<PagedMangaListDataClass>(HttpCode.OK)
|
||||
},
|
||||
)
|
||||
|
||||
/** all source search */
|
||||
val searchAll = handler(
|
||||
pathParam<String>("searchTerm"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Source global search")
|
||||
description("All source search")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, searchTerm -> // TODO
|
||||
ctx.json(Search.sourceGlobalSearch(searchTerm))
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
}
|
||||
)
|
||||
val searchAll =
|
||||
handler(
|
||||
pathParam<String>("searchTerm"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Source global search")
|
||||
description("All source search")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, searchTerm -> // TODO
|
||||
ctx.json(Search.sourceGlobalSearch(searchTerm))
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -30,25 +30,26 @@ object UpdateController {
|
||||
private val logger = KotlinLogging.logger { }
|
||||
|
||||
/** get recently updated manga chapters */
|
||||
val recentChapters = handler(
|
||||
pathParam<Int>("pageNum"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Updates fetch")
|
||||
description("Get recently updated manga chapters")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, pageNum ->
|
||||
ctx.future(
|
||||
future {
|
||||
Chapter.getRecentChapters(pageNum)
|
||||
val recentChapters =
|
||||
handler(
|
||||
pathParam<Int>("pageNum"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Updates fetch")
|
||||
description("Get recently updated manga chapters")
|
||||
}
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
json<PagedMangaChapterListDataClass>(HttpCode.OK)
|
||||
}
|
||||
)
|
||||
},
|
||||
behaviorOf = { ctx, pageNum ->
|
||||
ctx.future(
|
||||
future {
|
||||
Chapter.getRecentChapters(pageNum)
|
||||
},
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
json<PagedMangaChapterListDataClass>(HttpCode.OK)
|
||||
},
|
||||
)
|
||||
|
||||
/**
|
||||
* Class made for handling return type in the documentation for [recentChapters],
|
||||
@@ -56,42 +57,43 @@ object UpdateController {
|
||||
*/
|
||||
private class PagedMangaChapterListDataClass : PaginatedList<MangaChapterDataClass>(emptyList(), false)
|
||||
|
||||
val categoryUpdate = handler(
|
||||
formParam<Int?>("categoryId"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Updater start")
|
||||
description("Starts the updater")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, categoryId ->
|
||||
val updater by DI.global.instance<IUpdater>()
|
||||
if (categoryId == null) {
|
||||
logger.info { "Adding Library to Update Queue" }
|
||||
updater.addCategoriesToUpdateQueue(
|
||||
Category.getCategoryList(),
|
||||
clear = true,
|
||||
forceAll = false
|
||||
)
|
||||
} else {
|
||||
val category = Category.getCategoryById(categoryId)
|
||||
if (category != null) {
|
||||
val categoryUpdate =
|
||||
handler(
|
||||
formParam<Int?>("categoryId"),
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Updater start")
|
||||
description("Starts the updater")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx, categoryId ->
|
||||
val updater by DI.global.instance<IUpdater>()
|
||||
if (categoryId == null) {
|
||||
logger.info { "Adding Library to Update Queue" }
|
||||
updater.addCategoriesToUpdateQueue(
|
||||
listOf(category),
|
||||
Category.getCategoryList(),
|
||||
clear = true,
|
||||
forceAll = true
|
||||
forceAll = false,
|
||||
)
|
||||
} else {
|
||||
logger.info { "No Category found" }
|
||||
ctx.status(HttpCode.BAD_REQUEST)
|
||||
val category = Category.getCategoryById(categoryId)
|
||||
if (category != null) {
|
||||
updater.addCategoriesToUpdateQueue(
|
||||
listOf(category),
|
||||
clear = true,
|
||||
forceAll = true,
|
||||
)
|
||||
} else {
|
||||
logger.info { "No Category found" }
|
||||
ctx.status(HttpCode.BAD_REQUEST)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
httpCode(HttpCode.BAD_REQUEST)
|
||||
}
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
httpCode(HttpCode.BAD_REQUEST)
|
||||
},
|
||||
)
|
||||
|
||||
fun categoryUpdateWS(ws: WsConfig) {
|
||||
ws.onConnect { ctx ->
|
||||
@@ -105,42 +107,44 @@ object UpdateController {
|
||||
}
|
||||
}
|
||||
|
||||
val updateSummary = handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Updater summary")
|
||||
description("Gets the latest updater summary")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx ->
|
||||
val updater by DI.global.instance<IUpdater>()
|
||||
ctx.json(updater.status.value)
|
||||
},
|
||||
withResults = {
|
||||
json<UpdateStatus>(HttpCode.OK)
|
||||
}
|
||||
)
|
||||
|
||||
val reset = handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Updater reset")
|
||||
description("Stops and resets the Updater")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx ->
|
||||
val updater by DI.global.instance<IUpdater>()
|
||||
logger.info { "Resetting Updater" }
|
||||
ctx.future(
|
||||
future {
|
||||
updater.reset()
|
||||
}.thenApply {
|
||||
ctx.status(HttpCode.OK)
|
||||
val updateSummary =
|
||||
handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Updater summary")
|
||||
description("Gets the latest updater summary")
|
||||
}
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
}
|
||||
)
|
||||
},
|
||||
behaviorOf = { ctx ->
|
||||
val updater by DI.global.instance<IUpdater>()
|
||||
ctx.json(updater.status.value)
|
||||
},
|
||||
withResults = {
|
||||
json<UpdateStatus>(HttpCode.OK)
|
||||
},
|
||||
)
|
||||
|
||||
val reset =
|
||||
handler(
|
||||
documentWith = {
|
||||
withOperation {
|
||||
summary("Updater reset")
|
||||
description("Stops and resets the Updater")
|
||||
}
|
||||
},
|
||||
behaviorOf = { ctx ->
|
||||
val updater by DI.global.instance<IUpdater>()
|
||||
logger.info { "Resetting Updater" }
|
||||
ctx.future(
|
||||
future {
|
||||
updater.reset()
|
||||
}.thenApply {
|
||||
ctx.status(HttpCode.OK)
|
||||
},
|
||||
)
|
||||
},
|
||||
withResults = {
|
||||
httpCode(HttpCode.OK)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -35,10 +35,11 @@ object Category {
|
||||
|
||||
return transaction {
|
||||
if (CategoryTable.select { CategoryTable.name eq name }.firstOrNull() == null) {
|
||||
val newCategoryId = CategoryTable.insertAndGetId {
|
||||
it[CategoryTable.name] = name
|
||||
it[CategoryTable.order] = Int.MAX_VALUE
|
||||
}.value
|
||||
val newCategoryId =
|
||||
CategoryTable.insertAndGetId {
|
||||
it[CategoryTable.name] = name
|
||||
it[CategoryTable.order] = Int.MAX_VALUE
|
||||
}.value
|
||||
|
||||
normalizeCategories()
|
||||
|
||||
@@ -49,10 +50,20 @@ object Category {
|
||||
}
|
||||
}
|
||||
|
||||
fun updateCategory(categoryId: Int, name: String?, isDefault: Boolean?, includeInUpdate: Int?) {
|
||||
fun updateCategory(
|
||||
categoryId: Int,
|
||||
name: String?,
|
||||
isDefault: Boolean?,
|
||||
includeInUpdate: Int?,
|
||||
) {
|
||||
transaction {
|
||||
CategoryTable.update({ CategoryTable.id eq categoryId }) {
|
||||
if (categoryId != DEFAULT_CATEGORY_ID && name != null && !name.equals(DEFAULT_CATEGORY_NAME, ignoreCase = true)) it[CategoryTable.name] = name
|
||||
if (
|
||||
categoryId != DEFAULT_CATEGORY_ID && name != null &&
|
||||
!name.equals(DEFAULT_CATEGORY_NAME, ignoreCase = true)
|
||||
) {
|
||||
it[CategoryTable.name] = name
|
||||
}
|
||||
if (categoryId != DEFAULT_CATEGORY_ID && isDefault != null) it[CategoryTable.isDefault] = isDefault
|
||||
if (includeInUpdate != null) it[CategoryTable.includeInUpdate] = includeInUpdate
|
||||
}
|
||||
@@ -62,10 +73,16 @@ object Category {
|
||||
/**
|
||||
* Move the category from order number `from` to `to`
|
||||
*/
|
||||
fun reorderCategory(from: Int, to: Int) {
|
||||
fun reorderCategory(
|
||||
from: Int,
|
||||
to: Int,
|
||||
) {
|
||||
if (from == 0 || to == 0) return
|
||||
transaction {
|
||||
val categories = CategoryTable.select { CategoryTable.id neq DEFAULT_CATEGORY_ID }.orderBy(CategoryTable.order to SortOrder.ASC).toMutableList()
|
||||
val categories =
|
||||
CategoryTable.select {
|
||||
CategoryTable.id neq DEFAULT_CATEGORY_ID
|
||||
}.orderBy(CategoryTable.order to SortOrder.ASC).toMutableList()
|
||||
categories.add(to - 1, categories.removeAt(from - 1))
|
||||
categories.forEachIndexed { index, cat ->
|
||||
CategoryTable.update({ CategoryTable.id eq cat[CategoryTable.id].value }) {
|
||||
@@ -98,14 +115,15 @@ object Category {
|
||||
}
|
||||
}
|
||||
|
||||
private fun needsDefaultCategory() = transaction {
|
||||
MangaTable
|
||||
.leftJoin(CategoryMangaTable)
|
||||
.select { MangaTable.inLibrary eq true }
|
||||
.andWhere { CategoryMangaTable.manga.isNull() }
|
||||
.empty()
|
||||
.not()
|
||||
}
|
||||
private fun needsDefaultCategory() =
|
||||
transaction {
|
||||
MangaTable
|
||||
.leftJoin(CategoryMangaTable)
|
||||
.select { MangaTable.inLibrary eq true }
|
||||
.andWhere { CategoryMangaTable.manga.isNull() }
|
||||
.empty()
|
||||
.not()
|
||||
}
|
||||
|
||||
const val DEFAULT_CATEGORY_ID = 0
|
||||
const val DEFAULT_CATEGORY_NAME = "Default"
|
||||
@@ -156,11 +174,16 @@ object Category {
|
||||
}
|
||||
}
|
||||
|
||||
fun modifyMeta(categoryId: Int, key: String, value: String) {
|
||||
fun modifyMeta(
|
||||
categoryId: Int,
|
||||
key: String,
|
||||
value: String,
|
||||
) {
|
||||
transaction {
|
||||
val meta = transaction {
|
||||
CategoryMetaTable.select { (CategoryMetaTable.ref eq categoryId) and (CategoryMetaTable.key eq key) }
|
||||
}.firstOrNull()
|
||||
val meta =
|
||||
transaction {
|
||||
CategoryMetaTable.select { (CategoryMetaTable.ref eq categoryId) and (CategoryMetaTable.key eq key) }
|
||||
}.firstOrNull()
|
||||
|
||||
if (meta == null) {
|
||||
CategoryMetaTable.insert {
|
||||
|
||||
@@ -31,9 +31,16 @@ import suwayomi.tachidesk.manga.model.table.MangaTable
|
||||
import suwayomi.tachidesk.manga.model.table.toDataClass
|
||||
|
||||
object CategoryManga {
|
||||
fun addMangaToCategory(mangaId: Int, categoryId: Int) {
|
||||
fun addMangaToCategory(
|
||||
mangaId: Int,
|
||||
categoryId: Int,
|
||||
) {
|
||||
if (categoryId == DEFAULT_CATEGORY_ID) return
|
||||
fun notAlreadyInCategory() = CategoryMangaTable.select { (CategoryMangaTable.category eq categoryId) and (CategoryMangaTable.manga eq mangaId) }.isEmpty()
|
||||
|
||||
fun notAlreadyInCategory() =
|
||||
CategoryMangaTable.select {
|
||||
(CategoryMangaTable.category eq categoryId) and (CategoryMangaTable.manga eq mangaId)
|
||||
}.isEmpty()
|
||||
|
||||
transaction {
|
||||
if (notAlreadyInCategory()) {
|
||||
@@ -45,7 +52,10 @@ object CategoryManga {
|
||||
}
|
||||
}
|
||||
|
||||
fun removeMangaFromCategory(mangaId: Int, categoryId: Int) {
|
||||
fun removeMangaFromCategory(
|
||||
mangaId: Int,
|
||||
categoryId: Int,
|
||||
) {
|
||||
if (categoryId == DEFAULT_CATEGORY_ID) return
|
||||
transaction {
|
||||
CategoryMangaTable.deleteWhere { (CategoryMangaTable.category eq categoryId) and (CategoryMangaTable.manga eq mangaId) }
|
||||
@@ -57,12 +67,18 @@ object CategoryManga {
|
||||
*/
|
||||
fun getCategoryMangaList(categoryId: Int): List<MangaDataClass> {
|
||||
// Select the required columns from the MangaTable and add the aggregate functions to compute unread, download, and chapter counts
|
||||
val unreadCount = wrapAsExpression<Long>(
|
||||
ChapterTable.slice(ChapterTable.id.count()).select((ChapterTable.isRead eq false) and (ChapterTable.manga eq MangaTable.id))
|
||||
)
|
||||
val downloadedCount = wrapAsExpression<Long>(
|
||||
ChapterTable.slice(ChapterTable.id.count()).select((ChapterTable.isDownloaded eq true) and (ChapterTable.manga eq MangaTable.id))
|
||||
)
|
||||
val unreadCount =
|
||||
wrapAsExpression<Long>(
|
||||
ChapterTable.slice(
|
||||
ChapterTable.id.count(),
|
||||
).select((ChapterTable.isRead eq false) and (ChapterTable.manga eq MangaTable.id)),
|
||||
)
|
||||
val downloadedCount =
|
||||
wrapAsExpression<Long>(
|
||||
ChapterTable.slice(
|
||||
ChapterTable.id.count(),
|
||||
).select((ChapterTable.isDownloaded eq true) and (ChapterTable.manga eq MangaTable.id)),
|
||||
)
|
||||
|
||||
val chapterCount = ChapterTable.id.count().alias("chapter_count")
|
||||
val lastReadAt = ChapterTable.lastReadAt.max().alias("last_read_at")
|
||||
@@ -80,19 +96,20 @@ object CategoryManga {
|
||||
|
||||
return transaction {
|
||||
// Fetch data from the MangaTable and join with the CategoryMangaTable, if a category is specified
|
||||
val query = if (categoryId == DEFAULT_CATEGORY_ID) {
|
||||
MangaTable
|
||||
.leftJoin(ChapterTable, { MangaTable.id }, { ChapterTable.manga })
|
||||
.leftJoin(CategoryMangaTable)
|
||||
.slice(columns = selectedColumns)
|
||||
.select { (MangaTable.inLibrary eq true) and CategoryMangaTable.category.isNull() }
|
||||
} else {
|
||||
MangaTable
|
||||
.innerJoin(CategoryMangaTable)
|
||||
.leftJoin(ChapterTable, { MangaTable.id }, { ChapterTable.manga })
|
||||
.slice(columns = selectedColumns)
|
||||
.select { (MangaTable.inLibrary eq true) and (CategoryMangaTable.category eq categoryId) }
|
||||
}
|
||||
val query =
|
||||
if (categoryId == DEFAULT_CATEGORY_ID) {
|
||||
MangaTable
|
||||
.leftJoin(ChapterTable, { MangaTable.id }, { ChapterTable.manga })
|
||||
.leftJoin(CategoryMangaTable)
|
||||
.slice(columns = selectedColumns)
|
||||
.select { (MangaTable.inLibrary eq true) and CategoryMangaTable.category.isNull() }
|
||||
} else {
|
||||
MangaTable
|
||||
.innerJoin(CategoryMangaTable)
|
||||
.leftJoin(ChapterTable, { MangaTable.id }, { ChapterTable.manga })
|
||||
.slice(columns = selectedColumns)
|
||||
.select { (MangaTable.inLibrary eq true) and (CategoryMangaTable.category eq categoryId) }
|
||||
}
|
||||
|
||||
// Join with the ChapterTable to fetch the last read chapter for each manga
|
||||
query.groupBy(*MangaTable.columns.toTypedArray()).map(transform)
|
||||
@@ -104,7 +121,9 @@ object CategoryManga {
|
||||
*/
|
||||
fun getMangaCategories(mangaId: Int): List<CategoryDataClass> {
|
||||
return transaction {
|
||||
CategoryMangaTable.innerJoin(CategoryTable).select { CategoryMangaTable.manga eq mangaId }.orderBy(CategoryTable.order to SortOrder.ASC).map {
|
||||
CategoryMangaTable.innerJoin(CategoryTable).select {
|
||||
CategoryMangaTable.manga eq mangaId
|
||||
}.orderBy(CategoryTable.order to SortOrder.ASC).map {
|
||||
CategoryTable.toDataClass(it)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,10 @@ object Chapter {
|
||||
private val logger = KotlinLogging.logger { }
|
||||
|
||||
/** get chapter list when showing a manga */
|
||||
suspend fun getChapterList(mangaId: Int, onlineFetch: Boolean = false): List<ChapterDataClass> {
|
||||
suspend fun getChapterList(
|
||||
mangaId: Int,
|
||||
onlineFetch: Boolean = false,
|
||||
): List<ChapterDataClass> {
|
||||
return if (onlineFetch) {
|
||||
getSourceChapters(mangaId)
|
||||
} else {
|
||||
@@ -68,10 +71,11 @@ 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 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)
|
||||
@@ -88,21 +92,17 @@ object Chapter {
|
||||
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])
|
||||
meta = chapterMetas.getValue(dbChapter[ChapterTable.id]),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -111,10 +111,11 @@ object Chapter {
|
||||
val manga = getManga(mangaId)
|
||||
val source = getCatalogueSourceOrStub(manga.sourceId.toLong())
|
||||
|
||||
val sManga = SManga.create().apply {
|
||||
title = manga.title
|
||||
url = manga.url
|
||||
}
|
||||
val sManga =
|
||||
SManga.create().apply {
|
||||
title = manga.title
|
||||
url = manga.url
|
||||
}
|
||||
|
||||
val numberOfCurrentChapters = getCountOfMangaChapters(mangaId)
|
||||
val chapterList = source.getChapterList(sManga)
|
||||
@@ -143,9 +144,10 @@ object Chapter {
|
||||
it[fetchedAt] = now++
|
||||
it[ChapterTable.manga] = mangaId
|
||||
|
||||
it[realUrl] = runCatching {
|
||||
(source as? HttpSource)?.getChapterUrl(fetchedChapter)
|
||||
}.getOrNull()
|
||||
it[realUrl] =
|
||||
runCatching {
|
||||
(source as? HttpSource)?.getChapterUrl(fetchedChapter)
|
||||
}.getOrNull()
|
||||
}
|
||||
} else {
|
||||
ChapterTable.update({ ChapterTable.url eq fetchedChapter.url }) {
|
||||
@@ -157,9 +159,10 @@ object Chapter {
|
||||
it[sourceOrder] = index + 1
|
||||
it[ChapterTable.manga] = mangaId
|
||||
|
||||
it[realUrl] = runCatching {
|
||||
(source as? HttpSource)?.getChapterUrl(fetchedChapter)
|
||||
}.getOrNull()
|
||||
it[realUrl] =
|
||||
runCatching {
|
||||
(source as? HttpSource)?.getChapterUrl(fetchedChapter)
|
||||
}.getOrNull()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -169,18 +172,20 @@ object Chapter {
|
||||
}
|
||||
}
|
||||
|
||||
val newChapters = transaction {
|
||||
ChapterTable.select { ChapterTable.manga eq mangaId }
|
||||
.orderBy(ChapterTable.sourceOrder to SortOrder.DESC).toList()
|
||||
}
|
||||
val newChapters =
|
||||
transaction {
|
||||
ChapterTable.select { ChapterTable.manga eq mangaId }
|
||||
.orderBy(ChapterTable.sourceOrder to SortOrder.DESC).toList()
|
||||
}
|
||||
|
||||
// clear any orphaned/duplicate chapters that are in the db but not in `chapterList`
|
||||
val dbChapterCount = newChapters.count()
|
||||
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()
|
||||
}
|
||||
val dbChapterList =
|
||||
transaction {
|
||||
ChapterTable.select { ChapterTable.manga eq mangaId }
|
||||
.orderBy(ChapterTable.url to ASC).toList()
|
||||
}
|
||||
val chapterUrls = chapterList.map { it.url }.toSet()
|
||||
|
||||
dbChapterList.forEachIndexed { index, dbChapter ->
|
||||
@@ -203,7 +208,11 @@ object Chapter {
|
||||
return chapterList
|
||||
}
|
||||
|
||||
private fun downloadNewChapters(mangaId: Int, prevNumberOfChapters: Int, updatedChapterList: List<ResultRow>) {
|
||||
private fun downloadNewChapters(
|
||||
mangaId: Int,
|
||||
prevNumberOfChapters: Int,
|
||||
updatedChapterList: List<ResultRow>,
|
||||
) {
|
||||
// convert numbers to be index based
|
||||
val currentNumberOfChapters = (prevNumberOfChapters - 1).coerceAtLeast(0)
|
||||
val updatedNumberOfChapters = (updatedChapterList.size - 1).coerceAtLeast(0)
|
||||
@@ -224,8 +233,9 @@ object Chapter {
|
||||
// make sure to only consider the latest chapters. e.g. old unread chapters should be ignored
|
||||
val latestReadChapterIndex =
|
||||
updatedChapterList.indexOfFirst { it[ChapterTable.isRead] }.takeIf { it > -1 } ?: return
|
||||
val unreadChapters = updatedChapterList.subList(numberOfNewChapters, latestReadChapterIndex)
|
||||
.filter { !it[ChapterTable.isRead] }
|
||||
val unreadChapters =
|
||||
updatedChapterList.subList(numberOfNewChapters, latestReadChapterIndex)
|
||||
.filter { !it[ChapterTable.isRead] }
|
||||
|
||||
val skipDueToUnreadChapters = serverConfig.excludeEntryWithUnreadChapters.value && unreadChapters.isNotEmpty()
|
||||
if (skipDueToUnreadChapters) {
|
||||
@@ -235,9 +245,10 @@ object Chapter {
|
||||
val firstChapterToDownloadIndex =
|
||||
(numberOfNewChapters - serverConfig.autoDownloadAheadLimit.value).coerceAtLeast(0)
|
||||
|
||||
val chapterIdsToDownload = newChapters.subList(firstChapterToDownloadIndex, numberOfNewChapters)
|
||||
.filter { !it[ChapterTable.isRead] && !it[ChapterTable.isDownloaded] }
|
||||
.map { it[ChapterTable.id].value }
|
||||
val chapterIdsToDownload =
|
||||
newChapters.subList(firstChapterToDownloadIndex, numberOfNewChapters)
|
||||
.filter { !it[ChapterTable.isRead] && !it[ChapterTable.isDownloaded] }
|
||||
.map { it[ChapterTable.id].value }
|
||||
|
||||
if (chapterIdsToDownload.isEmpty()) {
|
||||
return
|
||||
@@ -254,7 +265,7 @@ object Chapter {
|
||||
isRead: Boolean?,
|
||||
isBookmarked: Boolean?,
|
||||
markPrevRead: Boolean?,
|
||||
lastPageRead: Int?
|
||||
lastPageRead: Int?,
|
||||
) {
|
||||
transaction {
|
||||
if (listOf(isRead, isBookmarked, lastPageRead).any { it != null }) {
|
||||
@@ -285,23 +296,26 @@ object Chapter {
|
||||
val isRead: Boolean? = null,
|
||||
val isBookmarked: Boolean? = null,
|
||||
val lastPageRead: Int? = null,
|
||||
val delete: Boolean? = null
|
||||
val delete: Boolean? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class MangaChapterBatchEditInput(
|
||||
val chapterIds: List<Int>? = null,
|
||||
val chapterIndexes: List<Int>? = null,
|
||||
val change: ChapterChange?
|
||||
val change: ChapterChange?,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ChapterBatchEditInput(
|
||||
val chapterIds: List<Int>? = null,
|
||||
val change: ChapterChange?
|
||||
val change: ChapterChange?,
|
||||
)
|
||||
|
||||
fun modifyChapters(input: MangaChapterBatchEditInput, mangaId: Int? = null) {
|
||||
fun modifyChapters(
|
||||
input: MangaChapterBatchEditInput,
|
||||
mangaId: Int? = null,
|
||||
) {
|
||||
// Make sure change is defined
|
||||
if (input.change == null) return
|
||||
val (isRead, isBookmarked, lastPageRead, delete) = input.change
|
||||
@@ -315,25 +329,26 @@ object Chapter {
|
||||
if (listOfNotNull(isRead, isBookmarked, lastPageRead).isEmpty()) return
|
||||
|
||||
// Make sure some filter is defined
|
||||
val condition = when {
|
||||
mangaId != null ->
|
||||
// mangaId is not null, scope query under manga
|
||||
when {
|
||||
input.chapterIds != null ->
|
||||
Op.build { (ChapterTable.manga eq mangaId) and (ChapterTable.id inList input.chapterIds) }
|
||||
input.chapterIndexes != null ->
|
||||
Op.build { (ChapterTable.manga eq mangaId) and (ChapterTable.sourceOrder inList input.chapterIndexes) }
|
||||
else -> null
|
||||
val condition =
|
||||
when {
|
||||
mangaId != null ->
|
||||
// mangaId is not null, scope query under manga
|
||||
when {
|
||||
input.chapterIds != null ->
|
||||
Op.build { (ChapterTable.manga eq mangaId) and (ChapterTable.id inList input.chapterIds) }
|
||||
input.chapterIndexes != null ->
|
||||
Op.build { (ChapterTable.manga eq mangaId) and (ChapterTable.sourceOrder inList input.chapterIndexes) }
|
||||
else -> null
|
||||
}
|
||||
else -> {
|
||||
// mangaId is null, only chapterIndexes is valid for this case
|
||||
when {
|
||||
input.chapterIds != null ->
|
||||
Op.build { (ChapterTable.id inList input.chapterIds) }
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// mangaId is null, only chapterIndexes is valid for this case
|
||||
when {
|
||||
input.chapterIds != null ->
|
||||
Op.build { (ChapterTable.id inList input.chapterIds) }
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
} ?: return
|
||||
} ?: return
|
||||
|
||||
transaction {
|
||||
val now = Instant.now().epochSecond
|
||||
@@ -368,7 +383,12 @@ object Chapter {
|
||||
}
|
||||
}
|
||||
|
||||
fun modifyChapterMeta(mangaId: Int, chapterIndex: Int, key: String, value: String) {
|
||||
fun modifyChapterMeta(
|
||||
mangaId: Int,
|
||||
chapterIndex: Int,
|
||||
key: String,
|
||||
value: String,
|
||||
) {
|
||||
transaction {
|
||||
val chapterId =
|
||||
ChapterTable.select { (ChapterTable.manga eq mangaId) and (ChapterTable.sourceOrder eq chapterIndex) }
|
||||
@@ -377,7 +397,11 @@ object Chapter {
|
||||
}
|
||||
}
|
||||
|
||||
fun modifyChapterMeta(chapterId: Int, key: String, value: String) {
|
||||
fun modifyChapterMeta(
|
||||
chapterId: Int,
|
||||
key: String,
|
||||
value: String,
|
||||
) {
|
||||
transaction {
|
||||
val meta =
|
||||
ChapterMetaTable.select { (ChapterMetaTable.ref eq chapterId) and (ChapterMetaTable.key eq key) }
|
||||
@@ -397,7 +421,10 @@ object Chapter {
|
||||
}
|
||||
}
|
||||
|
||||
fun deleteChapter(mangaId: Int, chapterIndex: Int) {
|
||||
fun deleteChapter(
|
||||
mangaId: Int,
|
||||
chapterIndex: Int,
|
||||
) {
|
||||
transaction {
|
||||
val chapterId =
|
||||
ChapterTable.select { (ChapterTable.manga eq mangaId) and (ChapterTable.sourceOrder eq chapterIndex) }
|
||||
@@ -411,19 +438,23 @@ object Chapter {
|
||||
}
|
||||
}
|
||||
|
||||
private fun deleteChapters(input: MangaChapterBatchEditInput, mangaId: Int? = null) {
|
||||
private fun deleteChapters(
|
||||
input: MangaChapterBatchEditInput,
|
||||
mangaId: Int? = null,
|
||||
) {
|
||||
if (input.chapterIds != null) {
|
||||
deleteChapters(input.chapterIds)
|
||||
} else if (input.chapterIndexes != null && mangaId != null) {
|
||||
transaction {
|
||||
val chapterIds = ChapterTable.slice(ChapterTable.manga, ChapterTable.id)
|
||||
.select { (ChapterTable.sourceOrder inList input.chapterIndexes) and (ChapterTable.manga eq mangaId) }
|
||||
.map { row ->
|
||||
val chapterId = row[ChapterTable.id].value
|
||||
ChapterDownloadHelper.delete(mangaId, chapterId)
|
||||
val chapterIds =
|
||||
ChapterTable.slice(ChapterTable.manga, ChapterTable.id)
|
||||
.select { (ChapterTable.sourceOrder inList input.chapterIndexes) and (ChapterTable.manga eq mangaId) }
|
||||
.map { row ->
|
||||
val chapterId = row[ChapterTable.id].value
|
||||
ChapterDownloadHelper.delete(mangaId, chapterId)
|
||||
|
||||
chapterId
|
||||
}
|
||||
chapterId
|
||||
}
|
||||
|
||||
ChapterTable.update({ ChapterTable.id inList chapterIds }) {
|
||||
it[isDownloaded] = false
|
||||
@@ -457,7 +488,7 @@ object Chapter {
|
||||
.map {
|
||||
MangaChapterDataClass(
|
||||
MangaTable.toDataClass(it),
|
||||
ChapterTable.toDataClass(it)
|
||||
ChapterTable.toDataClass(it),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,11 +12,18 @@ import java.io.File
|
||||
import java.io.InputStream
|
||||
|
||||
object ChapterDownloadHelper {
|
||||
fun getImage(mangaId: Int, chapterId: Int, index: Int): Pair<InputStream, String> {
|
||||
fun getImage(
|
||||
mangaId: Int,
|
||||
chapterId: Int,
|
||||
index: Int,
|
||||
): Pair<InputStream, String> {
|
||||
return provider(mangaId, chapterId).getImage().execute(index)
|
||||
}
|
||||
|
||||
fun delete(mangaId: Int, chapterId: Int): Boolean {
|
||||
fun delete(
|
||||
mangaId: Int,
|
||||
chapterId: Int,
|
||||
): Boolean {
|
||||
return provider(mangaId, chapterId).delete()
|
||||
}
|
||||
|
||||
@@ -25,13 +32,16 @@ object ChapterDownloadHelper {
|
||||
chapterId: Int,
|
||||
download: DownloadChapter,
|
||||
scope: CoroutineScope,
|
||||
step: suspend (DownloadChapter?, Boolean) -> Unit
|
||||
step: suspend (DownloadChapter?, Boolean) -> Unit,
|
||||
): Boolean {
|
||||
return provider(mangaId, chapterId).download().execute(download, scope, step)
|
||||
}
|
||||
|
||||
// return the appropriate provider based on how the download was saved. For the logic is simple but will evolve when new types of downloads are available
|
||||
private fun provider(mangaId: Int, chapterId: Int): ChaptersFilesProvider {
|
||||
private fun provider(
|
||||
mangaId: Int,
|
||||
chapterId: Int,
|
||||
): ChaptersFilesProvider {
|
||||
val chapterFolder = File(getChapterDownloadPath(mangaId, chapterId))
|
||||
val cbzFile = File(getChapterCbzPath(mangaId, chapterId))
|
||||
if (cbzFile.exists()) return ArchiveProvider(mangaId, chapterId)
|
||||
|
||||
@@ -29,9 +29,10 @@ object Library {
|
||||
val manga = getManga(mangaId)
|
||||
if (!manga.inLibrary) {
|
||||
transaction {
|
||||
val defaultCategories = CategoryTable.select {
|
||||
(CategoryTable.isDefault eq true) and (CategoryTable.id neq Category.DEFAULT_CATEGORY_ID)
|
||||
}.toList()
|
||||
val defaultCategories =
|
||||
CategoryTable.select {
|
||||
(CategoryTable.isDefault eq true) and (CategoryTable.id neq Category.DEFAULT_CATEGORY_ID)
|
||||
}.toList()
|
||||
val existingCategories = CategoryMangaTable.select { CategoryMangaTable.manga eq mangaId }.toList()
|
||||
|
||||
MangaTable.update({ MangaTable.id eq manga.id }) {
|
||||
@@ -66,7 +67,10 @@ object Library {
|
||||
}
|
||||
}
|
||||
|
||||
fun handleMangaThumbnail(mangaId: Int, inLibrary: Boolean) {
|
||||
fun handleMangaThumbnail(
|
||||
mangaId: Int,
|
||||
inLibrary: Boolean,
|
||||
) {
|
||||
scope.launch {
|
||||
try {
|
||||
if (inLibrary) {
|
||||
|
||||
@@ -58,7 +58,10 @@ import java.util.concurrent.ConcurrentHashMap
|
||||
private val logger = KotlinLogging.logger { }
|
||||
|
||||
object Manga {
|
||||
private fun truncate(text: String?, maxLength: Int): String? {
|
||||
private fun truncate(
|
||||
text: String?,
|
||||
maxLength: Int,
|
||||
): String? {
|
||||
return if (text?.length ?: 0 > maxLength) {
|
||||
text?.take(maxLength - 3) + "..."
|
||||
} else {
|
||||
@@ -66,7 +69,10 @@ object Manga {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getManga(mangaId: Int, onlineFetch: Boolean = false): MangaDataClass {
|
||||
suspend fun getManga(
|
||||
mangaId: Int,
|
||||
onlineFetch: Boolean = false,
|
||||
): MangaDataClass {
|
||||
var mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() }
|
||||
|
||||
return if (!onlineFetch && mangaEntry[MangaTable.initialized]) {
|
||||
@@ -79,14 +85,11 @@ object Manga {
|
||||
MangaDataClass(
|
||||
id = mangaId,
|
||||
sourceId = mangaEntry[MangaTable.sourceReference].toString(),
|
||||
|
||||
url = mangaEntry[MangaTable.url],
|
||||
title = mangaEntry[MangaTable.title],
|
||||
thumbnailUrl = proxyThumbnailUrl(mangaId),
|
||||
thumbnailUrlLastFetched = mangaEntry[MangaTable.thumbnailUrlLastFetched],
|
||||
|
||||
initialized = true,
|
||||
|
||||
artist = sManga.artist,
|
||||
author = sManga.author,
|
||||
description = sManga.description,
|
||||
@@ -100,7 +103,7 @@ object Manga {
|
||||
lastFetchedAt = mangaEntry[MangaTable.lastFetchedAt],
|
||||
chaptersLastFetchedAt = mangaEntry[MangaTable.chaptersLastFetchedAt],
|
||||
updateStrategy = UpdateStrategy.valueOf(mangaEntry[MangaTable.updateStrategy]),
|
||||
freshData = true
|
||||
freshData = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -108,12 +111,14 @@ 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 source =
|
||||
getCatalogueSourceOrNull(mangaEntry[MangaTable.sourceReference])
|
||||
?: return null
|
||||
val sManga =
|
||||
SManga.create().apply {
|
||||
url = mangaEntry[MangaTable.url]
|
||||
title = mangaEntry[MangaTable.title]
|
||||
}
|
||||
val networkManga = source.getMangaDetails(sManga)
|
||||
sManga.copyFrom(networkManga)
|
||||
|
||||
@@ -139,9 +144,10 @@ object Manga {
|
||||
clearThumbnail(mangaId)
|
||||
}
|
||||
|
||||
it[MangaTable.realUrl] = runCatching {
|
||||
(source as? HttpSource)?.getMangaUrl(sManga)
|
||||
}.getOrNull()
|
||||
it[MangaTable.realUrl] =
|
||||
runCatching {
|
||||
(source as? HttpSource)?.getMangaUrl(sManga)
|
||||
}.getOrNull()
|
||||
|
||||
it[MangaTable.lastFetchedAt] = Instant.now().epochSecond
|
||||
|
||||
@@ -152,7 +158,10 @@ object Manga {
|
||||
return sManga
|
||||
}
|
||||
|
||||
suspend fun getMangaFull(mangaId: Int, onlineFetch: Boolean = false): MangaDataClass {
|
||||
suspend fun getMangaFull(
|
||||
mangaId: Int,
|
||||
onlineFetch: Boolean = false,
|
||||
): MangaDataClass {
|
||||
val mangaDaaClass = getManga(mangaId, onlineFetch)
|
||||
|
||||
return transaction {
|
||||
@@ -186,17 +195,17 @@ object Manga {
|
||||
}
|
||||
}
|
||||
|
||||
private fun getMangaDataClass(mangaId: Int, mangaEntry: ResultRow) = MangaDataClass(
|
||||
private fun getMangaDataClass(
|
||||
mangaId: Int,
|
||||
mangaEntry: ResultRow,
|
||||
) = MangaDataClass(
|
||||
id = mangaId,
|
||||
sourceId = mangaEntry[MangaTable.sourceReference].toString(),
|
||||
|
||||
url = mangaEntry[MangaTable.url],
|
||||
title = mangaEntry[MangaTable.title],
|
||||
thumbnailUrl = proxyThumbnailUrl(mangaId),
|
||||
thumbnailUrlLastFetched = mangaEntry[MangaTable.thumbnailUrlLastFetched],
|
||||
|
||||
initialized = true,
|
||||
|
||||
artist = mangaEntry[MangaTable.artist],
|
||||
author = mangaEntry[MangaTable.author],
|
||||
description = mangaEntry[MangaTable.description],
|
||||
@@ -210,7 +219,7 @@ object Manga {
|
||||
lastFetchedAt = mangaEntry[MangaTable.lastFetchedAt],
|
||||
chaptersLastFetchedAt = mangaEntry[MangaTable.chaptersLastFetchedAt],
|
||||
updateStrategy = UpdateStrategy.valueOf(mangaEntry[MangaTable.updateStrategy]),
|
||||
freshData = false
|
||||
freshData = false,
|
||||
)
|
||||
|
||||
fun getMangaMetaMap(mangaId: Int): Map<String, String> {
|
||||
@@ -220,7 +229,11 @@ object Manga {
|
||||
}
|
||||
}
|
||||
|
||||
fun modifyMangaMeta(mangaId: Int, key: String, value: String) {
|
||||
fun modifyMangaMeta(
|
||||
mangaId: Int,
|
||||
key: String,
|
||||
value: String,
|
||||
) {
|
||||
transaction {
|
||||
val meta =
|
||||
MangaMetaTable.select { (MangaMetaTable.ref eq mangaId) and (MangaMetaTable.key eq key) }
|
||||
@@ -251,45 +264,51 @@ object Manga {
|
||||
val sourceId = mangaEntry[MangaTable.sourceReference]
|
||||
|
||||
return when (val source = getCatalogueSourceOrStub(sourceId)) {
|
||||
is HttpSource -> getImageResponse(cacheSaveDir, fileName) {
|
||||
val thumbnailUrl = mangaEntry[MangaTable.thumbnail_url]
|
||||
?: if (!mangaEntry[MangaTable.initialized]) {
|
||||
// initialize then try again
|
||||
getManga(mangaId)
|
||||
transaction {
|
||||
MangaTable.select { MangaTable.id eq mangaId }.first()
|
||||
}[MangaTable.thumbnail_url]!!
|
||||
} else {
|
||||
// source provides no thumbnail url for this manga
|
||||
throw NullPointerException("No thumbnail found")
|
||||
}
|
||||
is HttpSource ->
|
||||
getImageResponse(cacheSaveDir, fileName) {
|
||||
val thumbnailUrl =
|
||||
mangaEntry[MangaTable.thumbnail_url]
|
||||
?: if (!mangaEntry[MangaTable.initialized]) {
|
||||
// initialize then try again
|
||||
getManga(mangaId)
|
||||
transaction {
|
||||
MangaTable.select { MangaTable.id eq mangaId }.first()
|
||||
}[MangaTable.thumbnail_url]!!
|
||||
} else {
|
||||
// source provides no thumbnail url for this manga
|
||||
throw NullPointerException("No thumbnail found")
|
||||
}
|
||||
|
||||
source.client.newCall(
|
||||
GET(thumbnailUrl, source.headers)
|
||||
).await()
|
||||
}
|
||||
source.client.newCall(
|
||||
GET(thumbnailUrl, source.headers),
|
||||
).await()
|
||||
}
|
||||
|
||||
is LocalSource -> {
|
||||
val imageFile = mangaEntry[MangaTable.thumbnail_url]?.let {
|
||||
val file = File(it)
|
||||
if (file.exists()) {
|
||||
file
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} ?: throw IOException("Thumbnail does not exist")
|
||||
val contentType = ImageUtil.findImageType { imageFile.inputStream() }?.mime
|
||||
?: "image/jpeg"
|
||||
val imageFile =
|
||||
mangaEntry[MangaTable.thumbnail_url]?.let {
|
||||
val file = File(it)
|
||||
if (file.exists()) {
|
||||
file
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} ?: throw IOException("Thumbnail does not exist")
|
||||
val contentType =
|
||||
ImageUtil.findImageType { imageFile.inputStream() }?.mime
|
||||
?: "image/jpeg"
|
||||
imageFile.inputStream() to contentType
|
||||
}
|
||||
|
||||
is StubSource -> getImageResponse(cacheSaveDir, fileName) {
|
||||
val thumbnailUrl = mangaEntry[MangaTable.thumbnail_url]
|
||||
?: throw NullPointerException("No thumbnail found")
|
||||
network.client.newCall(
|
||||
GET(thumbnailUrl)
|
||||
).await()
|
||||
}
|
||||
is StubSource ->
|
||||
getImageResponse(cacheSaveDir, fileName) {
|
||||
val thumbnailUrl =
|
||||
mangaEntry[MangaTable.thumbnail_url]
|
||||
?: throw NullPointerException("No thumbnail found")
|
||||
network.client.newCall(
|
||||
GET(thumbnailUrl),
|
||||
).await()
|
||||
}
|
||||
|
||||
else -> throw IllegalArgumentException("Unknown source")
|
||||
}
|
||||
@@ -319,14 +338,18 @@ object Manga {
|
||||
|
||||
private val downloadAheadQueue = ConcurrentHashMap<String, ConcurrentHashMap.KeySetView<Int, Boolean>>()
|
||||
private var downloadAheadTimer: Timer? = null
|
||||
fun downloadAhead(mangaIds: List<Int>, latestReadChapterIds: List<Int> = emptyList()) {
|
||||
|
||||
private const val MANGAS_KEY = "mangaIds"
|
||||
private const val CHAPTERS_KEY = "chapterIds"
|
||||
|
||||
fun downloadAhead(
|
||||
mangaIds: List<Int>,
|
||||
latestReadChapterIds: List<Int> = emptyList(),
|
||||
) {
|
||||
if (serverConfig.autoDownloadAheadLimit.value == 0) {
|
||||
return
|
||||
}
|
||||
|
||||
val MANGAS_KEY = "mangaIds"
|
||||
val CHAPTERS_KEY = "chapterIds"
|
||||
|
||||
val updateDownloadAheadQueue = { key: String, ids: List<Int> ->
|
||||
val idSet = downloadAheadQueue[key] ?: ConcurrentHashMap.newKeySet()
|
||||
idSet.addAll(ids)
|
||||
@@ -339,17 +362,21 @@ object Manga {
|
||||
// handle cases where this function gets called multiple times in quick succession.
|
||||
// this could happen in case e.g. multiple chapters get marked as read without batching the operation
|
||||
downloadAheadTimer?.cancel()
|
||||
downloadAheadTimer = Timer().apply {
|
||||
schedule(
|
||||
object : TimerTask() {
|
||||
override fun run() {
|
||||
downloadAheadChapters(downloadAheadQueue[MANGAS_KEY]!!.toList(), downloadAheadQueue[CHAPTERS_KEY]!!.toList())
|
||||
downloadAheadQueue.clear()
|
||||
}
|
||||
},
|
||||
5000
|
||||
)
|
||||
}
|
||||
downloadAheadTimer =
|
||||
Timer().apply {
|
||||
schedule(
|
||||
object : TimerTask() {
|
||||
override fun run() {
|
||||
downloadAheadChapters(
|
||||
downloadAheadQueue[MANGAS_KEY]?.toList().orEmpty(),
|
||||
downloadAheadQueue[CHAPTERS_KEY]?.toList().orEmpty(),
|
||||
)
|
||||
downloadAheadQueue.clear()
|
||||
}
|
||||
},
|
||||
5000,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -370,29 +397,38 @@ object Manga {
|
||||
*
|
||||
* will download the unread chapters starting from chapter 15
|
||||
*/
|
||||
private fun downloadAheadChapters(mangaIds: List<Int>, latestReadChapterIds: List<Int>) {
|
||||
val mangaToLatestReadChapterIndex = transaction {
|
||||
ChapterTable.select { (ChapterTable.manga inList mangaIds) and (ChapterTable.isRead eq true) }
|
||||
.orderBy(ChapterTable.sourceOrder to SortOrder.DESC).groupBy { it[ChapterTable.manga].value }
|
||||
}.mapValues { (_, chapters) -> chapters.firstOrNull()?.let { it[ChapterTable.sourceOrder] } ?: 0 }
|
||||
private fun downloadAheadChapters(
|
||||
mangaIds: List<Int>,
|
||||
latestReadChapterIds: List<Int>,
|
||||
) {
|
||||
val mangaToLatestReadChapterIndex =
|
||||
transaction {
|
||||
ChapterTable.select { (ChapterTable.manga inList mangaIds) and (ChapterTable.isRead eq true) }
|
||||
.orderBy(ChapterTable.sourceOrder to SortOrder.DESC).groupBy { it[ChapterTable.manga].value }
|
||||
}.mapValues { (_, chapters) -> chapters.firstOrNull()?.let { it[ChapterTable.sourceOrder] } ?: 0 }
|
||||
|
||||
val mangaToUnreadChaptersMap = transaction {
|
||||
ChapterTable.select { (ChapterTable.manga inList mangaIds) and (ChapterTable.isRead eq false) }
|
||||
.orderBy(ChapterTable.sourceOrder to SortOrder.DESC)
|
||||
.groupBy { it[ChapterTable.manga].value }
|
||||
}
|
||||
val mangaToUnreadChaptersMap =
|
||||
transaction {
|
||||
ChapterTable.select { (ChapterTable.manga inList mangaIds) and (ChapterTable.isRead eq false) }
|
||||
.orderBy(ChapterTable.sourceOrder to SortOrder.DESC)
|
||||
.groupBy { it[ChapterTable.manga].value }
|
||||
}
|
||||
|
||||
val chapterIdsToDownload = mangaToUnreadChaptersMap.map { (mangaId, unreadChapters) ->
|
||||
val latestReadChapterIndex = mangaToLatestReadChapterIndex[mangaId] ?: 0
|
||||
val lastChapterToDownloadIndex =
|
||||
unreadChapters.indexOfLast { it[ChapterTable.sourceOrder] > latestReadChapterIndex && it[ChapterTable.id].value !in latestReadChapterIds }
|
||||
val unreadChaptersToConsider = unreadChapters.subList(0, lastChapterToDownloadIndex + 1)
|
||||
val firstChapterToDownloadIndex =
|
||||
(unreadChaptersToConsider.size - serverConfig.autoDownloadAheadLimit.value).coerceAtLeast(0)
|
||||
unreadChaptersToConsider.subList(firstChapterToDownloadIndex, lastChapterToDownloadIndex + 1)
|
||||
.filter { !it[ChapterTable.isDownloaded] }
|
||||
.map { it[ChapterTable.id].value }
|
||||
}.flatten()
|
||||
val chapterIdsToDownload =
|
||||
mangaToUnreadChaptersMap.map { (mangaId, unreadChapters) ->
|
||||
val latestReadChapterIndex = mangaToLatestReadChapterIndex[mangaId] ?: 0
|
||||
val lastChapterToDownloadIndex =
|
||||
unreadChapters.indexOfLast {
|
||||
it[ChapterTable.sourceOrder] > latestReadChapterIndex &&
|
||||
it[ChapterTable.id].value !in latestReadChapterIds
|
||||
}
|
||||
val unreadChaptersToConsider = unreadChapters.subList(0, lastChapterToDownloadIndex + 1)
|
||||
val firstChapterToDownloadIndex =
|
||||
(unreadChaptersToConsider.size - serverConfig.autoDownloadAheadLimit.value).coerceAtLeast(0)
|
||||
unreadChaptersToConsider.subList(firstChapterToDownloadIndex, lastChapterToDownloadIndex + 1)
|
||||
.filter { !it[ChapterTable.isDownloaded] }
|
||||
.map { it[ChapterTable.id].value }
|
||||
}.flatten()
|
||||
|
||||
logger.info { "downloadAheadChapters: download chapters [${chapterIdsToDownload.joinToString(", ")}]" }
|
||||
|
||||
|
||||
@@ -26,29 +26,35 @@ object MangaList {
|
||||
return "/api/v1/manga/$mangaId/thumbnail"
|
||||
}
|
||||
|
||||
suspend fun getMangaList(sourceId: Long, pageNum: Int = 1, popular: Boolean): PagedMangaListDataClass {
|
||||
suspend fun getMangaList(
|
||||
sourceId: Long,
|
||||
pageNum: Int = 1,
|
||||
popular: Boolean,
|
||||
): PagedMangaListDataClass {
|
||||
require(pageNum > 0) {
|
||||
"pageNum = $pageNum is not in valid range"
|
||||
}
|
||||
val source = getCatalogueSourceOrStub(sourceId)
|
||||
val mangasPage = if (popular) {
|
||||
source.getPopularManga(pageNum)
|
||||
} else {
|
||||
if (source.supportsLatest) {
|
||||
source.getLatestUpdates(pageNum)
|
||||
val mangasPage =
|
||||
if (popular) {
|
||||
source.getPopularManga(pageNum)
|
||||
} else {
|
||||
throw Exception("Source $source doesn't support latest")
|
||||
if (source.supportsLatest) {
|
||||
source.getLatestUpdates(pageNum)
|
||||
} else {
|
||||
throw Exception("Source $source doesn't support latest")
|
||||
}
|
||||
}
|
||||
}
|
||||
return mangasPage.processEntries(sourceId)
|
||||
}
|
||||
|
||||
fun MangasPage.insertOrGet(sourceId: Long): List<Int> {
|
||||
return transaction {
|
||||
mangas.map { manga ->
|
||||
val mangaEntry = MangaTable.select {
|
||||
(MangaTable.url eq manga.url) and (MangaTable.sourceReference eq sourceId)
|
||||
}.firstOrNull()
|
||||
val mangaEntry =
|
||||
MangaTable.select {
|
||||
(MangaTable.url eq manga.url) and (MangaTable.sourceReference eq sourceId)
|
||||
}.firstOrNull()
|
||||
if (mangaEntry == null) { // create manga entry
|
||||
MangaTable.insertAndGetId {
|
||||
it[url] = manga.url
|
||||
@@ -73,89 +79,87 @@ object MangaList {
|
||||
|
||||
fun MangasPage.processEntries(sourceId: Long): PagedMangaListDataClass {
|
||||
val mangasPage = this
|
||||
val mangaList = transaction {
|
||||
return@transaction mangasPage.mangas.map { manga ->
|
||||
var mangaEntry = MangaTable.select {
|
||||
(MangaTable.url eq manga.url) and (MangaTable.sourceReference eq sourceId)
|
||||
}.firstOrNull()
|
||||
if (mangaEntry == null) { // create manga entry
|
||||
val mangaId = MangaTable.insertAndGetId {
|
||||
it[url] = manga.url
|
||||
it[title] = manga.title
|
||||
val mangaList =
|
||||
transaction {
|
||||
return@transaction mangasPage.mangas.map { manga ->
|
||||
var mangaEntry =
|
||||
MangaTable.select {
|
||||
(MangaTable.url eq manga.url) and (MangaTable.sourceReference eq sourceId)
|
||||
}.firstOrNull()
|
||||
if (mangaEntry == null) { // create manga entry
|
||||
val mangaId =
|
||||
MangaTable.insertAndGetId {
|
||||
it[url] = manga.url
|
||||
it[title] = manga.title
|
||||
|
||||
it[artist] = manga.artist
|
||||
it[author] = manga.author
|
||||
it[description] = manga.description
|
||||
it[genre] = manga.genre
|
||||
it[status] = manga.status
|
||||
it[thumbnail_url] = manga.thumbnail_url
|
||||
it[updateStrategy] = manga.update_strategy.name
|
||||
it[artist] = manga.artist
|
||||
it[author] = manga.author
|
||||
it[description] = manga.description
|
||||
it[genre] = manga.genre
|
||||
it[status] = manga.status
|
||||
it[thumbnail_url] = manga.thumbnail_url
|
||||
it[updateStrategy] = manga.update_strategy.name
|
||||
|
||||
it[sourceReference] = sourceId
|
||||
}.value
|
||||
it[sourceReference] = sourceId
|
||||
}.value
|
||||
|
||||
mangaEntry = MangaTable.select {
|
||||
(MangaTable.url eq manga.url) and (MangaTable.sourceReference eq sourceId)
|
||||
}.first()
|
||||
mangaEntry =
|
||||
MangaTable.select {
|
||||
(MangaTable.url eq manga.url) and (MangaTable.sourceReference eq sourceId)
|
||||
}.first()
|
||||
|
||||
MangaDataClass(
|
||||
id = mangaId,
|
||||
sourceId = sourceId.toString(),
|
||||
|
||||
url = manga.url,
|
||||
title = manga.title,
|
||||
thumbnailUrl = proxyThumbnailUrl(mangaId),
|
||||
thumbnailUrlLastFetched = mangaEntry[MangaTable.thumbnailUrlLastFetched],
|
||||
|
||||
initialized = manga.initialized,
|
||||
|
||||
artist = manga.artist,
|
||||
author = manga.author,
|
||||
description = manga.description,
|
||||
genre = manga.genre.toGenreList(),
|
||||
status = MangaStatus.valueOf(manga.status).name,
|
||||
inLibrary = false, // It's a new manga entry
|
||||
inLibraryAt = 0,
|
||||
meta = getMangaMetaMap(mangaId),
|
||||
realUrl = mangaEntry[MangaTable.realUrl],
|
||||
lastFetchedAt = mangaEntry[MangaTable.lastFetchedAt],
|
||||
chaptersLastFetchedAt = mangaEntry[MangaTable.chaptersLastFetchedAt],
|
||||
updateStrategy = UpdateStrategy.valueOf(mangaEntry[MangaTable.updateStrategy]),
|
||||
freshData = true
|
||||
)
|
||||
} else {
|
||||
val mangaId = mangaEntry[MangaTable.id].value
|
||||
MangaDataClass(
|
||||
id = mangaId,
|
||||
sourceId = sourceId.toString(),
|
||||
|
||||
url = manga.url,
|
||||
title = manga.title,
|
||||
thumbnailUrl = proxyThumbnailUrl(mangaId),
|
||||
thumbnailUrlLastFetched = mangaEntry[MangaTable.thumbnailUrlLastFetched],
|
||||
|
||||
initialized = true,
|
||||
|
||||
artist = mangaEntry[MangaTable.artist],
|
||||
author = mangaEntry[MangaTable.author],
|
||||
description = mangaEntry[MangaTable.description],
|
||||
genre = mangaEntry[MangaTable.genre].toGenreList(),
|
||||
status = MangaStatus.valueOf(mangaEntry[MangaTable.status]).name,
|
||||
inLibrary = mangaEntry[MangaTable.inLibrary],
|
||||
inLibraryAt = mangaEntry[MangaTable.inLibraryAt],
|
||||
meta = getMangaMetaMap(mangaId),
|
||||
realUrl = mangaEntry[MangaTable.realUrl],
|
||||
lastFetchedAt = mangaEntry[MangaTable.lastFetchedAt],
|
||||
chaptersLastFetchedAt = mangaEntry[MangaTable.chaptersLastFetchedAt],
|
||||
updateStrategy = UpdateStrategy.valueOf(mangaEntry[MangaTable.updateStrategy]),
|
||||
freshData = false
|
||||
)
|
||||
MangaDataClass(
|
||||
id = mangaId,
|
||||
sourceId = sourceId.toString(),
|
||||
url = manga.url,
|
||||
title = manga.title,
|
||||
thumbnailUrl = proxyThumbnailUrl(mangaId),
|
||||
thumbnailUrlLastFetched = mangaEntry[MangaTable.thumbnailUrlLastFetched],
|
||||
initialized = manga.initialized,
|
||||
artist = manga.artist,
|
||||
author = manga.author,
|
||||
description = manga.description,
|
||||
genre = manga.genre.toGenreList(),
|
||||
status = MangaStatus.valueOf(manga.status).name,
|
||||
inLibrary = false, // It's a new manga entry
|
||||
inLibraryAt = 0,
|
||||
meta = getMangaMetaMap(mangaId),
|
||||
realUrl = mangaEntry[MangaTable.realUrl],
|
||||
lastFetchedAt = mangaEntry[MangaTable.lastFetchedAt],
|
||||
chaptersLastFetchedAt = mangaEntry[MangaTable.chaptersLastFetchedAt],
|
||||
updateStrategy = UpdateStrategy.valueOf(mangaEntry[MangaTable.updateStrategy]),
|
||||
freshData = true,
|
||||
)
|
||||
} else {
|
||||
val mangaId = mangaEntry[MangaTable.id].value
|
||||
MangaDataClass(
|
||||
id = mangaId,
|
||||
sourceId = sourceId.toString(),
|
||||
url = manga.url,
|
||||
title = manga.title,
|
||||
thumbnailUrl = proxyThumbnailUrl(mangaId),
|
||||
thumbnailUrlLastFetched = mangaEntry[MangaTable.thumbnailUrlLastFetched],
|
||||
initialized = true,
|
||||
artist = mangaEntry[MangaTable.artist],
|
||||
author = mangaEntry[MangaTable.author],
|
||||
description = mangaEntry[MangaTable.description],
|
||||
genre = mangaEntry[MangaTable.genre].toGenreList(),
|
||||
status = MangaStatus.valueOf(mangaEntry[MangaTable.status]).name,
|
||||
inLibrary = mangaEntry[MangaTable.inLibrary],
|
||||
inLibraryAt = mangaEntry[MangaTable.inLibraryAt],
|
||||
meta = getMangaMetaMap(mangaId),
|
||||
realUrl = mangaEntry[MangaTable.realUrl],
|
||||
lastFetchedAt = mangaEntry[MangaTable.lastFetchedAt],
|
||||
chaptersLastFetchedAt = mangaEntry[MangaTable.chaptersLastFetchedAt],
|
||||
updateStrategy = UpdateStrategy.valueOf(mangaEntry[MangaTable.updateStrategy]),
|
||||
freshData = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return PagedMangaListDataClass(
|
||||
mangaList,
|
||||
mangasPage.hasNextPage
|
||||
mangasPage.hasNextPage,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,21 +31,30 @@ object Page {
|
||||
* A page might have a imageUrl ready from the get go, or we might need to
|
||||
* go an extra step and call fetchImageUrl to get it.
|
||||
*/
|
||||
suspend fun getTrueImageUrl(page: Page, source: HttpSource): String {
|
||||
suspend fun getTrueImageUrl(
|
||||
page: Page,
|
||||
source: HttpSource,
|
||||
): String {
|
||||
if (page.imageUrl == null) {
|
||||
page.imageUrl = source.getImageUrl(page)
|
||||
}
|
||||
return page.imageUrl!!
|
||||
}
|
||||
|
||||
suspend fun getPageImage(mangaId: Int, chapterIndex: Int, index: Int, progressFlow: ((StateFlow<Int>) -> Unit)? = null): Pair<InputStream, String> {
|
||||
suspend fun getPageImage(
|
||||
mangaId: Int,
|
||||
chapterIndex: Int,
|
||||
index: Int,
|
||||
progressFlow: ((StateFlow<Int>) -> Unit)? = null,
|
||||
): Pair<InputStream, String> {
|
||||
val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() }
|
||||
val source = getCatalogueSourceOrStub(mangaEntry[MangaTable.sourceReference])
|
||||
val chapterEntry = transaction {
|
||||
ChapterTable.select {
|
||||
(ChapterTable.sourceOrder eq chapterIndex) and (ChapterTable.manga eq mangaId)
|
||||
}.first()
|
||||
}
|
||||
val chapterEntry =
|
||||
transaction {
|
||||
ChapterTable.select {
|
||||
(ChapterTable.sourceOrder eq chapterIndex) and (ChapterTable.manga eq mangaId)
|
||||
}.first()
|
||||
}
|
||||
val chapterId = chapterEntry[ChapterTable.id].value
|
||||
|
||||
val pageEntry =
|
||||
@@ -54,11 +63,12 @@ object Page {
|
||||
.orderBy(PageTable.index to SortOrder.ASC)
|
||||
.limit(1, index.toLong()).first()
|
||||
}
|
||||
val tachiyomiPage = Page(
|
||||
pageEntry[PageTable.index],
|
||||
pageEntry[PageTable.url],
|
||||
pageEntry[PageTable.imageUrl]
|
||||
)
|
||||
val tachiyomiPage =
|
||||
Page(
|
||||
pageEntry[PageTable.index],
|
||||
pageEntry[PageTable.url],
|
||||
pageEntry[PageTable.imageUrl],
|
||||
)
|
||||
progressFlow?.invoke(tachiyomiPage.progress)
|
||||
|
||||
// we treat Local source differently
|
||||
|
||||
@@ -20,13 +20,21 @@ import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogue
|
||||
import suwayomi.tachidesk.manga.model.dataclass.PagedMangaListDataClass
|
||||
|
||||
object Search {
|
||||
suspend fun sourceSearch(sourceId: Long, searchTerm: String, pageNum: Int): PagedMangaListDataClass {
|
||||
suspend fun sourceSearch(
|
||||
sourceId: Long,
|
||||
searchTerm: String,
|
||||
pageNum: Int,
|
||||
): PagedMangaListDataClass {
|
||||
val source = getCatalogueSourceOrStub(sourceId)
|
||||
val searchManga = source.getSearchManga(pageNum, searchTerm, getFilterListOf(source))
|
||||
return searchManga.processEntries(sourceId)
|
||||
}
|
||||
|
||||
suspend fun sourceFilter(sourceId: Long, pageNum: Int, filter: FilterData): PagedMangaListDataClass {
|
||||
suspend fun sourceFilter(
|
||||
sourceId: Long,
|
||||
pageNum: Int,
|
||||
filter: FilterData,
|
||||
): PagedMangaListDataClass {
|
||||
val source = getCatalogueSourceOrStub(sourceId)
|
||||
val filterList = if (filter.filter != null) buildFilterList(sourceId, filter.filter) else source.getFilterList()
|
||||
val searchManga = source.getSearchManga(pageNum, filter.searchTerm ?: "", filterList)
|
||||
@@ -35,14 +43,20 @@ object Search {
|
||||
|
||||
private val filterListCache = mutableMapOf<Long, FilterList>()
|
||||
|
||||
private fun getFilterListOf(source: CatalogueSource, reset: Boolean = false): FilterList {
|
||||
private fun getFilterListOf(
|
||||
source: CatalogueSource,
|
||||
reset: Boolean = false,
|
||||
): FilterList {
|
||||
if (reset || !filterListCache.containsKey(source.id)) {
|
||||
filterListCache[source.id] = source.getFilterList()
|
||||
}
|
||||
return filterListCache[source.id]!!
|
||||
}
|
||||
|
||||
fun getFilterList(sourceId: Long, reset: Boolean): List<FilterObject> {
|
||||
fun getFilterList(
|
||||
sourceId: Long,
|
||||
reset: Boolean,
|
||||
): List<FilterObject> {
|
||||
val source = getCatalogueSourceOrStub(sourceId)
|
||||
|
||||
return getFilterListOf(source, reset).list.map {
|
||||
@@ -70,30 +84,37 @@ object Search {
|
||||
is Filter.Select<*> -> FilterObject("Select", item)
|
||||
else -> throw RuntimeException("Illegal Group item type!")
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
else -> it
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Filter.Select<*>.getValuesType(): String = values::class.java.componentType!!.simpleName
|
||||
|
||||
class SerializableGroup(name: String, state: List<FilterObject>) : Filter<List<FilterObject>>(name, state)
|
||||
|
||||
data class FilterObject(
|
||||
val type: String,
|
||||
val filter: Filter<*>
|
||||
val filter: Filter<*>,
|
||||
)
|
||||
|
||||
fun setFilter(sourceId: Long, changes: List<FilterChange>) {
|
||||
fun setFilter(
|
||||
sourceId: Long,
|
||||
changes: List<FilterChange>,
|
||||
) {
|
||||
val source = getCatalogueSourceOrStub(sourceId)
|
||||
val filterList = getFilterListOf(source, false)
|
||||
updateFilterList(filterList, changes)
|
||||
}
|
||||
|
||||
private fun updateFilterList(filterList: FilterList, changes: List<FilterChange>): FilterList {
|
||||
private fun updateFilterList(
|
||||
filterList: FilterList,
|
||||
changes: List<FilterChange>,
|
||||
): FilterList {
|
||||
changes.forEach { change ->
|
||||
when (val filter = filterList[change.position]) {
|
||||
is Filter.Header -> {
|
||||
@@ -124,7 +145,10 @@ object Search {
|
||||
return filterList
|
||||
}
|
||||
|
||||
fun buildFilterList(sourceId: Long, changes: List<FilterChange>): FilterList {
|
||||
fun buildFilterList(
|
||||
sourceId: Long,
|
||||
changes: List<FilterChange>,
|
||||
): FilterList {
|
||||
val source = getCatalogueSourceOrStub(sourceId)
|
||||
val filterList = source.getFilterList()
|
||||
return updateFilterList(filterList, changes)
|
||||
@@ -135,13 +159,13 @@ object Search {
|
||||
@Serializable
|
||||
data class FilterChange(
|
||||
val position: Int,
|
||||
val state: String
|
||||
val state: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class FilterData(
|
||||
val searchTerm: String?,
|
||||
val filter: List<FilterChange>?
|
||||
val filter: List<FilterChange>?,
|
||||
)
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
|
||||
@@ -46,7 +46,7 @@ object Source {
|
||||
catalogueSource.supportsLatest,
|
||||
catalogueSource is ConfigurableSource,
|
||||
it[SourceTable.isNsfw],
|
||||
catalogueSource.toString()
|
||||
catalogueSource.toString(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -63,12 +63,12 @@ object Source {
|
||||
source[SourceTable.name],
|
||||
source[SourceTable.lang],
|
||||
getExtensionIconUrl(
|
||||
extension[ExtensionTable.apkName]
|
||||
extension[ExtensionTable.apkName],
|
||||
),
|
||||
catalogueSource.supportsLatest,
|
||||
catalogueSource is ConfigurableSource,
|
||||
source[SourceTable.isNsfw],
|
||||
catalogueSource.toString()
|
||||
catalogueSource.toString(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -85,7 +85,7 @@ object Source {
|
||||
*/
|
||||
data class PreferenceObject(
|
||||
val type: String,
|
||||
val props: Any
|
||||
val props: Any,
|
||||
)
|
||||
|
||||
var preferenceScreenMap: MutableMap<Long, PreferenceScreen> = mutableMapOf()
|
||||
@@ -119,7 +119,7 @@ object Source {
|
||||
|
||||
data class SourcePreferenceChange(
|
||||
val position: Int,
|
||||
val value: String
|
||||
val value: String,
|
||||
)
|
||||
|
||||
private val jsonMapper by DI.global.instance<JsonMapper>()
|
||||
@@ -137,7 +137,7 @@ object Source {
|
||||
"Set<String>" -> jsonMapper.fromJsonString(value, List::class.java as Class<List<String>>).toSet()
|
||||
else -> throw RuntimeException("Unsupported type conversion")
|
||||
}
|
||||
}
|
||||
},
|
||||
) {
|
||||
val screen = preferenceScreenMap[sourceId]!!
|
||||
val pref = screen.preferences[position]
|
||||
|
||||
@@ -12,5 +12,5 @@ data class BackupFlags(
|
||||
val includeCategories: Boolean,
|
||||
val includeChapters: Boolean,
|
||||
val includeTracking: Boolean,
|
||||
val includeHistory: Boolean
|
||||
val includeHistory: Boolean,
|
||||
)
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
package suwayomi.tachidesk.manga.impl.backup.models
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
interface Category : Serializable {
|
||||
|
||||
var id: Int?
|
||||
|
||||
var name: String
|
||||
|
||||
var order: Int
|
||||
|
||||
var flags: Int
|
||||
|
||||
companion object {
|
||||
|
||||
fun create(name: String): Category = CategoryImpl().apply {
|
||||
this.name = name
|
||||
}
|
||||
|
||||
fun createDefault(): Category = create("Default").apply { id = 0 }
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package suwayomi.tachidesk.manga.impl.backup.models
|
||||
|
||||
class CategoryImpl : Category {
|
||||
|
||||
override var id: Int? = null
|
||||
|
||||
override lateinit var name: String
|
||||
|
||||
override var order: Int = 0
|
||||
|
||||
override var flags: Int = 0
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other == null || javaClass != other.javaClass) return false
|
||||
|
||||
val category = other as Category
|
||||
return name == category.name
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return name.hashCode()
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
@file:Suppress("ktlint:standard:property-naming")
|
||||
|
||||
package suwayomi.tachidesk.manga.impl.backup.models
|
||||
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import java.io.Serializable
|
||||
|
||||
interface Chapter : SChapter, Serializable {
|
||||
|
||||
var id: Long?
|
||||
|
||||
var manga_id: Long?
|
||||
@@ -23,9 +24,9 @@ interface Chapter : SChapter, Serializable {
|
||||
get() = chapter_number >= 0f
|
||||
|
||||
companion object {
|
||||
|
||||
fun create(): Chapter = ChapterImpl().apply {
|
||||
chapter_number = -1f
|
||||
}
|
||||
fun create(): Chapter =
|
||||
ChapterImpl().apply {
|
||||
chapter_number = -1f
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
@file:Suppress("ktlint:standard:property-naming")
|
||||
|
||||
package suwayomi.tachidesk.manga.impl.backup.models
|
||||
|
||||
import org.jetbrains.exposed.sql.ResultRow
|
||||
import suwayomi.tachidesk.manga.model.table.ChapterTable
|
||||
|
||||
class ChapterImpl : Chapter {
|
||||
|
||||
override var id: Long? = null
|
||||
|
||||
override var manga_id: Long? = null
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
package suwayomi.tachidesk.manga.impl.backup.models
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
/**
|
||||
* Object containing the history statistics of a chapter
|
||||
*/
|
||||
interface History : Serializable {
|
||||
|
||||
/**
|
||||
* Id of history object.
|
||||
*/
|
||||
var id: Long?
|
||||
|
||||
/**
|
||||
* Chapter id of history object.
|
||||
*/
|
||||
var chapter_id: Long
|
||||
|
||||
/**
|
||||
* Last time chapter was read in time long format
|
||||
*/
|
||||
var last_read: Long
|
||||
|
||||
/**
|
||||
* Total time chapter was read - todo not yet implemented
|
||||
*/
|
||||
var time_read: Long
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* History constructor
|
||||
*
|
||||
* @param chapter chapter object
|
||||
* @return history object
|
||||
*/
|
||||
fun create(chapter: Chapter): History = HistoryImpl().apply {
|
||||
this.chapter_id = chapter.id!!
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package suwayomi.tachidesk.manga.impl.backup.models
|
||||
|
||||
/**
|
||||
* Object containing the history statistics of a chapter
|
||||
*/
|
||||
class HistoryImpl : History {
|
||||
|
||||
/**
|
||||
* Id of history object.
|
||||
*/
|
||||
override var id: Long? = null
|
||||
|
||||
/**
|
||||
* Chapter id of history object.
|
||||
*/
|
||||
override var chapter_id: Long = 0
|
||||
|
||||
/**
|
||||
* Last time chapter was read in time long format
|
||||
*/
|
||||
override var last_read: Long = 0
|
||||
|
||||
/**
|
||||
* Total time chapter was read - todo not yet implemented
|
||||
*/
|
||||
override var time_read: Long = 0
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
@file:Suppress("ktlint:standard:property-naming")
|
||||
|
||||
package suwayomi.tachidesk.manga.impl.backup.models
|
||||
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
@@ -15,7 +17,6 @@ object ReadingModeType {
|
||||
}
|
||||
|
||||
interface Manga : SManga {
|
||||
|
||||
var id: Long?
|
||||
|
||||
var source: Long
|
||||
@@ -48,11 +49,17 @@ interface Manga : SManga {
|
||||
return genre?.split(", ")?.map { it.trim() }
|
||||
}
|
||||
|
||||
private fun setChapterFlags(flag: Int, mask: Int) {
|
||||
private fun setChapterFlags(
|
||||
flag: Int,
|
||||
mask: Int,
|
||||
) {
|
||||
chapter_flags = chapter_flags and mask.inv() or (flag and mask)
|
||||
}
|
||||
|
||||
private fun setViewerFlags(flag: Int, mask: Int) {
|
||||
private fun setViewerFlags(
|
||||
flag: Int,
|
||||
mask: Int,
|
||||
) {
|
||||
viewer_flags = viewer_flags and mask.inv() or (flag and mask)
|
||||
}
|
||||
|
||||
@@ -86,7 +93,6 @@ interface Manga : SManga {
|
||||
set(rotationType) = setViewerFlags(rotationType, OrientationType.MASK)
|
||||
|
||||
companion object {
|
||||
|
||||
// Generic filter that does not filter anything
|
||||
const val SHOW_ALL = 0x00000000
|
||||
|
||||
@@ -115,15 +121,21 @@ interface Manga : SManga {
|
||||
const val CHAPTER_DISPLAY_NUMBER = 0x00100000
|
||||
const val CHAPTER_DISPLAY_MASK = 0x00100000
|
||||
|
||||
fun create(source: Long): Manga = MangaImpl().apply {
|
||||
this.source = source
|
||||
}
|
||||
fun create(source: Long): Manga =
|
||||
MangaImpl().apply {
|
||||
this.source = source
|
||||
}
|
||||
|
||||
fun create(pathUrl: String, title: String, source: Long = 0): Manga = MangaImpl().apply {
|
||||
url = pathUrl
|
||||
this.title = title
|
||||
this.source = source
|
||||
}
|
||||
fun create(
|
||||
pathUrl: String,
|
||||
title: String,
|
||||
source: Long = 0,
|
||||
): Manga =
|
||||
MangaImpl().apply {
|
||||
url = pathUrl
|
||||
this.title = title
|
||||
this.source = source
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
package suwayomi.tachidesk.manga.impl.backup.models
|
||||
|
||||
class MangaCategory {
|
||||
|
||||
var id: Long? = null
|
||||
|
||||
var manga_id: Long = 0
|
||||
|
||||
var category_id: Int = 0
|
||||
|
||||
companion object {
|
||||
|
||||
fun create(manga: Manga, category: Category): MangaCategory {
|
||||
val mc = MangaCategory()
|
||||
mc.manga_id = manga.id!!
|
||||
mc.category_id = category.id!!
|
||||
return mc
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
package suwayomi.tachidesk.manga.impl.backup.models
|
||||
|
||||
class MangaChapter(val manga: Manga, val chapter: Chapter)
|
||||
@@ -1,10 +0,0 @@
|
||||
package suwayomi.tachidesk.manga.impl.backup.models
|
||||
|
||||
/**
|
||||
* Object containing manga, chapter and history
|
||||
*
|
||||
* @param manga object containing manga
|
||||
* @param chapter object containing chater
|
||||
* @param history object containing history
|
||||
*/
|
||||
data class MangaChapterHistory(val manga: Manga, val chapter: Chapter, val history: History)
|
||||
@@ -1,3 +1,5 @@
|
||||
@file:Suppress("ktlint:standard:property-naming")
|
||||
|
||||
package suwayomi.tachidesk.manga.impl.backup.models
|
||||
|
||||
import eu.kanade.tachiyomi.source.model.UpdateStrategy
|
||||
@@ -5,7 +7,6 @@ import org.jetbrains.exposed.sql.ResultRow
|
||||
import suwayomi.tachidesk.manga.model.table.MangaTable
|
||||
|
||||
open class MangaImpl : Manga {
|
||||
|
||||
override var id: Long? = null
|
||||
|
||||
override var source: Long = -1
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
@file:Suppress("ktlint:standard:property-naming")
|
||||
|
||||
package suwayomi.tachidesk.manga.impl.backup.models
|
||||
|
||||
import java.io.Serializable
|
||||
|
||||
interface Track : Serializable {
|
||||
|
||||
var id: Long?
|
||||
|
||||
var manga_id: Long
|
||||
@@ -39,8 +40,9 @@ interface Track : Serializable {
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun create(serviceId: Int): Track = TrackImpl().apply {
|
||||
sync_id = serviceId
|
||||
}
|
||||
fun create(serviceId: Int): Track =
|
||||
TrackImpl().apply {
|
||||
sync_id = serviceId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
@file:Suppress("ktlint:standard:property-naming")
|
||||
|
||||
package suwayomi.tachidesk.manga.impl.backup.models
|
||||
|
||||
class TrackImpl : Track {
|
||||
|
||||
override var id: Long? = null
|
||||
|
||||
override var manga_id: Long = 0
|
||||
|
||||
@@ -51,7 +51,7 @@ object ProtoBackupExport : ProtoBackupBase() {
|
||||
private val logger = KotlinLogging.logger { }
|
||||
private val applicationDirs by DI.global.instance<ApplicationDirs>()
|
||||
private var backupSchedulerJobId: String = ""
|
||||
private const val lastAutomatedBackupKey = "lastAutomatedBackupKey"
|
||||
private const val LAST_AUTOMATED_BACKUP_KEY = "lastAutomatedBackupKey"
|
||||
private val preferences = Preferences.userNodeForPackage(ProtoBackupExport::class.java)
|
||||
|
||||
init {
|
||||
@@ -59,10 +59,10 @@ object ProtoBackupExport : ProtoBackupBase() {
|
||||
combine(serverConfig.backupInterval, serverConfig.backupTime) { interval, timeOfDay ->
|
||||
Pair(
|
||||
interval,
|
||||
timeOfDay
|
||||
timeOfDay,
|
||||
)
|
||||
},
|
||||
::scheduleAutomatedBackupTask
|
||||
::scheduleAutomatedBackupTask,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ object ProtoBackupExport : ProtoBackupBase() {
|
||||
val task = {
|
||||
cleanupAutomatedBackups()
|
||||
createAutomatedBackup()
|
||||
preferences.putLong(lastAutomatedBackupKey, System.currentTimeMillis())
|
||||
preferences.putLong(LAST_AUTOMATED_BACKUP_KEY, System.currentTimeMillis())
|
||||
}
|
||||
|
||||
val (hour, minute) = serverConfig.backupTime.value.split(":").map { it.toInt() }
|
||||
@@ -86,7 +86,7 @@ object ProtoBackupExport : ProtoBackupBase() {
|
||||
val backupInterval = serverConfig.backupInterval.value.days.coerceAtLeast(1.days)
|
||||
|
||||
// trigger last backup in case the server wasn't running on the scheduled time
|
||||
val lastAutomatedBackup = preferences.getLong(lastAutomatedBackupKey, System.currentTimeMillis())
|
||||
val lastAutomatedBackup = preferences.getLong(LAST_AUTOMATED_BACKUP_KEY, System.currentTimeMillis())
|
||||
val wasPreviousBackupTriggered =
|
||||
(System.currentTimeMillis() - lastAutomatedBackup) < backupInterval.inWholeMilliseconds
|
||||
if (!wasPreviousBackupTriggered) {
|
||||
@@ -105,8 +105,8 @@ object ProtoBackupExport : ProtoBackupBase() {
|
||||
includeCategories = true,
|
||||
includeChapters = true,
|
||||
includeTracking = true,
|
||||
includeHistory = true
|
||||
)
|
||||
includeHistory = true,
|
||||
),
|
||||
).use { input ->
|
||||
val automatedBackupDir = File(applicationDirs.automatedBackupRoot)
|
||||
automatedBackupDir.mkdirs()
|
||||
@@ -162,14 +162,15 @@ object ProtoBackupExport : ProtoBackupBase() {
|
||||
|
||||
val databaseManga = transaction { MangaTable.select { MangaTable.inLibrary eq true } }
|
||||
|
||||
val backup: Backup = transaction {
|
||||
Backup(
|
||||
backupManga(databaseManga, flags),
|
||||
backupCategories(),
|
||||
emptyList(),
|
||||
backupExtensionInfo(databaseManga)
|
||||
)
|
||||
}
|
||||
val backup: Backup =
|
||||
transaction {
|
||||
Backup(
|
||||
backupManga(databaseManga, flags),
|
||||
backupCategories(),
|
||||
emptyList(),
|
||||
backupExtensionInfo(databaseManga),
|
||||
)
|
||||
}
|
||||
|
||||
val byteArray = parser.encodeToByteArray(BackupSerializer, backup)
|
||||
|
||||
@@ -179,48 +180,54 @@ object ProtoBackupExport : ProtoBackupBase() {
|
||||
return byteStream.toByteArray().inputStream()
|
||||
}
|
||||
|
||||
private fun backupManga(databaseManga: Query, flags: BackupFlags): List<BackupManga> {
|
||||
private fun backupManga(
|
||||
databaseManga: Query,
|
||||
flags: BackupFlags,
|
||||
): List<BackupManga> {
|
||||
return databaseManga.map { mangaRow ->
|
||||
val backupManga = BackupManga(
|
||||
source = mangaRow[MangaTable.sourceReference],
|
||||
url = mangaRow[MangaTable.url],
|
||||
title = mangaRow[MangaTable.title],
|
||||
artist = mangaRow[MangaTable.artist],
|
||||
author = mangaRow[MangaTable.author],
|
||||
description = mangaRow[MangaTable.description],
|
||||
genre = mangaRow[MangaTable.genre]?.split(", ") ?: emptyList(),
|
||||
status = MangaStatus.valueOf(mangaRow[MangaTable.status]).value,
|
||||
thumbnailUrl = mangaRow[MangaTable.thumbnail_url],
|
||||
dateAdded = TimeUnit.SECONDS.toMillis(mangaRow[MangaTable.inLibraryAt]),
|
||||
viewer = 0, // not supported in Tachidesk
|
||||
updateStrategy = UpdateStrategy.valueOf(mangaRow[MangaTable.updateStrategy])
|
||||
)
|
||||
val backupManga =
|
||||
BackupManga(
|
||||
source = mangaRow[MangaTable.sourceReference],
|
||||
url = mangaRow[MangaTable.url],
|
||||
title = mangaRow[MangaTable.title],
|
||||
artist = mangaRow[MangaTable.artist],
|
||||
author = mangaRow[MangaTable.author],
|
||||
description = mangaRow[MangaTable.description],
|
||||
genre = mangaRow[MangaTable.genre]?.split(", ") ?: emptyList(),
|
||||
status = MangaStatus.valueOf(mangaRow[MangaTable.status]).value,
|
||||
thumbnailUrl = mangaRow[MangaTable.thumbnail_url],
|
||||
dateAdded = TimeUnit.SECONDS.toMillis(mangaRow[MangaTable.inLibraryAt]),
|
||||
viewer = 0, // not supported in Tachidesk
|
||||
updateStrategy = UpdateStrategy.valueOf(mangaRow[MangaTable.updateStrategy]),
|
||||
)
|
||||
|
||||
val mangaId = mangaRow[MangaTable.id].value
|
||||
|
||||
if (flags.includeChapters) {
|
||||
val chapters = transaction {
|
||||
ChapterTable.select { ChapterTable.manga eq mangaId }
|
||||
.orderBy(ChapterTable.sourceOrder to SortOrder.DESC)
|
||||
.map {
|
||||
ChapterTable.toDataClass(it)
|
||||
}
|
||||
}
|
||||
val chapters =
|
||||
transaction {
|
||||
ChapterTable.select { ChapterTable.manga eq mangaId }
|
||||
.orderBy(ChapterTable.sourceOrder to SortOrder.DESC)
|
||||
.map {
|
||||
ChapterTable.toDataClass(it)
|
||||
}
|
||||
}
|
||||
|
||||
backupManga.chapters = chapters.map {
|
||||
BackupChapter(
|
||||
it.url,
|
||||
it.name,
|
||||
it.scanlator,
|
||||
it.read,
|
||||
it.bookmarked,
|
||||
it.lastPageRead,
|
||||
TimeUnit.SECONDS.toMillis(it.fetchedAt),
|
||||
it.uploadDate,
|
||||
it.chapterNumber,
|
||||
chapters.size - it.index
|
||||
)
|
||||
}
|
||||
backupManga.chapters =
|
||||
chapters.map {
|
||||
BackupChapter(
|
||||
it.url,
|
||||
it.name,
|
||||
it.scanlator,
|
||||
it.read,
|
||||
it.bookmarked,
|
||||
it.lastPageRead,
|
||||
TimeUnit.SECONDS.toMillis(it.fetchedAt),
|
||||
it.uploadDate,
|
||||
it.chapterNumber,
|
||||
chapters.size - it.index,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (flags.includeCategories) {
|
||||
@@ -246,7 +253,7 @@ object ProtoBackupExport : ProtoBackupBase() {
|
||||
BackupCategory(
|
||||
it.name,
|
||||
it.order,
|
||||
0 // not supported in Tachidesk
|
||||
0, // not supported in Tachidesk
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -260,7 +267,7 @@ object ProtoBackupExport : ProtoBackupBase() {
|
||||
val sourceRow = SourceTable.select { SourceTable.id eq it }.firstOrNull()
|
||||
BackupSource(
|
||||
sourceRow?.get(SourceTable.name) ?: "",
|
||||
it
|
||||
it,
|
||||
)
|
||||
}
|
||||
.toList()
|
||||
|
||||
@@ -47,9 +47,12 @@ object ProtoBackupImport : ProtoBackupBase() {
|
||||
private val errors = mutableListOf<Pair<Date, String>>()
|
||||
|
||||
private val backupMutex = Mutex()
|
||||
|
||||
sealed class BackupRestoreState {
|
||||
data object Idle : BackupRestoreState()
|
||||
|
||||
data class RestoringCategories(val totalManga: Int) : BackupRestoreState()
|
||||
|
||||
data class RestoringManga(val current: Int, val totalManga: Int, val title: String) : BackupRestoreState()
|
||||
}
|
||||
|
||||
@@ -70,26 +73,28 @@ object ProtoBackupImport : ProtoBackupBase() {
|
||||
restoreCategories(backup.backupCategories)
|
||||
}
|
||||
|
||||
val categoryMapping = transaction {
|
||||
backup.backupCategories.associate {
|
||||
it.order to CategoryTable.select { CategoryTable.name eq it.name }.first()[CategoryTable.id].value
|
||||
val categoryMapping =
|
||||
transaction {
|
||||
backup.backupCategories.associate {
|
||||
it.order to CategoryTable.select { CategoryTable.name eq it.name }.first()[CategoryTable.id].value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Store source mapping for error messages
|
||||
sourceMapping = backup.getSourceMap()
|
||||
|
||||
// Restore individual manga
|
||||
backup.backupManga.forEachIndexed { index, manga ->
|
||||
backupRestoreState.value = BackupRestoreState.RestoringManga(
|
||||
current = index + 1,
|
||||
totalManga = backup.backupManga.size,
|
||||
title = manga.title
|
||||
)
|
||||
backupRestoreState.value =
|
||||
BackupRestoreState.RestoringManga(
|
||||
current = index + 1,
|
||||
totalManga = backup.backupManga.size,
|
||||
title = manga.title,
|
||||
)
|
||||
restoreManga(
|
||||
backupManga = manga,
|
||||
backupCategories = backup.backupCategories,
|
||||
categoryMapping = categoryMapping
|
||||
categoryMapping = categoryMapping,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -126,7 +131,7 @@ object ProtoBackupImport : ProtoBackupBase() {
|
||||
private fun restoreManga(
|
||||
backupManga: BackupManga,
|
||||
backupCategories: List<BackupCategory>,
|
||||
categoryMapping: Map<Int, Int>
|
||||
categoryMapping: Map<Int, Int>,
|
||||
) {
|
||||
val manga = backupManga.getMangaImpl()
|
||||
val chapters = backupManga.getChaptersImpl()
|
||||
@@ -150,36 +155,38 @@ object ProtoBackupImport : ProtoBackupBase() {
|
||||
history: List<BackupHistory>,
|
||||
tracks: List<Track>,
|
||||
backupCategories: List<BackupCategory>,
|
||||
categoryMapping: Map<Int, Int>
|
||||
categoryMapping: Map<Int, Int>,
|
||||
) {
|
||||
val dbManga = transaction {
|
||||
MangaTable.select { (MangaTable.url eq manga.url) and (MangaTable.sourceReference eq manga.source) }
|
||||
.firstOrNull()
|
||||
}
|
||||
val dbManga =
|
||||
transaction {
|
||||
MangaTable.select { (MangaTable.url eq manga.url) and (MangaTable.sourceReference eq manga.source) }
|
||||
.firstOrNull()
|
||||
}
|
||||
|
||||
if (dbManga == null) { // Manga not in database
|
||||
transaction {
|
||||
// insert manga to database
|
||||
val mangaId = MangaTable.insertAndGetId {
|
||||
it[url] = manga.url
|
||||
it[title] = manga.title
|
||||
val mangaId =
|
||||
MangaTable.insertAndGetId {
|
||||
it[url] = manga.url
|
||||
it[title] = manga.title
|
||||
|
||||
it[artist] = manga.artist
|
||||
it[author] = manga.author
|
||||
it[description] = manga.description
|
||||
it[genre] = manga.genre
|
||||
it[status] = manga.status
|
||||
it[thumbnail_url] = manga.thumbnail_url
|
||||
it[updateStrategy] = manga.update_strategy.name
|
||||
it[artist] = manga.artist
|
||||
it[author] = manga.author
|
||||
it[description] = manga.description
|
||||
it[genre] = manga.genre
|
||||
it[status] = manga.status
|
||||
it[thumbnail_url] = manga.thumbnail_url
|
||||
it[updateStrategy] = manga.update_strategy.name
|
||||
|
||||
it[sourceReference] = manga.source
|
||||
it[sourceReference] = manga.source
|
||||
|
||||
it[initialized] = manga.description != null
|
||||
it[initialized] = manga.description != null
|
||||
|
||||
it[inLibrary] = manga.favorite
|
||||
it[inLibrary] = manga.favorite
|
||||
|
||||
it[inLibraryAt] = TimeUnit.MILLISECONDS.toSeconds(manga.date_added)
|
||||
}.value
|
||||
it[inLibraryAt] = TimeUnit.MILLISECONDS.toSeconds(manga.date_added)
|
||||
}.value
|
||||
|
||||
// insert chapter data
|
||||
val chaptersLength = chapters.size
|
||||
|
||||
@@ -24,7 +24,7 @@ object ProtoBackupValidator {
|
||||
val missingTrackers: List<String>,
|
||||
val mangasMissingSources: List<String>,
|
||||
@JsonIgnore
|
||||
val missingSourceIds: List<Pair<Long, String>>
|
||||
val missingSourceIds: List<Pair<Long, String>>,
|
||||
)
|
||||
|
||||
fun validate(backup: Backup): ValidationResult {
|
||||
@@ -34,9 +34,10 @@ object ProtoBackupValidator {
|
||||
|
||||
val sources = backup.getSourceMap()
|
||||
|
||||
val missingSources = transaction {
|
||||
sources.filter { SourceTable.select { SourceTable.id eq it.key }.firstOrNull() == null }
|
||||
}
|
||||
val missingSources =
|
||||
transaction {
|
||||
sources.filter { SourceTable.select { SourceTable.id eq it.key }.firstOrNull() == null }
|
||||
}
|
||||
|
||||
// val trackers = backup.backupManga
|
||||
// .flatMap { it.tracking }
|
||||
@@ -56,7 +57,7 @@ object ProtoBackupValidator {
|
||||
.sorted(),
|
||||
missingTrackers,
|
||||
emptyList(),
|
||||
missingSources.toList()
|
||||
missingSources.toList(),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ data class Backup(
|
||||
@ProtoNumber(2) var backupCategories: List<BackupCategory> = emptyList(),
|
||||
// Bump by 100 to specify this is a 0.x value
|
||||
@ProtoNumber(100) var brokenBackupSources: List<BrokenBackupSource> = emptyList(),
|
||||
@ProtoNumber(101) var backupSources: List<BackupSource> = emptyList()
|
||||
@ProtoNumber(101) var backupSources: List<BackupSource> = emptyList(),
|
||||
) {
|
||||
fun getSourceMap(): Map<Long, String> {
|
||||
return (brokenBackupSources.map { BackupSource(it.name, it.sourceId) } + backupSources)
|
||||
|
||||
@@ -2,8 +2,6 @@ package suwayomi.tachidesk.manga.impl.backup.proto.models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.protobuf.ProtoNumber
|
||||
import suwayomi.tachidesk.manga.impl.backup.models.Category
|
||||
import suwayomi.tachidesk.manga.impl.backup.models.CategoryImpl
|
||||
|
||||
@Serializable
|
||||
class BackupCategory(
|
||||
@@ -11,23 +9,5 @@ class BackupCategory(
|
||||
@ProtoNumber(2) var order: Int = 0,
|
||||
// @ProtoNumber(3) val updateInterval: Int = 0, 1.x value not used in 0.x
|
||||
// Bump by 100 to specify this is a 0.x value
|
||||
@ProtoNumber(100) var flags: Int = 0
|
||||
) {
|
||||
fun getCategoryImpl(): CategoryImpl {
|
||||
return CategoryImpl().apply {
|
||||
name = this@BackupCategory.name
|
||||
flags = this@BackupCategory.flags
|
||||
order = this@BackupCategory.order
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun copyFrom(category: Category): BackupCategory {
|
||||
return BackupCategory(
|
||||
name = category.name,
|
||||
order = category.order,
|
||||
flags = category.flags
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ProtoNumber(100) var flags: Int = 0,
|
||||
)
|
||||
|
||||
@@ -20,7 +20,7 @@ data class BackupChapter(
|
||||
@ProtoNumber(8) var dateUpload: Long = 0,
|
||||
// chapterNumber is called number is 1.x
|
||||
@ProtoNumber(9) var chapterNumber: Float = 0F,
|
||||
@ProtoNumber(10) var sourceOrder: Int = 0
|
||||
@ProtoNumber(10) var sourceOrder: Int = 0,
|
||||
) {
|
||||
fun toChapterImpl(): ChapterImpl {
|
||||
return ChapterImpl().apply {
|
||||
@@ -49,7 +49,7 @@ data class BackupChapter(
|
||||
lastPageRead = chapter.last_page_read,
|
||||
dateFetch = chapter.date_fetch,
|
||||
dateUpload = chapter.date_upload,
|
||||
sourceOrder = chapter.source_order
|
||||
sourceOrder = chapter.source_order,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,11 @@ import kotlinx.serialization.protobuf.ProtoNumber
|
||||
@Serializable
|
||||
data class BrokenBackupHistory(
|
||||
@ProtoNumber(0) var url: String,
|
||||
@ProtoNumber(1) var lastRead: Long
|
||||
@ProtoNumber(1) var lastRead: Long,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class BackupHistory(
|
||||
@ProtoNumber(1) var url: String,
|
||||
@ProtoNumber(2) var lastRead: Long
|
||||
@ProtoNumber(2) var lastRead: Long,
|
||||
)
|
||||
|
||||
@@ -37,7 +37,7 @@ data class BackupManga(
|
||||
@ProtoNumber(102) var brokenHistory: List<BrokenBackupHistory> = emptyList(),
|
||||
@ProtoNumber(103) var viewer_flags: Int? = null,
|
||||
@ProtoNumber(104) var history: List<BackupHistory> = emptyList(),
|
||||
@ProtoNumber(105) var updateStrategy: UpdateStrategy = UpdateStrategy.ALWAYS_UPDATE
|
||||
@ProtoNumber(105) var updateStrategy: UpdateStrategy = UpdateStrategy.ALWAYS_UPDATE,
|
||||
) {
|
||||
fun getMangaImpl(): MangaImpl {
|
||||
return MangaImpl().apply {
|
||||
@@ -86,7 +86,7 @@ data class BackupManga(
|
||||
dateAdded = manga.date_added,
|
||||
viewer = manga.readingModeType,
|
||||
viewer_flags = manga.viewer_flags,
|
||||
chapterFlags = manga.chapter_flags
|
||||
chapterFlags = manga.chapter_flags,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,19 +7,19 @@ import kotlinx.serialization.protobuf.ProtoNumber
|
||||
@Serializable
|
||||
data class BrokenBackupSource(
|
||||
@ProtoNumber(0) var name: String = "",
|
||||
@ProtoNumber(1) var sourceId: Long
|
||||
@ProtoNumber(1) var sourceId: Long,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class BackupSource(
|
||||
@ProtoNumber(1) var name: String = "",
|
||||
@ProtoNumber(2) var sourceId: Long
|
||||
@ProtoNumber(2) var sourceId: Long,
|
||||
) {
|
||||
companion object {
|
||||
fun copyFrom(source: Source): BackupSource {
|
||||
return BackupSource(
|
||||
name = source.name,
|
||||
sourceId = source.id
|
||||
sourceId = source.id,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user