Compare commits

...

36 Commits

Author SHA1 Message Date
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
renovate[bot]
c98899d501 Update plugin shadowjar to v8.3.10 (#2007)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-09 12:01:08 -04:00
renovate[bot]
7654653a25 Update plugin buildconfig to v6.0.9 (#2006)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-09 12:00:44 -04:00
renovate[bot]
0c1a0ef408 Update Gradle to v9.5.0 (#2004)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-09 12:00:30 -04:00
renovate[bot]
e7f2192579 Update plugin ktlint to v14.2.0 (#2005)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-09 11:59:40 -04:00
renovate[bot]
5c3b1e0b07 Update moko to v0.26.4 (#2003)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-09 11:58:02 -04:00
renovate[bot]
6ad59f2e2b Update dex2jar to v2.4.36 (#2001)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-09 11:51:32 -04:00
renovate[bot]
03dd778fac Update jte to v3.2.4 (#2002)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-09 11:50:46 -04:00
renovate[bot]
6b833a38d1 Update kotlinx-coroutines monorepo to v1.11.0 (#1995)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-09 11:47:26 -04:00
renovate[bot]
a83885353c Update dependency com.typesafe:config to v1.4.8 (#1922)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-09 11:46:20 -04:00
renovate[bot]
10d7c7c06d Update polyglot to v25 (#1651)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-09 11:45:53 -04:00
Shozikan
3b575271cb Chore: Updated Moku's Description & Quick Spelling Fix (#1999)
Corrected spelling of 'abandoned' and clarified Moku's server management capabilities.
2026-05-09 11:44:29 -04:00
renovate[bot]
5ad92413f3 Update GitHub Artifact Actions (#1986)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-09 11:44:11 -04:00
renovate[bot]
7fbdb39319 Update dependency io.github.oshai:kotlin-logging-jvm to v8.0.02 (#1983)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-09 11:44:02 -04:00
renovate[bot]
92abdf7fb7 Update dependency com.auth0:java-jwt to v4.5.2 (#1980)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-09 11:43:47 -04:00
renovate[bot]
ea0c666cfe Update dependency com.android.tools.build:apksig to v9.2.1 (#1984)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-09 11:43:36 -04:00
renovate[bot]
b5e395a039 Update dependency com.ibm.icu:icu4j to v78.3 (#1985)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-09 11:43:25 -04:00
renovate[bot]
e530072a07 Update softprops/action-gh-release action to v3 (#1987)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-09 11:43:14 -04:00
renovate[bot]
f85cbe1ca5 Update dependency org.jsoup:jsoup to v1.22.2 (#1988)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-09 11:43:04 -04:00
renovate[bot]
5f9126eb2f Update dependency org.postgresql:postgresql to v42.7.11 (#1989)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-09 11:42:53 -04:00
renovate[bot]
d77c57ede0 Update dependency androidx.annotation:annotation to v1.10.0 (#1990)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-09 11:42:43 -04:00
renovate[bot]
02f9a0d1d7 Update dependency androidx.annotation:annotation to v1.10.0 (#1990)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-09 11:42:36 -04:00
renovate[bot]
53192f56ca Update serialization to v1.11.0 (#1996)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-09 11:42:23 -04:00
renovate[bot]
01d89cbb48 Update dependency org.bouncycastle:bcprov-jdk18on to v1.84 (#1994)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-09 11:42:04 -04:00
renovate[bot]
be55cb974b Update dependency io.insert-koin:koin-core to v4.2.1 (#1993)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-09 11:41:49 -04:00
renovate[bot]
34c394ed19 Update dependency com.squareup.okio:okio to v3.17.0 (#1992)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-09 11:41:39 -04:00
renovate[bot]
1433a21abd Update graphqlkotlin to v8.9.0 (#1981)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-09 11:41:33 -04:00
renovate[bot]
ec28794655 Update kotlin to v2.3.21 (#1991)
* Update kotlin to v2.3.21

* Context Parameters

* Use new format

* Lint

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Syer10 <syer10@users.noreply.github.com>
2026-05-09 11:41:27 -04:00
Syer10
72122b7cbf [skip ci] Update Changelog 2026-05-08 17:32:44 -04:00
renovate[bot]
392a7990d2 Update dependency com.github.junrar:junrar to v7.5.10 (#1926)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-08 17:30:48 -04:00
schroda
0bdcf8b4ba Handle serving non-default webui with "bundled" channel (#1924)
Channel "bundled" only works with the default webui.
So force change the flavor and log a warning for information
2026-05-08 17:30:42 -04:00
renovate[bot]
8295440bfd Update twelvemonkeys to v3.13.1 (#1919)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-05-08 17:30:36 -04:00
Syer10
e52aa6daf4 [skip ci] Fix WebUI changelog link 2026-05-08 16:52:34 -04:00
19 changed files with 167 additions and 144 deletions

View File

@@ -54,14 +54,14 @@ jobs:
run: ./gradlew :server:shadowJar --stacktrace run: ./gradlew :server:shadowJar --stacktrace
- name: Upload Jar - name: Upload Jar
uses: actions/upload-artifact@v6 uses: actions/upload-artifact@v7
with: with:
name: jar name: jar
path: master/server/build/*.jar path: master/server/build/*.jar
if-no-files-found: error if-no-files-found: error
- name: Upload icons - name: Upload icons
uses: actions/upload-artifact@v6 uses: actions/upload-artifact@v7
with: with:
name: icon name: icon
path: master/server/src/main/resources/icon path: master/server/src/main/resources/icon
@@ -71,7 +71,7 @@ jobs:
run: tar -cvzf scripts.tar.gz -C master/ scripts/ run: tar -cvzf scripts.tar.gz -C master/ scripts/
- name: Upload scripts.tar.gz - name: Upload scripts.tar.gz
uses: actions/upload-artifact@v6 uses: actions/upload-artifact@v7
with: with:
name: scripts name: scripts
path: scripts.tar.gz path: scripts.tar.gz
@@ -103,7 +103,7 @@ jobs:
run: jlink --add-modules java.base,java.compiler,java.datatransfer,java.desktop,java.instrument,java.logging,java.management,java.naming,java.prefs,java.scripting,java.se,java.security.jgss,java.security.sasl,java.sql,java.transaction.xa,java.xml,jdk.attach,jdk.crypto.ec,jdk.jdi,jdk.management,jdk.net,jdk.unsupported,jdk.unsupported.desktop,jdk.zipfs,jdk.accessibility --output suwa --strip-debug --no-man-pages --no-header-files --compress=2 run: jlink --add-modules java.base,java.compiler,java.datatransfer,java.desktop,java.instrument,java.logging,java.management,java.naming,java.prefs,java.scripting,java.se,java.security.jgss,java.security.sasl,java.sql,java.transaction.xa,java.xml,jdk.attach,jdk.crypto.ec,jdk.jdi,jdk.management,jdk.net,jdk.unsupported,jdk.unsupported.desktop,jdk.zipfs,jdk.accessibility --output suwa --strip-debug --no-man-pages --no-header-files --compress=2
- name: Upload JRE package - name: Upload JRE package
uses: actions/upload-artifact@v6 uses: actions/upload-artifact@v7
with: with:
name: ${{ matrix.name }}-jre name: ${{ matrix.name }}-jre
path: suwa path: suwa
@@ -134,26 +134,26 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Download Jar - name: Download Jar
uses: actions/download-artifact@v7 uses: actions/download-artifact@v8
with: with:
name: jar name: jar
path: server/build path: server/build
- name: Download JRE - name: Download JRE
uses: actions/download-artifact@v7 uses: actions/download-artifact@v8
if: matrix.name != 'linux-assets' && matrix.name != 'debian-all' if: matrix.name != 'linux-assets' && matrix.name != 'debian-all'
with: with:
name: ${{ matrix.jre }}-jre name: ${{ matrix.jre }}-jre
path: jre path: jre
- name: Download icons - name: Download icons
uses: actions/download-artifact@v7 uses: actions/download-artifact@v8
with: with:
name: icon name: icon
path: server/src/main/resources/icon path: server/src/main/resources/icon
- name: Download scripts.tar.gz - name: Download scripts.tar.gz
uses: actions/download-artifact@v7 uses: actions/download-artifact@v8
with: with:
name: scripts name: scripts
@@ -164,7 +164,7 @@ jobs:
scripts/bundler.sh -o upload/ ${{ matrix.name }} scripts/bundler.sh -o upload/ ${{ matrix.name }}
- name: Upload ${{ matrix.name }} release - name: Upload ${{ matrix.name }} release
uses: actions/upload-artifact@v6 uses: actions/upload-artifact@v7
with: with:
name: ${{ matrix.name }} name: ${{ matrix.name }}
path: upload/* path: upload/*
@@ -174,35 +174,35 @@ jobs:
needs: bundle needs: bundle
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/download-artifact@v7 - uses: actions/download-artifact@v8
with: with:
name: jar name: jar
path: release path: release
- uses: actions/download-artifact@v7 - uses: actions/download-artifact@v8
with: with:
name: debian-all name: debian-all
path: release path: release
- uses: actions/download-artifact@v7 - uses: actions/download-artifact@v8
with: with:
name: appimage name: appimage
path: release path: release
- uses: actions/download-artifact@v7 - uses: actions/download-artifact@v8
with: with:
name: linux-assets name: linux-assets
path: release path: release
- uses: actions/download-artifact@v7 - uses: actions/download-artifact@v8
with: with:
name: linux-x64 name: linux-x64
path: release path: release
- uses: actions/download-artifact@v7 - uses: actions/download-artifact@v8
with: with:
name: macOS-x64 name: macOS-x64
path: release path: release
- uses: actions/download-artifact@v7 - uses: actions/download-artifact@v8
with: with:
name: macOS-arm64 name: macOS-arm64
path: release path: release
- uses: actions/download-artifact@v7 - uses: actions/download-artifact@v8
with: with:
name: windows-x64 name: windows-x64
path: release path: release
@@ -240,7 +240,7 @@ jobs:
git push origin $TAG git push origin $TAG
- name: Upload Preview Release - name: Upload Preview Release
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v3
with: with:
token: ${{ secrets.DEPLOY_PREVIEW_TOKEN }} token: ${{ secrets.DEPLOY_PREVIEW_TOKEN }}
repository: "Suwayomi/Suwayomi-Server-preview" repository: "Suwayomi/Suwayomi-Server-preview"

View File

@@ -56,14 +56,14 @@ jobs:
run: ./gradlew :server:downloadWebUI :server:shadowJar --stacktrace run: ./gradlew :server:downloadWebUI :server:shadowJar --stacktrace
- name: Upload Jar - name: Upload Jar
uses: actions/upload-artifact@v6 uses: actions/upload-artifact@v7
with: with:
name: jar name: jar
path: master/server/build/*.jar path: master/server/build/*.jar
if-no-files-found: error if-no-files-found: error
- name: Upload icons - name: Upload icons
uses: actions/upload-artifact@v6 uses: actions/upload-artifact@v7
with: with:
name: icon name: icon
path: master/server/src/main/resources/icon path: master/server/src/main/resources/icon
@@ -73,7 +73,7 @@ jobs:
run: tar -cvzf scripts.tar.gz -C master/ scripts/ run: tar -cvzf scripts.tar.gz -C master/ scripts/
- name: Upload scripts.tar.gz - name: Upload scripts.tar.gz
uses: actions/upload-artifact@v6 uses: actions/upload-artifact@v7
with: with:
name: scripts name: scripts
path: scripts.tar.gz path: scripts.tar.gz
@@ -105,7 +105,7 @@ jobs:
run: jlink --add-modules java.base,java.compiler,java.datatransfer,java.desktop,java.instrument,java.logging,java.management,java.naming,java.prefs,java.scripting,java.se,java.security.jgss,java.security.sasl,java.sql,java.transaction.xa,java.xml,jdk.attach,jdk.crypto.ec,jdk.jdi,jdk.management,jdk.net,jdk.unsupported,jdk.unsupported.desktop,jdk.zipfs,jdk.accessibility --output suwa --strip-debug --no-man-pages --no-header-files --compress=2 run: jlink --add-modules java.base,java.compiler,java.datatransfer,java.desktop,java.instrument,java.logging,java.management,java.naming,java.prefs,java.scripting,java.se,java.security.jgss,java.security.sasl,java.sql,java.transaction.xa,java.xml,jdk.attach,jdk.crypto.ec,jdk.jdi,jdk.management,jdk.net,jdk.unsupported,jdk.unsupported.desktop,jdk.zipfs,jdk.accessibility --output suwa --strip-debug --no-man-pages --no-header-files --compress=2
- name: Upload JDK package - name: Upload JDK package
uses: actions/upload-artifact@v6 uses: actions/upload-artifact@v7
with: with:
name: ${{ matrix.name }}-jre name: ${{ matrix.name }}-jre
path: suwa path: suwa
@@ -136,26 +136,26 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Download Jar - name: Download Jar
uses: actions/download-artifact@v7 uses: actions/download-artifact@v8
with: with:
name: jar name: jar
path: server/build path: server/build
- name: Download JRE - name: Download JRE
uses: actions/download-artifact@v7 uses: actions/download-artifact@v8
if: matrix.name != 'linux-assets' && matrix.name != 'debian-all' if: matrix.name != 'linux-assets' && matrix.name != 'debian-all'
with: with:
name: ${{ matrix.jre }}-jre name: ${{ matrix.jre }}-jre
path: jre path: jre
- name: Download icons - name: Download icons
uses: actions/download-artifact@v7 uses: actions/download-artifact@v8
with: with:
name: icon name: icon
path: server/src/main/resources/icon path: server/src/main/resources/icon
- name: Download scripts.tar.gz - name: Download scripts.tar.gz
uses: actions/download-artifact@v7 uses: actions/download-artifact@v8
with: with:
name: scripts name: scripts
@@ -166,7 +166,7 @@ jobs:
scripts/bundler.sh -o upload/ ${{ matrix.name }} scripts/bundler.sh -o upload/ ${{ matrix.name }}
- name: Upload ${{ matrix.name }} files - name: Upload ${{ matrix.name }} files
uses: actions/upload-artifact@v6 uses: actions/upload-artifact@v7
with: with:
name: ${{ matrix.name }} name: ${{ matrix.name }}
path: upload/* path: upload/*
@@ -177,35 +177,35 @@ jobs:
needs: bundle needs: bundle
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/download-artifact@v7 - uses: actions/download-artifact@v8
with: with:
name: jar name: jar
path: release path: release
- uses: actions/download-artifact@v7 - uses: actions/download-artifact@v8
with: with:
name: debian-all name: debian-all
path: release path: release
- uses: actions/download-artifact@v7 - uses: actions/download-artifact@v8
with: with:
name: appimage name: appimage
path: release path: release
- uses: actions/download-artifact@v7 - uses: actions/download-artifact@v8
with: with:
name: linux-assets name: linux-assets
path: release path: release
- uses: actions/download-artifact@v7 - uses: actions/download-artifact@v8
with: with:
name: linux-x64 name: linux-x64
path: release path: release
- uses: actions/download-artifact@v7 - uses: actions/download-artifact@v8
with: with:
name: macOS-x64 name: macOS-x64
path: release path: release
- uses: actions/download-artifact@v7 - uses: actions/download-artifact@v8
with: with:
name: macOS-arm64 name: macOS-arm64
path: release path: release
- uses: actions/download-artifact@v7 - uses: actions/download-artifact@v8
with: with:
name: windows-x64 name: windows-x64
path: release path: release
@@ -214,7 +214,7 @@ jobs:
run: cd release && sha256sum * > Checksums.sha256 run: cd release && sha256sum * > Checksums.sha256
- name: Release - name: Release
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v3
with: with:
token: ${{ secrets.DEPLOY_RELEASE_TOKEN }} token: ${{ secrets.DEPLOY_RELEASE_TOKEN }}
draft: true draft: true

View File

@@ -13,7 +13,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- . - .
### Fixed ### Fixed
- . - (WebUI) Handle serving non-default webui with "bundled"
- (WebUI) Wait until WebUI is ready to open in browser
## [v2.2.2100] + [WebUI: v20260508.01] - 2026-05-08 ## [v2.2.2100] + [WebUI: v20260508.01] - 2026-05-08
@@ -89,7 +90,7 @@ See our [conversion server](https://github.com/Suwayomi/Suwayomi-converter) for
- (**Cloudflare/flaresolverr**) Fix sending POST requests as GET to flaresolverr - (**Cloudflare/flaresolverr**) Fix sending POST requests as GET to flaresolverr
### WebUI ### WebUI
- [See WebUI changes here][WebUI: v20250703.01] - [See WebUI changes here][WebUI: v20260508.01]
### Contributors ### Contributors
@cpiber, @Syer10, @lamaxama, @schroda, @AwkwardPeak7, @ItsGlassPlus1, @manti-X, @Youwes09, @renovate[bot], @D-Brox, @weblate, @Micka149, @TheRay82, @UnknownSkyrimPasserby, @KaceyKoo-gif, @333fred, @KolbyML, @Robonau, @ornaras, @SpicyCatGames, @FadedSociety, @ginocic, @zeedif, @CzechuPL, @mrintrepide, @renjfk, @thiagoalcr, @Smileskun, @dejavui, @allrobot @cpiber, @Syer10, @lamaxama, @schroda, @AwkwardPeak7, @ItsGlassPlus1, @manti-X, @Youwes09, @renovate[bot], @D-Brox, @weblate, @Micka149, @TheRay82, @UnknownSkyrimPasserby, @KaceyKoo-gif, @333fred, @KolbyML, @Robonau, @ornaras, @SpicyCatGames, @FadedSociety, @ginocic, @zeedif, @CzechuPL, @mrintrepide, @renjfk, @thiagoalcr, @Smileskun, @dejavui, @allrobot

View File

@@ -76,9 +76,9 @@ These clients are built-in options, and the server can keep them automatically u
- [Suwayomi-WebUI](https://github.com/Suwayomi/Suwayomi-WebUI): Web app, PWA - [Suwayomi-WebUI](https://github.com/Suwayomi/Suwayomi-WebUI): Web app, PWA
- [Suwayomi-VUI](https://github.com/Suwayomi/Suwayomi-VUI): Web app, PWA - [Suwayomi-VUI](https://github.com/Suwayomi/Suwayomi-VUI): Web app, PWA
##### Other clients (potentially inactive or abondend) ##### Other clients (potentially inactive or abandoned)
- [Tachidesk-VaadinUI](https://github.com/Suwayomi/Tachidesk-VaadinUI): Desktop app (windows, linux, mac); UI in the browser, manages its own suwayomi server instance - [Tachidesk-VaadinUI](https://github.com/Suwayomi/Tachidesk-VaadinUI): Desktop app (windows, linux, mac); UI in the browser, manages its own suwayomi server instance
- [Moku](https://github.com/Youwes09/Moku): Desktop app (windows, linux, mac), requires access to a running server - [Moku](https://github.com/Youwes09/Moku): Desktop app (windows, linux, mac), can manage its own suwayomi server instance
- [Tachidesk-JUI](https://github.com/Suwayomi/Tachidesk-JUI): Desktop app (windows, linux, mac); can manage its own suwayomi server instance - [Tachidesk-JUI](https://github.com/Suwayomi/Tachidesk-JUI): Desktop app (windows, linux, mac); can manage its own suwayomi server instance
- [Tachidesk-Sorayomi](https://github.com/Suwayomi/Tachidesk-Sorayomi): Web app; Desktop app (windows, linux, mac); Android app; requires access to a running server - [Tachidesk-Sorayomi](https://github.com/Suwayomi/Tachidesk-Sorayomi): Web app; Desktop app (windows, linux, mac); Android app; requires access to a running server
- [Tachidesk-qtui](https://github.com/Suwayomi/Tachidesk-qtui): Android app; iOS app Desktop app (linux); requires access to a running server - [Tachidesk-qtui](https://github.com/Suwayomi/Tachidesk-qtui): Android app; iOS app Desktop app (linux); requires access to a running server

View File

@@ -53,7 +53,7 @@ subprojects {
} }
compilerOptions { compilerOptions {
jvmTarget = JvmTarget.fromTarget(libs.versions.jvmTarget.get()) jvmTarget = JvmTarget.fromTarget(libs.versions.jvmTarget.get())
freeCompilerArgs.add("-Xcontext-receivers") freeCompilerArgs.add("-Xcontext-parameters")
} }
} }
} }

View File

@@ -1,22 +1,22 @@
[versions] [versions]
kotlin = "2.3.10" kotlin = "2.3.21"
coroutines = "1.10.2" coroutines = "1.11.0"
serialization = "1.10.0" serialization = "1.11.0"
jvmTarget = "21" jvmTarget = "21"
okhttp = "5.3.2" # Major version is locked by Tachiyomi extensions okhttp = "5.3.2" # Major version is locked by Tachiyomi extensions
javalin = "6.7.0" javalin = "7.2.0"
jte = "3.2.3" jte = "3.2.4"
jackson = "2.18.3" # 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" exposed = "0.61.0"
dex2jar = "2.4.34" dex2jar = "2.4.36"
polyglot = "24.2.2" polyglot = "25.0.3"
settings = "1.3.0" settings = "1.3.0"
twelvemonkeys = "3.13.0" twelvemonkeys = "3.13.1"
graphqlkotlin = "8.8.1" graphqlkotlin = "8.9.0"
xmlserialization = "0.91.3" xmlserialization = "0.91.3"
ktlint = "1.8.0" ktlint = "1.8.0"
koin = "4.1.1" koin = "4.2.1"
moko = "0.26.0" moko = "0.26.4"
[libraries] [libraries]
# Kotlin # Kotlin
@@ -39,22 +39,22 @@ serialization-xml = { module = "io.github.pdvrieze.xmlutil:serialization-jvm", v
# Logging # Logging
slf4japi = "org.slf4j:slf4j-api:2.0.17" slf4japi = "org.slf4j:slf4j-api:2.0.17"
logback = "ch.qos.logback:logback-classic:1.5.32" logback = "ch.qos.logback:logback-classic:1.5.32"
kotlinlogging = "io.github.oshai:kotlin-logging-jvm:8.0.01" kotlinlogging = "io.github.oshai:kotlin-logging-jvm:8.0.02"
# OkHttp # OkHttp
okhttp-core = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } okhttp-core = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" } okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" }
okhttp-dnsoverhttps = { module = "com.squareup.okhttp3:okhttp-dnsoverhttps", version.ref = "okhttp" } okhttp-dnsoverhttps = { module = "com.squareup.okhttp3:okhttp-dnsoverhttps", version.ref = "okhttp" }
okhttp-brotli = { module = "com.squareup.okhttp3:okhttp-brotli", version.ref = "okhttp" } okhttp-brotli = { module = "com.squareup.okhttp3:okhttp-brotli", version.ref = "okhttp" }
okio = "com.squareup.okio:okio:3.16.4" okio = "com.squareup.okio:okio:3.17.0"
# Javalin api # Javalin api
javalin-core = { module = "io.javalin:javalin", version.ref = "javalin" } javalin-core = { module = "io.javalin:javalin", version.ref = "javalin" }
javalin-openapi = { module = "io.javalin:javalin-openapi", version.ref = "javalin" } javalin-openapi = { module = "io.javalin:javalin-openapi", version.ref = "javalin" }
javalin-rendering = { module = "io.javalin:javalin-rendering", version.ref = "javalin" } javalin-rendering = { module = "io.javalin:javalin-rendering-jte", version.ref = "javalin" }
jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind", version.ref = "jackson" } jackson-databind = { module = "tools.jackson.core:jackson-databind", version.ref = "jackson" }
jackson-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson" } jackson-kotlin = { module = "tools.jackson.module:jackson-module-kotlin", version.ref = "jackson" }
jackson-annotations = { module = "com.fasterxml.jackson.core:jackson-annotations", version.ref = "jackson" } jackson-annotations = "com.fasterxml.jackson.core:jackson-annotations:2.21"
jte = { module = "gg.jte:jte", version.ref = "jte" } jte = { module = "gg.jte:jte", version.ref = "jte" }
kte = { module = "gg.jte:jte-kotlin", version.ref = "jte" } kte = { module = "gg.jte:jte-kotlin", version.ref = "jte" }
@@ -68,7 +68,7 @@ exposed-core = { module = "org.jetbrains.exposed:exposed-core", version.ref = "e
exposed-dao = { module = "org.jetbrains.exposed:exposed-dao", version.ref = "exposed" } exposed-dao = { module = "org.jetbrains.exposed:exposed-dao", version.ref = "exposed" }
exposed-jdbc = { module = "org.jetbrains.exposed:exposed-jdbc", version.ref = "exposed" } exposed-jdbc = { module = "org.jetbrains.exposed:exposed-jdbc", version.ref = "exposed" }
exposed-javatime = { module = "org.jetbrains.exposed:exposed-java-time", version.ref = "exposed" } exposed-javatime = { module = "org.jetbrains.exposed:exposed-java-time", version.ref = "exposed" }
postgres = "org.postgresql:postgresql:42.7.10" postgres = "org.postgresql:postgresql:42.7.11"
h2 = "com.h2database:h2:1.4.200" # current database driver, can't update to h2 v2 without sql migration h2 = "com.h2database:h2:1.4.200" # current database driver, can't update to h2 v2 without sql migration
hikaricp = "com.zaxxer:HikariCP:7.0.2" hikaricp = "com.zaxxer:HikariCP:7.0.2"
@@ -86,10 +86,10 @@ systemtray-desktop = "com.dorkbox:Desktop:1.1" # version locked by SystemTray
# dependencies of Tachiyomi extensions # dependencies of Tachiyomi extensions
injekt = "com.github.null2264:injekt-koin:ee267b2e27" injekt = "com.github.null2264:injekt-koin:ee267b2e27"
rxjava = "io.reactivex:rxjava:1.3.8" rxjava = "io.reactivex:rxjava:1.3.8"
jsoup = "org.jsoup:jsoup:1.22.1" jsoup = "org.jsoup:jsoup:1.22.2"
# Config # Config
config = "com.typesafe:config:1.4.5" config = "com.typesafe:config:1.4.8"
config4k = "io.github.config4k:config4k:0.7.0" config4k = "io.github.config4k:config4k:0.7.0"
# Sort # Sort
@@ -105,7 +105,7 @@ dex2jar-tools = { module = "de.femtopedia.dex2jar:dex-tools", version.ref = "dex
# APK # APK
apk-parser = "net.dongliu:apk-parser:2.6.10" apk-parser = "net.dongliu:apk-parser:2.6.10"
apksig = "com.android.tools.build:apksig:9.0.1" apksig = "com.android.tools.build:apksig:9.2.1"
# Xml # Xml
xmlpull = "xmlpull:xmlpull:1.1.3.4a" xmlpull = "xmlpull:xmlpull:1.1.3.4a"
@@ -115,13 +115,13 @@ appdirs = "ca.gosyer:kotlin-multiplatform-appdirs:2.0.0"
cache4k = "io.github.reactivecircus.cache4k:cache4k:0.14.0" cache4k = "io.github.reactivecircus.cache4k:cache4k:0.14.0"
zip4j = "net.lingala.zip4j:zip4j:2.11.6" zip4j = "net.lingala.zip4j:zip4j:2.11.6"
commonscompress = "org.apache.commons:commons-compress:1.28.0" commonscompress = "org.apache.commons:commons-compress:1.28.0"
junrar = "com.github.junrar:junrar:7.5.7" junrar = "com.github.junrar:junrar:7.5.10"
# AES/CBC/PKCS7Padding Cypher provider # AES/CBC/PKCS7Padding Cypher provider
bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.83" bouncycastle = "org.bouncycastle:bcprov-jdk18on:1.84"
# AndroidX annotations # AndroidX annotations
android-annotations = "androidx.annotation:annotation:1.9.1" android-annotations = "androidx.annotation:annotation:1.10.0"
# Substitute for duktape-android # Substitute for duktape-android
polyglot-core = { module = "org.graalvm.polyglot:polyglot", version.ref = "polyglot" } polyglot-core = { module = "org.graalvm.polyglot:polyglot", version.ref = "polyglot" }
@@ -132,7 +132,7 @@ settings-core = { module = "com.russhwolf:multiplatform-settings-jvm", version.r
settings-serialization = { module = "com.russhwolf:multiplatform-settings-serialization-jvm", version.ref = "settings" } settings-serialization = { module = "com.russhwolf:multiplatform-settings-serialization-jvm", version.ref = "settings" }
# ICU4J # ICU4J
icu4j = "com.ibm.icu:icu4j:78.2" icu4j = "com.ibm.icu:icu4j:78.3"
# Image Decoding implementation provider # Image Decoding implementation provider
twelvemonkeys-common-lang = { module = "com.twelvemonkeys.common:common-lang", version.ref = "twelvemonkeys" } twelvemonkeys-common-lang = { module = "com.twelvemonkeys.common:common-lang", version.ref = "twelvemonkeys" }
@@ -158,7 +158,7 @@ cronUtils = "com.cronutils:cron-utils:9.2.1"
kcef = "dev.datlag:kcef:2024.04.20.4" kcef = "dev.datlag:kcef:2024.04.20.4"
# User # User
jwt = "com.auth0:java-jwt:4.5.1" jwt = "com.auth0:java-jwt:4.5.2"
# lint - used for renovate to update ktlint version # lint - used for renovate to update ktlint version
ktlint = { module = "com.pinterest.ktlint:ktlint-cli", version.ref = "ktlint" } ktlint = { module = "com.pinterest.ktlint:ktlint-cli", version.ref = "ktlint" }
@@ -173,16 +173,16 @@ kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", versi
kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin"} kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin"}
# Linter # Linter
ktlint = { id = "org.jlleitschuh.gradle.ktlint", version = "14.0.1"} ktlint = { id = "org.jlleitschuh.gradle.ktlint", version = "14.2.0"}
# Build config # Build config
buildconfig = { id = "com.github.gmazzo.buildconfig", version = "6.0.7"} buildconfig = { id = "com.github.gmazzo.buildconfig", version = "6.0.9"}
# Download # Download
download = { id = "de.undercouch.download", version = "5.7.0"} download = { id = "de.undercouch.download", version = "5.7.0"}
# ShadowJar # ShadowJar
shadowjar = { id = "com.gradleup.shadow", version = "8.3.9"} shadowjar = { id = "com.gradleup.shadow", version = "8.3.10"}
# Moko # Moko
moko = { id = "dev.icerock.mobile.multiplatform-resources", version.ref = "moko" } moko = { id = "dev.icerock.mobile.multiplatform-resources", version.ref = "moko" }

Binary file not shown.

View File

@@ -1,7 +1,9 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.0-bin.zip
networkTimeout=10000 networkTimeout=10000
retries=0
retryBackOffMs=500
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

2
gradlew vendored
View File

@@ -57,7 +57,7 @@
# Darwin, MinGW, and NonStop. # Darwin, MinGW, and NonStop.
# #
# (3) This script is generated from the Groovy template # (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project. # within the Gradle project.
# #
# You can find Gradle at https://github.com/gradle/gradle/. # You can find Gradle at https://github.com/gradle/gradle/.

31
gradlew.bat vendored
View File

@@ -23,8 +23,8 @@
@rem @rem
@rem ########################################################################## @rem ##########################################################################
@rem Set local scope for the variables with windows NT shell @rem Set local scope for the variables, and ensure extensions are enabled
if "%OS%"=="Windows_NT" setlocal setlocal EnableExtensions
set DIRNAME=%~dp0 set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=. if "%DIRNAME%"=="" set DIRNAME=.
@@ -51,7 +51,7 @@ echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2 echo location of your Java installation. 1>&2
goto fail "%COMSPEC%" /c exit 1
:findJavaFromJavaHome :findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=% set JAVA_HOME=%JAVA_HOME:"=%
@@ -65,7 +65,7 @@ echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2 echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2 echo location of your Java installation. 1>&2
goto fail "%COMSPEC%" /c exit 1
:execute :execute
@rem Setup the command line @rem Setup the command line
@@ -73,21 +73,10 @@ goto fail
@rem Execute Gradle @rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* @rem endlocal doesn't take effect until after the line is parsed and variables are expanded
@rem which allows us to clear the local environment before executing the java command
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
:end :exitWithErrorLevel
@rem End local scope for the variables with windows NT shell @rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
if %ERRORLEVEL% equ 0 goto mainEnd "%COMSPEC%" /c exit %ERRORLEVEL%
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

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

View File

@@ -140,17 +140,16 @@ fun OkHttpClient.newCachelessCallWithProgress(
return progressClient.newCall(request) return progressClient.newCall(request)
} }
context(Json) context(_: Json)
inline fun <reified T> Response.parseAs(): T = decodeFromJsonResponse(serializer(), this) inline fun <reified T> Response.parseAs(): T = decodeFromJsonResponse(serializer(), this)
@OptIn(ExperimentalSerializationApi::class) context(json: Json)
context(Json)
fun <T> decodeFromJsonResponse( fun <T> decodeFromJsonResponse(
deserializer: DeserializationStrategy<T>, deserializer: DeserializationStrategy<T>,
response: Response, response: Response,
): T = ): T =
response.body.source().use { response.body.source().use {
decodeFromBufferedSource(deserializer, it) json.decodeFromBufferedSource(deserializer, it)
} }
class HttpException( class HttpException(

View File

@@ -6,7 +6,7 @@ import io.javalin.websocket.WsMessageContext
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json 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 import suwayomi.tachidesk.manga.impl.update.Websocket
object WebView : Websocket<String>() { object WebView : Websocket<String>() {

View File

@@ -13,10 +13,14 @@ import com.expediagroup.graphql.server.types.GraphQLRequest
import com.expediagroup.graphql.server.types.GraphQLServerRequest import com.expediagroup.graphql.server.types.GraphQLServerRequest
import io.javalin.http.Context import io.javalin.http.Context
import io.javalin.http.UploadedFile import io.javalin.http.UploadedFile
import io.javalin.json.JavalinJackson
import io.javalin.json.fromJsonStream
import io.javalin.json.fromJsonString import io.javalin.json.fromJsonString
import java.io.IOException import java.io.IOException
class JavalinGraphQLRequestParser : GraphQLRequestParser<Context> { class JavalinGraphQLRequestParser : GraphQLRequestParser<Context> {
val jsonMapper = JavalinJackson()
@Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE") @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
override suspend fun parseRequest(context: Context): GraphQLServerRequest? { override suspend fun parseRequest(context: Context): GraphQLServerRequest? {
return try { return try {
@@ -29,17 +33,17 @@ class JavalinGraphQLRequestParser : GraphQLRequestParser<Context> {
context.formParam("operations") context.formParam("operations")
?: throw IllegalArgumentException("Cannot find 'operations' body") ?: throw IllegalArgumentException("Cannot find 'operations' body")
} else { } else {
return context.bodyAsClass(GraphQLServerRequest::class.java) return context.bodyInputStream().use { jsonMapper.fromJsonStream<GraphQLServerRequest>(it) }
} }
val request = val request =
context.jsonMapper().fromJsonString<GraphQLServerRequest>(formParam) jsonMapper.fromJsonString<GraphQLServerRequest>(formParam)
val map = val map =
context context
.formParam("map") .formParam("map")
?.let { ?.let {
context.jsonMapper().fromJsonString<Map<String, List<String>>>(it) jsonMapper.fromJsonString<Map<String, List<String>>>(it)
}.orEmpty() }.orEmpty()
val mapItems = val mapItems =

View File

@@ -26,7 +26,7 @@ import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.job import kotlinx.coroutines.job
import kotlinx.coroutines.runBlocking 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.TachideskGraphQLContextFactory
import suwayomi.tachidesk.graphql.server.subscriptions.SubscriptionOperationMessage.ClientMessages.GQL_CONNECTION_INIT import suwayomi.tachidesk.graphql.server.subscriptions.SubscriptionOperationMessage.ClientMessages.GQL_CONNECTION_INIT
import suwayomi.tachidesk.graphql.server.subscriptions.SubscriptionOperationMessage.ClientMessages.GQL_SUBSCRIBE import suwayomi.tachidesk.graphql.server.subscriptions.SubscriptionOperationMessage.ClientMessages.GQL_SUBSCRIBE

View File

@@ -13,7 +13,7 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.onCompletion 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 suwayomi.tachidesk.graphql.server.toGraphQLContext
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.CopyOnWriteArrayList

View File

@@ -13,19 +13,24 @@ import io.github.oshai.kotlinlogging.KotlinLogging
import io.javalin.Javalin import io.javalin.Javalin
import io.javalin.apibuilder.ApiBuilder.after import io.javalin.apibuilder.ApiBuilder.after
import io.javalin.apibuilder.ApiBuilder.path import io.javalin.apibuilder.ApiBuilder.path
import io.javalin.config.RoutesConfig
import io.javalin.http.Context import io.javalin.http.Context
import io.javalin.http.HandlerType import io.javalin.http.HandlerType
import io.javalin.http.HttpStatus import io.javalin.http.HttpStatus
import io.javalin.http.NotFoundResponse import io.javalin.http.NotFoundResponse
import io.javalin.http.RedirectResponse import io.javalin.http.RedirectResponse
import io.javalin.http.UnauthorizedResponse import io.javalin.http.UnauthorizedResponse
import io.javalin.json.JavalinJackson3
import io.javalin.rendering.template.JavalinJte import io.javalin.rendering.template.JavalinJte
import io.javalin.websocket.WsContext import io.javalin.websocket.WsContext
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.future.future import kotlinx.coroutines.future.future
import kotlinx.coroutines.launch
import kotlinx.coroutines.withTimeoutOrNull
import org.eclipse.jetty.server.ServerConnector import org.eclipse.jetty.server.ServerConnector
import suwayomi.tachidesk.global.GlobalAPI import suwayomi.tachidesk.global.GlobalAPI
import suwayomi.tachidesk.graphql.GraphQL import suwayomi.tachidesk.graphql.GraphQL
@@ -47,7 +52,9 @@ import java.net.URLEncoder
import java.util.Locale import java.util.Locale
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture
import kotlin.concurrent.thread import kotlin.concurrent.thread
import kotlin.text.get
import kotlin.time.Duration.Companion.days import kotlin.time.Duration.Companion.days
import kotlin.time.Duration.Companion.seconds
object JavalinSetup { object JavalinSetup {
private val logger = KotlinLogging.logger {} private val logger = KotlinLogging.logger {}
@@ -58,10 +65,12 @@ object JavalinSetup {
fun javalinSetup() { fun javalinSetup() {
val app = val app =
Javalin.create { config -> Javalin.start { config ->
val templateEngine = TemplateEngine.createPrecompiled(ContentType.Html) val templateEngine = TemplateEngine.createPrecompiled(ContentType.Html)
config.fileRenderer(JavalinJte(templateEngine)) config.fileRenderer(JavalinJte(templateEngine))
config.jsonMapper(JavalinJackson3())
WebInterfaceManager.setup(config) WebInterfaceManager.setup(config)
// config.registerPlugin(OpenApiPlugin(getOpenApiOptions())) // config.registerPlugin(OpenApiPlugin(getOpenApiOptions()))
@@ -104,7 +113,8 @@ object JavalinSetup {
} }
} }
config.router.apiBuilder { config.routes.defineCore()
config.routes.apiBuilder {
path(ServerSubpath.maybeAddAsPrefix("api/")) { path(ServerSubpath.maybeAddAsPrefix("api/")) {
path("v1/") { path("v1/") {
GlobalAPI.defineEndpoints() GlobalAPI.defineEndpoints()
@@ -117,17 +127,37 @@ object JavalinSetup {
after { ctx -> after { ctx ->
// If not matched, the request was for an invalid endpoint // If not matched, the request was for an invalid endpoint
// Return a 404 instead of redirecting to the UI for usability // Return a 404 instead of redirecting to the UI for usability
if (ctx.endpointHandlerPath() == "*") { if (ctx.endpoints().lastHttpEndpoint()?.path == "*") {
throw NotFoundResponse() throw NotFoundResponse()
} }
} }
} }
} }
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(
thread(start = false) {
app.stop()
},
)
}
fun RoutesConfig.defineCore() {
val loginPath = ServerSubpath.maybeAddAsPrefix("/login.html") val loginPath = ServerSubpath.maybeAddAsPrefix("/login.html")
app.get(loginPath) { ctx -> get(loginPath) { ctx ->
val locale: Locale = LocalizationHelper.ctxToLocale(ctx) val locale: Locale = LocalizationHelper.ctxToLocale(ctx)
ctx.header("content-type", "text/html") ctx.header("content-type", "text/html")
val httpCacheSeconds = 1.days.inWholeSeconds val httpCacheSeconds = 1.days.inWholeSeconds
@@ -141,7 +171,7 @@ object JavalinSetup {
) )
} }
app.post(loginPath) { ctx -> post(loginPath) { ctx ->
val username = ctx.formParam("user") val username = ctx.formParam("user")
val password = ctx.formParam("pass") val password = ctx.formParam("pass")
val isValid = val isValid =
@@ -174,7 +204,7 @@ object JavalinSetup {
) )
} }
app.beforeMatched { ctx -> beforeMatched { ctx ->
val isWebManifest = val isWebManifest =
listOf("site.webmanifest", "manifest.json", "login.html").any { ctx.path().endsWith(it) } listOf("site.webmanifest", "manifest.json", "login.html").any { ctx.path().endsWith(it) }
val isPageIcon = val isPageIcon =
@@ -219,60 +249,43 @@ object JavalinSetup {
ctx.setAttribute(Attribute.TachideskBasic, credentialsValid()) ctx.setAttribute(Attribute.TachideskBasic, credentialsValid())
} }
app.events { event -> wsBefore {
event.serverStarted {
if (serverConfig.initialOpenInBrowserEnabled.value) {
Browser.openInBrowser()
}
}
}
app.wsBefore {
it.onConnect { ctx -> it.onConnect { ctx ->
ctx.setAttribute(Attribute.TachideskUser, getUserFromWsContext(ctx)) ctx.setAttribute(Attribute.TachideskUser, getUserFromWsContext(ctx))
} }
} }
// when JVM is prompted to shutdown, stop javalin gracefully exception(NullPointerException::class.java) { e, ctx ->
Runtime.getRuntime().addShutdownHook(
thread(start = false) {
app.stop()
},
)
app.exception(NullPointerException::class.java) { e, ctx ->
logger.error(e) { "NullPointerException while handling the request" } logger.error(e) { "NullPointerException while handling the request" }
ctx.status(404) ctx.status(404)
} }
app.exception(NoSuchElementException::class.java) { e, ctx -> exception(NoSuchElementException::class.java) { e, ctx ->
logger.error(e) { "NoSuchElementException while handling the request" } logger.error(e) { "NoSuchElementException while handling the request" }
ctx.status(404) ctx.status(404)
} }
app.exception(IOException::class.java) { e, ctx -> exception(IOException::class.java) { e, ctx ->
logger.error(e) { "IOException while handling the request" } logger.error(e) { "IOException while handling the request" }
ctx.status(500) ctx.status(500)
ctx.result(e.message ?: "Internal Server Error") 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" } logger.error(e) { "IllegalArgumentException while handling the request" }
ctx.status(400) ctx.status(400)
ctx.result(e.message ?: "Bad Request") 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" } logger.error(e) { "UnauthorizedException while handling the request" }
ctx.status(HttpStatus.UNAUTHORIZED) ctx.status(HttpStatus.UNAUTHORIZED)
ctx.result(e.message ?: "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" } logger.error(e) { "ForbiddenException while handling the request" }
ctx.status(HttpStatus.FORBIDDEN) ctx.status(HttpStatus.FORBIDDEN)
ctx.result(e.message ?: "Forbidden") ctx.result(e.message ?: "Forbidden")
} }
app.start()
} }
// private fun getOpenApiOptions(): OpenApiOptions { // private fun getOpenApiOptions(): OpenApiOptions {

View File

@@ -71,15 +71,14 @@ fun <T> getParam(
is Param.FormParam -> ctx.formParamAsClass(param.key, clazz) is Param.FormParam -> ctx.formParamAsClass(param.key, clazz)
is Param.PathParam -> ctx.pathParamAsClass(param.key, clazz) is Param.PathParam -> ctx.pathParamAsClass(param.key, clazz)
is Param.QueryParam -> ctx.queryParamAsClass(param.key, clazz) is Param.QueryParam -> ctx.queryParamAsClass(param.key, clazz)
else -> throw IllegalStateException("Invalid param")
}.let { }.let {
if (param.nullable) { if (param.nullable) {
it.allowNullable().get() ?: param.defaultValue it.getOrNull() ?: param.defaultValue
} else { } else {
if (param.defaultValue != null) { if (param.defaultValue != null) {
it.getOrDefault(param.defaultValue!!) it.getOrDefault(param.defaultValue!!)
} else { } else {
it.get() it.required().get()
} }
} }
} }

View File

@@ -17,6 +17,7 @@ import io.github.oshai.kotlinlogging.KLogger
import io.github.oshai.kotlinlogging.KotlinLogging import io.github.oshai.kotlinlogging.KotlinLogging
import io.github.reactivecircus.cache4k.Cache import io.github.reactivecircus.cache4k.Cache
import io.javalin.config.JavalinConfig import io.javalin.config.JavalinConfig
import io.javalin.http.staticfiles.AliasCheck
import io.javalin.http.staticfiles.Location import io.javalin.http.staticfiles.Location
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
@@ -26,6 +27,7 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.sample import kotlinx.coroutines.flow.sample
@@ -39,7 +41,6 @@ import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.jsonPrimitive
import net.lingala.zip4j.ZipFile import net.lingala.zip4j.ZipFile
import org.eclipse.jetty.server.handler.ContextHandler
import suwayomi.tachidesk.graphql.types.AboutWebUI import suwayomi.tachidesk.graphql.types.AboutWebUI
import suwayomi.tachidesk.graphql.types.UpdateState import suwayomi.tachidesk.graphql.types.UpdateState
import suwayomi.tachidesk.graphql.types.UpdateState.DOWNLOADING import suwayomi.tachidesk.graphql.types.UpdateState.DOWNLOADING
@@ -90,7 +91,7 @@ object WebInterfaceManager {
private val preferences = Injekt.get<Application>().getSharedPreferences("server_util", Context.MODE_PRIVATE) private val preferences = Injekt.get<Application>().getSharedPreferences("server_util", Context.MODE_PRIVATE)
private var currentUpdateTaskId: String = "" private var currentUpdateTaskId: String = ""
private var isSetupComplete = false val isSetupComplete = MutableStateFlow(false)
private val json: Json by injectLazy() private val json: Json by injectLazy()
private val network: NetworkHelper by injectLazy() private val network: NetworkHelper by injectLazy()
@@ -180,7 +181,7 @@ object WebInterfaceManager {
// Use canonical path to avoid Jetty alias issues // Use canonical path to avoid Jetty alias issues
staticFiles.directory = File(applicationDirs.webUIServe).canonicalPath staticFiles.directory = File(applicationDirs.webUIServe).canonicalPath
staticFiles.location = Location.EXTERNAL staticFiles.location = Location.EXTERNAL
staticFiles.aliasCheck = ContextHandler.ApproveAliases() staticFiles.aliasCheck = AliasCheck { _, _ -> true }
} }
serveWebUI = { serveWebUI = {
@@ -196,7 +197,7 @@ object WebInterfaceManager {
@OptIn(DelicateCoroutinesApi::class) @OptIn(DelicateCoroutinesApi::class)
GlobalScope.launchIO { GlobalScope.launchIO {
setupWebUI() setupWebUI()
isSetupComplete = true isSetupComplete.value = true
} }
} }
@@ -260,7 +261,7 @@ object WebInterfaceManager {
val lastAutomatedUpdate = preferences.getLong(LAST_WEBUI_UPDATE_CHECK_KEY, System.currentTimeMillis()) val lastAutomatedUpdate = preferences.getLong(LAST_WEBUI_UPDATE_CHECK_KEY, System.currentTimeMillis())
val task = { val task = {
if (isSetupComplete) { if (isSetupComplete.value) {
val log = val log =
KotlinLogging.logger( KotlinLogging.logger(
"${logger.name}::scheduleWebUIUpdateCheck(" + "${logger.name}::scheduleWebUIUpdateCheck(" +
@@ -304,11 +305,25 @@ object WebInterfaceManager {
return return
} }
val flavor = WebUIFlavor.current
val servedFlavor = getServedWebUIFlavor() val servedFlavor = getServedWebUIFlavor()
val log = 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)) { if (doesLocalWebUIExist(applicationDirs.webUIRoot)) {
val currentVersion = getLocalVersion() val currentVersion = getLocalVersion()