diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ca9bb5e4..0d3102a6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,7 +3,7 @@ kotlin = "2.0.21" coroutines = "1.9.0" serialization = "1.7.3" okhttp = "5.0.0-alpha.14" # Major version is locked by Tachiyomi extensions -javalin = "4.6.8" # Javalin 5.0.0+ requires Java 11 +javalin = "6.3.0" # Javalin 5.0.0+ requires Java 11 jackson = "2.13.3" # jackson version locked by javalin, ref: `io.javalin.core.util.OptionalDependency` exposed = "0.40.1" dex2jar = "v64" # Stuck until https://github.com/ThexXTURBOXx/dex2jar/issues/27 is fixed @@ -104,6 +104,7 @@ xmlpull = "xmlpull:xmlpull:1.1.3.4a" # Disk & File appdirs = "net.harawata:appdirs:1.2.2" +cache4k = "io.github.reactivecircus.cache4k:cache4k:0.13.0" zip4j = "net.lingala.zip4j:zip4j:2.11.5" commonscompress = "org.apache.commons:commons-compress:1.27.1" junrar = "com.github.junrar:junrar:7.5.5" @@ -200,7 +201,7 @@ okhttp = [ ] javalin = [ "javalin-core", - "javalin-openapi", + #"javalin-openapi", ] jackson = [ "jackson-databind", diff --git a/server/build.gradle.kts b/server/build.gradle.kts index e682b81c..9b84c0d0 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -72,6 +72,7 @@ dependencies { implementation(libs.asm) // Disk & File + implementation(libs.cache4k) implementation(libs.zip4j) implementation(libs.commonscompress) implementation(libs.junrar) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/global/controller/GlobalMetaController.kt b/server/src/main/kotlin/suwayomi/tachidesk/global/controller/GlobalMetaController.kt index 3916a557..d3c9da40 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/global/controller/GlobalMetaController.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/global/controller/GlobalMetaController.kt @@ -7,7 +7,7 @@ package suwayomi.tachidesk.global.controller * 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 io.javalin.http.HttpCode +import io.javalin.http.HttpStatus import suwayomi.tachidesk.global.impl.GlobalMeta import suwayomi.tachidesk.server.util.formParam import suwayomi.tachidesk.server.util.handler @@ -28,7 +28,7 @@ object GlobalMetaController { ctx.status(200) }, withResults = { - httpCode(HttpCode.OK) + httpCode(HttpStatus.OK) }, ) @@ -48,8 +48,8 @@ object GlobalMetaController { ctx.status(200) }, withResults = { - httpCode(HttpCode.OK) - httpCode(HttpCode.NOT_FOUND) + httpCode(HttpStatus.OK) + httpCode(HttpStatus.NOT_FOUND) }, ) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/global/controller/SettingsController.kt b/server/src/main/kotlin/suwayomi/tachidesk/global/controller/SettingsController.kt index c9ddea29..c456f517 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/global/controller/SettingsController.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/global/controller/SettingsController.kt @@ -7,7 +7,7 @@ package suwayomi.tachidesk.global.controller * 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 io.javalin.http.HttpCode +import io.javalin.http.HttpStatus import suwayomi.tachidesk.global.impl.About import suwayomi.tachidesk.global.impl.AboutDataClass import suwayomi.tachidesk.global.impl.AppUpdate @@ -31,7 +31,7 @@ object SettingsController { ctx.json(About.getAbout()) }, withResults = { - json(HttpCode.OK) + json(HttpStatus.OK) }, ) @@ -45,12 +45,13 @@ object SettingsController { } }, behaviorOf = { ctx -> - ctx.future( - future { AppUpdate.checkUpdate() }, - ) + ctx.future { + future { AppUpdate.checkUpdate() } + .thenApply { ctx.json(it) } + } }, withResults = { - json>(HttpCode.OK) + json>(HttpStatus.OK) }, ) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/controller/GraphQLController.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/controller/GraphQLController.kt index 68e66931..701d90c1 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/controller/GraphQLController.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/controller/GraphQLController.kt @@ -9,6 +9,7 @@ package suwayomi.tachidesk.graphql.controller import io.javalin.http.ContentType import io.javalin.http.Context +import io.javalin.http.HttpStatus import io.javalin.websocket.WsConfig import suwayomi.tachidesk.graphql.server.TachideskGraphQLServer import suwayomi.tachidesk.graphql.server.TemporaryFileStorage @@ -20,11 +21,17 @@ object GraphQLController { /** execute graphql query */ fun execute(ctx: Context) { - ctx.future( + ctx.future { future { server.execute(ctx) - }, - ) + }.thenApply { + if (it != null) { + ctx.json(it) + } else { + ctx.status(HttpStatus.BAD_REQUEST) + } + } + } } fun playground(ctx: Context) { diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/BackupMutation.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/BackupMutation.kt index 752c887c..916f8a43 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/BackupMutation.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/BackupMutation.kt @@ -30,7 +30,7 @@ class BackupMutation { val (clientMutationId, backup) = input return future { - val restoreId = ProtoBackupImport.restore(backup.content) + val restoreId = ProtoBackupImport.restore(backup.content()) withTimeout(10.seconds) { ProtoBackupImport.notifyFlow.first { diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/ExtensionMutation.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/ExtensionMutation.kt index 1ff152ab..83a65c8c 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/ExtensionMutation.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/ExtensionMutation.kt @@ -165,9 +165,9 @@ class ExtensionMutation { return future { asDataFetcherResult { - Extension.installExternalExtension(extensionFile.content, extensionFile.filename) + Extension.installExternalExtension(extensionFile.content(), extensionFile.filename()) - val dbExtension = transaction { ExtensionTable.select { ExtensionTable.apkName eq extensionFile.filename }.first() } + val dbExtension = transaction { ExtensionTable.select { ExtensionTable.apkName eq extensionFile.filename() }.first() } InstallExternalExtensionPayload( clientMutationId, diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/BackupQuery.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/BackupQuery.kt index 4e3e16d9..ba286c00 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/BackupQuery.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/BackupQuery.kt @@ -26,7 +26,7 @@ class BackupQuery { ) fun validateBackup(input: ValidateBackupInput): ValidateBackupResult { - val result = ProtoBackupValidator.validate(input.backup.content) + val result = ProtoBackupValidator.validate(input.backup.content()) return ValidateBackupResult( result.missingSourceIds.map { ValidateBackupSource(it.first, it.second) }, result.missingTrackers.map { ValidateBackupTracker(it) }, diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/JavalinGraphQLRequestParser.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/JavalinGraphQLRequestParser.kt index 56639f51..d300454e 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/JavalinGraphQLRequestParser.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/JavalinGraphQLRequestParser.kt @@ -13,7 +13,7 @@ import com.expediagroup.graphql.server.types.GraphQLRequest import com.expediagroup.graphql.server.types.GraphQLServerRequest import io.javalin.http.Context import io.javalin.http.UploadedFile -import io.javalin.plugin.json.jsonMapper +import io.javalin.json.fromJsonString import java.io.IOException class JavalinGraphQLRequestParser : GraphQLRequestParser { @@ -33,20 +33,13 @@ class JavalinGraphQLRequestParser : GraphQLRequestParser { } val request = - context.jsonMapper().fromJsonString( - formParam, - GraphQLServerRequest::class.java, - ) + context.jsonMapper().fromJsonString(formParam) - @Suppress("UNCHECKED_CAST") val map = context .formParam("map") ?.let { - context.jsonMapper().fromJsonString( - it, - Map::class.java as Class>>, - ) + context.jsonMapper().fromJsonString>>(it) }.orEmpty() val mapItems = diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/subscriptions/ApolloSubscriptionProtocolHandler.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/subscriptions/ApolloSubscriptionProtocolHandler.kt index 25f12949..c63dc19c 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/subscriptions/ApolloSubscriptionProtocolHandler.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/subscriptions/ApolloSubscriptionProtocolHandler.kt @@ -69,7 +69,7 @@ class ApolloSubscriptionProtocolHandler( if (operationMessage.type != GQL_PING.type) { logger.debug { - "GraphQL subscription client message, sessionId=${context.sessionId} type=${operationMessage.type} operationName=${ + "GraphQL subscription client message, sessionId=${context.sessionId()} type=${operationMessage.type} operationName=${ getOperationName(operationMessage.payload) } ${ if (serverConfig.gqlDebugLogsEnabled.value) { @@ -118,7 +118,7 @@ class ApolloSubscriptionProtocolHandler( if (sessionState.doesOperationExist(operationMessage)) { sessionState.terminateSession(context, CloseStatus(4409, "Subscriber for ${operationMessage.id} already exists")) - logger.info("Already subscribed to operation ${operationMessage.id} for session ${context.sessionId}") + logger.info("Already subscribed to operation ${operationMessage.id} for session ${context.sessionId()}") return emptyFlow() } @@ -174,7 +174,7 @@ class ApolloSubscriptionProtocolHandler( private fun onPing(): Flow = flowOf(pongMessage) private fun onDisconnect(context: WsContext): Flow { - logger.debug("Session \"${context.sessionId}\" disconnected") + logger.debug("Session \"${context.sessionId()}\" disconnected") sessionState.terminateSession(context, CloseStatus(1000, "Normal Closure")) return emptyFlow() } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/subscriptions/ApolloSubscriptionSessionState.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/subscriptions/ApolloSubscriptionSessionState.kt index 05515023..332d3b01 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/subscriptions/ApolloSubscriptionSessionState.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/subscriptions/ApolloSubscriptionSessionState.kt @@ -36,14 +36,14 @@ internal class ApolloSubscriptionSessionState { context: WsContext, graphQLContext: GraphQLContext, ) { - cachedGraphQLContext[context.sessionId] = graphQLContext + cachedGraphQLContext[context.sessionId()] = graphQLContext } /** * Return the graphQL context for this session. */ fun getGraphQLContext(context: WsContext): GraphQLContext = - cachedGraphQLContext[context.sessionId] ?: emptyMap().toGraphQLContext() + cachedGraphQLContext[context.sessionId()] ?: emptyMap().toGraphQLContext() /** * Save the operation that is sending data to the client. @@ -58,7 +58,7 @@ internal class ApolloSubscriptionSessionState { val id = operationMessage.id if (id != null) { activeOperations[id] = subscription - sessionToOperationId.getOrPut(context.sessionId) { CopyOnWriteArrayList() } += id + sessionToOperationId.getOrPut(context.sessionId()) { CopyOnWriteArrayList() } += id } } @@ -87,10 +87,10 @@ internal class ApolloSubscriptionSessionState { context: WsContext, code: CloseStatus, ) { - sessionToOperationId.remove(context.sessionId)?.forEach { + sessionToOperationId.remove(context.sessionId())?.forEach { removeActiveOperation(it) } - cachedGraphQLContext.remove(context.sessionId) + cachedGraphQLContext.remove(context.sessionId()) context.closeSession(code) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/BackupController.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/BackupController.kt index c4507eb1..1713e597 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/BackupController.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/BackupController.kt @@ -1,6 +1,6 @@ package suwayomi.tachidesk.manga.controller -import io.javalin.http.HttpCode +import io.javalin.http.HttpStatus import suwayomi.tachidesk.manga.impl.backup.BackupFlags import suwayomi.tachidesk.manga.impl.backup.proto.ProtoBackupExport import suwayomi.tachidesk.manga.impl.backup.proto.ProtoBackupImport @@ -28,14 +28,16 @@ object BackupController { } }, behaviorOf = { ctx -> - ctx.future( + ctx.future { future { - ProtoBackupImport.restoreLegacy(ctx.bodyAsInputStream()) - }, - ) + ProtoBackupImport.restoreLegacy(ctx.bodyInputStream()) + }.thenApply { + ctx.json(it) + } + } }, withResults = { - httpCode(HttpCode.OK) + httpCode(HttpStatus.OK) }, ) @@ -54,15 +56,17 @@ object BackupController { }, behaviorOf = { ctx -> // TODO: rewrite this with ctx.uploadedFiles(), don't call the multipart field "backup.proto.gz" - ctx.future( + ctx.future { future { - ProtoBackupImport.restoreLegacy(ctx.uploadedFile("backup.proto.gz")!!.content) - }, - ) + ProtoBackupImport.restoreLegacy(ctx.uploadedFile("backup.proto.gz")!!.content()) + }.thenApply { + ctx.json(it) + } + } }, withResults = { - httpCode(HttpCode.OK) - httpCode(HttpCode.NOT_FOUND) + httpCode(HttpStatus.OK) + httpCode(HttpStatus.NOT_FOUND) }, ) @@ -77,7 +81,7 @@ object BackupController { }, behaviorOf = { ctx -> ctx.contentType("application/octet-stream") - ctx.future( + ctx.future { future { ProtoBackupExport.createBackup( BackupFlags( @@ -88,11 +92,11 @@ object BackupController { includeHistory = true, ), ) - }, - ) + }.thenApply { ctx.result(it) } + } }, withResults = { - stream(HttpCode.OK) + stream(HttpStatus.OK) }, ) @@ -109,7 +113,7 @@ object BackupController { ctx.contentType("application/octet-stream") ctx.header("Content-Disposition", """attachment; filename="${Backup.getFilename()}"""") - ctx.future( + ctx.future { future { ProtoBackupExport.createBackup( BackupFlags( @@ -120,11 +124,11 @@ object BackupController { includeHistory = true, ), ) - }, - ) + }.thenApply { ctx.result(it) } + } }, withResults = { - stream(HttpCode.OK) + stream(HttpStatus.OK) }, ) @@ -138,14 +142,16 @@ object BackupController { } }, behaviorOf = { ctx -> - ctx.future( + ctx.future { future { - ProtoBackupValidator.validate(ctx.bodyAsInputStream()) - }, - ) + ProtoBackupValidator.validate(ctx.bodyInputStream()) + }.thenApply { + ctx.json(it) + } + } }, withResults = { - json(HttpCode.OK) + json(HttpStatus.OK) }, ) @@ -167,14 +173,16 @@ object BackupController { } }, behaviorOf = { ctx -> - ctx.future( + ctx.future { future { - ProtoBackupValidator.validate(ctx.uploadedFile("backup.proto.gz")!!.content) - }, - ) + ProtoBackupValidator.validate(ctx.uploadedFile("backup.proto.gz")!!.content()) + }.thenApply { + ctx.json(it) + } + } }, withResults = { - json(HttpCode.OK) + json(HttpStatus.OK) }, ) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/CategoryController.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/CategoryController.kt index 4aed4491..0ea20c1f 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/CategoryController.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/CategoryController.kt @@ -7,7 +7,7 @@ package suwayomi.tachidesk.manga.controller * 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 io.javalin.http.HttpCode +import io.javalin.http.HttpStatus import suwayomi.tachidesk.manga.impl.Category import suwayomi.tachidesk.manga.impl.CategoryManga import suwayomi.tachidesk.manga.model.dataclass.CategoryDataClass @@ -31,7 +31,7 @@ object CategoryController { ctx.json(Category.getCategoryList()) }, withResults = { - json>(HttpCode.OK) + json>(HttpStatus.OK) }, ) @@ -49,12 +49,12 @@ object CategoryController { if (Category.createCategory(name) != -1) { ctx.status(200) } else { - ctx.status(HttpCode.BAD_REQUEST) + ctx.status(HttpStatus.BAD_REQUEST) } }, withResults = { - httpCode(HttpCode.OK) - httpCode(HttpCode.BAD_REQUEST) + httpCode(HttpStatus.OK) + httpCode(HttpStatus.BAD_REQUEST) }, ) @@ -77,7 +77,7 @@ object CategoryController { ctx.status(200) }, withResults = { - httpCode(HttpCode.OK) + httpCode(HttpStatus.OK) }, ) @@ -96,7 +96,7 @@ object CategoryController { ctx.status(200) }, withResults = { - httpCode(HttpCode.OK) + httpCode(HttpStatus.OK) }, ) @@ -114,7 +114,7 @@ object CategoryController { ctx.json(CategoryManga.getCategoryMangaList(categoryId)) }, withResults = { - json>(HttpCode.OK) + json>(HttpStatus.OK) }, ) @@ -134,7 +134,7 @@ object CategoryController { ctx.status(200) }, withResults = { - httpCode(HttpCode.OK) + httpCode(HttpStatus.OK) }, ) @@ -155,8 +155,8 @@ object CategoryController { ctx.status(200) }, withResults = { - httpCode(HttpCode.OK) - httpCode(HttpCode.NOT_FOUND) + httpCode(HttpStatus.OK) + httpCode(HttpStatus.NOT_FOUND) }, ) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/DownloadController.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/DownloadController.kt index fd947ff3..56cc6e04 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/DownloadController.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/DownloadController.kt @@ -7,7 +7,7 @@ package suwayomi.tachidesk.manga.controller * 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 io.javalin.http.HttpCode +import io.javalin.http.HttpStatus import io.javalin.websocket.WsConfig import kotlinx.serialization.json.Json import suwayomi.tachidesk.manga.impl.download.DownloadManager @@ -48,7 +48,7 @@ object DownloadController { DownloadManager.start() }, withResults = { - httpCode(HttpCode.OK) + httpCode(HttpStatus.OK) }, ) @@ -62,12 +62,13 @@ object DownloadController { } }, behaviorOf = { ctx -> - ctx.future( - future { DownloadManager.stop() }, - ) + ctx.future { + future { DownloadManager.stop() } + .thenApply { ctx.status(HttpStatus.OK) } + } }, withResults = { - httpCode(HttpCode.OK) + httpCode(HttpStatus.OK) }, ) @@ -81,12 +82,13 @@ object DownloadController { } }, behaviorOf = { ctx -> - ctx.future( - future { DownloadManager.clear() }, - ) + ctx.future { + future { DownloadManager.clear() } + .thenApply { ctx.status(HttpStatus.OK) } + } }, withResults = { - httpCode(HttpCode.OK) + httpCode(HttpStatus.OK) }, ) @@ -102,15 +104,15 @@ object DownloadController { } }, behaviorOf = { ctx, chapterIndex, mangaId -> - ctx.future( + ctx.future { future { DownloadManager.enqueueWithChapterIndex(mangaId, chapterIndex) - }, - ) + }.thenApply { ctx.status(HttpStatus.OK) } + } }, withResults = { - httpCode(HttpCode.OK) - httpCode(HttpCode.NOT_FOUND) + httpCode(HttpStatus.OK) + httpCode(HttpStatus.NOT_FOUND) }, ) @@ -125,14 +127,14 @@ object DownloadController { }, behaviorOf = { ctx -> val inputs = json.decodeFromString(ctx.body()) - ctx.future( + ctx.future { future { DownloadManager.enqueue(inputs) - }, - ) + }.thenApply { ctx.status(HttpStatus.OK) } + } }, withResults = { - httpCode(HttpCode.OK) + httpCode(HttpStatus.OK) }, ) @@ -148,14 +150,14 @@ object DownloadController { }, behaviorOf = { ctx -> val input = json.decodeFromString(ctx.body()) - ctx.future( + ctx.future { future { DownloadManager.dequeue(input) - }, - ) + }.thenApply { ctx.status(HttpStatus.OK) } + } }, withResults = { - httpCode(HttpCode.OK) + httpCode(HttpStatus.OK) }, ) @@ -176,7 +178,7 @@ object DownloadController { ctx.status(200) }, withResults = { - httpCode(HttpCode.OK) + httpCode(HttpStatus.OK) }, ) @@ -196,7 +198,7 @@ object DownloadController { DownloadManager.reorder(chapterIndex, mangaId, to) }, withResults = { - httpCode(HttpCode.OK) + httpCode(HttpStatus.OK) }, ) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/ExtensionController.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/ExtensionController.kt index da0cc099..9b43d9de 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/ExtensionController.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/ExtensionController.kt @@ -7,7 +7,7 @@ package suwayomi.tachidesk.manga.controller * 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 io.javalin.http.HttpCode +import io.javalin.http.HttpStatus import mu.KotlinLogging import suwayomi.tachidesk.manga.impl.extension.Extension import suwayomi.tachidesk.manga.impl.extension.ExtensionsList @@ -31,14 +31,16 @@ object ExtensionController { } }, behaviorOf = { ctx -> - ctx.future( + ctx.future { future { ExtensionsList.getExtensionList() - }, - ) + }.thenApply { + ctx.json(it) + } + } }, withResults = { - json>(HttpCode.OK) + json>(HttpStatus.OK) }, ) @@ -53,16 +55,18 @@ object ExtensionController { } }, behaviorOf = { ctx, pkgName -> - ctx.future( + ctx.future { future { Extension.installExtension(pkgName) - }, - ) + }.thenApply { + ctx.status(it) + } + } }, withResults = { - httpCode(HttpCode.CREATED) - httpCode(HttpCode.FOUND) - httpCode(HttpCode.INTERNAL_SERVER_ERROR) + httpCode(HttpStatus.CREATED) + httpCode(HttpStatus.FOUND) + httpCode(HttpStatus.INTERNAL_SERVER_ERROR) }, ) @@ -81,18 +85,23 @@ object ExtensionController { }, behaviorOf = { ctx -> val uploadedFile = ctx.uploadedFile("file")!! - logger.debug { "Uploaded extension file name: " + uploadedFile.filename } + logger.debug { "Uploaded extension file name: " + uploadedFile.filename() } - ctx.future( + ctx.future { future { - Extension.installExternalExtension(uploadedFile.content, uploadedFile.filename) - }, - ) + Extension.installExternalExtension( + uploadedFile.content(), + uploadedFile.filename(), + ) + }.thenApply { + ctx.status(it) + } + } }, withResults = { - httpCode(HttpCode.CREATED) - httpCode(HttpCode.FOUND) - httpCode(HttpCode.INTERNAL_SERVER_ERROR) + httpCode(HttpStatus.CREATED) + httpCode(HttpStatus.FOUND) + httpCode(HttpStatus.INTERNAL_SERVER_ERROR) }, ) @@ -107,17 +116,19 @@ object ExtensionController { } }, behaviorOf = { ctx, pkgName -> - ctx.future( + ctx.future { future { Extension.updateExtension(pkgName) - }, - ) + }.thenApply { + ctx.status(it) + } + } }, withResults = { - httpCode(HttpCode.CREATED) - httpCode(HttpCode.FOUND) - httpCode(HttpCode.NOT_FOUND) - httpCode(HttpCode.INTERNAL_SERVER_ERROR) + httpCode(HttpStatus.CREATED) + httpCode(HttpStatus.FOUND) + httpCode(HttpStatus.NOT_FOUND) + httpCode(HttpStatus.INTERNAL_SERVER_ERROR) }, ) @@ -136,10 +147,10 @@ object ExtensionController { ctx.status(200) }, withResults = { - httpCode(HttpCode.CREATED) - httpCode(HttpCode.FOUND) - httpCode(HttpCode.NOT_FOUND) - httpCode(HttpCode.INTERNAL_SERVER_ERROR) + httpCode(HttpStatus.CREATED) + httpCode(HttpStatus.FOUND) + httpCode(HttpStatus.NOT_FOUND) + httpCode(HttpStatus.INTERNAL_SERVER_ERROR) }, ) @@ -154,19 +165,19 @@ object ExtensionController { } }, behaviorOf = { ctx, apkName -> - ctx.future( + ctx.future { future { Extension.getExtensionIcon(apkName) } .thenApply { ctx.header("content-type", it.second) val httpCacheSeconds = 365.days.inWholeSeconds ctx.header("cache-control", "max-age=$httpCacheSeconds, immutable") - it.first - }, - ) + ctx.result(it.first) + } + } }, withResults = { - image(HttpCode.OK) - httpCode(HttpCode.NOT_FOUND) + image(HttpStatus.OK) + httpCode(HttpStatus.NOT_FOUND) }, ) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/MangaController.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/MangaController.kt index 3575d282..1eb6364c 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/MangaController.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/MangaController.kt @@ -7,7 +7,7 @@ package suwayomi.tachidesk.manga.controller * 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 io.javalin.http.HttpCode +import io.javalin.http.HttpStatus import kotlinx.serialization.json.Json import suwayomi.tachidesk.manga.impl.CategoryManga import suwayomi.tachidesk.manga.impl.Chapter @@ -41,15 +41,15 @@ object MangaController { } }, behaviorOf = { ctx, mangaId, onlineFetch -> - ctx.future( + ctx.future { future { Manga.getManga(mangaId, onlineFetch) - }, - ) + }.thenApply { ctx.json(it) } + } }, withResults = { - json(HttpCode.OK) - httpCode(HttpCode.NOT_FOUND) + json(HttpStatus.OK) + httpCode(HttpStatus.NOT_FOUND) }, ) @@ -65,15 +65,15 @@ object MangaController { } }, behaviorOf = { ctx, mangaId, onlineFetch -> - ctx.future( + ctx.future { future { Manga.getMangaFull(mangaId, onlineFetch) - }, - ) + }.thenApply { ctx.json(it) } + } }, withResults = { - json(HttpCode.OK) - httpCode(HttpCode.NOT_FOUND) + json(HttpStatus.OK) + httpCode(HttpStatus.NOT_FOUND) }, ) @@ -88,19 +88,19 @@ object MangaController { } }, behaviorOf = { ctx, mangaId -> - ctx.future( + 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 - }, - ) + ctx.result(it.first) + } + } }, withResults = { - image(HttpCode.OK) - httpCode(HttpCode.NOT_FOUND) + image(HttpStatus.OK) + httpCode(HttpStatus.NOT_FOUND) }, ) @@ -115,13 +115,14 @@ object MangaController { } }, behaviorOf = { ctx, mangaId -> - ctx.future( - future { Library.addMangaToLibrary(mangaId) }, - ) + ctx.future { + future { Library.addMangaToLibrary(mangaId) } + .thenApply { ctx.status(HttpStatus.OK) } + } }, withResults = { - httpCode(HttpCode.OK) - httpCode(HttpCode.NOT_FOUND) + httpCode(HttpStatus.OK) + httpCode(HttpStatus.NOT_FOUND) }, ) @@ -136,13 +137,14 @@ object MangaController { } }, behaviorOf = { ctx, mangaId -> - ctx.future( - future { Library.removeMangaFromLibrary(mangaId) }, - ) + ctx.future { + future { Library.removeMangaFromLibrary(mangaId) } + .thenApply { ctx.status(HttpStatus.OK) } + } }, withResults = { - httpCode(HttpCode.OK) - httpCode(HttpCode.NOT_FOUND) + httpCode(HttpStatus.OK) + httpCode(HttpStatus.NOT_FOUND) }, ) @@ -160,7 +162,7 @@ object MangaController { ctx.json(CategoryManga.getMangaCategories(mangaId)) }, withResults = { - json>(HttpCode.OK) + json>(HttpStatus.OK) }, ) @@ -180,7 +182,7 @@ object MangaController { ctx.status(200) }, withResults = { - httpCode(HttpCode.OK) + httpCode(HttpStatus.OK) }, ) @@ -200,7 +202,7 @@ object MangaController { ctx.status(200) }, withResults = { - httpCode(HttpCode.OK) + httpCode(HttpStatus.OK) }, ) @@ -221,8 +223,8 @@ object MangaController { ctx.status(200) }, withResults = { - httpCode(HttpCode.OK) - httpCode(HttpCode.NOT_FOUND) + httpCode(HttpStatus.OK) + httpCode(HttpStatus.NOT_FOUND) }, ) @@ -242,11 +244,14 @@ object MangaController { } }, behaviorOf = { ctx, mangaId, onlineFetch -> - ctx.future(future { Chapter.getChapterList(mangaId, onlineFetch) }) + ctx.future { + future { Chapter.getChapterList(mangaId, onlineFetch) } + .thenApply { ctx.json(it) } + } }, withResults = { - json>(HttpCode.OK) - httpCode(HttpCode.NOT_FOUND) + json>(HttpStatus.OK) + httpCode(HttpStatus.NOT_FOUND) }, ) @@ -266,7 +271,7 @@ object MangaController { Chapter.modifyChapters(input, mangaId) }, withResults = { - httpCode(HttpCode.OK) + httpCode(HttpStatus.OK) }, ) @@ -291,7 +296,7 @@ object MangaController { ) }, withResults = { - httpCode(HttpCode.OK) + httpCode(HttpStatus.OK) }, ) @@ -307,11 +312,14 @@ object MangaController { } }, behaviorOf = { ctx, mangaId, chapterIndex -> - ctx.future(future { getChapterDownloadReadyByIndex(chapterIndex, mangaId) }) + ctx.future { + future { getChapterDownloadReadyByIndex(chapterIndex, mangaId) } + .thenApply { ctx.json(it) } + } }, withResults = { - json(HttpCode.OK) - httpCode(HttpCode.NOT_FOUND) + json(HttpStatus.OK) + httpCode(HttpStatus.NOT_FOUND) }, ) @@ -336,7 +344,7 @@ object MangaController { ctx.status(200) }, withResults = { - httpCode(HttpCode.OK) + httpCode(HttpStatus.OK) }, ) @@ -357,8 +365,8 @@ object MangaController { ctx.status(200) }, withResults = { - httpCode(HttpCode.OK) - httpCode(HttpCode.NOT_FOUND) + httpCode(HttpStatus.OK) + httpCode(HttpStatus.NOT_FOUND) }, ) @@ -381,8 +389,8 @@ object MangaController { ctx.status(200) }, withResults = { - httpCode(HttpCode.OK) - httpCode(HttpCode.NOT_FOUND) + httpCode(HttpStatus.OK) + httpCode(HttpStatus.NOT_FOUND) }, ) @@ -401,19 +409,19 @@ object MangaController { } }, behaviorOf = { ctx, mangaId, chapterIndex, index -> - ctx.future( + 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 - }, - ) + ctx.result(it.first) + } + } }, withResults = { - image(HttpCode.OK) - httpCode(HttpCode.NOT_FOUND) + image(HttpStatus.OK) + httpCode(HttpStatus.NOT_FOUND) }, ) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/SourceController.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/SourceController.kt index ded3f639..d67be69c 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/SourceController.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/SourceController.kt @@ -7,7 +7,7 @@ package suwayomi.tachidesk.manga.controller * 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 io.javalin.http.HttpCode +import io.javalin.http.HttpStatus import kotlinx.serialization.json.Json import suwayomi.tachidesk.manga.impl.MangaList import suwayomi.tachidesk.manga.impl.Search @@ -38,7 +38,7 @@ object SourceController { ctx.json(Source.getSourceList()) }, withResults = { - json>(HttpCode.OK) + json>(HttpStatus.OK) }, ) @@ -56,8 +56,8 @@ object SourceController { ctx.json(Source.getSource(sourceId)!!) }, withResults = { - json(HttpCode.OK) - httpCode(HttpCode.NOT_FOUND) + json(HttpStatus.OK) + httpCode(HttpStatus.NOT_FOUND) }, ) @@ -73,14 +73,14 @@ object SourceController { } }, behaviorOf = { ctx, sourceId, pageNum -> - ctx.future( + ctx.future { future { MangaList.getMangaList(sourceId, pageNum, popular = true) - }, - ) + }.thenApply { ctx.json(it) } + } }, withResults = { - json(HttpCode.OK) + json(HttpStatus.OK) }, ) @@ -96,14 +96,14 @@ object SourceController { } }, behaviorOf = { ctx, sourceId, pageNum -> - ctx.future( + ctx.future { future { MangaList.getMangaList(sourceId, pageNum, popular = false) - }, - ) + }.thenApply { ctx.json(it) } + } }, withResults = { - json(HttpCode.OK) + json(HttpStatus.OK) }, ) @@ -121,7 +121,7 @@ object SourceController { ctx.json(Source.getSourcePreferences(sourceId)) }, withResults = { - json>(HttpCode.OK) + json>(HttpStatus.OK) }, ) @@ -141,7 +141,7 @@ object SourceController { ctx.json(Source.setSourcePreference(sourceId, preferenceChange.position, preferenceChange.value)) }, withResults = { - httpCode(HttpCode.OK) + httpCode(HttpStatus.OK) }, ) @@ -160,7 +160,7 @@ object SourceController { ctx.json(Search.getFilterList(sourceId, reset)) }, withResults = { - json>(HttpCode.OK) + json>(HttpStatus.OK) }, ) @@ -189,7 +189,7 @@ object SourceController { ctx.json(Search.setFilter(sourceId, filterChange)) }, withResults = { - httpCode(HttpCode.OK) + httpCode(HttpStatus.OK) }, ) @@ -206,10 +206,13 @@ object SourceController { } }, behaviorOf = { ctx, sourceId, searchTerm, pageNum -> - ctx.future(future { Search.sourceSearch(sourceId, searchTerm, pageNum) }) + ctx.future { + future { Search.sourceSearch(sourceId, searchTerm, pageNum) } + .thenApply { ctx.json(it) } + } }, withResults = { - json(HttpCode.OK) + json(HttpStatus.OK) }, ) @@ -227,10 +230,13 @@ object SourceController { }, behaviorOf = { ctx, sourceId, pageNum -> val filter = json.decodeFromString(ctx.body()) - ctx.future(future { Search.sourceFilter(sourceId, pageNum, filter) }) + ctx.future { + future { Search.sourceFilter(sourceId, pageNum, filter) } + .thenApply { ctx.json(it) } + } }, withResults = { - json(HttpCode.OK) + json(HttpStatus.OK) }, ) @@ -249,7 +255,7 @@ object SourceController { ctx.json(Search.sourceGlobalSearch(searchTerm)) }, withResults = { - httpCode(HttpCode.OK) + httpCode(HttpStatus.OK) }, ) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/TrackController.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/TrackController.kt index 1239799d..28541091 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/TrackController.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/TrackController.kt @@ -7,7 +7,7 @@ package suwayomi.tachidesk.manga.controller * 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 io.javalin.http.HttpCode +import io.javalin.http.HttpStatus import kotlinx.serialization.json.Json import mu.KotlinLogging import suwayomi.tachidesk.manga.impl.track.Track @@ -36,7 +36,7 @@ object TrackController { ctx.json(Track.getTrackerList()) }, withResults = { - json>(HttpCode.OK) + json>(HttpStatus.OK) }, ) @@ -52,11 +52,14 @@ object TrackController { behaviorOf = { ctx -> val input = json.decodeFromString(ctx.body()) logger.debug { "tracker login $input" } - ctx.future(future { Track.login(input) }) + ctx.future { + future { Track.login(input) } + .thenApply { ctx.status(HttpStatus.OK) } + } }, withResults = { - httpCode(HttpCode.OK) - httpCode(HttpCode.NOT_FOUND) + httpCode(HttpStatus.OK) + httpCode(HttpStatus.NOT_FOUND) }, ) @@ -72,11 +75,14 @@ object TrackController { behaviorOf = { ctx -> val input = json.decodeFromString(ctx.body()) logger.debug { "tracker logout $input" } - ctx.future(future { Track.logout(input) }) + ctx.future { + future { Track.logout(input) } + .thenApply { ctx.status(HttpStatus.OK) } + } }, withResults = { - httpCode(HttpCode.OK) - httpCode(HttpCode.NOT_FOUND) + httpCode(HttpStatus.OK) + httpCode(HttpStatus.NOT_FOUND) }, ) @@ -92,11 +98,14 @@ object TrackController { behaviorOf = { ctx -> val input = json.decodeFromString(ctx.body()) logger.debug { "tracker search $input" } - ctx.future(future { Track.search(input) }) + ctx.future { + future { Track.search(input) } + .thenApply { ctx.json(it) } + } }, withResults = { - httpCode(HttpCode.OK) - httpCode(HttpCode.NOT_FOUND) + httpCode(HttpStatus.OK) + httpCode(HttpStatus.NOT_FOUND) }, ) @@ -112,10 +121,13 @@ object TrackController { } }, behaviorOf = { ctx, mangaId, trackerId, remoteId -> - ctx.future(future { Track.bind(mangaId, trackerId, remoteId.toLong()) }) + ctx.future { + future { Track.bind(mangaId, trackerId, remoteId.toLong()) } + .thenApply { ctx.status(HttpStatus.OK) } + } }, withResults = { - httpCode(HttpCode.OK) + httpCode(HttpStatus.OK) }, ) @@ -131,10 +143,13 @@ object TrackController { behaviorOf = { ctx -> val input = json.decodeFromString(ctx.body()) logger.debug { "tracker update $input" } - ctx.future(future { Track.update(input) }) + ctx.future { + future { Track.update(input) } + .thenApply { ctx.status(HttpStatus.OK) } + } }, withResults = { - httpCode(HttpCode.OK) + httpCode(HttpStatus.OK) }, ) @@ -148,19 +163,19 @@ object TrackController { } }, behaviorOf = { ctx, trackerId -> - ctx.future( + ctx.future { future { Track.getTrackerThumbnail(trackerId) } .thenApply { ctx.header("content-type", it.second) val httpCacheSeconds = 1.days.inWholeSeconds ctx.header("cache-control", "max-age=$httpCacheSeconds") - it.first - }, - ) + ctx.result(it.first) + } + } }, withResults = { - image(HttpCode.OK) - httpCode(HttpCode.NOT_FOUND) + image(HttpStatus.OK) + httpCode(HttpStatus.NOT_FOUND) }, ) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/UpdateController.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/UpdateController.kt index 9083af32..d7869a90 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/UpdateController.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/UpdateController.kt @@ -1,6 +1,6 @@ package suwayomi.tachidesk.manga.controller -import io.javalin.http.HttpCode +import io.javalin.http.HttpStatus import io.javalin.websocket.WsConfig import mu.KotlinLogging import suwayomi.tachidesk.manga.impl.Category @@ -39,14 +39,14 @@ object UpdateController { } }, behaviorOf = { ctx, pageNum -> - ctx.future( + ctx.future { future { Chapter.getRecentChapters(pageNum) - }, - ) + }.thenApply { ctx.json(it) } + } }, withResults = { - json(HttpCode.OK) + json(HttpStatus.OK) }, ) @@ -84,13 +84,13 @@ object UpdateController { ) } else { logger.info { "No Category found" } - ctx.status(HttpCode.BAD_REQUEST) + ctx.status(HttpStatus.BAD_REQUEST) } } }, withResults = { - httpCode(HttpCode.OK) - httpCode(HttpCode.BAD_REQUEST) + httpCode(HttpStatus.OK) + httpCode(HttpStatus.BAD_REQUEST) }, ) @@ -119,7 +119,7 @@ object UpdateController { ctx.json(updater.statusDeprecated.value) }, withResults = { - json(HttpCode.OK) + json(HttpStatus.OK) }, ) @@ -134,16 +134,16 @@ object UpdateController { behaviorOf = { ctx -> val updater = Injekt.get() logger.info { "Resetting Updater" } - ctx.future( + ctx.future { future { updater.reset() }.thenApply { - ctx.status(HttpCode.OK) - }, - ) + ctx.status(HttpStatus.OK) + } + } }, withResults = { - httpCode(HttpCode.OK) + httpCode(HttpStatus.OK) }, ) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Chapter.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Chapter.kt index 1e906b69..099395bc 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Chapter.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Chapter.kt @@ -7,13 +7,12 @@ package suwayomi.tachidesk.manga.impl * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -import com.google.common.cache.Cache -import com.google.common.cache.CacheBuilder import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.util.chapter.ChapterRecognition import eu.kanade.tachiyomi.util.chapter.ChapterSanitizer.sanitize +import io.github.reactivecircus.cache4k.Cache import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.serialization.Serializable @@ -47,8 +46,8 @@ import suwayomi.tachidesk.manga.model.table.toDataClass import suwayomi.tachidesk.server.serverConfig import java.time.Instant import java.util.TreeSet -import java.util.concurrent.TimeUnit import kotlin.math.max +import kotlin.time.Duration.Companion.minutes private fun List.removeDuplicates(currentChapter: ChapterDataClass): List = groupBy { it.chapterNumber } @@ -124,9 +123,9 @@ object Chapter { } val map: Cache = - CacheBuilder - .newBuilder() - .expireAfterAccess(10, TimeUnit.MINUTES) + Cache + .Builder() + .expireAfterAccess(10.minutes) .build() suspend fun fetchChapterList(mangaId: Int): List { diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Manga.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Manga.kt index 289f5575..3f1d71a7 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Manga.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Manga.kt @@ -15,7 +15,7 @@ import eu.kanade.tachiyomi.source.local.LocalSource import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.UpdateStrategy import eu.kanade.tachiyomi.source.online.HttpSource -import io.javalin.http.HttpCode +import io.javalin.http.HttpStatus import mu.KLogger import mu.KotlinLogging import okhttp3.CacheControl @@ -305,9 +305,9 @@ object Manga { val tryToRefreshUrl = !refreshUrl && listOf( - HttpCode.GONE.status, - HttpCode.MOVED_PERMANENTLY.status, - HttpCode.NOT_FOUND.status, + HttpStatus.GONE.code, + HttpStatus.MOVED_PERMANENTLY.code, + HttpStatus.NOT_FOUND.code, 523, // (Cloudflare) Origin Is Unreachable 522, // (Cloudflare) Connection timed out ).contains(e.code) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Search.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Search.kt index a9bde717..9ab51dbc 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Search.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Search.kt @@ -10,7 +10,8 @@ package suwayomi.tachidesk.manga.impl import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.FilterList -import io.javalin.plugin.json.JsonMapper +import io.javalin.json.JsonMapper +import io.javalin.json.fromJsonString import kotlinx.serialization.Serializable import suwayomi.tachidesk.manga.impl.MangaList.processEntries import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub @@ -129,7 +130,7 @@ object Search { is Filter.CheckBox -> filter.state = change.state.toBooleanStrict() is Filter.TriState -> filter.state = change.state.toInt() is Filter.Group<*> -> { - val groupChange = jsonMapper.fromJsonString(change.state, FilterChange::class.java) + val groupChange = jsonMapper.fromJsonString(change.state) when (val groupFilter = filter.state[groupChange.position]) { is Filter.CheckBox -> groupFilter.state = groupChange.state.toBooleanStrict() diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Source.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Source.kt index 063d1733..226718ad 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Source.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Source.kt @@ -11,7 +11,8 @@ import androidx.preference.Preference import androidx.preference.PreferenceScreen import eu.kanade.tachiyomi.source.ConfigurableSource import eu.kanade.tachiyomi.source.sourcePreferences -import io.javalin.plugin.json.JsonMapper +import io.javalin.json.JsonMapper +import io.javalin.json.fromJsonString import mu.KotlinLogging import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.insert @@ -131,11 +132,10 @@ object Source { value: String, getValue: (Preference) -> Any = { pref -> println(jsonMapper::class.java.name) - @Suppress("UNCHECKED_CAST") when (pref.defaultValueType) { "String" -> value "Boolean" -> value.toBoolean() - "Set" -> jsonMapper.fromJsonString(value, List::class.java as Class>).toSet() + "Set" -> jsonMapper.fromJsonString>(value).toSet() else -> throw RuntimeException("Unsupported type conversion") } }, diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/DownloadManager.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/DownloadManager.kt index 9275b40b..38a676c2 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/DownloadManager.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/DownloadManager.kt @@ -104,11 +104,11 @@ object DownloadManager { } fun addClient(ctx: WsContext) { - clients[ctx.sessionId] = ctx + clients[ctx.sessionId()] = ctx } fun removeClient(ctx: WsContext) { - clients.remove(ctx.sessionId) + clients.remove(ctx.sessionId()) } fun notifyClient(ctx: WsContext) { diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/update/UpdaterSocket.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/update/UpdaterSocket.kt index a7af9144..ca333f4a 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/update/UpdaterSocket.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/update/UpdaterSocket.kt @@ -41,7 +41,7 @@ object UpdaterSocket : Websocket() { } override fun addClient(ctx: WsContext) { - logger.info { ctx.sessionId } + logger.info { ctx.sessionId() } super.addClient(ctx) if (job?.isActive != true) { job = start() diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/update/Websocket.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/update/Websocket.kt index 940920a4..b3e4d2ea 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/update/Websocket.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/update/Websocket.kt @@ -8,12 +8,12 @@ abstract class Websocket { protected val clients = ConcurrentHashMap() open fun addClient(ctx: WsContext) { - clients[ctx.sessionId] = ctx + clients[ctx.sessionId()] = ctx notifyClient(ctx, null) } open fun removeClient(ctx: WsContext) { - clients.remove(ctx.sessionId) + clients.remove(ctx.sessionId()) } open fun notifyAllClients(value: T) { diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/JavalinSetup.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/JavalinSetup.kt index bc1cf0ec..ffb107df 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/JavalinSetup.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/JavalinSetup.kt @@ -9,12 +9,8 @@ package suwayomi.tachidesk.server import io.javalin.Javalin import io.javalin.apibuilder.ApiBuilder.path -import io.javalin.core.security.RouteRole +import io.javalin.http.UnauthorizedResponse import io.javalin.http.staticfiles.Location -import io.javalin.plugin.openapi.OpenApiOptions -import io.javalin.plugin.openapi.OpenApiPlugin -import io.javalin.plugin.openapi.ui.SwaggerOptions -import io.swagger.v3.oas.models.info.Info import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob @@ -22,7 +18,6 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.future.future import kotlinx.coroutines.runBlocking import mu.KotlinLogging -import org.eclipse.jetty.server.Server import org.eclipse.jetty.server.ServerConnector import suwayomi.tachidesk.global.GlobalAPI import suwayomi.tachidesk.graphql.GraphQL @@ -31,9 +26,7 @@ import suwayomi.tachidesk.server.util.Browser import suwayomi.tachidesk.server.util.WebInterfaceManager import uy.kohesive.injekt.injectLazy import java.io.IOException -import java.lang.IllegalArgumentException import java.util.concurrent.CompletableFuture -import kotlin.NoSuchElementException import kotlin.concurrent.thread object JavalinSetup { @@ -46,31 +39,11 @@ object JavalinSetup { fun future(block: suspend CoroutineScope.() -> T): CompletableFuture = scope.future(block = block) fun javalinSetup() { - val server = Server() - val connector = - ServerConnector(server).apply { - host = serverConfig.ip.value - port = serverConfig.port.value - } - server.addConnector(connector) - - serverConfig.subscribeTo(combine(serverConfig.ip, serverConfig.port) { ip, port -> Pair(ip, port) }, { (newIp, newPort) -> - val oldIp = connector.host - val oldPort = connector.port - - connector.host = newIp - connector.port = newPort - connector.stop() - connector.start() - - logger.info { "Server ip and/or port changed from $oldIp:$oldPort to $newIp:$newPort " } - }) - val app = Javalin.create { config -> if (serverConfig.webUIEnabled.value) { val serveWebUI = { - config.addSinglePageRoot("/", applicationDirs.webUIRoot + "/index.html", Location.EXTERNAL) + config.spaRoot.addFile("/root", applicationDirs.webUIRoot + "/index.html", Location.EXTERNAL) } WebInterfaceManager.setServeWebUI(serveWebUI) @@ -79,31 +52,74 @@ object JavalinSetup { } logger.info { "Serving web static files for ${serverConfig.webUIFlavor.value}" } - config.addStaticFiles(applicationDirs.webUIRoot, Location.EXTERNAL) + config.staticFiles.add(applicationDirs.webUIRoot, Location.EXTERNAL) serveWebUI() - config.registerPlugin(OpenApiPlugin(getOpenApiOptions())) + // config.registerPlugin(OpenApiPlugin(getOpenApiOptions())) } - config.server { server } + var connectorAdded = false + config.jetty.modifyServer { server -> + if (!connectorAdded) { + val connector = + ServerConnector(server).apply { + host = serverConfig.ip.value + port = serverConfig.port.value + } + server.addConnector(connector) - config.enableCorsForAllOrigins() + serverConfig.subscribeTo( + combine( + serverConfig.ip, + serverConfig.port, + ) { ip, port -> Pair(ip, port) }, + { (newIp, newPort) -> + val oldIp = connector.host + val oldPort = connector.port - config.accessManager { handler, ctx, _ -> - fun credentialsValid(): Boolean { - val (username, password) = ctx.basicAuthCredentials() - return username == serverConfig.basicAuthUsername.value && password == serverConfig.basicAuthPassword.value + connector.host = newIp + connector.port = newPort + connector.stop() + connector.start() + + logger.info { "Server ip and/or port changed from $oldIp:$oldPort to $newIp:$newPort " } + }, + ) + connectorAdded = true } + } - if (serverConfig.basicAuthEnabled.value && !(ctx.basicAuthCredentialsExist() && credentialsValid())) { - ctx.header("WWW-Authenticate", "Basic") - ctx.status(401).json("Unauthorized") - } else { - handler.handle(ctx) + config.bundledPlugins.enableCors { cors -> + cors.addRule { + it.anyHost() + } + } + + config.router.apiBuilder { + path("api/") { + path("v1/") { + GlobalAPI.defineEndpoints() + MangaAPI.defineEndpoints() + } + GraphQL.defineEndpoints() } } } + app.beforeMatched { ctx -> + fun credentialsValid(): Boolean { + val basicAuthCredentials = ctx.basicAuthCredentials() ?: return true + val (username, password) = basicAuthCredentials + return username == serverConfig.basicAuthUsername.value && + password == serverConfig.basicAuthPassword.value + } + + if (serverConfig.basicAuthEnabled.value && !credentialsValid()) { + ctx.header("WWW-Authenticate", "Basic") + throw UnauthorizedResponse() + } + } + app.events { event -> event.serverStarted { if (serverConfig.initialOpenInBrowserEnabled.value) { @@ -139,36 +155,22 @@ object JavalinSetup { ctx.result(e.message ?: "Bad Request") } - app.routes { - path("api/") { - path("v1/") { - GlobalAPI.defineEndpoints() - MangaAPI.defineEndpoints() - } - GraphQL.defineEndpoints() - } - } - app.start() } - private fun getOpenApiOptions(): OpenApiOptions { - val applicationInfo = - Info().apply { - version("1.0") - description("Suwayomi-Server Api") - } - return OpenApiOptions(applicationInfo).apply { - path("/api/openapi.json") - swagger( - SwaggerOptions("/api/swagger-ui").apply { - title("Suwayomi-Server Swagger Documentation") - }, - ) - } - } - - object Auth { - enum class Role : RouteRole { ANYONE, USER_READ, USER_WRITE } - } + // private fun getOpenApiOptions(): OpenApiOptions { + // val applicationInfo = + // Info().apply { + // version("1.0") + // description("Suwayomi-Server Api") + // } + // return OpenApiOptions(applicationInfo).apply { + // path("/api/openapi.json") + // swagger( + // SwaggerOptions("/api/swagger-ui").apply { + // title("Suwayomi-Server Swagger Documentation") + // }, + // ) + // } + // } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/ServerSetup.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/ServerSetup.kt index ac615b0d..37999abc 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/ServerSetup.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/ServerSetup.kt @@ -13,8 +13,8 @@ import eu.kanade.tachiyomi.App import eu.kanade.tachiyomi.createAppModule import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.source.local.LocalSource -import io.javalin.plugin.json.JavalinJackson -import io.javalin.plugin.json.JsonMapper +import io.javalin.json.JavalinJackson +import io.javalin.json.JsonMapper import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged @@ -237,9 +237,7 @@ fun applicationSetup() { runMigrations(applicationDirs) // Disable jetty's logging - System.setProperty("org.eclipse.jetty.util.log.announce", "false") - System.setProperty("org.eclipse.jetty.util.log.class", "org.eclipse.jetty.util.log.StdErrLog") - System.setProperty("org.eclipse.jetty.LEVEL", "OFF") + setLogLevelFor("org.eclipse.jetty", Level.OFF) // socks proxy settings serverConfig.subscribeTo( diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/util/AppMutex.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/util/AppMutex.kt index 9f13406b..aa7eff3e 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/util/AppMutex.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/util/AppMutex.kt @@ -7,7 +7,8 @@ package suwayomi.tachidesk.server.util * 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 io.javalin.plugin.json.JsonMapper +import io.javalin.json.JsonMapper +import io.javalin.json.fromJsonString import mu.KotlinLogging import okhttp3.OkHttpClient import okhttp3.Request.Builder @@ -59,7 +60,7 @@ object AppMutex { } return try { - jsonMapper.fromJsonString(response, AboutDataClass::class.java) + jsonMapper.fromJsonString(response) AppMutexState.TachideskInstanceRunning } catch (e: IOException) { AppMutexState.OtherApplicationRunning diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/util/DocumentationDsl.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/util/DocumentationDsl.kt index db082052..9d847d0f 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/util/DocumentationDsl.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/util/DocumentationDsl.kt @@ -1,11 +1,8 @@ package suwayomi.tachidesk.server.util import io.javalin.http.Context -import io.javalin.http.HttpCode -import io.javalin.plugin.openapi.dsl.DocumentedHandler -import io.javalin.plugin.openapi.dsl.OpenApiDocumentation -import io.javalin.plugin.openapi.dsl.documented -import io.swagger.v3.oas.models.Operation +import io.javalin.http.Handler +import io.javalin.http.HttpStatus fun getSimpleParamItem( ctx: Context, @@ -160,30 +157,30 @@ sealed class Param { class ResultsBuilder { val results = mutableListOf() - inline fun json(code: HttpCode) { + inline fun json(code: HttpStatus) { results += ResultType.MimeType(code, "application/json", T::class.java) } - fun plainText(code: HttpCode) { + fun plainText(code: HttpStatus) { results += ResultType.MimeType(code, "text/plain", String::class.java) } - fun image(code: HttpCode) { + fun image(code: HttpStatus) { results += ResultType.MimeType(code, "image/*", ByteArray::class.java) } - fun stream(code: HttpCode) { + fun stream(code: HttpStatus) { results += ResultType.MimeType(code, "application/octet-stream", ByteArray::class.java) } inline fun mime( - code: HttpCode, + code: HttpStatus, mime: String, ) { results += ResultType.MimeType(code, mime, T::class.java) } - fun httpCode(code: HttpCode) { + fun httpCode(code: HttpStatus) { results += ResultType.StatusCode(code) } } @@ -192,20 +189,20 @@ sealed class ResultType { abstract fun applyTo(documentation: OpenApiDocumentation) data class MimeType( - val code: HttpCode, + val code: HttpStatus, val mime: String, private val clazz: Class<*>, ) : ResultType() { override fun applyTo(documentation: OpenApiDocumentation) { - documentation.result(code.status.toString(), clazz, mime) + documentation.result(code.code.toString(), clazz, mime) } } data class StatusCode( - val code: HttpCode, + val code: HttpStatus, ) : ResultType() { override fun applyTo(documentation: OpenApiDocumentation) { - documentation.result(code.status.toString()) + documentation.result(code.code.toString()) } } } @@ -576,3 +573,67 @@ inline fun < ) }, ) + +@Suppress("UNUSED") +class OpenApiDocumentation { + fun operation(block: Operation.() -> Unit) {} + + fun result( + toString: Any, + clazz: Class<*>, + mime: String, + ) { + } + + fun result(toString: Any) { + } + + fun formParam( + key: String, + defaultValue: T? = null, + isRequired: Boolean = false, + ) {} + + fun queryParam( + key: String, + defaultValue: T? = null, + isRepeatable: Boolean = false, + ) {} + + fun pathParam( + key: String, + defaultValue: T? = null, + ) {} + + fun body() {} + + fun uploadedFile( + name: String, + block: (DocumentationFile) -> Unit, + ) {} +} + +class DocumentationFile { + fun description(string: String) {} + + fun required(boolean: Boolean) {} +} + +class DocumentedHandler( + private val handler: (ctx: Context) -> Unit, +) : Handler { + override fun handle(ctx: Context) { + handler(ctx) + } +} + +fun documented( + documentation: OpenApiDocumentation, + handle: (ctx: Context) -> Unit, +): DocumentedHandler = DocumentedHandler(handle) + +class Operation { + fun summary(string: String) {} + + fun description(string: String) {} +} diff --git a/server/src/test/kotlin/suwayomi/tachidesk/manga/controller/UpdateControllerTest.kt b/server/src/test/kotlin/suwayomi/tachidesk/manga/controller/UpdateControllerTest.kt index b2af8d1f..919a66d2 100644 --- a/server/src/test/kotlin/suwayomi/tachidesk/manga/controller/UpdateControllerTest.kt +++ b/server/src/test/kotlin/suwayomi/tachidesk/manga/controller/UpdateControllerTest.kt @@ -3,7 +3,7 @@ package suwayomi.tachidesk.manga.controller import suwayomi.tachidesk.test.ApplicationTest // import io.javalin.http.Context -// import io.javalin.http.HttpCode +// import io.javalin.http.HttpStatus // import io.mockk.every // import io.mockk.mockk // import io.mockk.verify @@ -31,7 +31,7 @@ internal class UpdateControllerTest : ApplicationTest() { // fun `POST non existent Category Id should give error`() { // every { ctx.formParam("category") } returns "1" // UpdateController.categoryUpdate(ctx) -// verify { ctx.status(HttpCode.BAD_REQUEST) } +// verify { ctx.status(HttpStatus.BAD_REQUEST) } // val updater by DI.global.instance() // assertEquals(0, updater.status.value.numberOfJobs) // } @@ -43,7 +43,7 @@ internal class UpdateControllerTest : ApplicationTest() { // CategoryManga.addMangaToCategory(1, 1) // every { ctx.formParam("category") } returns "1" // UpdateController.categoryUpdate(ctx) -// verify { ctx.status(HttpCode.OK) } +// verify { ctx.status(HttpStatus.OK) } // val updater by DI.global.instance() // assertEquals(1, updater.status.value.numberOfJobs) // } @@ -59,7 +59,7 @@ internal class UpdateControllerTest : ApplicationTest() { // createLibraryManga("mangaInDefault") // every { ctx.formParam("category") } returns null // UpdateController.categoryUpdate(ctx) -// verify { ctx.status(HttpCode.OK) } +// verify { ctx.status(HttpStatus.OK) } // val updater by DI.global.instance() // assertEquals(3, updater.status.value.numberOfJobs) // }