Compare commits

..

8 Commits

Author SHA1 Message Date
renovate[bot]
b5f9c24631 Update dependency com.github.Suwayomi:exposed-migrations to v3.10.1 2026-05-11 23:21:11 +00:00
Akiaki0324
5bdb945406 fix: truncate filenames by bytes instead of characters to avoid File name too long (#1933)
* fix: truncate filenames by bytes instead of characters to avoid IOException File name too long

* add a CHANGELOG.md entry.
2026-05-10 19:02:11 -04:00
David Brochero
3064f51d25 fix: don't resuse invalidated cf_clearance cookie on CloudFlareInterceptor (#1916)
* fix: let FlareSolverr handle it's own `cf_clearance` cookie

also dedups cookies

* linting

* suggested changes

* my bad

* add to changelog
2026-05-10 19:01:51 -04:00
renovate[bot]
edf376e3dd Update graphqlkotlin to v10 alpha (major) (#1923)
* Update graphqlkotlin to v9

* Update to the v10 alpha due to nullability issues in v9

* Fixes

* Remove asDataFetcherResult

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Syer10 <syer10@users.noreply.github.com>
2026-05-10 19:01:34 -04:00
renovate[bot]
dff66547b4 Update jackson monorepo (#1906)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-09 18:16:05 -04:00
Mitchell Syer
6fef27bb56 Wait until WebUI is ready to open in browser (#2010)
* Wait until WebUI is ready

* Changelog

* Move openInBrowser out of timeout
2026-05-09 18:15:43 -04:00
Mitchell Syer
505e966653 Fix Polyglot (#2011) 2026-05-09 18:15:33 -04:00
renovate[bot]
6ee3348f50 Update javalin to v7 (major) (#1920)
* Update javalin to v7

* Update Javalin usage to v7 and Jackson 3

* Import fix

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Syer10 <syer10@users.noreply.github.com>
2026-05-09 14:52:00 -04:00
36 changed files with 1311 additions and 1327 deletions

View File

@@ -9,6 +9,9 @@ package xyz.nulldev.androidcompat.util
// adopted from: https://github.com/tachiyomiorg/tachiyomi/blob/4cefbce7c34e724b409b6ba127f3c6c5c346ad8d/app/src/main/java/eu/kanade/tachiyomi/util/storage/DiskUtil.kt
object SafePath {
private const val MAX_FILENAME_CHARS = 240
private const val MAX_FILENAME_UTF8_BYTES = 240
/**
* Mutate the given filename to make it valid for a FAT filesystem,
* replacing any invalid characters with "_". This method doesn't allow hidden files (starting
@@ -27,9 +30,39 @@ object SafePath {
sb.append('_')
}
}
// Even though vfat allows 255 UCS-2 chars, we might eventually write to
// ext4 through a FUSE layer, so use that limit minus 15 reserved characters.
return sb.toString().take(240)
return truncateFilename(sb.toString())
}
private fun truncateFilename(filename: String): String {
// Keep a safety margin under common filesystem limits and satisfy both
// character count and UTF-8 byte-length constraints.
val output = StringBuilder(minOf(filename.length, MAX_FILENAME_CHARS))
var usedBytes = 0
var index = 0
while (index < filename.length && output.length < MAX_FILENAME_CHARS) {
val codePoint = Character.codePointAt(filename, index)
val codePointBytes = utf8ByteCount(codePoint)
if (usedBytes + codePointBytes > MAX_FILENAME_UTF8_BYTES) {
break
}
output.appendCodePoint(codePoint)
usedBytes += codePointBytes
index += Character.charCount(codePoint)
}
return output.toString()
}
private fun utf8ByteCount(codePoint: Int): Int =
when {
codePoint <= 0x7f -> 1
codePoint <= 0x7ff -> 2
codePoint <= 0xffff -> 3
else -> 4
}
/**

View File

@@ -13,7 +13,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- .
### Fixed
- (CloudFlareInterceptor) Don't send the `cf_clearance` cookie back to Flaresolverr
- (WebUI) Handle serving non-default webui with "bundled"
- (WebUI) Wait until WebUI is ready to open in browser
- (Downloads) Truncate filenames by byte length to prevent "File name too long" IO errors
## [v2.2.2100] + [WebUI: v20260508.01] - 2026-05-08

View File

@@ -6,13 +6,13 @@ jvmTarget = "21"
okhttp = "5.3.2" # Major version is locked by Tachiyomi extensions
javalin = "7.2.0"
jte = "3.2.4"
jackson = "3.1.2" # jackson version locked by javalin, ref: `io.javalin.core.util.OptionalDependency`
jackson = "3.1.3" # jackson version locked by javalin, ref: `io.javalin.core.util.OptionalDependency`
exposed = "0.61.0"
dex2jar = "2.4.36"
polyglot = "25.0.3"
settings = "1.3.0"
twelvemonkeys = "3.13.1"
graphqlkotlin = "8.9.0"
graphqlkotlin = "10.0.0-alpha.3"
xmlserialization = "0.91.3"
ktlint = "1.8.0"
koin = "4.2.1"
@@ -54,7 +54,7 @@ javalin-openapi = { module = "io.javalin:javalin-openapi", version.ref = "javali
javalin-rendering = { module = "io.javalin:javalin-rendering-jte", version.ref = "javalin" }
jackson-databind = { module = "tools.jackson.core:jackson-databind", version.ref = "jackson" }
jackson-kotlin = { module = "tools.jackson.module:jackson-module-kotlin", version.ref = "jackson" }
jackson-annotations = "com.fasterxml.jackson.core:jackson-annotations:2.20"
jackson-annotations = "com.fasterxml.jackson.core:jackson-annotations:2.21"
jte = { module = "gg.jte:jte", version.ref = "jte" }
kte = { module = "gg.jte:jte-kotlin", version.ref = "jte" }
@@ -73,7 +73,7 @@ h2 = "com.h2database:h2:1.4.200" # current database driver, can't update to h2 v
hikaricp = "com.zaxxer:HikariCP:7.0.2"
# Exposed Migrations
exposed-migrations = "com.github.Suwayomi:exposed-migrations:3.8.0"
exposed-migrations = "com.github.Suwayomi:exposed-migrations:3.10.1"
# Dependency Injection
koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" }

View File

@@ -171,6 +171,7 @@ tasks {
"Implementation-Vendor" to "The Suwayomi Project",
"Specification-Version" to getTachideskVersion(),
"Implementation-Version" to getTachideskRevision(),
"Multi-Release" to true, // needed for polyglot
)
}
archiveBaseName.set(rootProject.name)

View File

@@ -103,7 +103,7 @@ class CloudflareInterceptor(
companion object {
private val ERROR_CODES = listOf(403, 503)
private val SERVER_CHECK = arrayOf("cloudflare-nginx", "cloudflare")
private val COOKIE_NAMES = listOf("cf_clearance")
val COOKIE_NAMES = listOf("cf_clearance")
private val CHROME_IMAGE_TEMPLATE_REGEX = Regex("""<title>(.*?) \(\d+×\d+\)</title>""")
}
}
@@ -205,8 +205,11 @@ object CFClearance {
session = serverConfig.flareSolverrSessionName.value,
sessionTtlMinutes = serverConfig.flareSolverrSessionTtl.value,
cookies =
network.cookieStore.get(originalRequest.url).map {
FlareSolverCookie(it.name, it.value)
network.cookieStore
.get(originalRequest.url)
.filter { it.name !in CloudflareInterceptor.COOKIE_NAMES }
.map { cookie ->
FlareSolverCookie(cookie.name, cookie.value)
},
returnOnlyCookies = onlyCookies,
maxTimeout = timeout.inWholeMilliseconds.toInt(),

View File

@@ -1,27 +0,0 @@
package suwayomi.tachidesk.graphql
import com.expediagroup.graphql.server.extensions.toGraphQLError
import graphql.execution.DataFetcherResult
import io.github.oshai.kotlinlogging.KotlinLogging
val logger = KotlinLogging.logger { }
inline fun <T> asDataFetcherResult(block: () -> T): DataFetcherResult<T?> {
val result =
runCatching {
block()
}
if (result.isFailure) {
logger.error(result.exceptionOrNull()) { "asDataFetcherResult: failed due to" }
return DataFetcherResult
.newResult<T?>()
.error(result.exceptionOrNull()?.toGraphQLError())
.build()
}
return DataFetcherResult
.newResult<T?>()
.data(result.getOrNull())
.build()
}

View File

@@ -3,12 +3,8 @@ package suwayomi.tachidesk.graphql.cache
import org.dataloader.CacheMap
import java.util.concurrent.CompletableFuture
class CustomCacheMap<K, V> : CacheMap<K, V> {
private val cache: MutableMap<K, CompletableFuture<V>>
init {
cache = HashMap()
}
class CustomCacheMap<K : Any, V : Any> : CacheMap<K, V> {
private val cache: MutableMap<K, CompletableFuture<V>> = HashMap()
override fun containsKey(key: K): Boolean = cache.containsKey(key)
@@ -18,12 +14,12 @@ class CustomCacheMap<K, V> : CacheMap<K, V> {
override fun getAll(): Collection<CompletableFuture<V>> = cache.values
override fun set(
override fun putIfAbsentAtomically(
key: K,
value: CompletableFuture<V>,
): CacheMap<K, V> {
): CompletableFuture<V> {
cache[key] = value
return this
return value
}
override fun delete(key: K): CacheMap<K, V> {
@@ -35,4 +31,6 @@ class CustomCacheMap<K, V> : CacheMap<K, V> {
cache.clear()
return this
}
override fun size(): Int = cache.size
}

View File

@@ -24,11 +24,11 @@ import suwayomi.tachidesk.graphql.types.ChapterType
import suwayomi.tachidesk.manga.model.table.ChapterTable
import suwayomi.tachidesk.server.JavalinSetup.future
class ChapterDataLoader : KotlinDataLoader<Int, ChapterType?> {
class ChapterDataLoader : KotlinDataLoader<Int, ChapterType> {
override val dataLoaderName = "ChapterDataLoader"
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterType?> =
DataLoaderFactory.newDataLoader<Int, ChapterType> { ids ->
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterType> =
DataLoaderFactory.newDataLoader { ids ->
future {
transaction {
addLogger(Slf4jSqlDebugLogger)
@@ -48,7 +48,7 @@ class ChaptersForMangaDataLoader : KotlinDataLoader<Int, ChapterNodeList> {
override val dataLoaderName = "ChaptersForMangaDataLoader"
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterNodeList> =
DataLoaderFactory.newDataLoader<Int, ChapterNodeList> { ids ->
DataLoaderFactory.newDataLoader { ids ->
future {
transaction {
addLogger(Slf4jSqlDebugLogger)
@@ -68,7 +68,7 @@ class DownloadedChapterCountForMangaDataLoader : KotlinDataLoader<Int, Int> {
override val dataLoaderName = "DownloadedChapterCountForMangaDataLoader"
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, Int> =
DataLoaderFactory.newDataLoader<Int, Int> { ids ->
DataLoaderFactory.newDataLoader { ids ->
future {
transaction {
addLogger(Slf4jSqlDebugLogger)
@@ -90,7 +90,7 @@ class UnreadChapterCountForMangaDataLoader : KotlinDataLoader<Int, Int> {
override val dataLoaderName = "UnreadChapterCountForMangaDataLoader"
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, Int> =
DataLoaderFactory.newDataLoader<Int, Int> { ids ->
DataLoaderFactory.newDataLoader { ids ->
future {
transaction {
addLogger(Slf4jSqlDebugLogger)
@@ -112,7 +112,7 @@ class BookmarkedChapterCountForMangaDataLoader : KotlinDataLoader<Int, Int> {
override val dataLoaderName = "BookmarkedChapterCountForMangaDataLoader"
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, Int> =
DataLoaderFactory.newDataLoader<Int, Int> { ids ->
DataLoaderFactory.newDataLoader { ids ->
future {
transaction {
addLogger(Slf4jSqlDebugLogger)
@@ -157,11 +157,11 @@ class HasDuplicateChaptersForMangaDataLoader : KotlinDataLoader<Int, Boolean> {
}
}
class LastReadChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType?> {
class LastReadChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType> {
override val dataLoaderName = "LastReadChapterForMangaDataLoader"
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterType?> =
DataLoaderFactory.newDataLoader<Int, ChapterType?> { ids ->
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterType> =
DataLoaderFactory.newDataLoader { ids ->
future {
transaction {
addLogger(Slf4jSqlDebugLogger)
@@ -177,11 +177,11 @@ class LastReadChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType?> {
}
}
class LatestReadChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType?> {
class LatestReadChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType> {
override val dataLoaderName = "LatestReadChapterForMangaDataLoader"
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterType?> =
DataLoaderFactory.newDataLoader<Int, ChapterType?> { ids ->
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterType> =
DataLoaderFactory.newDataLoader { ids ->
future {
transaction {
addLogger(Slf4jSqlDebugLogger)
@@ -197,11 +197,11 @@ class LatestReadChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType?>
}
}
class LatestFetchedChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType?> {
class LatestFetchedChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType> {
override val dataLoaderName = "LatestFetchedChapterForMangaDataLoader"
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterType?> =
DataLoaderFactory.newDataLoader<Int, ChapterType?> { ids ->
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterType> =
DataLoaderFactory.newDataLoader { ids ->
future {
transaction {
addLogger(Slf4jSqlDebugLogger)
@@ -217,11 +217,11 @@ class LatestFetchedChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType
}
}
class LatestUploadedChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType?> {
class LatestUploadedChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType> {
override val dataLoaderName = "LatestUploadedChapterForMangaDataLoader"
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterType?> =
DataLoaderFactory.newDataLoader<Int, ChapterType?> { ids ->
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterType> =
DataLoaderFactory.newDataLoader { ids ->
future {
transaction {
addLogger(Slf4jSqlDebugLogger)
@@ -237,11 +237,11 @@ class LatestUploadedChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterTyp
}
}
class FirstUnreadChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType?> {
class FirstUnreadChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType> {
override val dataLoaderName = "FirstUnreadChapterForMangaDataLoader"
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterType?> =
DataLoaderFactory.newDataLoader<Int, ChapterType?> { ids ->
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterType> =
DataLoaderFactory.newDataLoader { ids ->
future {
transaction {
addLogger(Slf4jSqlDebugLogger)
@@ -257,11 +257,11 @@ class FirstUnreadChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType?>
}
}
class HighestNumberedChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType?> {
class HighestNumberedChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType> {
override val dataLoaderName = "HighestNumberedChapterForMangaDataLoader"
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterType?> =
DataLoaderFactory.newDataLoader<Int, ChapterType?> { ids ->
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, ChapterType> =
DataLoaderFactory.newDataLoader { ids ->
future {
transaction {
addLogger(Slf4jSqlDebugLogger)

View File

@@ -20,10 +20,10 @@ import suwayomi.tachidesk.manga.model.table.ExtensionTable
import suwayomi.tachidesk.manga.model.table.SourceTable
import suwayomi.tachidesk.server.JavalinSetup.future
class ExtensionDataLoader : KotlinDataLoader<String, ExtensionType?> {
class ExtensionDataLoader : KotlinDataLoader<String, ExtensionType> {
override val dataLoaderName = "ExtensionDataLoader"
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<String, ExtensionType?> =
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<String, ExtensionType> =
DataLoaderFactory.newDataLoader { ids ->
future {
transaction {
@@ -40,10 +40,10 @@ class ExtensionDataLoader : KotlinDataLoader<String, ExtensionType?> {
}
}
class ExtensionForSourceDataLoader : KotlinDataLoader<Long, ExtensionType?> {
class ExtensionForSourceDataLoader : KotlinDataLoader<Long, ExtensionType> {
override val dataLoaderName = "ExtensionForSourceDataLoader"
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Long, ExtensionType?> =
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Long, ExtensionType> =
DataLoaderFactory.newDataLoader { ids ->
future {
transaction {

View File

@@ -25,10 +25,10 @@ import suwayomi.tachidesk.manga.model.table.CategoryMangaTable
import suwayomi.tachidesk.manga.model.table.MangaTable
import suwayomi.tachidesk.server.JavalinSetup.future
class MangaDataLoader : KotlinDataLoader<Int, MangaType?> {
class MangaDataLoader : KotlinDataLoader<Int, MangaType> {
override val dataLoaderName = "MangaDataLoader"
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, MangaType?> =
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Int, MangaType> =
DataLoaderFactory.newDataLoader { ids ->
future {
transaction {
@@ -122,6 +122,6 @@ class MangaForIdsDataLoader : KotlinDataLoader<List<Int>, MangaNodeList> {
}
}
},
DataLoaderOptions.newOptions().setCacheMap(CustomCacheMap<List<Int>, MangaNodeList>()),
DataLoaderOptions.newOptions().setCacheMap(CustomCacheMap<List<Int>, MangaNodeList>()).build(),
)
}

View File

@@ -20,11 +20,11 @@ import suwayomi.tachidesk.manga.model.table.MangaMetaTable
import suwayomi.tachidesk.manga.model.table.SourceMetaTable
import suwayomi.tachidesk.server.JavalinSetup.future
class GlobalMetaDataLoader : KotlinDataLoader<String, GlobalMetaType?> {
class GlobalMetaDataLoader : KotlinDataLoader<String, GlobalMetaType> {
override val dataLoaderName = "GlobalMetaDataLoader"
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<String, GlobalMetaType?> =
DataLoaderFactory.newDataLoader<String, GlobalMetaType?> { ids ->
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<String, GlobalMetaType> =
DataLoaderFactory.newDataLoader<String, GlobalMetaType> { ids ->
future {
transaction {
addLogger(Slf4jSqlDebugLogger)

View File

@@ -22,10 +22,10 @@ import suwayomi.tachidesk.manga.model.table.ExtensionTable
import suwayomi.tachidesk.manga.model.table.SourceTable
import suwayomi.tachidesk.server.JavalinSetup.future
class SourceDataLoader : KotlinDataLoader<Long, SourceType?> {
class SourceDataLoader : KotlinDataLoader<Long, SourceType> {
override val dataLoaderName = "SourceDataLoader"
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Long, SourceType?> =
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<Long, SourceType> =
DataLoaderFactory.newDataLoader { ids ->
future {
transaction {

View File

@@ -1,3 +1,5 @@
@file:Suppress("RedundantNullableReturnType", "unused")
package suwayomi.tachidesk.graphql.mutations
import com.expediagroup.graphql.generator.annotations.GraphQLDeprecated

View File

@@ -1,3 +1,5 @@
@file:Suppress("RedundantNullableReturnType", "unused")
package suwayomi.tachidesk.graphql.mutations
import graphql.execution.DataFetcherResult
@@ -15,7 +17,6 @@ import org.jetbrains.exposed.sql.or
import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.transactions.transaction
import org.jetbrains.exposed.sql.update
import suwayomi.tachidesk.graphql.asDataFetcherResult
import suwayomi.tachidesk.graphql.directives.RequireAuth
import suwayomi.tachidesk.graphql.types.CategoryMetaType
import suwayomi.tachidesk.graphql.types.CategoryType
@@ -42,13 +43,12 @@ class CategoryMutation {
)
@RequireAuth
fun setCategoryMeta(input: SetCategoryMetaInput): DataFetcherResult<SetCategoryMetaPayload?> =
asDataFetcherResult {
fun setCategoryMeta(input: SetCategoryMetaInput): SetCategoryMetaPayload? {
val (clientMutationId, meta) = input
Category.modifyMeta(meta.categoryId, meta.key, meta.value)
SetCategoryMetaPayload(clientMutationId, meta)
return SetCategoryMetaPayload(clientMutationId, meta)
}
data class DeleteCategoryMetaInput(
@@ -64,8 +64,7 @@ class CategoryMutation {
)
@RequireAuth
fun deleteCategoryMeta(input: DeleteCategoryMetaInput): DataFetcherResult<DeleteCategoryMetaPayload?> =
asDataFetcherResult {
fun deleteCategoryMeta(input: DeleteCategoryMetaInput): DeleteCategoryMetaPayload? {
val (clientMutationId, categoryId, key) = input
val (meta, category) =
@@ -90,7 +89,7 @@ class CategoryMutation {
} to category
}
DeleteCategoryMetaPayload(clientMutationId, meta, category)
return DeleteCategoryMetaPayload(clientMutationId, meta, category)
}
data class SetCategoryMetasItem(
@@ -110,8 +109,7 @@ class CategoryMutation {
)
@RequireAuth
fun setCategoryMetas(input: SetCategoryMetasInput): DataFetcherResult<SetCategoryMetasPayload?> =
asDataFetcherResult {
fun setCategoryMetas(input: SetCategoryMetasInput): SetCategoryMetasPayload? {
val (clientMutationId, items) = input
val metaByCategoryId =
@@ -145,7 +143,7 @@ class CategoryMutation {
updatedMetas to categories
}
SetCategoryMetasPayload(clientMutationId, updatedMetas, categories)
return SetCategoryMetasPayload(clientMutationId, updatedMetas, categories)
}
data class DeleteCategoryMetasItem(
@@ -166,8 +164,7 @@ class CategoryMutation {
)
@RequireAuth
fun deleteCategoryMetas(input: DeleteCategoryMetasInput): DataFetcherResult<DeleteCategoryMetasPayload?> =
asDataFetcherResult {
fun deleteCategoryMetas(input: DeleteCategoryMetasInput): DeleteCategoryMetasPayload? {
val (clientMutationId, items) = input
items.forEach { item ->
@@ -222,7 +219,7 @@ class CategoryMutation {
.distinctBy { it.id }
}
DeleteCategoryMetasPayload(clientMutationId, allDeletedMetas, categories)
return DeleteCategoryMetasPayload(clientMutationId, allDeletedMetas, categories)
}
data class UpdateCategoryPatch(
@@ -291,8 +288,7 @@ class CategoryMutation {
}
@RequireAuth
fun updateCategory(input: UpdateCategoryInput): DataFetcherResult<UpdateCategoryPayload?> =
asDataFetcherResult {
fun updateCategory(input: UpdateCategoryInput): UpdateCategoryPayload? {
val (clientMutationId, id, patch) = input
updateCategories(listOf(id), patch)
@@ -302,15 +298,14 @@ class CategoryMutation {
CategoryType(CategoryTable.selectAll().where { CategoryTable.id eq id }.first())
}
UpdateCategoryPayload(
return UpdateCategoryPayload(
clientMutationId = clientMutationId,
category = category,
)
}
@RequireAuth
fun updateCategories(input: UpdateCategoriesInput): DataFetcherResult<UpdateCategoriesPayload?> =
asDataFetcherResult {
fun updateCategories(input: UpdateCategoriesInput): UpdateCategoriesPayload? {
val (clientMutationId, ids, patch) = input
updateCategories(ids, patch)
@@ -320,7 +315,7 @@ class CategoryMutation {
CategoryTable.selectAll().where { CategoryTable.id inList ids }.map { CategoryType(it) }
}
UpdateCategoriesPayload(
return UpdateCategoriesPayload(
clientMutationId = clientMutationId,
categories = categories,
)
@@ -338,8 +333,7 @@ class CategoryMutation {
)
@RequireAuth
fun updateCategoryOrder(input: UpdateCategoryOrderInput): DataFetcherResult<UpdateCategoryOrderPayload?> =
asDataFetcherResult {
fun updateCategoryOrder(input: UpdateCategoryOrderInput): UpdateCategoryOrderPayload? {
val (clientMutationId, categoryId, position) = input
require(position > 0) {
"'order' must not be <= 0"
@@ -376,7 +370,7 @@ class CategoryMutation {
CategoryTable.selectAll().orderBy(CategoryTable.order).map { CategoryType(it) }
}
UpdateCategoryOrderPayload(
return UpdateCategoryOrderPayload(
clientMutationId = clientMutationId,
categories = categories,
)
@@ -397,8 +391,7 @@ class CategoryMutation {
)
@RequireAuth
fun createCategory(input: CreateCategoryInput): DataFetcherResult<CreateCategoryPayload?> =
asDataFetcherResult {
fun createCategory(input: CreateCategoryInput): CreateCategoryPayload? {
val (clientMutationId, name, order, default, includeInUpdate, includeInDownload) = input
transaction {
require(CategoryTable.selectAll().where { CategoryTable.name eq input.name }.isEmpty()) {
@@ -442,7 +435,7 @@ class CategoryMutation {
CategoryType(CategoryTable.selectAll().where { CategoryTable.id eq id }.first())
}
CreateCategoryPayload(clientMutationId, category)
return CreateCategoryPayload(clientMutationId, category)
}
data class DeleteCategoryInput(
@@ -457,11 +450,10 @@ class CategoryMutation {
)
@RequireAuth
fun deleteCategory(input: DeleteCategoryInput): DataFetcherResult<DeleteCategoryPayload?> {
return asDataFetcherResult {
fun deleteCategory(input: DeleteCategoryInput): DeleteCategoryPayload? {
val (clientMutationId, categoryId) = input
if (categoryId == 0) { // Don't delete default category
return@asDataFetcherResult DeleteCategoryPayload(
return DeleteCategoryPayload(
clientMutationId,
null,
emptyList(),
@@ -496,8 +488,7 @@ class CategoryMutation {
} to mangas
}
DeleteCategoryPayload(clientMutationId, category, mangas)
}
return DeleteCategoryPayload(clientMutationId, category, mangas)
}
data class UpdateMangaCategoriesPatch(
@@ -547,8 +538,7 @@ class CategoryMutation {
}
@RequireAuth
fun updateMangaCategories(input: UpdateMangaCategoriesInput): DataFetcherResult<UpdateMangaCategoriesPayload?> =
asDataFetcherResult {
fun updateMangaCategories(input: UpdateMangaCategoriesInput): UpdateMangaCategoriesPayload? {
val (clientMutationId, id, patch) = input
updateMangas(listOf(id), patch)
@@ -558,15 +548,14 @@ class CategoryMutation {
MangaType(MangaTable.selectAll().where { MangaTable.id eq id }.first())
}
UpdateMangaCategoriesPayload(
return UpdateMangaCategoriesPayload(
clientMutationId = clientMutationId,
manga = manga,
)
}
@RequireAuth
fun updateMangasCategories(input: UpdateMangasCategoriesInput): DataFetcherResult<UpdateMangasCategoriesPayload?> =
asDataFetcherResult {
fun updateMangasCategories(input: UpdateMangasCategoriesInput): UpdateMangasCategoriesPayload? {
val (clientMutationId, ids, patch) = input
updateMangas(ids, patch)
@@ -576,7 +565,7 @@ class CategoryMutation {
MangaTable.selectAll().where { MangaTable.id inList ids }.map { MangaType(it) }
}
UpdateMangasCategoriesPayload(
return UpdateMangasCategoriesPayload(
clientMutationId = clientMutationId,
mangas = mangas,
)

View File

@@ -1,6 +1,7 @@
@file:Suppress("RedundantNullableReturnType", "unused")
package suwayomi.tachidesk.graphql.mutations
import graphql.execution.DataFetcherResult
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.jetbrains.exposed.dao.id.EntityID
@@ -16,7 +17,6 @@ import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.statements.BatchUpdateStatement
import org.jetbrains.exposed.sql.transactions.transaction
import org.jetbrains.exposed.sql.update
import suwayomi.tachidesk.graphql.asDataFetcherResult
import suwayomi.tachidesk.graphql.directives.RequireAuth
import suwayomi.tachidesk.graphql.types.ChapterMetaType
import suwayomi.tachidesk.graphql.types.ChapterType
@@ -120,8 +120,7 @@ class ChapterMutation {
}
@RequireAuth
fun updateChapter(input: UpdateChapterInput): DataFetcherResult<UpdateChapterPayload?> =
asDataFetcherResult {
fun updateChapter(input: UpdateChapterInput): UpdateChapterPayload? {
val (clientMutationId, id, patch) = input
updateChapters(listOf(id), patch)
@@ -131,15 +130,14 @@ class ChapterMutation {
ChapterType(ChapterTable.selectAll().where { ChapterTable.id eq id }.first())
}
UpdateChapterPayload(
return UpdateChapterPayload(
clientMutationId = clientMutationId,
chapter = chapter,
)
}
@RequireAuth
fun updateChapters(input: UpdateChaptersInput): DataFetcherResult<UpdateChaptersPayload?> =
asDataFetcherResult {
fun updateChapters(input: UpdateChaptersInput): UpdateChaptersPayload? {
val (clientMutationId, ids, patch) = input
updateChapters(ids, patch)
@@ -149,7 +147,7 @@ class ChapterMutation {
ChapterTable.selectAll().where { ChapterTable.id inList ids }.map { ChapterType(it) }
}
UpdateChaptersPayload(
return UpdateChaptersPayload(
clientMutationId = clientMutationId,
chapters = chapters,
)
@@ -166,11 +164,10 @@ class ChapterMutation {
)
@RequireAuth
fun fetchChapters(input: FetchChaptersInput): CompletableFuture<DataFetcherResult<FetchChaptersPayload?>> {
fun fetchChapters(input: FetchChaptersInput): CompletableFuture<FetchChaptersPayload?> {
val (clientMutationId, mangaId) = input
return future {
asDataFetcherResult {
Chapter.fetchChapterList(mangaId)
val chapters =
@@ -188,7 +185,6 @@ class ChapterMutation {
)
}
}
}
data class SetChapterMetaInput(
val clientMutationId: String? = null,
@@ -201,13 +197,12 @@ class ChapterMutation {
)
@RequireAuth
fun setChapterMeta(input: SetChapterMetaInput): DataFetcherResult<SetChapterMetaPayload?> =
asDataFetcherResult {
fun setChapterMeta(input: SetChapterMetaInput): SetChapterMetaPayload? {
val (clientMutationId, meta) = input
Chapter.modifyChapterMeta(meta.chapterId, meta.key, meta.value)
SetChapterMetaPayload(clientMutationId, meta)
return SetChapterMetaPayload(clientMutationId, meta)
}
data class DeleteChapterMetaInput(
@@ -223,8 +218,7 @@ class ChapterMutation {
)
@RequireAuth
fun deleteChapterMeta(input: DeleteChapterMetaInput): DataFetcherResult<DeleteChapterMetaPayload?> =
asDataFetcherResult {
fun deleteChapterMeta(input: DeleteChapterMetaInput): DeleteChapterMetaPayload? {
val (clientMutationId, chapterId, key) = input
val (meta, chapter) =
@@ -249,7 +243,7 @@ class ChapterMutation {
} to chapter
}
DeleteChapterMetaPayload(clientMutationId, meta, chapter)
return DeleteChapterMetaPayload(clientMutationId, meta, chapter)
}
data class SetChapterMetasItem(
@@ -269,8 +263,7 @@ class ChapterMutation {
)
@RequireAuth
fun setChapterMetas(input: SetChapterMetasInput): DataFetcherResult<SetChapterMetasPayload?> =
asDataFetcherResult {
fun setChapterMetas(input: SetChapterMetasInput): SetChapterMetasPayload? {
val (clientMutationId, items) = input
val metaByChapterId =
@@ -304,7 +297,7 @@ class ChapterMutation {
updatedMetas to chapters
}
SetChapterMetasPayload(clientMutationId, updatedMetas, chapters)
return SetChapterMetasPayload(clientMutationId, updatedMetas, chapters)
}
data class DeleteChapterMetasItem(
@@ -325,8 +318,7 @@ class ChapterMutation {
)
@RequireAuth
fun deleteChapterMetas(input: DeleteChapterMetasInput): DataFetcherResult<DeleteChapterMetasPayload?> =
asDataFetcherResult {
fun deleteChapterMetas(input: DeleteChapterMetasInput): DeleteChapterMetasPayload? {
val (clientMutationId, items) = input
items.forEach { item ->
@@ -381,7 +373,7 @@ class ChapterMutation {
.distinctBy { it.id }
}
DeleteChapterMetasPayload(clientMutationId, allDeletedMetas, chapters)
return DeleteChapterMetasPayload(clientMutationId, allDeletedMetas, chapters)
}
data class FetchChapterPagesInput(
@@ -405,12 +397,11 @@ class ChapterMutation {
)
@RequireAuth
fun fetchChapterPages(input: FetchChapterPagesInput): CompletableFuture<DataFetcherResult<FetchChapterPagesPayload?>> {
fun fetchChapterPages(input: FetchChapterPagesInput): CompletableFuture<FetchChapterPagesPayload?> {
val (clientMutationId, chapterId) = input
val paramsMap = input.toParams()
return future {
asDataFetcherResult {
var chapter = getChapterDownloadReadyById(chapterId)
val syncResult = KoreaderSyncService.checkAndPullProgress(chapter.id)
var syncConflictInfo: SyncConflictInfoType? = null
@@ -467,5 +458,4 @@ class ChapterMutation {
)
}
}
}
}

View File

@@ -1,3 +1,5 @@
@file:Suppress("RedundantNullableReturnType", "unused")
package suwayomi.tachidesk.graphql.mutations
import graphql.execution.DataFetcherResult
@@ -5,7 +7,6 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withTimeout
import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.transactions.transaction
import suwayomi.tachidesk.graphql.asDataFetcherResult
import suwayomi.tachidesk.graphql.directives.RequireAuth
import suwayomi.tachidesk.graphql.types.ChapterType
import suwayomi.tachidesk.graphql.types.DownloadStatus
@@ -30,13 +31,12 @@ class DownloadMutation {
)
@RequireAuth
fun deleteDownloadedChapters(input: DeleteDownloadedChaptersInput): DataFetcherResult<DeleteDownloadedChaptersPayload?> {
fun deleteDownloadedChapters(input: DeleteDownloadedChaptersInput): DeleteDownloadedChaptersPayload? {
val (clientMutationId, chapters) = input
return asDataFetcherResult {
Chapter.deleteChapters(chapters)
DeleteDownloadedChaptersPayload(
return DeleteDownloadedChaptersPayload(
clientMutationId = clientMutationId,
chapters =
transaction {
@@ -47,7 +47,6 @@ class DownloadMutation {
},
)
}
}
data class DeleteDownloadedChapterInput(
val clientMutationId: String? = null,
@@ -60,13 +59,12 @@ class DownloadMutation {
)
@RequireAuth
fun deleteDownloadedChapter(input: DeleteDownloadedChapterInput): DataFetcherResult<DeleteDownloadedChapterPayload?> {
fun deleteDownloadedChapter(input: DeleteDownloadedChapterInput): DeleteDownloadedChapterPayload? {
val (clientMutationId, chapter) = input
return asDataFetcherResult {
Chapter.deleteChapters(listOf(chapter))
DeleteDownloadedChapterPayload(
return DeleteDownloadedChapterPayload(
clientMutationId = clientMutationId,
chapters =
transaction {
@@ -74,7 +72,6 @@ class DownloadMutation {
},
)
}
}
data class EnqueueChapterDownloadsInput(
val clientMutationId: String? = null,
@@ -87,13 +84,10 @@ class DownloadMutation {
)
@RequireAuth
fun enqueueChapterDownloads(
input: EnqueueChapterDownloadsInput,
): CompletableFuture<DataFetcherResult<EnqueueChapterDownloadsPayload?>> {
fun enqueueChapterDownloads(input: EnqueueChapterDownloadsInput): CompletableFuture<EnqueueChapterDownloadsPayload?> {
val (clientMutationId, chapters) = input
return future {
asDataFetcherResult {
DownloadManager.enqueue(DownloadManager.EnqueueInput(chapters))
EnqueueChapterDownloadsPayload(
@@ -110,7 +104,6 @@ class DownloadMutation {
)
}
}
}
data class EnqueueChapterDownloadInput(
val clientMutationId: String? = null,
@@ -123,11 +116,10 @@ class DownloadMutation {
)
@RequireAuth
fun enqueueChapterDownload(input: EnqueueChapterDownloadInput): CompletableFuture<DataFetcherResult<EnqueueChapterDownloadPayload?>> {
fun enqueueChapterDownload(input: EnqueueChapterDownloadInput): CompletableFuture<EnqueueChapterDownloadPayload?> {
val (clientMutationId, chapter) = input
return future {
asDataFetcherResult {
DownloadManager.enqueue(DownloadManager.EnqueueInput(listOf(chapter)))
EnqueueChapterDownloadPayload(
@@ -143,7 +135,6 @@ class DownloadMutation {
)
}
}
}
data class DequeueChapterDownloadsInput(
val clientMutationId: String? = null,
@@ -156,13 +147,10 @@ class DownloadMutation {
)
@RequireAuth
fun dequeueChapterDownloads(
input: DequeueChapterDownloadsInput,
): CompletableFuture<DataFetcherResult<DequeueChapterDownloadsPayload?>> {
fun dequeueChapterDownloads(input: DequeueChapterDownloadsInput): CompletableFuture<DequeueChapterDownloadsPayload?> {
val (clientMutationId, chapters) = input
return future {
asDataFetcherResult {
DownloadManager.dequeue(DownloadManager.EnqueueInput(chapters))
DequeueChapterDownloadsPayload(
@@ -181,7 +169,6 @@ class DownloadMutation {
)
}
}
}
data class DequeueChapterDownloadInput(
val clientMutationId: String? = null,
@@ -194,11 +181,10 @@ class DownloadMutation {
)
@RequireAuth
fun dequeueChapterDownload(input: DequeueChapterDownloadInput): CompletableFuture<DataFetcherResult<DequeueChapterDownloadPayload?>> {
fun dequeueChapterDownload(input: DequeueChapterDownloadInput): CompletableFuture<DequeueChapterDownloadPayload?> {
val (clientMutationId, chapter) = input
return future {
asDataFetcherResult {
DownloadManager.dequeue(DownloadManager.EnqueueInput(listOf(chapter)))
DequeueChapterDownloadPayload(
@@ -217,7 +203,6 @@ class DownloadMutation {
)
}
}
}
data class StartDownloaderInput(
val clientMutationId: String? = null,
@@ -229,9 +214,8 @@ class DownloadMutation {
)
@RequireAuth
fun startDownloader(input: StartDownloaderInput): CompletableFuture<DataFetcherResult<StartDownloaderPayload?>> =
fun startDownloader(input: StartDownloaderInput): CompletableFuture<StartDownloaderPayload?> =
future {
asDataFetcherResult {
DownloadManager.start()
StartDownloaderPayload(
@@ -246,7 +230,6 @@ class DownloadMutation {
},
)
}
}
data class StopDownloaderInput(
val clientMutationId: String? = null,
@@ -258,9 +241,8 @@ class DownloadMutation {
)
@RequireAuth
fun stopDownloader(input: StopDownloaderInput): CompletableFuture<DataFetcherResult<StopDownloaderPayload?>> =
fun stopDownloader(input: StopDownloaderInput): CompletableFuture<StopDownloaderPayload?> =
future {
asDataFetcherResult {
DownloadManager.stop()
StopDownloaderPayload(
@@ -275,7 +257,6 @@ class DownloadMutation {
},
)
}
}
data class ClearDownloaderInput(
val clientMutationId: String? = null,
@@ -287,9 +268,8 @@ class DownloadMutation {
)
@RequireAuth
fun clearDownloader(input: ClearDownloaderInput): CompletableFuture<DataFetcherResult<ClearDownloaderPayload?>> =
fun clearDownloader(input: ClearDownloaderInput): CompletableFuture<ClearDownloaderPayload?> =
future {
asDataFetcherResult {
DownloadManager.clear()
ClearDownloaderPayload(
@@ -304,7 +284,6 @@ class DownloadMutation {
},
)
}
}
data class ReorderChapterDownloadInput(
val clientMutationId: String? = null,
@@ -318,11 +297,10 @@ class DownloadMutation {
)
@RequireAuth
fun reorderChapterDownload(input: ReorderChapterDownloadInput): CompletableFuture<DataFetcherResult<ReorderChapterDownloadPayload?>> {
fun reorderChapterDownload(input: ReorderChapterDownloadInput): CompletableFuture<ReorderChapterDownloadPayload?> {
val (clientMutationId, chapter, to) = input
return future {
asDataFetcherResult {
DownloadManager.reorder(chapter, to)
ReorderChapterDownloadPayload(
@@ -338,5 +316,4 @@ class DownloadMutation {
)
}
}
}
}

View File

@@ -1,3 +1,5 @@
@file:Suppress("RedundantNullableReturnType", "unused")
package suwayomi.tachidesk.graphql.mutations
import eu.kanade.tachiyomi.source.local.LocalSource
@@ -5,7 +7,6 @@ import graphql.execution.DataFetcherResult
import io.javalin.http.UploadedFile
import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.transactions.transaction
import suwayomi.tachidesk.graphql.asDataFetcherResult
import suwayomi.tachidesk.graphql.directives.RequireAuth
import suwayomi.tachidesk.graphql.types.ExtensionType
import suwayomi.tachidesk.manga.impl.extension.Extension
@@ -75,11 +76,10 @@ class ExtensionMutation {
}
@RequireAuth
fun updateExtension(input: UpdateExtensionInput): CompletableFuture<DataFetcherResult<UpdateExtensionPayload?>> {
fun updateExtension(input: UpdateExtensionInput): CompletableFuture<UpdateExtensionPayload?> {
val (clientMutationId, id, patch) = input
return future {
asDataFetcherResult {
updateExtensions(listOf(id), patch)
val extension =
@@ -97,14 +97,12 @@ class ExtensionMutation {
)
}
}
}
@RequireAuth
fun updateExtensions(input: UpdateExtensionsInput): CompletableFuture<DataFetcherResult<UpdateExtensionsPayload?>> {
fun updateExtensions(input: UpdateExtensionsInput): CompletableFuture<UpdateExtensionsPayload?> {
val (clientMutationId, ids, patch) = input
return future {
asDataFetcherResult {
updateExtensions(ids, patch)
val extensions =
@@ -121,7 +119,6 @@ class ExtensionMutation {
)
}
}
}
data class FetchExtensionsInput(
val clientMutationId: String? = null,
@@ -133,11 +130,10 @@ class ExtensionMutation {
)
@RequireAuth
fun fetchExtensions(input: FetchExtensionsInput): CompletableFuture<DataFetcherResult<FetchExtensionsPayload?>> {
fun fetchExtensions(input: FetchExtensionsInput): CompletableFuture<FetchExtensionsPayload?> {
val (clientMutationId) = input
return future {
asDataFetcherResult {
ExtensionsList.fetchExtensions()
val extensions =
@@ -154,7 +150,6 @@ class ExtensionMutation {
)
}
}
}
data class InstallExternalExtensionInput(
val clientMutationId: String? = null,
@@ -167,13 +162,10 @@ class ExtensionMutation {
)
@RequireAuth
fun installExternalExtension(
input: InstallExternalExtensionInput,
): CompletableFuture<DataFetcherResult<InstallExternalExtensionPayload?>> {
fun installExternalExtension(input: InstallExternalExtensionInput): CompletableFuture<InstallExternalExtensionPayload?> {
val (clientMutationId, extensionFile) = input
return future {
asDataFetcherResult {
Extension.installExternalExtension(extensionFile.content(), extensionFile.filename())
val dbExtension =
@@ -185,5 +177,4 @@ class ExtensionMutation {
)
}
}
}
}

View File

@@ -1,3 +1,5 @@
@file:Suppress("RedundantNullableReturnType", "unused")
package suwayomi.tachidesk.graphql.mutations
import suwayomi.tachidesk.graphql.directives.RequireAuth

View File

@@ -1,9 +1,10 @@
@file:Suppress("RedundantNullableReturnType", "unused")
package suwayomi.tachidesk.graphql.mutations
import graphql.execution.DataFetcherResult
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withTimeout
import suwayomi.tachidesk.graphql.asDataFetcherResult
import suwayomi.tachidesk.graphql.directives.RequireAuth
import suwayomi.tachidesk.graphql.types.UpdateState.DOWNLOADING
import suwayomi.tachidesk.graphql.types.UpdateState.ERROR
@@ -26,9 +27,8 @@ class InfoMutation {
)
@RequireAuth
fun updateWebUI(input: WebUIUpdateInput): CompletableFuture<DataFetcherResult<WebUIUpdatePayload?>> {
fun updateWebUI(input: WebUIUpdateInput): CompletableFuture<WebUIUpdatePayload?> {
return future {
asDataFetcherResult {
withTimeout(30.seconds) {
if (WebInterfaceManager.status.value.state === DOWNLOADING) {
return@withTimeout WebUIUpdatePayload(input.clientMutationId, WebInterfaceManager.status.value)
@@ -59,12 +59,10 @@ class InfoMutation {
}
}
}
}
@RequireAuth
fun resetWebUIUpdateStatus(): CompletableFuture<DataFetcherResult<WebUIUpdateStatus?>> =
fun resetWebUIUpdateStatus(): CompletableFuture<WebUIUpdateStatus?> =
future {
asDataFetcherResult {
withTimeout(30.seconds) {
val isUpdateFinished = WebInterfaceManager.status.value.state != DOWNLOADING
if (!isUpdateFinished) {
@@ -76,5 +74,4 @@ class InfoMutation {
WebInterfaceManager.status.first { it.state == IDLE }
}
}
}
}

View File

@@ -1,10 +1,11 @@
@file:Suppress("RedundantNullableReturnType", "unused")
package suwayomi.tachidesk.graphql.mutations
import graphql.execution.DataFetcherResult
import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.transactions.transaction
import org.jetbrains.exposed.sql.update
import suwayomi.tachidesk.graphql.asDataFetcherResult
import suwayomi.tachidesk.graphql.directives.RequireAuth
import suwayomi.tachidesk.graphql.types.ChapterType
import suwayomi.tachidesk.graphql.types.KoSyncConnectPayload
@@ -62,9 +63,8 @@ class KoreaderSyncMutation {
)
@RequireAuth
fun pushKoSyncProgress(input: PushKoSyncProgressInput): CompletableFuture<DataFetcherResult<PushKoSyncProgressPayload?>> =
fun pushKoSyncProgress(input: PushKoSyncProgressInput): CompletableFuture<PushKoSyncProgressPayload?> =
future {
asDataFetcherResult {
KoreaderSyncService.pushProgress(input.chapterId)
val chapter =
@@ -82,7 +82,6 @@ class KoreaderSyncMutation {
chapter = chapter,
)
}
}
data class PullKoSyncProgressInput(
val clientMutationId: String? = null,
@@ -96,9 +95,8 @@ class KoreaderSyncMutation {
)
@RequireAuth
fun pullKoSyncProgress(input: PullKoSyncProgressInput): CompletableFuture<DataFetcherResult<PullKoSyncProgressPayload?>> =
fun pullKoSyncProgress(input: PullKoSyncProgressInput): CompletableFuture<PullKoSyncProgressPayload?> =
future {
asDataFetcherResult {
val syncResult = KoreaderSyncService.checkAndPullProgress(input.chapterId)
var syncConflictInfo: SyncConflictInfoType? = null
@@ -136,5 +134,4 @@ class KoreaderSyncMutation {
syncConflict = syncConflictInfo,
)
}
}
}

View File

@@ -1,6 +1,7 @@
@file:Suppress("RedundantNullableReturnType", "unused")
package suwayomi.tachidesk.graphql.mutations
import graphql.execution.DataFetcherResult
import org.jetbrains.exposed.sql.LikePattern
import org.jetbrains.exposed.sql.Op
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
@@ -12,7 +13,6 @@ import org.jetbrains.exposed.sql.or
import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.transactions.transaction
import org.jetbrains.exposed.sql.update
import suwayomi.tachidesk.graphql.asDataFetcherResult
import suwayomi.tachidesk.graphql.directives.RequireAuth
import suwayomi.tachidesk.graphql.types.MangaMetaType
import suwayomi.tachidesk.graphql.types.MangaType
@@ -98,11 +98,10 @@ class MangaMutation {
}
@RequireAuth
fun updateManga(input: UpdateMangaInput): CompletableFuture<DataFetcherResult<UpdateMangaPayload?>> {
fun updateManga(input: UpdateMangaInput): CompletableFuture<UpdateMangaPayload?> {
val (clientMutationId, id, patch) = input
return future {
asDataFetcherResult {
updateMangas(listOf(id), patch)
val manga =
@@ -116,14 +115,12 @@ class MangaMutation {
)
}
}
}
@RequireAuth
fun updateMangas(input: UpdateMangasInput): CompletableFuture<DataFetcherResult<UpdateMangasPayload?>> {
fun updateMangas(input: UpdateMangasInput): CompletableFuture<UpdateMangasPayload?> {
val (clientMutationId, ids, patch) = input
return future {
asDataFetcherResult {
updateMangas(ids, patch)
val mangas =
@@ -137,7 +134,6 @@ class MangaMutation {
)
}
}
}
data class FetchMangaInput(
val clientMutationId: String? = null,
@@ -150,11 +146,10 @@ class MangaMutation {
)
@RequireAuth
fun fetchManga(input: FetchMangaInput): CompletableFuture<DataFetcherResult<FetchMangaPayload?>> {
fun fetchManga(input: FetchMangaInput): CompletableFuture<FetchMangaPayload?> {
val (clientMutationId, id) = input
return future {
asDataFetcherResult {
Manga.fetchManga(id)
val manga =
@@ -167,7 +162,6 @@ class MangaMutation {
)
}
}
}
data class SetMangaMetaInput(
val clientMutationId: String? = null,
@@ -180,14 +174,12 @@ class MangaMutation {
)
@RequireAuth
fun setMangaMeta(input: SetMangaMetaInput): DataFetcherResult<SetMangaMetaPayload?> {
fun setMangaMeta(input: SetMangaMetaInput): SetMangaMetaPayload? {
val (clientMutationId, meta) = input
return asDataFetcherResult {
Manga.modifyMangaMeta(meta.mangaId, meta.key, meta.value)
SetMangaMetaPayload(clientMutationId, meta)
}
return SetMangaMetaPayload(clientMutationId, meta)
}
data class DeleteMangaMetaInput(
@@ -203,10 +195,9 @@ class MangaMutation {
)
@RequireAuth
fun deleteMangaMeta(input: DeleteMangaMetaInput): DataFetcherResult<DeleteMangaMetaPayload?> {
fun deleteMangaMeta(input: DeleteMangaMetaInput): DeleteMangaMetaPayload? {
val (clientMutationId, mangaId, key) = input
return asDataFetcherResult {
val (meta, manga) =
transaction {
val meta =
@@ -229,8 +220,7 @@ class MangaMutation {
} to manga
}
DeleteMangaMetaPayload(clientMutationId, meta, manga)
}
return DeleteMangaMetaPayload(clientMutationId, meta, manga)
}
data class SetMangaMetasItem(
@@ -250,10 +240,9 @@ class MangaMutation {
)
@RequireAuth
fun setMangaMetas(input: SetMangaMetasInput): DataFetcherResult<SetMangaMetasPayload?> {
fun setMangaMetas(input: SetMangaMetasInput): SetMangaMetasPayload? {
val (clientMutationId, items) = input
return asDataFetcherResult {
val metaByMangaId =
items
.flatMap { item ->
@@ -285,8 +274,7 @@ class MangaMutation {
updatedMetas to mangas
}
SetMangaMetasPayload(clientMutationId, updatedMetas, mangas)
}
return SetMangaMetasPayload(clientMutationId, updatedMetas, mangas)
}
data class DeleteMangaMetasItem(
@@ -307,10 +295,9 @@ class MangaMutation {
)
@RequireAuth
fun deleteMangaMetas(input: DeleteMangaMetasInput): DataFetcherResult<DeleteMangaMetasPayload?> {
fun deleteMangaMetas(input: DeleteMangaMetasInput): DeleteMangaMetasPayload? {
val (clientMutationId, items) = input
return asDataFetcherResult {
items.forEach { item ->
require(!item.keys.isNullOrEmpty() || !item.prefixes.isNullOrEmpty()) {
"Either 'keys' or 'prefixes' must be provided for each item"
@@ -363,7 +350,6 @@ class MangaMutation {
.distinctBy { it.id }
}
DeleteMangaMetasPayload(clientMutationId, allDeletedMetas, mangas)
}
return DeleteMangaMetasPayload(clientMutationId, allDeletedMetas, mangas)
}
}

View File

@@ -1,3 +1,5 @@
@file:Suppress("RedundantNullableReturnType", "unused")
package suwayomi.tachidesk.graphql.mutations
import graphql.execution.DataFetcherResult
@@ -12,7 +14,6 @@ import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.transactions.transaction
import suwayomi.tachidesk.global.impl.GlobalMeta
import suwayomi.tachidesk.global.model.table.GlobalMetaTable
import suwayomi.tachidesk.graphql.asDataFetcherResult
import suwayomi.tachidesk.graphql.directives.RequireAuth
import suwayomi.tachidesk.graphql.types.GlobalMetaType
import suwayomi.tachidesk.graphql.types.MetaInput
@@ -29,14 +30,12 @@ class MetaMutation {
)
@RequireAuth
fun setGlobalMeta(input: SetGlobalMetaInput): DataFetcherResult<SetGlobalMetaPayload?> {
fun setGlobalMeta(input: SetGlobalMetaInput): SetGlobalMetaPayload? {
val (clientMutationId, meta) = input
return asDataFetcherResult {
GlobalMeta.modifyMeta(meta.key, meta.value)
SetGlobalMetaPayload(clientMutationId, meta)
}
return SetGlobalMetaPayload(clientMutationId, meta)
}
data class DeleteGlobalMetaInput(
@@ -50,10 +49,9 @@ class MetaMutation {
)
@RequireAuth
fun deleteGlobalMeta(input: DeleteGlobalMetaInput): DataFetcherResult<DeleteGlobalMetaPayload?> {
fun deleteGlobalMeta(input: DeleteGlobalMetaInput): DeleteGlobalMetaPayload? {
val (clientMutationId, key) = input
return asDataFetcherResult {
val meta =
transaction {
val meta =
@@ -71,8 +69,7 @@ class MetaMutation {
}
}
DeleteGlobalMetaPayload(clientMutationId, meta)
}
return DeleteGlobalMetaPayload(clientMutationId, meta)
}
data class SetGlobalMetasInput(
@@ -86,10 +83,9 @@ class MetaMutation {
)
@RequireAuth
fun setGlobalMetas(input: SetGlobalMetasInput): DataFetcherResult<SetGlobalMetasPayload?> {
fun setGlobalMetas(input: SetGlobalMetasInput): SetGlobalMetasPayload? {
val (clientMutationId, metas) = input
return asDataFetcherResult {
val metaMap = metas.associate { it.key to it.value }
GlobalMeta.modifyMetas(metaMap)
@@ -101,8 +97,7 @@ class MetaMutation {
.map { GlobalMetaType(it) }
}
SetGlobalMetasPayload(clientMutationId, updatedMetas)
}
return SetGlobalMetasPayload(clientMutationId, updatedMetas)
}
data class DeleteGlobalMetasInput(
@@ -117,10 +112,9 @@ class MetaMutation {
)
@RequireAuth
fun deleteGlobalMetas(input: DeleteGlobalMetasInput): DataFetcherResult<DeleteGlobalMetasPayload?> {
fun deleteGlobalMetas(input: DeleteGlobalMetasInput): DeleteGlobalMetasPayload? {
val (clientMutationId, keys, prefixes) = input
return asDataFetcherResult {
require(!keys.isNullOrEmpty() || !prefixes.isNullOrEmpty()) {
"Either 'keys' or 'prefixes' must be provided"
}
@@ -153,7 +147,6 @@ class MetaMutation {
metas
}
DeleteGlobalMetasPayload(clientMutationId, metas)
}
return DeleteGlobalMetasPayload(clientMutationId, metas)
}
}

View File

@@ -1,3 +1,5 @@
@file:Suppress("RedundantNullableReturnType", "unused")
package suwayomi.tachidesk.graphql.mutations
import com.expediagroup.graphql.generator.annotations.GraphQLIgnore

View File

@@ -1,3 +1,5 @@
@file:Suppress("RedundantNullableReturnType", "unused")
package suwayomi.tachidesk.graphql.mutations
import androidx.preference.CheckBoxPreference
@@ -5,7 +7,6 @@ import androidx.preference.EditTextPreference
import androidx.preference.ListPreference
import androidx.preference.MultiSelectListPreference
import androidx.preference.SwitchPreferenceCompat
import graphql.execution.DataFetcherResult
import org.jetbrains.exposed.sql.LikePattern
import org.jetbrains.exposed.sql.Op
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
@@ -16,7 +17,6 @@ import org.jetbrains.exposed.sql.deleteWhere
import org.jetbrains.exposed.sql.or
import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.transactions.transaction
import suwayomi.tachidesk.graphql.asDataFetcherResult
import suwayomi.tachidesk.graphql.directives.RequireAuth
import suwayomi.tachidesk.graphql.types.FilterChange
import suwayomi.tachidesk.graphql.types.MangaType
@@ -47,14 +47,12 @@ class SourceMutation {
)
@RequireAuth
fun setSourceMeta(input: SetSourceMetaInput): DataFetcherResult<SetSourceMetaPayload?> {
fun setSourceMeta(input: SetSourceMetaInput): SetSourceMetaPayload? {
val (clientMutationId, meta) = input
return asDataFetcherResult {
Source.modifyMeta(meta.sourceId, meta.key, meta.value)
SetSourceMetaPayload(clientMutationId, meta)
}
return SetSourceMetaPayload(clientMutationId, meta)
}
data class DeleteSourceMetaInput(
@@ -70,10 +68,9 @@ class SourceMutation {
)
@RequireAuth
fun deleteSourceMeta(input: DeleteSourceMetaInput): DataFetcherResult<DeleteSourceMetaPayload?> {
fun deleteSourceMeta(input: DeleteSourceMetaInput): DeleteSourceMetaPayload? {
val (clientMutationId, sourceId, key) = input
return asDataFetcherResult {
val (meta, source) =
transaction {
val meta =
@@ -100,8 +97,7 @@ class SourceMutation {
} to source
}
DeleteSourceMetaPayload(clientMutationId, meta, source)
}
return DeleteSourceMetaPayload(clientMutationId, meta, source)
}
data class SetSourceMetasItem(
@@ -121,10 +117,9 @@ class SourceMutation {
)
@RequireAuth
fun setSourceMetas(input: SetSourceMetasInput): DataFetcherResult<SetSourceMetasPayload?> {
fun setSourceMetas(input: SetSourceMetasInput): SetSourceMetasPayload? {
val (clientMutationId, items) = input
return asDataFetcherResult {
val metaBySourceId =
items
.flatMap { item ->
@@ -156,8 +151,7 @@ class SourceMutation {
updatedMetas to sources
}
SetSourceMetasPayload(clientMutationId, updatedMetas, sources)
}
return SetSourceMetasPayload(clientMutationId, updatedMetas, sources)
}
data class DeleteSourceMetasItem(
@@ -178,10 +172,9 @@ class SourceMutation {
)
@RequireAuth
fun deleteSourceMetas(input: DeleteSourceMetasInput): DataFetcherResult<DeleteSourceMetasPayload?> {
fun deleteSourceMetas(input: DeleteSourceMetasInput): DeleteSourceMetasPayload? {
val (clientMutationId, items) = input
return asDataFetcherResult {
items.forEach { item ->
require(!item.keys.isNullOrEmpty() || !item.prefixes.isNullOrEmpty()) {
"Either 'keys' or 'prefixes' must be provided for each item"
@@ -234,8 +227,7 @@ class SourceMutation {
.distinctBy { it.id }
}
DeleteSourceMetasPayload(clientMutationId, allDeletedMetas, sources)
}
return DeleteSourceMetasPayload(clientMutationId, allDeletedMetas, sources)
}
enum class FetchSourceMangaType {
@@ -260,11 +252,10 @@ class SourceMutation {
)
@RequireAuth
fun fetchSourceManga(input: FetchSourceMangaInput): CompletableFuture<DataFetcherResult<FetchSourceMangaPayload?>> {
fun fetchSourceManga(input: FetchSourceMangaInput): CompletableFuture<FetchSourceMangaPayload?> {
val (clientMutationId, sourceId, type, page, query, filters) = input
return future {
asDataFetcherResult {
val source = GetCatalogueSource.getCatalogueSourceOrNull(sourceId)!!
val mangasPage =
when (type) {
@@ -305,7 +296,6 @@ class SourceMutation {
)
}
}
}
data class SourcePreferenceChange(
val position: Int,
@@ -329,10 +319,9 @@ class SourceMutation {
)
@RequireAuth
fun updateSourcePreference(input: UpdateSourcePreferenceInput): DataFetcherResult<UpdateSourcePreferencePayload?> {
fun updateSourcePreference(input: UpdateSourcePreferenceInput): UpdateSourcePreferencePayload? {
val (clientMutationId, sourceId, change) = input
return asDataFetcherResult {
Source.setSourcePreference(sourceId, change.position, "") { preference ->
when (preference) {
is SwitchPreferenceCompat -> change.switchState
@@ -344,7 +333,7 @@ class SourceMutation {
} ?: throw Exception("Expected change to ${preference::class.simpleName}")
}
UpdateSourcePreferencePayload(
return UpdateSourcePreferencePayload(
clientMutationId = clientMutationId,
preferences = Source.getSourcePreferencesRaw(sourceId).map { preferenceOf(it) },
source =
@@ -353,5 +342,4 @@ class SourceMutation {
},
)
}
}
}

View File

@@ -1,3 +1,5 @@
@file:Suppress("RedundantNullableReturnType", "unused")
package suwayomi.tachidesk.graphql.mutations
import com.expediagroup.graphql.generator.annotations.GraphQLDeprecated
@@ -6,7 +8,6 @@ import graphql.execution.DataFetcherResult
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.transactions.transaction
import suwayomi.tachidesk.graphql.asDataFetcherResult
import suwayomi.tachidesk.graphql.directives.RequireAuth
import suwayomi.tachidesk.graphql.types.TrackRecordType
import suwayomi.tachidesk.graphql.types.TrackerType
@@ -222,11 +223,10 @@ class TrackMutation {
)
@RequireAuth
fun trackProgress(input: TrackProgressInput): CompletableFuture<DataFetcherResult<TrackProgressPayload?>> {
fun trackProgress(input: TrackProgressInput): CompletableFuture<TrackProgressPayload?> {
val (clientMutationId, mangaId) = input
return future {
asDataFetcherResult {
Track.trackChapter(mangaId)
val trackRecords =
transaction {
@@ -241,7 +241,6 @@ class TrackMutation {
)
}
}
}
data class UpdateTrackInput(
val clientMutationId: String? = null,

View File

@@ -1,9 +1,10 @@
@file:Suppress("RedundantNullableReturnType", "unused")
package suwayomi.tachidesk.graphql.mutations
import graphql.execution.DataFetcherResult
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.withTimeout
import suwayomi.tachidesk.graphql.asDataFetcherResult
import suwayomi.tachidesk.graphql.directives.RequireAuth
import suwayomi.tachidesk.graphql.types.LibraryUpdateStatus
import suwayomi.tachidesk.graphql.types.UpdateStatus
@@ -28,7 +29,7 @@ class UpdateMutation {
)
@RequireAuth
fun updateLibrary(input: UpdateLibraryInput): CompletableFuture<DataFetcherResult<UpdateLibraryPayload?>> {
fun updateLibrary(input: UpdateLibraryInput): CompletableFuture<UpdateLibraryPayload?> {
updater.addCategoriesToUpdateQueue(
Category.getCategoryList().filter { input.categories?.contains(it.id) ?: true },
clear = true,
@@ -36,7 +37,6 @@ class UpdateMutation {
)
return future {
asDataFetcherResult {
UpdateLibraryPayload(
input.clientMutationId,
updateStatus =
@@ -48,7 +48,6 @@ class UpdateMutation {
)
}
}
}
data class UpdateLibraryMangaInput(
val clientMutationId: String? = null,
@@ -60,7 +59,7 @@ class UpdateMutation {
)
@RequireAuth
fun updateLibraryManga(input: UpdateLibraryMangaInput): CompletableFuture<DataFetcherResult<UpdateLibraryMangaPayload?>> {
fun updateLibraryManga(input: UpdateLibraryMangaInput): CompletableFuture<UpdateLibraryMangaPayload?> {
updateLibrary(
UpdateLibraryInput(
clientMutationId = input.clientMutationId,
@@ -69,7 +68,6 @@ class UpdateMutation {
)
return future {
asDataFetcherResult {
UpdateLibraryMangaPayload(
input.clientMutationId,
updateStatus =
@@ -79,7 +77,6 @@ class UpdateMutation {
)
}
}
}
data class UpdateCategoryMangaInput(
val clientMutationId: String? = null,
@@ -92,7 +89,7 @@ class UpdateMutation {
)
@RequireAuth
fun updateCategoryManga(input: UpdateCategoryMangaInput): CompletableFuture<DataFetcherResult<UpdateCategoryMangaPayload?>> {
fun updateCategoryManga(input: UpdateCategoryMangaInput): CompletableFuture<UpdateCategoryMangaPayload?> {
updateLibrary(
UpdateLibraryInput(
clientMutationId = input.clientMutationId,
@@ -101,7 +98,6 @@ class UpdateMutation {
)
return future {
asDataFetcherResult {
UpdateCategoryMangaPayload(
input.clientMutationId,
updateStatus =
@@ -111,7 +107,6 @@ class UpdateMutation {
)
}
}
}
data class UpdateStopInput(
val clientMutationId: String? = null,

View File

@@ -1,3 +1,5 @@
@file:Suppress("RedundantNullableReturnType", "unused")
package suwayomi.tachidesk.graphql.mutations
import graphql.schema.DataFetchingEnvironment

View File

@@ -11,6 +11,7 @@ import com.expediagroup.graphql.server.execution.GraphQLRequestParser
import com.expediagroup.graphql.server.types.GraphQLBatchRequest
import com.expediagroup.graphql.server.types.GraphQLRequest
import com.expediagroup.graphql.server.types.GraphQLServerRequest
import io.github.oshai.kotlinlogging.KotlinLogging
import io.javalin.http.Context
import io.javalin.http.UploadedFile
import io.javalin.json.JavalinJackson
@@ -19,11 +20,12 @@ import io.javalin.json.fromJsonString
import java.io.IOException
class JavalinGraphQLRequestParser : GraphQLRequestParser<Context> {
val jsonMapper = JavalinJackson()
private val logger = KotlinLogging.logger {}
@Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
override suspend fun parseRequest(context: Context): GraphQLServerRequest? {
return try {
val jsonMapper = context.jsonMapper()
val contentType = context.contentType()
val formParam =
if (
@@ -77,7 +79,8 @@ class JavalinGraphQLRequestParser : GraphQLRequestParser<Context> {
)
}
}
} catch (_: IOException) {
} catch (e: IOException) {
logger.error(e) { "Error when parsing request" }
null
}
}

View File

@@ -10,7 +10,6 @@ package suwayomi.tachidesk.graphql.server
import com.expediagroup.graphql.generator.execution.FlowSubscriptionExecutionStrategy
import com.expediagroup.graphql.server.execution.GraphQLRequestHandler
import com.expediagroup.graphql.server.execution.GraphQLServer
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import graphql.ExceptionWhileDataFetching
import graphql.GraphQL
import graphql.execution.AsyncExecutionStrategy
@@ -27,6 +26,7 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import suwayomi.tachidesk.graphql.server.subscriptions.ApolloSubscriptionProtocolHandler
import suwayomi.tachidesk.server.JavalinSetup.future
import tools.jackson.module.kotlin.jacksonObjectMapper
class TachideskGraphQLServer(
requestParser: JavalinGraphQLRequestParser,

View File

@@ -58,7 +58,7 @@ private class GraphqlCursorCoercing : Coercing<Cursor, String> {
),
)
}
return Cursor(input.value)
return Cursor(input.value!!)
}
private fun valueToLiteralImpl(input: Any): StringValue = StringValue.newStringValue(input.toString()).build()

View File

@@ -71,7 +71,7 @@ private class GraphqlDurationAsStringCoercing : Coercing<Duration, String> {
)
}
return try {
Duration.parse(input.value)
Duration.parse(input.value!!)
} catch (e: IllegalArgumentException) {
throw CoercingParseLiteralException(
"Invalid duration format: ${input.value}. Expected ISO-8601 duration string (e.g., 'PT30M', 'P1D')",

View File

@@ -53,7 +53,7 @@ private class GraphqlLongAsStringCoercing : Coercing<Long, String> {
),
)
}
return input.value.toLong()
return input.value!!.toLong()
}
private fun valueToLiteralImpl(input: Any): StringValue = StringValue.newStringValue(input.toString()).build()

View File

@@ -9,9 +9,6 @@ package suwayomi.tachidesk.graphql.server.subscriptions
import com.expediagroup.graphql.server.execution.GraphQLRequestHandler
import com.expediagroup.graphql.server.types.GraphQLRequest
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.convertValue
import com.fasterxml.jackson.module.kotlin.readValue
import io.github.oshai.kotlinlogging.KotlinLogging
import io.javalin.http.Header
import io.javalin.websocket.WsContext
@@ -41,6 +38,9 @@ import suwayomi.tachidesk.server.JavalinSetup.Attribute
import suwayomi.tachidesk.server.JavalinSetup.getAttributeOrSet
import suwayomi.tachidesk.server.user.UserType
import suwayomi.tachidesk.server.user.getUserFromToken
import tools.jackson.databind.ObjectMapper
import tools.jackson.module.kotlin.convertValue
import tools.jackson.module.kotlin.readValue
/**
* Implementation of the `graphql-transport-ws` protocol defined by Denis Badurina

View File

@@ -27,7 +27,10 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.future.future
import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeoutOrNull
import org.eclipse.jetty.server.ServerConnector
import suwayomi.tachidesk.global.GlobalAPI
import suwayomi.tachidesk.graphql.GraphQL
@@ -51,6 +54,7 @@ import java.util.concurrent.CompletableFuture
import kotlin.concurrent.thread
import kotlin.text.get
import kotlin.time.Duration.Companion.days
import kotlin.time.Duration.Companion.seconds
object JavalinSetup {
private val logger = KotlinLogging.logger {}
@@ -132,10 +136,15 @@ object JavalinSetup {
config.events.serverStarted {
if (serverConfig.initialOpenInBrowserEnabled.value) {
scope.launch {
withTimeoutOrNull(10.seconds) {
WebInterfaceManager.isSetupComplete.first { it }
}
Browser.openInBrowser()
}
}
}
}
// when JVM is prompted to shutdown, stop javalin gracefully
Runtime.getRuntime().addShutdownHook(

View File

@@ -27,6 +27,7 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.sample
@@ -90,7 +91,7 @@ object WebInterfaceManager {
private val preferences = Injekt.get<Application>().getSharedPreferences("server_util", Context.MODE_PRIVATE)
private var currentUpdateTaskId: String = ""
private var isSetupComplete = false
val isSetupComplete = MutableStateFlow(false)
private val json: Json by injectLazy()
private val network: NetworkHelper by injectLazy()
@@ -196,7 +197,7 @@ object WebInterfaceManager {
@OptIn(DelicateCoroutinesApi::class)
GlobalScope.launchIO {
setupWebUI()
isSetupComplete = true
isSetupComplete.value = true
}
}
@@ -260,7 +261,7 @@ object WebInterfaceManager {
val lastAutomatedUpdate = preferences.getLong(LAST_WEBUI_UPDATE_CHECK_KEY, System.currentTimeMillis())
val task = {
if (isSetupComplete) {
if (isSetupComplete.value) {
val log =
KotlinLogging.logger(
"${logger.name}::scheduleWebUIUpdateCheck(" +

View File

@@ -0,0 +1,50 @@
package suwayomi.tachidesk
import xyz.nulldev.androidcompat.util.SafePath
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
class SafePathTest {
@Test
fun invalidCharactersAreReplacedAndEdgesAreTrimmed() {
val input = " .a:b*c?d<e>f|g\\h/i. "
val result = SafePath.buildValidFilename(input)
assertEquals("a_b_c_d_e_f_g_h_i", result)
}
@Test
fun emptyAfterTrimReturnsInvalidMarker() {
assertEquals("(invalid)", SafePath.buildValidFilename(" ... . "))
}
@Test
fun resultIsTruncatedTo240Characters() {
val input = "a".repeat(300)
val result = SafePath.buildValidFilename(input)
assertEquals(240, result.length)
assertEquals("a".repeat(240), result)
}
@Test
fun mixed256CharactersCanExceed255Utf8Bytes() {
val mixed256 =
buildString {
repeat(128) {
append('a')
append('你')
}
}
val result = SafePath.buildValidFilename(mixed256)
assertEquals(120, result.length)
assertEquals(mixed256.take(120), result)
assertEquals(240, result.toByteArray(Charsets.UTF_8).size)
assertTrue(result.toByteArray(Charsets.UTF_8).size == 240)
}
}