Merge branch 'master' of https://github.com/Suwayomi/Suwayomi-Server
This commit is contained in:
@@ -71,7 +71,7 @@ fun createAppModule(app: Application): Module {
|
||||
}
|
||||
}
|
||||
|
||||
single {
|
||||
single<ProtoBuf> {
|
||||
ProtoBuf
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,17 +140,16 @@ fun OkHttpClient.newCachelessCallWithProgress(
|
||||
return progressClient.newCall(request)
|
||||
}
|
||||
|
||||
context(Json)
|
||||
context(_: Json)
|
||||
inline fun <reified T> Response.parseAs(): T = decodeFromJsonResponse(serializer(), this)
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
context(Json)
|
||||
context(json: Json)
|
||||
fun <T> decodeFromJsonResponse(
|
||||
deserializer: DeserializationStrategy<T>,
|
||||
response: Response,
|
||||
): T =
|
||||
response.body.source().use {
|
||||
decodeFromBufferedSource(deserializer, it)
|
||||
json.decodeFromBufferedSource(deserializer, it)
|
||||
}
|
||||
|
||||
class HttpException(
|
||||
|
||||
@@ -6,7 +6,7 @@ import io.javalin.websocket.WsMessageContext
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.eclipse.jetty.websocket.api.CloseStatus
|
||||
import org.eclipse.jetty.websocket.core.CloseStatus
|
||||
import suwayomi.tachidesk.manga.impl.update.Websocket
|
||||
|
||||
object WebView : Websocket<String>() {
|
||||
|
||||
+7
-3
@@ -13,10 +13,14 @@ 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.json.JavalinJackson
|
||||
import io.javalin.json.fromJsonStream
|
||||
import io.javalin.json.fromJsonString
|
||||
import java.io.IOException
|
||||
|
||||
class JavalinGraphQLRequestParser : GraphQLRequestParser<Context> {
|
||||
val jsonMapper = JavalinJackson()
|
||||
|
||||
@Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
|
||||
override suspend fun parseRequest(context: Context): GraphQLServerRequest? {
|
||||
return try {
|
||||
@@ -29,17 +33,17 @@ class JavalinGraphQLRequestParser : GraphQLRequestParser<Context> {
|
||||
context.formParam("operations")
|
||||
?: throw IllegalArgumentException("Cannot find 'operations' body")
|
||||
} else {
|
||||
return context.bodyAsClass(GraphQLServerRequest::class.java)
|
||||
return context.bodyInputStream().use { jsonMapper.fromJsonStream<GraphQLServerRequest>(it) }
|
||||
}
|
||||
|
||||
val request =
|
||||
context.jsonMapper().fromJsonString<GraphQLServerRequest>(formParam)
|
||||
jsonMapper.fromJsonString<GraphQLServerRequest>(formParam)
|
||||
|
||||
val map =
|
||||
context
|
||||
.formParam("map")
|
||||
?.let {
|
||||
context.jsonMapper().fromJsonString<Map<String, List<String>>>(it)
|
||||
jsonMapper.fromJsonString<Map<String, List<String>>>(it)
|
||||
}.orEmpty()
|
||||
|
||||
val mapItems =
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ import kotlinx.coroutines.flow.onCompletion
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.job
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.eclipse.jetty.websocket.api.CloseStatus
|
||||
import org.eclipse.jetty.websocket.core.CloseStatus
|
||||
import suwayomi.tachidesk.graphql.server.TachideskGraphQLContextFactory
|
||||
import suwayomi.tachidesk.graphql.server.subscriptions.SubscriptionOperationMessage.ClientMessages.GQL_CONNECTION_INIT
|
||||
import suwayomi.tachidesk.graphql.server.subscriptions.SubscriptionOperationMessage.ClientMessages.GQL_SUBSCRIBE
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.onCompletion
|
||||
import org.eclipse.jetty.websocket.api.CloseStatus
|
||||
import org.eclipse.jetty.websocket.core.CloseStatus
|
||||
import suwayomi.tachidesk.graphql.server.toGraphQLContext
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
|
||||
@@ -14,6 +14,8 @@ import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.SourceFactory
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import net.dongliu.apk.parser.ApkFile
|
||||
import net.dongliu.apk.parser.bean.Icon
|
||||
import okhttp3.CacheControl
|
||||
import okio.buffer
|
||||
import okio.sink
|
||||
@@ -37,7 +39,9 @@ import suwayomi.tachidesk.manga.impl.util.PackageTools.getPackageInfo
|
||||
import suwayomi.tachidesk.manga.impl.util.PackageTools.loadExtensionSources
|
||||
import suwayomi.tachidesk.manga.impl.util.network.await
|
||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource
|
||||
import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.clearCachedImage
|
||||
import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.getImageResponse
|
||||
import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.saveImage
|
||||
import suwayomi.tachidesk.manga.model.table.ExtensionTable
|
||||
import suwayomi.tachidesk.manga.model.table.SourceTable
|
||||
import suwayomi.tachidesk.server.ApplicationDirs
|
||||
@@ -115,7 +119,6 @@ object Extension {
|
||||
|
||||
val dirPathWithoutType = "${applicationDirs.extensionsRoot}/$fileNameWithoutType"
|
||||
val jarFilePath = "$dirPathWithoutType.jar"
|
||||
val dexFilePath = "$dirPathWithoutType.dex"
|
||||
|
||||
val packageInfo = getPackageInfo(apkFilePath)
|
||||
val pkgName = packageInfo.packageName
|
||||
@@ -155,79 +158,115 @@ object Extension {
|
||||
|
||||
dex2jar(apkFilePath, jarFilePath, fileNameWithoutType)
|
||||
extractAssetsFromApk(apkFilePath, jarFilePath)
|
||||
extractAndCacheApkIcon(apkFilePath, apkName)
|
||||
|
||||
// clean up
|
||||
File(apkFilePath).delete()
|
||||
File(dexFilePath).delete()
|
||||
|
||||
// collect sources from the extension
|
||||
val extensionMainClassInstance = loadExtensionSources(jarFilePath, className)
|
||||
val sources: List<CatalogueSource> =
|
||||
when (extensionMainClassInstance) {
|
||||
is Source -> listOf(extensionMainClassInstance)
|
||||
is SourceFactory -> extensionMainClassInstance.createSources()
|
||||
else -> throw RuntimeException("Unknown source class type! ${extensionMainClassInstance.javaClass}")
|
||||
}.map { it as CatalogueSource }
|
||||
try {
|
||||
// collect sources from the extension
|
||||
val extensionMainClassInstance = loadExtensionSources(jarFilePath, className)
|
||||
val sources: List<CatalogueSource> =
|
||||
when (extensionMainClassInstance) {
|
||||
is Source -> listOf(extensionMainClassInstance)
|
||||
is SourceFactory -> extensionMainClassInstance.createSources()
|
||||
else -> throw RuntimeException("Unknown source class type! ${extensionMainClassInstance.javaClass}")
|
||||
}.map { it as CatalogueSource }
|
||||
|
||||
val langs = sources.map { it.lang }.toSet()
|
||||
val extensionLang =
|
||||
when (langs.size) {
|
||||
0 -> ""
|
||||
1 -> langs.first()
|
||||
else -> "all"
|
||||
}
|
||||
val langs = sources.map { it.lang }.toSet()
|
||||
val extensionLang =
|
||||
when (langs.size) {
|
||||
0 -> ""
|
||||
1 -> langs.first()
|
||||
else -> "all"
|
||||
}
|
||||
|
||||
val extensionName =
|
||||
packageInfo.applicationInfo.nonLocalizedLabel
|
||||
.toString()
|
||||
.substringAfter("Tachiyomi: ")
|
||||
val extensionName =
|
||||
packageInfo.applicationInfo.nonLocalizedLabel
|
||||
.toString()
|
||||
.substringAfter("Tachiyomi: ")
|
||||
|
||||
// update extension info
|
||||
transaction {
|
||||
if (ExtensionTable.selectAll().where { ExtensionTable.pkgName eq pkgName }.firstOrNull() == null) {
|
||||
ExtensionTable.insert {
|
||||
// update extension info
|
||||
transaction {
|
||||
if (ExtensionTable.selectAll().where { ExtensionTable.pkgName eq pkgName }.firstOrNull() == null) {
|
||||
ExtensionTable.insert {
|
||||
it[this.apkName] = apkName
|
||||
it[name] = extensionName
|
||||
it[this.pkgName] = packageInfo.packageName
|
||||
it[versionName] = packageInfo.versionName
|
||||
it[versionCode] = packageInfo.versionCode
|
||||
it[lang] = extensionLang
|
||||
it[this.isNsfw] = isNsfw
|
||||
}
|
||||
}
|
||||
|
||||
ExtensionTable.update({ ExtensionTable.pkgName eq pkgName }) {
|
||||
it[this.apkName] = apkName
|
||||
it[name] = extensionName
|
||||
it[this.pkgName] = packageInfo.packageName
|
||||
it[this.isInstalled] = true
|
||||
it[this.classFQName] = className
|
||||
it[versionName] = packageInfo.versionName
|
||||
it[versionCode] = packageInfo.versionCode
|
||||
it[lang] = extensionLang
|
||||
it[this.isNsfw] = isNsfw
|
||||
}
|
||||
|
||||
val extensionId =
|
||||
ExtensionTable
|
||||
.selectAll()
|
||||
.where { ExtensionTable.pkgName eq pkgName }
|
||||
.first()[ExtensionTable.id]
|
||||
.value
|
||||
|
||||
sources.forEach { httpSource ->
|
||||
SourceTable.insert {
|
||||
it[id] = httpSource.id
|
||||
it[name] = httpSource.name
|
||||
it[lang] = httpSource.lang
|
||||
it[extension] = extensionId
|
||||
it[SourceTable.isNsfw] = isNsfw
|
||||
}
|
||||
logger.debug { "Installed source ${httpSource.name} (${httpSource.lang}) with id:${httpSource.id}" }
|
||||
}
|
||||
}
|
||||
return 201 // we installed successfully
|
||||
} catch (e: Throwable) {
|
||||
// free up the file descriptor if exists
|
||||
PackageTools.jarLoaderMap.remove(jarFilePath)?.close()
|
||||
File(jarFilePath).delete()
|
||||
|
||||
ExtensionTable.update({ ExtensionTable.pkgName eq pkgName }) {
|
||||
it[this.apkName] = apkName
|
||||
it[this.isInstalled] = true
|
||||
it[this.classFQName] = className
|
||||
it[versionName] = packageInfo.versionName
|
||||
it[versionCode] = packageInfo.versionCode
|
||||
}
|
||||
|
||||
val extensionId =
|
||||
ExtensionTable
|
||||
.selectAll()
|
||||
.where { ExtensionTable.pkgName eq pkgName }
|
||||
.first()[ExtensionTable.id]
|
||||
.value
|
||||
|
||||
sources.forEach { httpSource ->
|
||||
SourceTable.insert {
|
||||
it[id] = httpSource.id
|
||||
it[name] = httpSource.name
|
||||
it[lang] = httpSource.lang
|
||||
it[extension] = extensionId
|
||||
it[SourceTable.isNsfw] = isNsfw
|
||||
}
|
||||
logger.debug { "Installed source ${httpSource.name} (${httpSource.lang}) with id:${httpSource.id}" }
|
||||
}
|
||||
uninstallExtension(pkgName)
|
||||
throw e
|
||||
}
|
||||
return 201 // we installed successfully
|
||||
} else {
|
||||
return 302 // extension was already installed
|
||||
}
|
||||
}
|
||||
|
||||
private fun extractAndCacheApkIcon(
|
||||
apkFilePath: String,
|
||||
apkName: String,
|
||||
) {
|
||||
val iconCacheDir = "${applicationDirs.extensionsRoot}/icon"
|
||||
try {
|
||||
val iconData =
|
||||
ApkFile(File(apkFilePath)).use { apk ->
|
||||
apk.allIcons
|
||||
.filterIsInstance<Icon>()
|
||||
.mapNotNull { it.data?.let { data -> data to it.density } }
|
||||
.maxByOrNull { (_, density) -> density }
|
||||
?.first
|
||||
}
|
||||
if (iconData == null) {
|
||||
logger.warn { "No icon found in APK $apkName" }
|
||||
return
|
||||
}
|
||||
|
||||
File(iconCacheDir).mkdirs()
|
||||
clearCachedImage(iconCacheDir, apkName)
|
||||
saveImage("$iconCacheDir/$apkName", iconData.inputStream(), null)
|
||||
} catch (e: Exception) {
|
||||
logger.warn(e) { "Failed to extract icon from APK $apkName" }
|
||||
}
|
||||
}
|
||||
|
||||
private fun extractAssetsFromApk(
|
||||
apkPath: String,
|
||||
jarPath: String,
|
||||
|
||||
@@ -71,6 +71,9 @@ object ImageUtil {
|
||||
if (bytes.compareWith(charByteArrayOf(0xFF, 0x0A))) {
|
||||
return JXL
|
||||
}
|
||||
if (bytes.compareWith(charByteArrayOf(0x00, 0x00, 0x00, 0x0C, 0x4A, 0x58, 0x4C, 0x20, 0x0D, 0x0A, 0x87, 0x0A))) {
|
||||
return JXL
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
}
|
||||
return null
|
||||
|
||||
@@ -13,12 +13,14 @@ import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import io.javalin.Javalin
|
||||
import io.javalin.apibuilder.ApiBuilder.after
|
||||
import io.javalin.apibuilder.ApiBuilder.path
|
||||
import io.javalin.config.RoutesConfig
|
||||
import io.javalin.http.Context
|
||||
import io.javalin.http.HandlerType
|
||||
import io.javalin.http.HttpStatus
|
||||
import io.javalin.http.NotFoundResponse
|
||||
import io.javalin.http.RedirectResponse
|
||||
import io.javalin.http.UnauthorizedResponse
|
||||
import io.javalin.json.JavalinJackson3
|
||||
import io.javalin.rendering.template.JavalinJte
|
||||
import io.javalin.websocket.WsContext
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -47,6 +49,7 @@ import java.net.URLEncoder
|
||||
import java.util.Locale
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import kotlin.concurrent.thread
|
||||
import kotlin.text.get
|
||||
import kotlin.time.Duration.Companion.days
|
||||
|
||||
object JavalinSetup {
|
||||
@@ -58,10 +61,12 @@ object JavalinSetup {
|
||||
|
||||
fun javalinSetup() {
|
||||
val app =
|
||||
Javalin.create { config ->
|
||||
Javalin.start { config ->
|
||||
val templateEngine = TemplateEngine.createPrecompiled(ContentType.Html)
|
||||
config.fileRenderer(JavalinJte(templateEngine))
|
||||
|
||||
config.jsonMapper(JavalinJackson3())
|
||||
|
||||
WebInterfaceManager.setup(config)
|
||||
|
||||
// config.registerPlugin(OpenApiPlugin(getOpenApiOptions()))
|
||||
@@ -104,7 +109,8 @@ object JavalinSetup {
|
||||
}
|
||||
}
|
||||
|
||||
config.router.apiBuilder {
|
||||
config.routes.defineCore()
|
||||
config.routes.apiBuilder {
|
||||
path(ServerSubpath.maybeAddAsPrefix("api/")) {
|
||||
path("v1/") {
|
||||
GlobalAPI.defineEndpoints()
|
||||
@@ -117,17 +123,32 @@ object JavalinSetup {
|
||||
after { ctx ->
|
||||
// If not matched, the request was for an invalid endpoint
|
||||
// Return a 404 instead of redirecting to the UI for usability
|
||||
if (ctx.endpointHandlerPath() == "*") {
|
||||
if (ctx.endpoints().lastHttpEndpoint()?.path == "*") {
|
||||
throw NotFoundResponse()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config.events.serverStarted {
|
||||
if (serverConfig.initialOpenInBrowserEnabled.value) {
|
||||
Browser.openInBrowser()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// when JVM is prompted to shutdown, stop javalin gracefully
|
||||
Runtime.getRuntime().addShutdownHook(
|
||||
thread(start = false) {
|
||||
app.stop()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fun RoutesConfig.defineCore() {
|
||||
val loginPath = ServerSubpath.maybeAddAsPrefix("/login.html")
|
||||
|
||||
app.get(loginPath) { ctx ->
|
||||
get(loginPath) { ctx ->
|
||||
val locale: Locale = LocalizationHelper.ctxToLocale(ctx)
|
||||
ctx.header("content-type", "text/html")
|
||||
val httpCacheSeconds = 1.days.inWholeSeconds
|
||||
@@ -141,7 +162,7 @@ object JavalinSetup {
|
||||
)
|
||||
}
|
||||
|
||||
app.post(loginPath) { ctx ->
|
||||
post(loginPath) { ctx ->
|
||||
val username = ctx.formParam("user")
|
||||
val password = ctx.formParam("pass")
|
||||
val isValid =
|
||||
@@ -174,7 +195,7 @@ object JavalinSetup {
|
||||
)
|
||||
}
|
||||
|
||||
app.beforeMatched { ctx ->
|
||||
beforeMatched { ctx ->
|
||||
val isWebManifest =
|
||||
listOf("site.webmanifest", "manifest.json", "login.html").any { ctx.path().endsWith(it) }
|
||||
val isPageIcon =
|
||||
@@ -219,60 +240,43 @@ object JavalinSetup {
|
||||
ctx.setAttribute(Attribute.TachideskBasic, credentialsValid())
|
||||
}
|
||||
|
||||
app.events { event ->
|
||||
event.serverStarted {
|
||||
if (serverConfig.initialOpenInBrowserEnabled.value) {
|
||||
Browser.openInBrowser()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
app.wsBefore {
|
||||
wsBefore {
|
||||
it.onConnect { ctx ->
|
||||
ctx.setAttribute(Attribute.TachideskUser, getUserFromWsContext(ctx))
|
||||
}
|
||||
}
|
||||
|
||||
// when JVM is prompted to shutdown, stop javalin gracefully
|
||||
Runtime.getRuntime().addShutdownHook(
|
||||
thread(start = false) {
|
||||
app.stop()
|
||||
},
|
||||
)
|
||||
|
||||
app.exception(NullPointerException::class.java) { e, ctx ->
|
||||
exception(NullPointerException::class.java) { e, ctx ->
|
||||
logger.error(e) { "NullPointerException while handling the request" }
|
||||
ctx.status(404)
|
||||
}
|
||||
app.exception(NoSuchElementException::class.java) { e, ctx ->
|
||||
exception(NoSuchElementException::class.java) { e, ctx ->
|
||||
logger.error(e) { "NoSuchElementException while handling the request" }
|
||||
ctx.status(404)
|
||||
}
|
||||
app.exception(IOException::class.java) { e, ctx ->
|
||||
exception(IOException::class.java) { e, ctx ->
|
||||
logger.error(e) { "IOException while handling the request" }
|
||||
ctx.status(500)
|
||||
ctx.result(e.message ?: "Internal Server Error")
|
||||
}
|
||||
|
||||
app.exception(IllegalArgumentException::class.java) { e, ctx ->
|
||||
exception(IllegalArgumentException::class.java) { e, ctx ->
|
||||
logger.error(e) { "IllegalArgumentException while handling the request" }
|
||||
ctx.status(400)
|
||||
ctx.result(e.message ?: "Bad Request")
|
||||
}
|
||||
|
||||
app.exception(UnauthorizedException::class.java) { e, ctx ->
|
||||
exception(UnauthorizedException::class.java) { e, ctx ->
|
||||
logger.error(e) { "UnauthorizedException while handling the request" }
|
||||
ctx.status(HttpStatus.UNAUTHORIZED)
|
||||
ctx.result(e.message ?: "Unauthorized")
|
||||
}
|
||||
|
||||
app.exception(ForbiddenException::class.java) { e, ctx ->
|
||||
exception(ForbiddenException::class.java) { e, ctx ->
|
||||
logger.error(e) { "ForbiddenException while handling the request" }
|
||||
ctx.status(HttpStatus.FORBIDDEN)
|
||||
ctx.result(e.message ?: "Forbidden")
|
||||
}
|
||||
|
||||
app.start()
|
||||
}
|
||||
|
||||
// private fun getOpenApiOptions(): OpenApiOptions {
|
||||
|
||||
@@ -71,15 +71,14 @@ fun <T> getParam(
|
||||
is Param.FormParam -> ctx.formParamAsClass(param.key, clazz)
|
||||
is Param.PathParam -> ctx.pathParamAsClass(param.key, clazz)
|
||||
is Param.QueryParam -> ctx.queryParamAsClass(param.key, clazz)
|
||||
else -> throw IllegalStateException("Invalid param")
|
||||
}.let {
|
||||
if (param.nullable) {
|
||||
it.allowNullable().get() ?: param.defaultValue
|
||||
it.getOrNull() ?: param.defaultValue
|
||||
} else {
|
||||
if (param.defaultValue != null) {
|
||||
it.getOrDefault(param.defaultValue!!)
|
||||
} else {
|
||||
it.get()
|
||||
it.required().get()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import io.github.oshai.kotlinlogging.KLogger
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import io.github.reactivecircus.cache4k.Cache
|
||||
import io.javalin.config.JavalinConfig
|
||||
import io.javalin.http.staticfiles.AliasCheck
|
||||
import io.javalin.http.staticfiles.Location
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
@@ -39,7 +40,6 @@ import kotlinx.serialization.json.jsonArray
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import net.lingala.zip4j.ZipFile
|
||||
import org.eclipse.jetty.server.handler.ContextHandler
|
||||
import suwayomi.tachidesk.graphql.types.AboutWebUI
|
||||
import suwayomi.tachidesk.graphql.types.UpdateState
|
||||
import suwayomi.tachidesk.graphql.types.UpdateState.DOWNLOADING
|
||||
@@ -180,7 +180,7 @@ object WebInterfaceManager {
|
||||
// Use canonical path to avoid Jetty alias issues
|
||||
staticFiles.directory = File(applicationDirs.webUIServe).canonicalPath
|
||||
staticFiles.location = Location.EXTERNAL
|
||||
staticFiles.aliasCheck = ContextHandler.ApproveAliases()
|
||||
staticFiles.aliasCheck = AliasCheck { _, _ -> true }
|
||||
}
|
||||
|
||||
serveWebUI = {
|
||||
@@ -206,20 +206,12 @@ object WebInterfaceManager {
|
||||
|
||||
if (ServerSubpath.isDefined() && orgIndexHtml.exists()) {
|
||||
val originalIndexHtml = orgIndexHtml.readText()
|
||||
val subpathInjectionScript =
|
||||
"""
|
||||
<script>
|
||||
// <<suwayomi-subpath-injection>>
|
||||
const baseTag = document.createElement('base');
|
||||
baseTag.href = location.origin + "${ServerSubpath.asRootPath()}";
|
||||
document.head.appendChild(baseTag);
|
||||
</script>
|
||||
""".trimIndent()
|
||||
val subpathInjectionBaseTag = "<base href=\"${ServerSubpath.asRootPath()}\">"
|
||||
|
||||
val indexHtmlWithSubpathInjection =
|
||||
originalIndexHtml.replace(
|
||||
"<head>",
|
||||
"<head>$subpathInjectionScript",
|
||||
"<head>$subpathInjectionBaseTag",
|
||||
)
|
||||
|
||||
orgIndexHtml.writeText(indexHtmlWithSubpathInjection)
|
||||
@@ -312,11 +304,25 @@ object WebInterfaceManager {
|
||||
return
|
||||
}
|
||||
|
||||
val flavor = WebUIFlavor.current
|
||||
val servedFlavor = getServedWebUIFlavor()
|
||||
|
||||
val log =
|
||||
KotlinLogging.logger("${logger.name} setupWebUI(flavor= ${flavor.uiName}, servedFlavor= ${servedFlavor.uiName})")
|
||||
KotlinLogging.logger(
|
||||
"${logger.name} setupWebUI(flavor= ${WebUIFlavor.current.uiName}, servedFlavor= ${servedFlavor.uiName}, channel= ${serverConfig.webUIChannel})",
|
||||
)
|
||||
|
||||
val flavor =
|
||||
if (serverConfig.webUIChannel.value == WebUIChannel.BUNDLED) {
|
||||
if (serverConfig.webUIFlavor.value != WebUIFlavor.default) {
|
||||
log.warn {
|
||||
"Changed flavor to ${WebUIFlavor.default.uiName}. Channel \"${WebUIChannel.BUNDLED}\" only works with the default flavor"
|
||||
}
|
||||
}
|
||||
|
||||
WebUIFlavor.default
|
||||
} else {
|
||||
WebUIFlavor.current
|
||||
}
|
||||
|
||||
if (doesLocalWebUIExist(applicationDirs.webUIRoot)) {
|
||||
val currentVersion = getLocalVersion()
|
||||
|
||||
Reference in New Issue
Block a user