mirror of
https://github.com/Suwayomi/Suwayomi-Server.git
synced 2026-07-01 18:04:35 -05:00
Compare commits
45 Commits
socks-auth
...
v1.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fda4cd6783 | ||
|
|
ecd1604e25 | ||
|
|
0f061900af | ||
|
|
c47f5ea85e | ||
|
|
306eb0e3c7 | ||
|
|
e64025ded8 | ||
|
|
c1fe2da636 | ||
|
|
ff23f58a4f | ||
|
|
fc2f5ffdf9 | ||
|
|
6dd9ed7fb0 | ||
|
|
2f362abb91 | ||
|
|
96807a64cf | ||
|
|
7df5f1c4c4 | ||
|
|
cf1ede9cf7 | ||
|
|
729385588a | ||
|
|
668d5cf8f0 | ||
|
|
72b1b5b0f9 | ||
|
|
fbf726c174 | ||
|
|
c441eed847 | ||
|
|
e8e83ed49c | ||
|
|
cdc21b067c | ||
|
|
48e19f7914 | ||
|
|
89dd570b30 | ||
|
|
16474d4328 | ||
|
|
9db612bf03 | ||
|
|
7d92dbc5c0 | ||
|
|
a9efca8687 | ||
|
|
dbfea5d02b | ||
|
|
a6b05c4a27 | ||
|
|
6d539d3404 | ||
|
|
b2aff1efc9 | ||
|
|
8a20a1ef50 | ||
|
|
33cbfa9751 | ||
|
|
b95a8d44d4 | ||
|
|
54df9d634a | ||
|
|
4eb9a696ff | ||
|
|
7b4fb4682b | ||
|
|
da4275530d | ||
|
|
1c417e909a | ||
|
|
fc53d69f82 | ||
|
|
dda86cdb93 | ||
|
|
525a974e3a | ||
|
|
b18c155e22 | ||
|
|
07e011092a | ||
|
|
6803ac0611 |
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -23,7 +23,7 @@ Note that the issue will be automatically closed if you do not fill out the titl
|
||||
---
|
||||
|
||||
## Device information
|
||||
- Suwayomi-Server version: (Example: v1.0.0-r1438-win32)
|
||||
- Suwayomi-Server version: (Example: v1.1.0-r1532-win32)
|
||||
- Server Operating System: (Example: Ubuntu 20.04)
|
||||
- Server Desktop Environment: N/A or (Example: Gnome 40)
|
||||
- Server JVM version: bundled with win32 or (Example: Java 8 Update 281 or OpenJDK 8u281)
|
||||
|
||||
32
.github/workflows/publish.yml
vendored
32
.github/workflows/publish.yml
vendored
@@ -55,14 +55,14 @@ jobs:
|
||||
arguments: :server:downloadWebUI :server:shadowJar --stacktrace
|
||||
|
||||
- name: Upload Jar
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: jar
|
||||
path: master/server/build/*.jar
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload icons
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: icon
|
||||
path: master/server/src/main/resources/icon
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
run: tar -cvzf scripts.tar.gz -C master/ scripts/
|
||||
|
||||
- name: Upload scripts.tar.gz
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: scripts
|
||||
path: scripts.tar.gz
|
||||
@@ -96,19 +96,19 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download Jar
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: jar
|
||||
path: server/build
|
||||
|
||||
- name: Download icons
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: icon
|
||||
path: server/src/main/resources/icon
|
||||
|
||||
- name: Download scripts.tar.gz
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: scripts
|
||||
|
||||
@@ -119,7 +119,7 @@ jobs:
|
||||
scripts/bundler.sh -o upload/ ${{ matrix.os }}
|
||||
|
||||
- name: Upload ${{ matrix.os }} files
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.os }}
|
||||
path: upload/*
|
||||
@@ -130,35 +130,35 @@ jobs:
|
||||
needs: bundle
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: jar
|
||||
path: release
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: debian-all
|
||||
path: release
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: linux-assets
|
||||
path: release
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: linux-x64
|
||||
path: release
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: macOS-x64
|
||||
path: release
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: macOS-arm64
|
||||
path: release
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: windows-x64
|
||||
path: release
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: windows-x86
|
||||
path: release
|
||||
@@ -169,6 +169,6 @@ jobs:
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
token: ${{ secrets.WINGET_PUBLISH_PAT }}
|
||||
token: ${{ secrets.DEPLOY_RELEASE_TOKEN }}
|
||||
draft: true
|
||||
files: release/*
|
||||
|
||||
795
CHANGELOG.md
795
CHANGELOG.md
@@ -1,3 +1,798 @@
|
||||
# Server: v1.1.0 + WevUI: v1.1.0
|
||||
## TL;DR
|
||||
- Update Manga Info in browse
|
||||
- Full Tracking support
|
||||
- Import Tracking from backups
|
||||
- Improved support for library filters
|
||||
- Improved thumbnail handling
|
||||
- Many minor bugfixes
|
||||
- WebUI changes: https://github.com/Suwayomi/Suwayomi-WebUI/releases/tag/v1.1.0
|
||||
|
||||
## Suwayomi-Server Changelog
|
||||
- ([r1531](https://github.com/Suwayomi/Suwayomi-Server/commit/ecd1604e25a17a6ef68d568b5d81e69f6e9f7702)) Update metadata in source browse only if new data is not null ([#962](https://github.com/Suwayomi/Suwayomi-Server/pull/962) by @schroda)
|
||||
- ([r1530](https://github.com/Suwayomi/Suwayomi-Server/commit/0f061900afbd4036b4d081bcb3f39e4f60160ac3)) Fix browse source ([#961](https://github.com/Suwayomi/Suwayomi-Server/pull/961) by @Syer10)
|
||||
- ([r1529](https://github.com/Suwayomi/Suwayomi-Server/commit/c47f5ea85e75c7119d828a19eda392b2fb9faecd)) [skip ci] doc: add NixOS installation ([#959](https://github.com/Suwayomi/Suwayomi-Server/pull/959) by @RatCornu)
|
||||
- ([r1528](https://github.com/Suwayomi/Suwayomi-Server/commit/306eb0e3c774b5a8d0d2d4ade2d7091904fe6e58)) Update manga info when browsing if not in library ([#958](https://github.com/Suwayomi/Suwayomi-Server/pull/958) by @Syer10)
|
||||
- ([r1527](https://github.com/Suwayomi/Suwayomi-Server/commit/e64025ded814a15129999586133d84085e0c5779)) Correctly set name of logger ([#956](https://github.com/Suwayomi/Suwayomi-Server/pull/956) by @schroda)
|
||||
- ([r1526](https://github.com/Suwayomi/Suwayomi-Server/commit/c1fe2da636b675091ec0ac93162764883938ffc6)) Fix/failing thumbnail requests with http 410 ([#955](https://github.com/Suwayomi/Suwayomi-Server/pull/955) by @schroda)
|
||||
- ([r1525](https://github.com/Suwayomi/Suwayomi-Server/commit/ff23f58a4f4e8e0b4d459957f0e0701265e0c364)) Support partial mutation responses ([#954](https://github.com/Suwayomi/Suwayomi-Server/pull/954) by @schroda)
|
||||
- ([r1524](https://github.com/Suwayomi/Suwayomi-Server/commit/fc2f5ffdf9c1e8675f9b978031a49fb4ce3af601)) Fix/failing track progress update for logged out trackers ([#953](https://github.com/Suwayomi/Suwayomi-Server/pull/953) by @schroda)
|
||||
- ([r1523](https://github.com/Suwayomi/Suwayomi-Server/commit/6dd9ed7fb0816b2f163bec43d04c433099e7e529)) Fix/prevent importing unsupported trackers from backup II ([#945](https://github.com/Suwayomi/Suwayomi-Server/pull/945) by @schroda)
|
||||
- ([r1522](https://github.com/Suwayomi/Suwayomi-Server/commit/2f362abb91be875e943b1364eb86d70a4144dd6f)) Prevent importing unsupported tracker from backup ([#944](https://github.com/Suwayomi/Suwayomi-Server/pull/944) by @schroda)
|
||||
- ([r1521](https://github.com/Suwayomi/Suwayomi-Server/commit/96807a64cf1b13b6db655d46e90c42717170ce62)) [skip ci] Update README.md ([#941](https://github.com/Suwayomi/Suwayomi-Server/pull/941) by @FumoVite)
|
||||
- ([r1520](https://github.com/Suwayomi/Suwayomi-Server/commit/7df5f1c4c4408cfbbd56697ba10f018393df2b4a)) Feature/backup tracking ([#940](https://github.com/Suwayomi/Suwayomi-Server/pull/940) by @schroda)
|
||||
- ([r1519](https://github.com/Suwayomi/Suwayomi-Server/commit/cf1ede9cf70a2d72a7ff84b9ead24a394ceee2ce)) Update lastPageRead on chapter update ([#939](https://github.com/Suwayomi/Suwayomi-Server/pull/939) by @schroda)
|
||||
- ([r1518](https://github.com/Suwayomi/Suwayomi-Server/commit/729385588a3d8e06ec8be38865a12c47e88f6bcb)) Prevent greater last page read than page count ([#938](https://github.com/Suwayomi/Suwayomi-Server/pull/938) by @schroda)
|
||||
- ([r1517](https://github.com/Suwayomi/Suwayomi-Server/commit/668d5cf8f02e35cc53d1430a239ae67837c64f51)) Prevent IndexOutOfBoundsException when removing duplicated chapters ([#935](https://github.com/Suwayomi/Suwayomi-Server/pull/935) by @schroda)
|
||||
- ([r1516](https://github.com/Suwayomi/Suwayomi-Server/commit/72b1b5b0f9b86f82a0e203802d9a4b6339277c01)) Exit track progress update early in case new chapter is same as current local ([#937](https://github.com/Suwayomi/Suwayomi-Server/pull/937) by @schroda)
|
||||
- ([r1515](https://github.com/Suwayomi/Suwayomi-Server/commit/fbf726c17434212cdf94b39f52a25a0050d77287)) Use "AsyncExecutionStrategy" for mutations ([#932](https://github.com/Suwayomi/Suwayomi-Server/pull/932) by @schroda)
|
||||
- ([r1514](https://github.com/Suwayomi/Suwayomi-Server/commit/c441eed84773fdc295e6d004e4f4628453b54659)) Exclude duplicated chapters from auto download limit ([#923](https://github.com/Suwayomi/Suwayomi-Server/pull/923) by @schroda)
|
||||
- ([r1513](https://github.com/Suwayomi/Suwayomi-Server/commit/e8e83ed49caac2d25f29073d1bd3b5b385aa2d98)) Remove duplicated mangas from gql "mangas" query ([#924](https://github.com/Suwayomi/Suwayomi-Server/pull/924) by @schroda)
|
||||
- ([r1512](https://github.com/Suwayomi/Suwayomi-Server/commit/cdc21b067c1a341d68ea7a9c1ee565dc3959f552)) Fix/recognition of already downloaded chapters ([#922](https://github.com/Suwayomi/Suwayomi-Server/pull/922) by @schroda)
|
||||
- ([r1511](https://github.com/Suwayomi/Suwayomi-Server/commit/48e19f7914fee1ea1789b217d5df9b05acb49203)) Feature/auto download of new chapters improve handling of unhandable reuploads ([#921](https://github.com/Suwayomi/Suwayomi-Server/pull/921) by @schroda)
|
||||
- ([r1510](https://github.com/Suwayomi/Suwayomi-Server/commit/89dd570b3057bee34643858b4a42bfac7d88a82b)) Add mutation to fetch the latest track data from the tracker ([#920](https://github.com/Suwayomi/Suwayomi-Server/pull/920) by @schroda, @Syer10)
|
||||
- ([r1509](https://github.com/Suwayomi/Suwayomi-Server/commit/16474d4328651f1236722556b7f59628a0f9dbda)) Feature/tracking gql add option to delete remote binding on tracker ([#919](https://github.com/Suwayomi/Suwayomi-Server/pull/919) by @schroda, @Syer10)
|
||||
- ([r1508](https://github.com/Suwayomi/Suwayomi-Server/commit/9db612bf0317950d0291047b9ee64a0787e49bf2)) Move trigger for track progress update to client ([#918](https://github.com/Suwayomi/Suwayomi-Server/pull/918) by @schroda)
|
||||
- ([r1507](https://github.com/Suwayomi/Suwayomi-Server/commit/7d92dbc5c0a47176099eb310eaf17a4788ba2ce4)) Fix/tracking progress update in case local chapter is smaller than remote ([#917](https://github.com/Suwayomi/Suwayomi-Server/pull/917) by @schroda)
|
||||
- ([r1506](https://github.com/Suwayomi/Suwayomi-Server/commit/a9efca86870cec6d74f58535e2e007eb6c8831c2)) Add chapter bookmark count field to MangaType ([#912](https://github.com/Suwayomi/Suwayomi-Server/pull/912) by @schroda)
|
||||
- ([r1505](https://github.com/Suwayomi/Suwayomi-Server/commit/dbfea5d02b898884fdeb2be2959fe8a73a465704)) Update inLibraryAt timestamp when adding manga to library ([#911](https://github.com/Suwayomi/Suwayomi-Server/pull/911) by @schroda)
|
||||
- ([r1504](https://github.com/Suwayomi/Suwayomi-Server/commit/a6b05c4a2759d0d5f834a54cad6c8417fe49a0d2)) Feature/refresh outdated thumbnail url on fetch failure ([#910](https://github.com/Suwayomi/Suwayomi-Server/pull/910) by @schroda)
|
||||
- ([r1503](https://github.com/Suwayomi/Suwayomi-Server/commit/6d539d34040c4e95692b57ce4fedfbeaa73083d0)) Fix/update subscription clear data loader cache ([#908](https://github.com/Suwayomi/Suwayomi-Server/pull/908) by @schroda)
|
||||
- ([r1502](https://github.com/Suwayomi/Suwayomi-Server/commit/b2aff1efc9e6527e70ba519e5171096394e6ccf7)) Fix MAL after restarting the server ([#903](https://github.com/Suwayomi/Suwayomi-Server/pull/903) by @Syer10)
|
||||
- ([r1501](https://github.com/Suwayomi/Suwayomi-Server/commit/8a20a1ef5094efc05426ed420bbde40358fdf2dd)) Add first unread chapter field to MangaType ([#900](https://github.com/Suwayomi/Suwayomi-Server/pull/900) by @schroda)
|
||||
- ([r1500](https://github.com/Suwayomi/Suwayomi-Server/commit/33cbfa9751c3ef7a6babfcff9595782cbac5acae)) Fix/electron launch error not logged ([#895](https://github.com/Suwayomi/Suwayomi-Server/pull/895) by @schroda)
|
||||
- ([r1499](https://github.com/Suwayomi/Suwayomi-Server/commit/b95a8d44d4bb7c94a04e66b3d6cc0fc101f4880b)) Always fetch thumbnail of manga from local source ([#898](https://github.com/Suwayomi/Suwayomi-Server/pull/898) by @schroda)
|
||||
|
||||
## [Suwayomi-WebUI Changelog](https://github.com/Suwayomi/Suwayomi-WebUI/blob/master/CHANGELOG.md#v110-r1689)
|
||||
|
||||
# Server: v1.0.0 + WevUI: r1409
|
||||
## TL;DR
|
||||
- GraphQL API
|
||||
- Rename to Suwayomi
|
||||
- New Launcher for Suwayomi
|
||||
- Automatic WebUI Updates
|
||||
- Preserve download queue through server restarts
|
||||
- Improve compatability with Android extensions
|
||||
- Add support for ComicInfo creation and reading
|
||||
- Support changing settings with WebUI and other clients
|
||||
- Support more SOCKS proxy settings
|
||||
- Fix support for Oracle JRE
|
||||
- Performance improvements
|
||||
- Partial Tracking support
|
||||
- Support Custom Repos
|
||||
- [FlareSolverr(Cloudflare Bypass)](https://github.com/FlareSolverr/FlareSolverr) support
|
||||
- And many more fixes and features, this was a big release
|
||||
- WebUI changes:
|
||||
- Uhh, idk, find out yourself...
|
||||
|
||||
## Suwayomi-Server Changelog
|
||||
- ([r1494](https://github.com/Suwayomi/Suwayomi-Server/commit/1c417e909a3e10628e0febbe69a2dc1d8a1e98c0)) Support Comic Info creation on download ([#887](https://github.com/Suwayomi/Suwayomi-Server/pull/887) by @Syer10)
|
||||
- ([r1493](https://github.com/Suwayomi/Suwayomi-Server/commit/fc53d69f82058208b43c53d3940f60c3ac4cac87)) Add auth and version support to socks proxy ([#883](https://github.com/Suwayomi/Suwayomi-Server/pull/883) by @AriaMoradi)
|
||||
- ([r1492](https://github.com/Suwayomi/Suwayomi-Server/commit/dda86cdb930db172a53a63c326df94a8c9c8394f)) Seperate out migrations to allow run-once migrations ([#882](https://github.com/Suwayomi/Suwayomi-Server/pull/882) by @Syer10)
|
||||
- ([r1491](https://github.com/Suwayomi/Suwayomi-Server/commit/525a974e3aa9e789c80e475d8684a258e5b44bcd)) Start Server after routes are defined ([#881](https://github.com/Suwayomi/Suwayomi-Server/pull/881) by @Syer10)
|
||||
- ([r1490](https://github.com/Suwayomi/Suwayomi-Server/commit/b18c155e22fb77da9c74d75d0744c7cb7017fdd6)) Fix Downloader Memory Leak ([#880](https://github.com/Suwayomi/Suwayomi-Server/pull/880) by @Syer10)
|
||||
- ([r1489](https://github.com/Suwayomi/Suwayomi-Server/commit/07e011092a25e31cbe170e65659d75c7a0befc8a)) Support Token Expiry properly ([#878](https://github.com/Suwayomi/Suwayomi-Server/pull/878) by @Syer10)
|
||||
- ([r1488](https://github.com/Suwayomi/Suwayomi-Server/commit/6803ac0611b674b4edea1c1fc76627b324710ead)) move qtui to inactive list as it hasen't had commits in 2 years (by @AriaMoradi)
|
||||
- ([r1487](https://github.com/Suwayomi/Suwayomi-Server/commit/af0dde5ae8b3021eb2c2b260f957afc1c66a709d)) Add Source Meta ([#875](https://github.com/Suwayomi/Suwayomi-Server/pull/875) by @Syer10)
|
||||
- ([r1486](https://github.com/Suwayomi/Suwayomi-Server/commit/ea6edaecc4caa8e930df14078ed9abc1d90f8ece)) Fix local source being accidentally removed ([#874](https://github.com/Suwayomi/Suwayomi-Server/pull/874) by @Syer10)
|
||||
- ([r1485](https://github.com/Suwayomi/Suwayomi-Server/commit/eb2054bd5e340cfe4d9647fe2735ec05073c4a31)) Add VUI as a webUI flavor ([#873](https://github.com/Suwayomi/Suwayomi-Server/pull/873) by @schroda)
|
||||
- ([r1484](https://github.com/Suwayomi/Suwayomi-Server/commit/b277b3e3af502dd88e49234cbb9df61465abd9c9)) Add thumbnail fetch timestamp to the gql manga type ([#872](https://github.com/Suwayomi/Suwayomi-Server/pull/872) by @schroda)
|
||||
- ([r1483](https://github.com/Suwayomi/Suwayomi-Server/commit/9dc3a4e6ee6d53ee522d525d2a34072f752c59f2)) Use correct name for scores data loader ([#870](https://github.com/Suwayomi/Suwayomi-Server/pull/870) by @schroda)
|
||||
- ([r1482](https://github.com/Suwayomi/Suwayomi-Server/commit/6fbd2f10799206a4a316f4fda6012b33a330e738)) Feature/remove download ahead logic ([#867](https://github.com/Suwayomi/Suwayomi-Server/pull/867) by @schroda, @Syer10)
|
||||
- ([r1481](https://github.com/Suwayomi/Suwayomi-Server/commit/9edbc7f1d7b810ae9ac223a90686208b56a9693f)) Feature/support different webui flavors ([#863](https://github.com/Suwayomi/Suwayomi-Server/pull/863) by @schroda)
|
||||
- ([r1480](https://github.com/Suwayomi/Suwayomi-Server/commit/8aa75be0d321ca84044306589a813a18394fc008)) Cleanup gql subscription session state correctly ([#859](https://github.com/Suwayomi/Suwayomi-Server/pull/859) by @schroda)
|
||||
- ([r1479](https://github.com/Suwayomi/Suwayomi-Server/commit/dc124fb15c543a0a59e51819b05f3bc1b2d89d58)) Make flaresolverr session options configurable ([#854](https://github.com/Suwayomi/Suwayomi-Server/pull/854) by @chancez)
|
||||
- ([r1478](https://github.com/Suwayomi/Suwayomi-Server/commit/9109d1ca3e67695a6e3ed6d37a9ac322276a51c8)) Use a session with flaresolverr ([#853](https://github.com/Suwayomi/Suwayomi-Server/pull/853) by @chancez)
|
||||
- ([r1477](https://github.com/Suwayomi/Suwayomi-Server/commit/02296f1d1c14cebda3d4d0c3f0f58897f32d8a42)) Change flaresolverr settings to be non optional ([#852](https://github.com/Suwayomi/Suwayomi-Server/pull/852) by @schroda)
|
||||
- ([r1476](https://github.com/Suwayomi/Suwayomi-Server/commit/63e1082b97787e4406332e54b00a11fb2c74e381)) Minor fixes for FlareSolverr ([#851](https://github.com/Suwayomi/Suwayomi-Server/pull/851) by @Syer10)
|
||||
- ([r1475](https://github.com/Suwayomi/Suwayomi-Server/commit/285f228660b897df373612a5edf9ccbaea5934d5)) Gracefully shutdown server in case webUI can't be setup ([#850](https://github.com/Suwayomi/Suwayomi-Server/pull/850) by @schroda)
|
||||
- ([r1474](https://github.com/Suwayomi/Suwayomi-Server/commit/c18cf069b1ce6733c2ccdc4dd3f7c102b46b117b)) Prevent invalid webUI from stopping the server ([#849](https://github.com/Suwayomi/Suwayomi-Server/pull/849) by @schroda)
|
||||
- ([r1473](https://github.com/Suwayomi/Suwayomi-Server/commit/fc64f4758913239100e86f23f2ecb5dbfb797b80)) Fix/excessive logging ([#848](https://github.com/Suwayomi/Suwayomi-Server/pull/848) by @schroda)
|
||||
- ([r1472](https://github.com/Suwayomi/Suwayomi-Server/commit/562b940d9161660f30f0d1bf4f6594177f040f79)) Remove dot before cookie ([#845](https://github.com/Suwayomi/Suwayomi-Server/pull/845) by @Syer10)
|
||||
- ([r1471](https://github.com/Suwayomi/Suwayomi-Server/commit/d658e07583f3aed2bac0afded7c2b302e8c6411b)) Implement FlareSolverr ([#844](https://github.com/Suwayomi/Suwayomi-Server/pull/844) by @Syer10)
|
||||
- ([r1470](https://github.com/Suwayomi/Suwayomi-Server/commit/9121a6341c6f478df79391d0ae718fbe1a12c650)) Fix Tracker Status and Scores ([#843](https://github.com/Suwayomi/Suwayomi-Server/pull/843) by @Syer10)
|
||||
- ([r1469](https://github.com/Suwayomi/Suwayomi-Server/commit/4bec027f113eac78b0686834f0648c2af364fd93)) Change Track.bind to use trackerId + remoteId ([#842](https://github.com/Suwayomi/Suwayomi-Server/pull/842) by @Syer10)
|
||||
- ([r1468](https://github.com/Suwayomi/Suwayomi-Server/commit/b9053e3057d86510e0a3aff6ac3101e8dc703006)) Fix graphql tracking ([#840](https://github.com/Suwayomi/Suwayomi-Server/pull/840) by @Syer10)
|
||||
- ([r1467](https://github.com/Suwayomi/Suwayomi-Server/commit/062113847800ac2b6f92172e2b20c24487b3543c)) Improve Tracker Icons Implementation ([#836](https://github.com/Suwayomi/Suwayomi-Server/pull/836) by @Syer10)
|
||||
- ([r1466](https://github.com/Suwayomi/Suwayomi-Server/commit/ce42e89e25c520ac8ddf35972f04ffe8f9e168f5)) Add MangaUpdates ([#834](https://github.com/Suwayomi/Suwayomi-Server/pull/834) by @Syer10)
|
||||
- ([r1465](https://github.com/Suwayomi/Suwayomi-Server/commit/46e1e4c043717fe9b299a79b00a8c692f9aa27f1)) Table for Track Searches ([#833](https://github.com/Suwayomi/Suwayomi-Server/pull/833) by @Syer10)
|
||||
- ([r1464](https://github.com/Suwayomi/Suwayomi-Server/commit/621468a183305ec4febf831c512f78ed8f5876ec)) Apply natural sort to local manga pages in Directory format ([#826](https://github.com/Suwayomi/Suwayomi-Server/pull/826) by @Mercenar)
|
||||
- ([r1463](https://github.com/Suwayomi/Suwayomi-Server/commit/d8876cf96a9fb5fb4c5ea67e2722a85a58009bf6)) Add mutex to "updateExtensionDatabase" ([#829](https://github.com/Suwayomi/Suwayomi-Server/pull/829) by @schroda)
|
||||
- ([r1462](https://github.com/Suwayomi/Suwayomi-Server/commit/57d5bc6480dd0aa62b8a315785f1dec2b02257e5)) Add support for configuring which categories are downloaded automatically ([#832](https://github.com/Suwayomi/Suwayomi-Server/pull/832) by @chancez)
|
||||
- ([r1461](https://github.com/Suwayomi/Suwayomi-Server/commit/f224918f339980cf97dc26d2490860eadff39555)) Create bin folder ([#822](https://github.com/Suwayomi/Suwayomi-Server/pull/822) by @Syer10)
|
||||
- ([r1460](https://github.com/Suwayomi/Suwayomi-Server/commit/7b290dc465c7c85f09203bfed75f74d54f123e0b)) Update User Agent ([#821](https://github.com/Suwayomi/Suwayomi-Server/pull/821) by @Syer10)
|
||||
- ([r1459](https://github.com/Suwayomi/Suwayomi-Server/commit/b1412dda34214eb28b6802eed9a59b2da03ded8c)) Update Java 8 ([#820](https://github.com/Suwayomi/Suwayomi-Server/pull/820) by @Syer10)
|
||||
- ([r1458](https://github.com/Suwayomi/Suwayomi-Server/commit/28e4ac8dcb5b3b5f07390f6d27def9d6a3e8bb2e)) Remove Playwright ([#643](https://github.com/Suwayomi/Suwayomi-Server/pull/643) by @Syer10)
|
||||
- ([r1457](https://github.com/Suwayomi/Suwayomi-Server/commit/79eeb6d703e264f537c81b2dc8f327b1ba6a2b8e)) [skip ci] add VUI to README.md ([#819](https://github.com/Suwayomi/Suwayomi-Server/pull/819) by @Robonau)
|
||||
- ([r1456](https://github.com/Suwayomi/Suwayomi-Server/commit/0d0e735d0e7510b72ecdbd44a92db2949f15b17b)) Fix brotli ([#818](https://github.com/Suwayomi/Suwayomi-Server/pull/818) by @Syer10)
|
||||
- ([r1455](https://github.com/Suwayomi/Suwayomi-Server/commit/d994502d06f3754297c72030f5feabe776f3da99)) Update Electron ([#817](https://github.com/Suwayomi/Suwayomi-Server/pull/817) by @Syer10)
|
||||
- ([r1454](https://github.com/Suwayomi/Suwayomi-Server/commit/dfbd7a65aeafb5ec782388e84f43377f443833ef)) [skip ci] Correct wrong tracker oauth example url ([#814](https://github.com/Suwayomi/Suwayomi-Server/pull/814) by @schroda)
|
||||
- ([r1453](https://github.com/Suwayomi/Suwayomi-Server/commit/f99f94c8d7e87e90969d518082a43c699be856f7)) Enable tracking ([#813](https://github.com/Suwayomi/Suwayomi-Server/pull/813) by @schroda)
|
||||
- ([r1452](https://github.com/Suwayomi/Suwayomi-Server/commit/41c643496a5240c235b07263cd8bcdd63a99dd4b)) Add more chapter fields to MangaType ([#812](https://github.com/Suwayomi/Suwayomi-Server/pull/812) by @schroda)
|
||||
- ([r1451](https://github.com/Suwayomi/Suwayomi-Server/commit/e5476f8a01dc831a7cefba208b9a596b55c41301)) Extension repo fixes and improvements ([#811](https://github.com/Suwayomi/Suwayomi-Server/pull/811) by @Syer10)
|
||||
- ([r1450](https://github.com/Suwayomi/Suwayomi-Server/commit/188fb188cebe52462831503263ed38c8a6944609)) Set Mac Launcher Executable ([#810](https://github.com/Suwayomi/Suwayomi-Server/pull/810) by @Syer10)
|
||||
- ([r1449](https://github.com/Suwayomi/Suwayomi-Server/commit/c852592b340a7d4615bae904430de52ae1c8e37b)) Prevent adding duplicated extensions to the db table ([#808](https://github.com/Suwayomi/Suwayomi-Server/pull/808) by @schroda)
|
||||
- ([r1448](https://github.com/Suwayomi/Suwayomi-Server/commit/3a1e0c5a639fbe559e3407c331fbe7135cb3a59e)) Remove extension obsolete flag when updating db after extension list fetch ([#807](https://github.com/Suwayomi/Suwayomi-Server/pull/807) by @schroda)
|
||||
- ([r1447](https://github.com/Suwayomi/Suwayomi-Server/commit/6376972130e6babb5b65e62634435ce07edbc4b8)) Remove caching of extensions for gql mutation ([#806](https://github.com/Suwayomi/Suwayomi-Server/pull/806) by @schroda)
|
||||
- ([r1446](https://github.com/Suwayomi/Suwayomi-Server/commit/c70c860a82f8ccc681e5e13f5f7edc6e6f9c669c)) Create Client IDs ([#804](https://github.com/Suwayomi/Suwayomi-Server/pull/804) by @Syer10)
|
||||
- ([r1445](https://github.com/Suwayomi/Suwayomi-Server/commit/5a178ada742c26e269736a2afc6744c444cde947)) add trackers support ([#720](https://github.com/Suwayomi/Suwayomi-Server/pull/720) by @tachimanga, @Syer10)
|
||||
- ([r1444](https://github.com/Suwayomi/Suwayomi-Server/commit/230427e75851148b75ba9eca91019eb24242640b)) Support Custom Repos ([#803](https://github.com/Suwayomi/Suwayomi-Server/pull/803) by @Syer10)
|
||||
- ([r1443](https://github.com/Suwayomi/Suwayomi-Server/commit/abf1af41a39152b2aa5b69a794f855be8bec8937)) Update bundled webui ([#802](https://github.com/Suwayomi/Suwayomi-Server/pull/802) by @Syer10)
|
||||
- ([r1442](https://github.com/Suwayomi/Suwayomi-Server/commit/61e2548bb7842977b5dc20e7421723ad811ecbca)) Deb fixes ([#801](https://github.com/Suwayomi/Suwayomi-Server/pull/801) by @Syer10)
|
||||
- ([r1441](https://github.com/Suwayomi/Suwayomi-Server/commit/f739c542928b04147885f72f0c95305464852653)) Rename the project ([#795](https://github.com/Suwayomi/Suwayomi-Server/pull/795) by @Syer10)
|
||||
- ([r1440](https://github.com/Suwayomi/Suwayomi-Server/commit/3ed84de320c59b79d3068b9d9c577aa022a6212c)) [skip ci] Add API info ([#798](https://github.com/Suwayomi/Suwayomi-Server/pull/798) by @brianmakesthings)
|
||||
- ([r1439](https://github.com/Suwayomi/Suwayomi-Server/commit/621b4c09467dba6874eb0b85dedaa19698d19324)) Correctly calculate the first chapter to download index ([#796](https://github.com/Suwayomi/Suwayomi-Server/pull/796) by @schroda)
|
||||
- ([r1438](https://github.com/Suwayomi/Suwayomi-Server/commit/11be9691018b6c353194c12a7298f0414620e974)) Fix/download subscription returning outdated data for finished downloads ([#794](https://github.com/Suwayomi/Suwayomi-Server/pull/794) by @schroda)
|
||||
- ([r1437](https://github.com/Suwayomi/Suwayomi-Server/commit/ea958cd8f7ccb147c5d21c5e8815ab32c5eea71d)) Correctly emit the current status immediately ([#792](https://github.com/Suwayomi/Suwayomi-Server/pull/792) by @schroda)
|
||||
- ([r1436](https://github.com/Suwayomi/Suwayomi-Server/commit/56048dcdb05b99083e2ba7ff9fef8025edfa9bf6)) Update Github Actions ([#788](https://github.com/Suwayomi/Suwayomi-Server/pull/788) by @Syer10)
|
||||
- ([r1435](https://github.com/Suwayomi/Suwayomi-Server/commit/fb545947ecbada33261bc84eac3b9b98ca0ef49a)) Feature/gql improve webui update status ([#783](https://github.com/Suwayomi/Suwayomi-Server/pull/783) by @schroda)
|
||||
- ([r1434](https://github.com/Suwayomi/Suwayomi-Server/commit/df57070b7026fcf4cf6a219eee8653b8c28ea5be)) Make sure to always send finished chapter downloads with the download status ([#782](https://github.com/Suwayomi/Suwayomi-Server/pull/782) by @schroda)
|
||||
- ([r1433](https://github.com/Suwayomi/Suwayomi-Server/commit/9b27d7ee23df1b8250e9fd0cde91056f9ed798a4)) Improve Http Client Configuration ([#786](https://github.com/Suwayomi/Suwayomi-Server/pull/786) by @Syer10)
|
||||
- ([r1432](https://github.com/Suwayomi/Suwayomi-Server/commit/a2d3fa6e1d7a7918891c561a56044923f4ed87de)) Use new Tachiyomi backup filename format ([#787](https://github.com/Suwayomi/Suwayomi-Server/pull/787) by @Syer10)
|
||||
- ([r1431](https://github.com/Suwayomi/Suwayomi-Server/commit/94b670eb8100693462e1d5c77f459bb764747059)) Fix/gql about webui query same response type as webui update info ([#781](https://github.com/Suwayomi/Suwayomi-Server/pull/781) by @schroda)
|
||||
- ([r1430](https://github.com/Suwayomi/Suwayomi-Server/commit/d65ed6ced7732a68e54d67be02ef7b69e33271de)) Fix Bundler Script ([#780](https://github.com/Suwayomi/Suwayomi-Server/pull/780) by @Syer10)
|
||||
- ([r1429](https://github.com/Suwayomi/Suwayomi-Server/commit/db50eb75265c81d076febec093a29c65b205a9d3)) Disable download ahead limit by default ([#778](https://github.com/Suwayomi/Suwayomi-Server/pull/778) by @schroda)
|
||||
- ([r1428](https://github.com/Suwayomi/Suwayomi-Server/commit/d21b2018cb3ab3a28a4c29ffa3b845ddc5f15e5d)) Add mutation to clear the cached images ([#775](https://github.com/Suwayomi/Suwayomi-Server/pull/775) by @schroda)
|
||||
- ([r1427](https://github.com/Suwayomi/Suwayomi-Server/commit/9110c07ed9b9ee51c10075b0affcc337e4b2a386)) Correctly select enum webui flavor via "ui name" ([#772](https://github.com/Suwayomi/Suwayomi-Server/pull/772) by @schroda)
|
||||
- ([r1426](https://github.com/Suwayomi/Suwayomi-Server/commit/2298e7127959683c9d91d1ad4af7c5ca65bb04f7)) Feature/gql about webui query ([#773](https://github.com/Suwayomi/Suwayomi-Server/pull/773) by @schroda)
|
||||
- ([r1425](https://github.com/Suwayomi/Suwayomi-Server/commit/909bd76e08988ad881bd2b31a3eee06c4bdd683d)) Cleanup parent folders when deleting downloaded chapters ([#776](https://github.com/Suwayomi/Suwayomi-Server/pull/776) by @schroda)
|
||||
- ([r1424](https://github.com/Suwayomi/Suwayomi-Server/commit/50cd0c4e108256d88760a073e45f095ca32a0303)) Fix Queries Containing % ([#766](https://github.com/Suwayomi/Suwayomi-Server/pull/766) by @Syer10)
|
||||
- ([r1423](https://github.com/Suwayomi/Suwayomi-Server/commit/460fc235e3e3a494e06ad753caff7def67834df2)) Add Cache-Control to Extension Icons ([#765](https://github.com/Suwayomi/Suwayomi-Server/pull/765) by @Syer10)
|
||||
- ([r1422](https://github.com/Suwayomi/Suwayomi-Server/commit/c38a3d9eba983fd98f27291e0ac084d1711c00d3)) Update served webUI after update ([#764](https://github.com/Suwayomi/Suwayomi-Server/pull/764) by @schroda)
|
||||
- ([r1421](https://github.com/Suwayomi/Suwayomi-Server/commit/b303291e94cc6cc5e0b868439568eb44d01f56b8)) Always get the latest commit count for jar name ([#763](https://github.com/Suwayomi/Suwayomi-Server/pull/763) by @schroda)
|
||||
- ([r1420](https://github.com/Suwayomi/Suwayomi-Server/commit/7993da038e4d5723e33a182e68b72888b5ec4728)) Fix/initial auto backup never triggered in case server was not running ([#762](https://github.com/Suwayomi/Suwayomi-Server/pull/762) by @schroda)
|
||||
- ([r1419](https://github.com/Suwayomi/Suwayomi-Server/commit/05bf4f552542053689ea491951c0afb1686e40b5)) Fix/auto download new chapters initial fetch ([#761](https://github.com/Suwayomi/Suwayomi-Server/pull/761) by @schroda)
|
||||
- ([r1418](https://github.com/Suwayomi/Suwayomi-Server/commit/db36896f9253497b9a5ac009a35621e3b8da839a)) Fix chapter duplicates if its a different url but same chapter list size ([#759](https://github.com/Suwayomi/Suwayomi-Server/pull/759) by @Syer10)
|
||||
- ([r1417](https://github.com/Suwayomi/Suwayomi-Server/commit/16dbad8bdf3768e911d82591e3b46949ac0d99c1)) Fix path to Preference file if it contains a invalid path character ([#750](https://github.com/Suwayomi/Suwayomi-Server/pull/750) by @Syer10)
|
||||
- ([r1416](https://github.com/Suwayomi/Suwayomi-Server/commit/8a4c717d248f9265251e5083abeb5a0f4616c801)) Check for all downloaded pages during a chapter download ([#752](https://github.com/Suwayomi/Suwayomi-Server/pull/752) by @schroda)
|
||||
- ([r1415](https://github.com/Suwayomi/Suwayomi-Server/commit/442a290966677bd45273d48b7b5694bbc3451e17)) Improve Extensions List ([#753](https://github.com/Suwayomi/Suwayomi-Server/pull/753) by @Syer10)
|
||||
- ([r1414](https://github.com/Suwayomi/Suwayomi-Server/commit/0785f4d0f5e440301573f50cd7fc248fe571c1d2)) Chapter Fetch Improvements ([#754](https://github.com/Suwayomi/Suwayomi-Server/pull/754) by @Syer10)
|
||||
- ([r1413](https://github.com/Suwayomi/Suwayomi-Server/commit/21e325af9c317eaf1f43b0300bd440550d83c330)) Correctly handle download of new chapters of not started entries ([#755](https://github.com/Suwayomi/Suwayomi-Server/pull/755) by @schroda)
|
||||
- ([r1412](https://github.com/Suwayomi/Suwayomi-Server/commit/3e9d29ea7f1f99127244be02485ea00d95b8b4a3)) Remove username and password from config log ([#756](https://github.com/Suwayomi/Suwayomi-Server/pull/756) by @schroda)
|
||||
- ([r1411](https://github.com/Suwayomi/Suwayomi-Server/commit/4324373e6173223746c27b8ef8670c12cf485916)) Fix/chapter list fetch updating and inserting chapters into database ([#749](https://github.com/Suwayomi/Suwayomi-Server/pull/749) by @schroda)
|
||||
- ([r1410](https://github.com/Suwayomi/Suwayomi-Server/commit/673053d29151c473f64fd0626a3dbe72ed5a94e5)) Migrate preferences only if necessary ([#748](https://github.com/Suwayomi/Suwayomi-Server/pull/748) by @schroda)
|
||||
- ([r1409](https://github.com/Suwayomi/Suwayomi-Server/commit/5b3975f8861f3e130ee1eeb7c227fab8dc0cbd93)) Only batch update in case list is not empty ([#747](https://github.com/Suwayomi/Suwayomi-Server/pull/747) by @schroda)
|
||||
- ([r1408](https://github.com/Suwayomi/Suwayomi-Server/commit/7ed8f4385957fe8aea52995a4fdde60b3ae95c90)) Fix/backup import failure not resetting status ([#746](https://github.com/Suwayomi/Suwayomi-Server/pull/746) by @schroda)
|
||||
- ([r1407](https://github.com/Suwayomi/Suwayomi-Server/commit/dcbb1c0dd1130386f4d37b634aa175e749904cc7)) Handle backups with categories having default category name ([#745](https://github.com/Suwayomi/Suwayomi-Server/pull/745) by @schroda)
|
||||
- ([r1406](https://github.com/Suwayomi/Suwayomi-Server/commit/5d4d417f3e8113a31216e779aacbbb549d867ddc)) Extract downloaded webUI zip in temp folder for validation ([#744](https://github.com/Suwayomi/Suwayomi-Server/pull/744) by @schroda)
|
||||
- ([r1405](https://github.com/Suwayomi/Suwayomi-Server/commit/5943c6a2c63ca897c8c8d832d695b8b6a0f0da38)) Feature/improve browsing source performance ([#743](https://github.com/Suwayomi/Suwayomi-Server/pull/743) by @schroda)
|
||||
- ([r1404](https://github.com/Suwayomi/Suwayomi-Server/commit/6d33d726630c0ef69a0b85006b9e9a394c119026)) #733: Improve perfs on getChapterList with onlineFetch (Less databases calls) ([#737](https://github.com/Suwayomi/Suwayomi-Server/pull/737) by @alexandrejournet)
|
||||
- ([r1403](https://github.com/Suwayomi/Suwayomi-Server/commit/9d2b098837cab10b3ff28bc02b3f00d30064656c)) Fix/updater update stuck in running status after failure ([#731](https://github.com/Suwayomi/Suwayomi-Server/pull/731) by @schroda)
|
||||
- ([r1402](https://github.com/Suwayomi/Suwayomi-Server/commit/17bc2d23318b6b92da23feaaa2b50b7badd9b828)) Fetch mangas during the update ([#729](https://github.com/Suwayomi/Suwayomi-Server/pull/729) by @schroda)
|
||||
- ([r1401](https://github.com/Suwayomi/Suwayomi-Server/commit/1c192b8db6890c3cc1c9aa0a598f9f6edd4c6677)) Fix Updater ([#742](https://github.com/Suwayomi/Suwayomi-Server/pull/742) by @Syer10)
|
||||
- ([r1400](https://github.com/Suwayomi/Suwayomi-Server/commit/76595233fc028a24156a3421baa67c85c90aa486)) Prevent mangas from being added to the default category ([#741](https://github.com/Suwayomi/Suwayomi-Server/pull/741) by @schroda)
|
||||
- ([r1399](https://github.com/Suwayomi/Suwayomi-Server/commit/6531b80998a1166c7fd821d894823a739617d68a)) Delete outdated thumbnails when inserting mangas into database ([#739](https://github.com/Suwayomi/Suwayomi-Server/pull/739) by @schroda)
|
||||
- ([r1398](https://github.com/Suwayomi/Suwayomi-Server/commit/05707e29d7fe102025476211ad6af9842fae0842)) Add missing settings to gql ([#738](https://github.com/Suwayomi/Suwayomi-Server/pull/738) by @schroda)
|
||||
- ([r1397](https://github.com/Suwayomi/Suwayomi-Server/commit/616ed4637d69dd1f329827a1df790240e62e0fff)) Handle disabled download ahead limit for new chapters auto download ([#734](https://github.com/Suwayomi/Suwayomi-Server/pull/734) by @schroda)
|
||||
- ([r1396](https://github.com/Suwayomi/Suwayomi-Server/commit/912c340a01a05adb41b88ea8907b4085b290ca96)) Fix update subscription returning stale data ([#727](https://github.com/Suwayomi/Suwayomi-Server/pull/727) by @schroda)
|
||||
- ([r1395](https://github.com/Suwayomi/Suwayomi-Server/commit/583a2f0fad7e842b11ac738b7165b3e3f892a0f0)) Migrate to XML Settings from Preferences ([#722](https://github.com/Suwayomi/Suwayomi-Server/pull/722) by @Syer10)
|
||||
- ([r1394](https://github.com/Suwayomi/Suwayomi-Server/commit/60015bc041c6bdac4b214c3b23d297e29c7b174a)) Return source for preference mutation ([#728](https://github.com/Suwayomi/Suwayomi-Server/pull/728) by @schroda)
|
||||
- ([r1393](https://github.com/Suwayomi/Suwayomi-Server/commit/029f445d0a7b5a58de8c5c88b4aec71106fe9821)) Revert Dex2Jar ([#721](https://github.com/Suwayomi/Suwayomi-Server/pull/721) by @Syer10)
|
||||
- ([r1392](https://github.com/Suwayomi/Suwayomi-Server/commit/150416b578173c0e046c403a9ca8fb1f4e3f797b)) Fix/default log level ([#719](https://github.com/Suwayomi/Suwayomi-Server/pull/719) by @schroda)
|
||||
- ([r1391](https://github.com/Suwayomi/Suwayomi-Server/commit/6684576de1c872882250bfc0bddd32247e0702cd)) Fix more missing functions ([#718](https://github.com/Suwayomi/Suwayomi-Server/pull/718) by @schroda)
|
||||
- ([r1390](https://github.com/Suwayomi/Suwayomi-Server/commit/289acc9296ffa0189e35a525a3f7be52205ed912)) Fix Kavita ([#716](https://github.com/Suwayomi/Suwayomi-Server/pull/716) by @Syer10)
|
||||
- ([r1389](https://github.com/Suwayomi/Suwayomi-Server/commit/2cf9a407e80863fc12a4202490b19142264b7c2f)) Fix MangaDex and Other Sources ([#715](https://github.com/Suwayomi/Suwayomi-Server/pull/715) by @Syer10)
|
||||
- ([r1388](https://github.com/Suwayomi/Suwayomi-Server/commit/682c36464716e42b8f3e54c2cd008776132cc03f)) Address Build Warnings and Cleanup ([#707](https://github.com/Suwayomi/Suwayomi-Server/pull/707) by @Syer10)
|
||||
- ([r1387](https://github.com/Suwayomi/Suwayomi-Server/commit/e70730e9a8d141557af115481617c286578e3933)) Query for mangas in specific categories ([#712](https://github.com/Suwayomi/Suwayomi-Server/pull/712) by @schroda)
|
||||
- ([r1386](https://github.com/Suwayomi/Suwayomi-Server/commit/0ba6c88d69fa8a80cfb077baf56ef7bcf5d6c944)) Fix/graphql mangas query genre based filtering ([#713](https://github.com/Suwayomi/Suwayomi-Server/pull/713) by @schroda)
|
||||
- ([r1385](https://github.com/Suwayomi/Suwayomi-Server/commit/c56bdea1e2ccbdbaf8ccf40694a377a426a1e2dd)) Do not log ping messages ([#709](https://github.com/Suwayomi/Suwayomi-Server/pull/709) by @schroda)
|
||||
- ([r1384](https://github.com/Suwayomi/Suwayomi-Server/commit/a449a01a24db2a3160bddeb6edc051c4f6e2a615)) Fix/web interface manager get latest compatible version ([#706](https://github.com/Suwayomi/Suwayomi-Server/pull/706) by @schroda)
|
||||
- ([r1383](https://github.com/Suwayomi/Suwayomi-Server/commit/849acfca3d44c3d035e232dd4ccc55818c789198)) Switch to a new Ktlint Formatter ([#705](https://github.com/Suwayomi/Suwayomi-Server/pull/705) by @Syer10)
|
||||
- ([r1382](https://github.com/Suwayomi/Suwayomi-Server/commit/3cd3cb01861f609b323910713b9e686d2a27a4f4)) Fix/graphql subscriptions logging ([#704](https://github.com/Suwayomi/Suwayomi-Server/pull/704) by @schroda)
|
||||
- ([r1381](https://github.com/Suwayomi/Suwayomi-Server/commit/feead100f2ef191ea9f44d4f22a5e99473132487)) Update dependencies ([#701](https://github.com/Suwayomi/Suwayomi-Server/pull/701) by @Syer10)
|
||||
- ([r1380](https://github.com/Suwayomi/Suwayomi-Server/commit/a9987e6ab0322b2956f74fb04de980a1293a80d2)) Support more image types ([#700](https://github.com/Suwayomi/Suwayomi-Server/pull/700) by @Syer10)
|
||||
- ([r1379](https://github.com/Suwayomi/Suwayomi-Server/commit/ef0a6f54b845779e6d4650b10722424fa6953cef)) Feature/auto download ahead ([#681](https://github.com/Suwayomi/Suwayomi-Server/pull/681) by @schroda)
|
||||
- ([r1378](https://github.com/Suwayomi/Suwayomi-Server/commit/c8865ad185a7b76cb20793d5f8db395657cd7dde)) Implement Non-Final 1.5 Extensions API ([#699](https://github.com/Suwayomi/Suwayomi-Server/pull/699) by @Syer10)
|
||||
- ([r1377](https://github.com/Suwayomi/Suwayomi-Server/commit/354968fba764cc845568ea4b2521dc1ec79beb18)) Update version "name" and "code" when installing external extension ([#698](https://github.com/Suwayomi/Suwayomi-Server/pull/698) by @schroda)
|
||||
- ([r1376](https://github.com/Suwayomi/Suwayomi-Server/commit/f985ed2131889480b3fa06caab010482ff75cc5f)) Order chapters to download by manga and source order ([#697](https://github.com/Suwayomi/Suwayomi-Server/pull/697) by @schroda)
|
||||
- ([r1375](https://github.com/Suwayomi/Suwayomi-Server/commit/be2628875f2e966156b5073733ced1806c832bdc)) Correctly select results using cursors while sorting ([#696](https://github.com/Suwayomi/Suwayomi-Server/pull/696) by @schroda)
|
||||
- ([r1374](https://github.com/Suwayomi/Suwayomi-Server/commit/9430c8c580d1e06a596609e14af69754f327684a)) [skip ci] Added new Tachidesk-VaadinUI Client ([#695](https://github.com/Suwayomi/Suwayomi-Server/pull/695) by @aless2003)
|
||||
- ([r1373](https://github.com/Suwayomi/Suwayomi-Server/commit/ea2cf5d4ff3b05068d09a415a10052b1ef7e20b8)) Fix File Upload ([#694](https://github.com/Suwayomi/Suwayomi-Server/pull/694) by @Syer10)
|
||||
- ([r1372](https://github.com/Suwayomi/Suwayomi-Server/commit/3b36974d84760a72e669a8690f539c8c35b133e5)) Fixed Bitmap missing method when using Baozi Manhua extensions. ([#687](https://github.com/Suwayomi/Suwayomi-Server/pull/687) by @vuhe)
|
||||
- ([r1371](https://github.com/Suwayomi/Suwayomi-Server/commit/41fea1d2a01f8ad6fe6076d06dcd0b839b7091a4)) remove @Synchronized in CloudflareInterceptor.kt for performance ([#688](https://github.com/Suwayomi/Suwayomi-Server/pull/688) by @MangaCrushTeam)
|
||||
- ([r1370](https://github.com/Suwayomi/Suwayomi-Server/commit/d81fafc9f636175069785555e20ed4bcb8468a29)) Correctly detect initial fetch of chapters ([#689](https://github.com/Suwayomi/Suwayomi-Server/pull/689) by @schroda)
|
||||
- ([r1369](https://github.com/Suwayomi/Suwayomi-Server/commit/0a73177996567ecffd8d2a0ed3aae397e0cbe38c)) Update graphqlkotlin to v6.5.6 ([#685](https://github.com/Suwayomi/Suwayomi-Server/pull/685) by @schroda)
|
||||
- ([r1368](https://github.com/Suwayomi/Suwayomi-Server/commit/c9423ef425e327e3908b2ab4cbc2febcc4d139e2)) Send every download status change to the subscriber ([#684](https://github.com/Suwayomi/Suwayomi-Server/pull/684) by @schroda)
|
||||
- ([r1367](https://github.com/Suwayomi/Suwayomi-Server/commit/7086055ec33f84b6d667ac193e734f14e1d113d9)) Handle finished downloads that weren't removed from the queue ([#683](https://github.com/Suwayomi/Suwayomi-Server/pull/683) by @schroda)
|
||||
- ([r1366](https://github.com/Suwayomi/Suwayomi-Server/commit/553b35d218d4b8a3569d37f5264004e448d259b4)) Feature/improve automatic chapter downloads ([#680](https://github.com/Suwayomi/Suwayomi-Server/pull/680) by @schroda)
|
||||
- ([r1365](https://github.com/Suwayomi/Suwayomi-Server/commit/c910026308543f8c80e375868638aa5c1dcad76c)) Do not reset already loaded config when updating config file ([#679](https://github.com/Suwayomi/Suwayomi-Server/pull/679) by @schroda)
|
||||
- ([r1364](https://github.com/Suwayomi/Suwayomi-Server/commit/35be9f14e4ff185d75639344f6a2cedeb4a5636a)) Return correct latest compatible webUI version ([#677](https://github.com/Suwayomi/Suwayomi-Server/pull/677) by @schroda)
|
||||
- ([r1363](https://github.com/Suwayomi/Suwayomi-Server/commit/abcbec9c2ab109cddac367224461ff42c92552ce)) Fix/downloader not creating folder or cbz file ([#676](https://github.com/Suwayomi/Suwayomi-Server/pull/676) by @schroda)
|
||||
- ([r1362](https://github.com/Suwayomi/Suwayomi-Server/commit/ff6f5d7e89b33a20614ccbe3b76ed3f358839cf0)) Add more fields to the manga graphql type ([#675](https://github.com/Suwayomi/Suwayomi-Server/pull/675) by @schroda)
|
||||
- ([r1361](https://github.com/Suwayomi/Suwayomi-Server/commit/56deea9fb30961eb37de52ce2b85d3ec671ca018)) Feature/graphql logging ([#674](https://github.com/Suwayomi/Suwayomi-Server/pull/674) by @schroda)
|
||||
- ([r1360](https://github.com/Suwayomi/Suwayomi-Server/commit/1c9a139006f7a9e399c964b2a88650fb757d8369)) Always return "ArchiveProvider" in case "downloadAsCbz" is enabled ([#671](https://github.com/Suwayomi/Suwayomi-Server/pull/671) by @schroda)
|
||||
- ([r1359](https://github.com/Suwayomi/Suwayomi-Server/commit/4d89c324b98a3d9ad4c2cf05d81623a24534aecb)) Fix Oracle JRE Extension Install ([#670](https://github.com/Suwayomi/Suwayomi-Server/pull/670) by @Syer10)
|
||||
- ([r1358](https://github.com/Suwayomi/Suwayomi-Server/commit/a76ce0391160e11dc4159a5d60b401542a2200b7)) Throw error instead of returning null ([#666](https://github.com/Suwayomi/Suwayomi-Server/pull/666) by @schroda)
|
||||
- ([r1357](https://github.com/Suwayomi/Suwayomi-Server/commit/9ee3f46ff0512e5d7fb853d35c112168f3a1d839)) Feature/graphql chapter pages mutation handle downloaded chapters ([#665](https://github.com/Suwayomi/Suwayomi-Server/pull/665) by @schroda)
|
||||
- ([r1356](https://github.com/Suwayomi/Suwayomi-Server/commit/3343007cf850a471b0a8b8a6674e7d492282c5fc)) Add mutation to install external extension ([#667](https://github.com/Suwayomi/Suwayomi-Server/pull/667) by @schroda)
|
||||
- ([r1355](https://github.com/Suwayomi/Suwayomi-Server/commit/c42d314b76b53c50e158111485ebf2ef973f1471)) Move source download dirs to new download subfolder ([#660](https://github.com/Suwayomi/Suwayomi-Server/pull/660) by @schroda)
|
||||
- ([r1354](https://github.com/Suwayomi/Suwayomi-Server/commit/8db6c2153e479e7e3b104570326c835f418eeaae)) Fix some settings not being applied properly ([#661](https://github.com/Suwayomi/Suwayomi-Server/pull/661) by @Syer10)
|
||||
- ([r1353](https://github.com/Suwayomi/Suwayomi-Server/commit/5baf54335b3c3551687bd6169f9001c3b418f2fd)) Feature/updater provide more info about update ([#657](https://github.com/Suwayomi/Suwayomi-Server/pull/657) by @schroda)
|
||||
- ([r1352](https://github.com/Suwayomi/Suwayomi-Server/commit/d9019b8f46fb61862846fa04673dee2db174b716)) Correctly emit changed values ([#656](https://github.com/Suwayomi/Suwayomi-Server/pull/656) by @schroda)
|
||||
- ([r1351](https://github.com/Suwayomi/Suwayomi-Server/commit/a31446557d4f3151ecacd960a37d9425010f2d90)) Feature/graphql server settings ([#629](https://github.com/Suwayomi/Suwayomi-Server/pull/629) by @schroda)
|
||||
- ([r1350](https://github.com/Suwayomi/Suwayomi-Server/commit/321fbe22dd2b666291614c70cf11bd4a16b54088)) Feature/listen to server config value changes ([#617](https://github.com/Suwayomi/Suwayomi-Server/pull/617) by @schroda)
|
||||
- ([r1349](https://github.com/Suwayomi/Suwayomi-Server/commit/01ab912bd9940c7ea191ad2dd0b0dc7b0166c628)) Remove unnecessary "downloadNewChapters" call in "fetchChapters" mutation ([#652](https://github.com/Suwayomi/Suwayomi-Server/pull/652) by @schroda)
|
||||
- ([r1348](https://github.com/Suwayomi/Suwayomi-Server/commit/557bad60bc0cef220ea7a20b3bde5e98a341f24a)) Prevent last page read to be greater than max page count ([#655](https://github.com/Suwayomi/Suwayomi-Server/pull/655) by @schroda)
|
||||
- ([r1347](https://github.com/Suwayomi/Suwayomi-Server/commit/f2dd67d87f38c30c8df6f3718ce392197afbff9a)) Feature/decouple thumbnail downloads and cache ([#581](https://github.com/Suwayomi/Suwayomi-Server/pull/581) by @schroda)
|
||||
- ([r1346](https://github.com/Suwayomi/Suwayomi-Server/commit/b8b92c8d698e4ed548ce2000140ae7a8f3d9b349)) Suspend setupBundledWebUI() ([#650](https://github.com/Suwayomi/Suwayomi-Server/pull/650) by @Syer10)
|
||||
- ([r1345](https://github.com/Suwayomi/Suwayomi-Server/commit/74ff112e7a9cf0dddf5896ef86a08ecc9c9ce7c3)) Feature/graphql web UI ([#649](https://github.com/Suwayomi/Suwayomi-Server/pull/649) by @schroda)
|
||||
- ([r1344](https://github.com/Suwayomi/Suwayomi-Server/commit/684bb1875c667a8887bfeebcf8c38e1d9c23b8fd)) Fix/webinterfacemanager update to bundled webui ([#648](https://github.com/Suwayomi/Suwayomi-Server/pull/648) by @schroda)
|
||||
- ([r1343](https://github.com/Suwayomi/Suwayomi-Server/commit/f6fec2424c6d3548bc178c790aad6259309579f5)) Fix/extracting assets from apks ([#644](https://github.com/Suwayomi/Suwayomi-Server/pull/644) by @schroda)
|
||||
- ([r1342](https://github.com/Suwayomi/Suwayomi-Server/commit/2889029b706798c8ebe497c04aee39e63b33b9a9)) Fix/downloader manager persisting queue ([#639](https://github.com/Suwayomi/Suwayomi-Server/pull/639) by @schroda)
|
||||
- ([r1341](https://github.com/Suwayomi/Suwayomi-Server/commit/b56b4fa8134f4ab6246a8f7202afd70d5d2094ea)) Update Local Source to latest Tachiyomi ([#637](https://github.com/Suwayomi/Suwayomi-Server/pull/637) by @Syer10)
|
||||
- ([r1340](https://github.com/Suwayomi/Suwayomi-Server/commit/00bc055d6938856342b108720d2804259635d4b4)) Fix/load extension log load failure ([#641](https://github.com/Suwayomi/Suwayomi-Server/pull/641) by @schroda)
|
||||
- ([r1339](https://github.com/Suwayomi/Suwayomi-Server/commit/6fd291c7e3904cea8e08c21dd391bb36c87f8d9d)) Fetch downloaded chapters page again in case the stored file can't be retrieved ([#640](https://github.com/Suwayomi/Suwayomi-Server/pull/640) by @schroda)
|
||||
- ([r1338](https://github.com/Suwayomi/Suwayomi-Server/commit/dbdb787076acf8c06368b6b624bf87f32631ed64)) Restore download queue async ([#638](https://github.com/Suwayomi/Suwayomi-Server/pull/638) by @schroda)
|
||||
- ([r1337](https://github.com/Suwayomi/Suwayomi-Server/commit/fc788a718d5dd95163d88ff5e0ae2951ba08f833)) Add 128 px icon ([#636](https://github.com/Suwayomi/Suwayomi-Server/pull/636) by @Syer10)
|
||||
- ([r1336](https://github.com/Suwayomi/Suwayomi-Server/commit/e093fe6a0689f90e40cc6bd7808cca4ea804e98c)) Add CookieManager implementation ([#635](https://github.com/Suwayomi/Suwayomi-Server/pull/635) by @Syer10)
|
||||
- ([r1335](https://github.com/Suwayomi/Suwayomi-Server/commit/cdce3680429fb5e5332c4d74346452483ae7a99f)) Fix Graphql-WS errors and Improve Downloader Subscription ([#634](https://github.com/Suwayomi/Suwayomi-Server/pull/634) by @Syer10)
|
||||
- ([r1334](https://github.com/Suwayomi/Suwayomi-Server/commit/689847d864ccdb29b70833e988b9811fe41af377)) Update dependencies ([#611](https://github.com/Suwayomi/Suwayomi-Server/pull/611) by @Syer10)
|
||||
- ([r1333](https://github.com/Suwayomi/Suwayomi-Server/commit/3675580d876c53fa8b964db01b0b873e1afb0e2f)) Add Subscriptions to GraphiQL and Update ([#631](https://github.com/Suwayomi/Suwayomi-Server/pull/631) by @Syer10)
|
||||
- ([r1332](https://github.com/Suwayomi/Suwayomi-Server/commit/92f494d0fe4420cf8ada1ebe4a20a5762ebc4969)) Implement Graphql-WS Subscriptions ([#630](https://github.com/Suwayomi/Suwayomi-Server/pull/630) by @Syer10)
|
||||
- ([r1331](https://github.com/Suwayomi/Suwayomi-Server/commit/06d7a6d892ec98b15583a980065acb1bfed2cefb)) Info Queries ([#627](https://github.com/Suwayomi/Suwayomi-Server/pull/627) by @Syer10)
|
||||
- ([r1330](https://github.com/Suwayomi/Suwayomi-Server/commit/e2754200af7eb8af99bd3ca2956465f6da838027)) Use Tachidesk-Launcher ([#618](https://github.com/Suwayomi/Suwayomi-Server/pull/618) by @Syer10)
|
||||
- ([r1329](https://github.com/Suwayomi/Suwayomi-Server/commit/cdb083ff4805b215892ab38a90609b3389e64f04)) Downloader Queries and Mutations ([#610](https://github.com/Suwayomi/Suwayomi-Server/pull/610) by @Syer10)
|
||||
- ([r1328](https://github.com/Suwayomi/Suwayomi-Server/commit/c3fb08d634f263dbc7f1f3c4832ae695d6d4ea2d)) Library Update Queries and Mutations ([#609](https://github.com/Suwayomi/Suwayomi-Server/pull/609) by @Syer10)
|
||||
- ([r1327](https://github.com/Suwayomi/Suwayomi-Server/commit/78a167aacf29e86d959c3b7679714c6b255a3931)) Fix/webui setup failure in case bundled webui is missing ([#625](https://github.com/Suwayomi/Suwayomi-Server/pull/625) by @schroda)
|
||||
- ([r1326](https://github.com/Suwayomi/Suwayomi-Server/commit/5a913fdfbbfb96223625b4372b49e4e407311adf)) Make path to local source changeable ([#626](https://github.com/Suwayomi/Suwayomi-Server/pull/626) by @schroda)
|
||||
- ([r1325](https://github.com/Suwayomi/Suwayomi-Server/commit/f0a190e8d2e1a0f50f6f97d85b698f4799b1d383)) Update to bundled webUI version if necessary ([#619](https://github.com/Suwayomi/Suwayomi-Server/pull/619) by @schroda)
|
||||
- ([r1324](https://github.com/Suwayomi/Suwayomi-Server/commit/a2715fb85140bbdd65b7e9e079ffd7342a5df868)) Feature/webui update download failure do not immediately fallback to bundled version ([#620](https://github.com/Suwayomi/Suwayomi-Server/pull/620) by @schroda)
|
||||
- ([r1323](https://github.com/Suwayomi/Suwayomi-Server/commit/47e5b03f45734a5963a65ebb771a3be5c06f0b5f)) Fix some manga filters ([#624](https://github.com/Suwayomi/Suwayomi-Server/pull/624) by @Syer10)
|
||||
- ([r1322](https://github.com/Suwayomi/Suwayomi-Server/commit/251141a5c3b66babc0b99a208349bbe16582b2d0)) Fix/downloader ([#622](https://github.com/Suwayomi/Suwayomi-Server/pull/622) by @schroda)
|
||||
- ([r1321](https://github.com/Suwayomi/Suwayomi-Server/commit/6ac8f4c45d4be32065ef1c73396bb2c78060cce5)) Use mathematical modulo implementation for calculations ([#616](https://github.com/Suwayomi/Suwayomi-Server/pull/616) by @schroda)
|
||||
- ([r1320](https://github.com/Suwayomi/Suwayomi-Server/commit/7ebefa7c42578a1500da8bedf7603a4b924ca699)) Fix/updater scheduling auto updates ([#615](https://github.com/Suwayomi/Suwayomi-Server/pull/615) by @schroda)
|
||||
- ([r1319](https://github.com/Suwayomi/Suwayomi-Server/commit/9e4c90f220a49e63bf0f9b21588757a08e6c4502)) Always update the last webUI update check timestamp ([#614](https://github.com/Suwayomi/Suwayomi-Server/pull/614) by @schroda)
|
||||
- ([r1318](https://github.com/Suwayomi/Suwayomi-Server/commit/50f988641bd0b93dd740e49561bbe1a8e864a587)) Fix/ha scheduler rescheduling ha tasks ([#613](https://github.com/Suwayomi/Suwayomi-Server/pull/613) by @schroda)
|
||||
- ([r1317](https://github.com/Suwayomi/Suwayomi-Server/commit/e53b9d4790859a001bc2eee3d8c9ecaec3615cbd)) Fix/ha scheduler not triggering missed executions due to not meeting the threshold ([#612](https://github.com/Suwayomi/Suwayomi-Server/pull/612) by @schroda)
|
||||
- ([r1316](https://github.com/Suwayomi/Suwayomi-Server/commit/027805c4d50c8cb300693f56c6b4db6a6ab51fba)) Preserve download queue through server restarts ([#599](https://github.com/Suwayomi/Suwayomi-Server/pull/599) by @schroda)
|
||||
- ([r1315](https://github.com/Suwayomi/Suwayomi-Server/commit/c02496c4f0df624ebf08521973314c43b945a3b8)) Fix/updater automated update max interval of 23 hours ([#606](https://github.com/Suwayomi/Suwayomi-Server/pull/606) by @schroda)
|
||||
- ([r1314](https://github.com/Suwayomi/Suwayomi-Server/commit/2a83f290a5714c203e67f1bea0518920af736967)) Use "backupInterval" to disable auto backups ([#608](https://github.com/Suwayomi/Suwayomi-Server/pull/608) by @schroda)
|
||||
- ([r1313](https://github.com/Suwayomi/Suwayomi-Server/commit/d4f9b0b1bc044d13dea0b80d638890dc2460a8c0)) Feature/log to file ([#607](https://github.com/Suwayomi/Suwayomi-Server/pull/607) by @schroda)
|
||||
- ([r1312](https://github.com/Suwayomi/Suwayomi-Server/commit/2452b03a49572b73a30bea0ea87cf576f864fe3f)) Schedule automated update only once per hour ([#605](https://github.com/Suwayomi/Suwayomi-Server/pull/605) by @schroda)
|
||||
- ([r1311](https://github.com/Suwayomi/Suwayomi-Server/commit/2ce423b6cbdf5dcd68d27db93bbab394cec06a40)) Correctly check if a new version is available for the preview channel ([#604](https://github.com/Suwayomi/Suwayomi-Server/pull/604) by @schroda)
|
||||
- ([r1310](https://github.com/Suwayomi/Suwayomi-Server/commit/e9206158b83c67dd4b89fc7f1c36b41e84a94e31)) Feature/move server frontend mapping to the frontend ([#591](https://github.com/Suwayomi/Suwayomi-Server/pull/591) by @schroda)
|
||||
- ([r1309](https://github.com/Suwayomi/Suwayomi-Server/commit/8690e918dd1a05637234544e055b0870aa660aa5)) Feature/automatically download new chapters ([#596](https://github.com/Suwayomi/Suwayomi-Server/pull/596) by @schroda)
|
||||
- ([r1308](https://github.com/Suwayomi/Suwayomi-Server/commit/c1d702a51c69538f9bd785e515aa3d881d582370)) Feature/improve automated backup ([#597](https://github.com/Suwayomi/Suwayomi-Server/pull/597) by @schroda)
|
||||
- ([r1307](https://github.com/Suwayomi/Suwayomi-Server/commit/0338ac3810df42c47374d08917c883b10b95bc8e)) Extract assets from apk file ([#602](https://github.com/Suwayomi/Suwayomi-Server/pull/602) by @schroda)
|
||||
- ([r1306](https://github.com/Suwayomi/Suwayomi-Server/commit/526fef85e4f887fc9b27a1ddf0d097c027ef2cc8)) Feature/global update trigger automatically ([#593](https://github.com/Suwayomi/Suwayomi-Server/pull/593) by @schroda)
|
||||
- ([r1305](https://github.com/Suwayomi/Suwayomi-Server/commit/49f2d8588ad8797ebb559c704fd01cbc2c76ae9e)) Feature/automated backups ([#595](https://github.com/Suwayomi/Suwayomi-Server/pull/595) by @schroda)
|
||||
- ([r1304](https://github.com/Suwayomi/Suwayomi-Server/commit/9a80992aec5edfc5293f1fed79d5e34cad14cb74)) Correctly read resource in build jar and dev mode ([#594](https://github.com/Suwayomi/Suwayomi-Server/pull/594) by @schroda)
|
||||
- ([r1303](https://github.com/Suwayomi/Suwayomi-Server/commit/32d0890dba4f6bf9bf6da55717ec687e82fc9ce1)) Proxy thumbnail urls ([#589](https://github.com/Suwayomi/Suwayomi-Server/pull/589) by @Syer10)
|
||||
- ([r1302](https://github.com/Suwayomi/Suwayomi-Server/commit/b4d37f9ba2e53627047fa5597dd17980e56ac8c2)) Make sure "UserConfig" is up-to-date ([#590](https://github.com/Suwayomi/Suwayomi-Server/pull/590) by @schroda)
|
||||
- ([r1301](https://github.com/Suwayomi/Suwayomi-Server/commit/5372ef8f0c4c6250d6a08bccc1b913c67cddeab5)) Manga for Source data loader ([#588](https://github.com/Suwayomi/Suwayomi-Server/pull/588) by @Syer10)
|
||||
- ([r1300](https://github.com/Suwayomi/Suwayomi-Server/commit/a11b654c3d1d1cbf299fb4496039175fc0ad075c)) Backup creation and restore gql endpoints ([#587](https://github.com/Suwayomi/Suwayomi-Server/pull/587) by @Syer10)
|
||||
- ([r1299](https://github.com/Suwayomi/Suwayomi-Server/commit/1a9a0b3394c84387d7dde93c3ffbbb10f95dd37b)) Exclude "default" category from reordering ([#586](https://github.com/Suwayomi/Suwayomi-Server/pull/586) by @schroda)
|
||||
- ([r1298](https://github.com/Suwayomi/Suwayomi-Server/commit/890920a57b922057dc3f336de93a1c507a14f435)) Freeze graphql playground scripts to working versions ([#585](https://github.com/Suwayomi/Suwayomi-Server/pull/585) by @schroda)
|
||||
- ([r1297](https://github.com/Suwayomi/Suwayomi-Server/commit/7fe7de5fdf71dcfcd9d0ab575b393e8a66cf1807)) Fix fetchSourceManga filtering (by @Syer10)
|
||||
- ([r1296](https://github.com/Suwayomi/Suwayomi-Server/commit/b9b115d0ea1e581ca33264b19d9bf6b5d417e2da)) Rewrite filter and preference mutations ([#577](https://github.com/Suwayomi/Suwayomi-Server/pull/577) by @Syer10)
|
||||
- ([r1295](https://github.com/Suwayomi/Suwayomi-Server/commit/08af195f1160b2753fb6bf9f7c6558a7b893b507)) Fix graphql/plugin-explorer urls ([#584](https://github.com/Suwayomi/Suwayomi-Server/pull/584) by @schroda)
|
||||
- ([r1294](https://github.com/Suwayomi/Suwayomi-Server/commit/71cde729fc708155f5fd733c0b85a610bdcc9d26)) Delete tmp files on request failure ([#582](https://github.com/Suwayomi/Suwayomi-Server/pull/582) by @schroda)
|
||||
- ([r1293](https://github.com/Suwayomi/Suwayomi-Server/commit/077f0a03f69a6e48d5a0274a6a40f319ce9e60a2)) Update "dex2jar" to v61 ([#583](https://github.com/Suwayomi/Suwayomi-Server/pull/583) by @schroda)
|
||||
- ([r1292](https://github.com/Suwayomi/Suwayomi-Server/commit/812eb8001b66ef412644cafa64743b3f7845ae95)) Add fetch chapter pages ([#576](https://github.com/Suwayomi/Suwayomi-Server/pull/576) by @Syer10)
|
||||
- ([r1291](https://github.com/Suwayomi/Suwayomi-Server/commit/b59af683ac418ba01ed4437b8ad3ca1588d25221)) Do not count mangas as part of categories that aren't in the library ([#574](https://github.com/Suwayomi/Suwayomi-Server/pull/574) by @schroda)
|
||||
- ([r1290](https://github.com/Suwayomi/Suwayomi-Server/commit/561d680e783cdf03a9c48907a56a4b436cb43908)) Exclude mangas with specific state from global update ([#537](https://github.com/Suwayomi/Suwayomi-Server/pull/537) by @schroda)
|
||||
- ([r1289](https://github.com/Suwayomi/Suwayomi-Server/commit/7c3eff2ba72d37be67e31984c7579684f86fc879)) Complete source mutations ([#567](https://github.com/Suwayomi/Suwayomi-Server/pull/567) by @Syer10)
|
||||
- ([r1288](https://github.com/Suwayomi/Suwayomi-Server/commit/300c0a8f35496e9329af16c3c30de50872918bf4)) Category Mutations ([#566](https://github.com/Suwayomi/Suwayomi-Server/pull/566) by @Syer10)
|
||||
- ([r1287](https://github.com/Suwayomi/Suwayomi-Server/commit/51bfdc094748079387e299ebed0c69c9c1554e85)) Feature/make config settings changeable during runtime ([#545](https://github.com/Suwayomi/Suwayomi-Server/pull/545) by @schroda)
|
||||
- ([r1286](https://github.com/Suwayomi/Suwayomi-Server/commit/a64566c0f3ae6491e7f91a56cb6634ed47dfc3da)) fill in the cover according to spec ([#571](https://github.com/Suwayomi/Suwayomi-Server/pull/571) by @AriaMoradi)
|
||||
- ([r1285](https://github.com/Suwayomi/Suwayomi-Server/commit/dbb9a80ea6f778b0c298e941f7885f186f0a48f8)) use commons-compress everywhere ([#570](https://github.com/Suwayomi/Suwayomi-Server/pull/570) by @AriaMoradi)
|
||||
- ([r1284](https://github.com/Suwayomi/Suwayomi-Server/commit/e930c54246894f853620d06b62759a54d1243161)) improve zip parsing ([#569](https://github.com/Suwayomi/Suwayomi-Server/pull/569) by @AriaMoradi)
|
||||
- ([r1283](https://github.com/Suwayomi/Suwayomi-Server/commit/dfff047cbfb14e7d1506b0ce37e31584fffc6a60)) Fix cascade migration ([#565](https://github.com/Suwayomi/Suwayomi-Server/pull/565) by @Syer10)
|
||||
- ([r1282](https://github.com/Suwayomi/Suwayomi-Server/commit/44fb2b02bcd355efcba0687541239fa9ba30ba1c)) Fix global meta delete ([#564](https://github.com/Suwayomi/Suwayomi-Server/pull/564) by @Syer10)
|
||||
- ([r1281](https://github.com/Suwayomi/Suwayomi-Server/commit/6a7efafd9f20500d87507122f8ff88c349868936)) Improve database column references and default category handling ([#563](https://github.com/Suwayomi/Suwayomi-Server/pull/563) by @Syer10)
|
||||
- ([r1280](https://github.com/Suwayomi/Suwayomi-Server/commit/241abc3956a1c335a85077c5dbf7c0a82ccac4f2)) Add items that are related to the deleted meta ([#562](https://github.com/Suwayomi/Suwayomi-Server/pull/562) by @Syer10)
|
||||
- ([r1279](https://github.com/Suwayomi/Suwayomi-Server/commit/1e82c879bf3d93e28add11fbe23422d77f452e57)) Add default category to the database ([#561](https://github.com/Suwayomi/Suwayomi-Server/pull/561) by @Syer10)
|
||||
- ([r1278](https://github.com/Suwayomi/Suwayomi-Server/commit/a81d01d2e3f1f8b6ba89d99a1206d538cebf1672)) Don't use data fetchers in mutations ([#559](https://github.com/Suwayomi/Suwayomi-Server/pull/559) by @Syer10)
|
||||
- ([r1277](https://github.com/Suwayomi/Suwayomi-Server/commit/2230796504ce938c510c5cdab0a3c7875db99702)) Extension mutations ([#560](https://github.com/Suwayomi/Suwayomi-Server/pull/560) by @Syer10)
|
||||
- ([r1276](https://github.com/Suwayomi/Suwayomi-Server/commit/458ca7c7cfd2ea69dcb46038b26d57ff41f7157f)) Fix update chapters ([#557](https://github.com/Suwayomi/Suwayomi-Server/pull/557) by @Syer10)
|
||||
- ([r1275](https://github.com/Suwayomi/Suwayomi-Server/commit/3f91663ecf7f4e34b0ba87010e26c3b597adcc81)) Rewrite meta and add meta mutations ([#556](https://github.com/Suwayomi/Suwayomi-Server/pull/556) by @Syer10)
|
||||
- ([r1274](https://github.com/Suwayomi/Suwayomi-Server/commit/04a671382afe70a70d9a2c63337fc7ab965b4859)) Improve GQL Playground ([#558](https://github.com/Suwayomi/Suwayomi-Server/pull/558) by @Syer10)
|
||||
- ([r1273](https://github.com/Suwayomi/Suwayomi-Server/commit/945ec818e590132587ed8323d036268bdfabefec)) Remove category filter ([#551](https://github.com/Suwayomi/Suwayomi-Server/pull/551) by @Syer10)
|
||||
- ([r1272](https://github.com/Suwayomi/Suwayomi-Server/commit/ff7ac8a78580d33d9c04b087304a2e3eae73e190)) Fetch Manga and Chapters in GQL ([#555](https://github.com/Suwayomi/Suwayomi-Server/pull/555) by @Syer10)
|
||||
- ([r1271](https://github.com/Suwayomi/Suwayomi-Server/commit/603105e2ea480a39ba20904e0d05383a62d35836)) Fix StringFilter ([#554](https://github.com/Suwayomi/Suwayomi-Server/pull/554) by @Syer10)
|
||||
- ([r1270](https://github.com/Suwayomi/Suwayomi-Server/commit/5475567b485cacc2da8bda5c737dddc2865d3e3e)) Cleanup download type ([#553](https://github.com/Suwayomi/Suwayomi-Server/pull/553) by @Syer10)
|
||||
- ([r1269](https://github.com/Suwayomi/Suwayomi-Server/commit/2aec0adb086c29c278f1ca94545ea52e15dc73ab)) Category mangas ([#552](https://github.com/Suwayomi/Suwayomi-Server/pull/552) by @Syer10)
|
||||
- ([r1268](https://github.com/Suwayomi/Suwayomi-Server/commit/54fc3761bf0413d69ce07a2f1b7fe926dbe9148f)) Put graphql under api ([#549](https://github.com/Suwayomi/Suwayomi-Server/pull/549) by @Syer10)
|
||||
- ([r1267](https://github.com/Suwayomi/Suwayomi-Server/commit/99e1912bfeb7547f480725ee9fb6b3372f44fb66)) Fix manga/source and manga/chapters for graphql ([#548](https://github.com/Suwayomi/Suwayomi-Server/pull/548) by @Syer10)
|
||||
- ([r1266](https://github.com/Suwayomi/Suwayomi-Server/commit/ecc1cabafddd2bc9b03b742ef32dfc777130a8f3)) Merge pull request #547 from Suwayomi/graphql ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @AriaMoradi)
|
||||
- ([r1265](https://github.com/Suwayomi/Suwayomi-Server/commit/1a5b847b239633de0e32945597a4b255e94704f8)) Update README.md (by @AriaMoradi)
|
||||
- ([r1264](https://github.com/Suwayomi/Suwayomi-Server/commit/d3409e7133735e5f495be1a872d335dc776e89f1)) Update README.md (by @AriaMoradi)
|
||||
- ([r1263](https://github.com/Suwayomi/Suwayomi-Server/commit/4e553e3eb3cb661ceeeca9323c220bcc550c1fc6)) better description about the Tachiyomi extension (by @AriaMoradi)
|
||||
- ([r1262](https://github.com/Suwayomi/Suwayomi-Server/commit/4577bbc572ac1a73e000e2cdbb52f447e0344ef2)) More mutations ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @Syer10)
|
||||
- ([r1261](https://github.com/Suwayomi/Suwayomi-Server/commit/da8ca2349688237cba6d390c65ef32c62f9c1be3)) Start working on mutations ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @Syer10)
|
||||
- ([r1260](https://github.com/Suwayomi/Suwayomi-Server/commit/988853be63fdb0e462018ee087a91b0de078895d)) Seems like this should return null if it errors ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @Syer10)
|
||||
- ([r1259](https://github.com/Suwayomi/Suwayomi-Server/commit/cde5dc5bfa4ce6cce6d565b41589672a754460c0)) Update "dex2jar" to v60 ([#538](https://github.com/Suwayomi/Suwayomi-Server/pull/538) by @schroda)
|
||||
- ([r1258](https://github.com/Suwayomi/Suwayomi-Server/commit/b617250effc103adcf915de47b9098f0f1063e22)) Delete updates query since the chapters query can now mimic it ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @Syer10)
|
||||
- ([r1257](https://github.com/Suwayomi/Suwayomi-Server/commit/313da995365d41100eec73025d927b0a9f84e275)) Add in library filter for chapters ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @Syer10)
|
||||
- ([r1256](https://github.com/Suwayomi/Suwayomi-Server/commit/442e24521682354aa5000d47fd95893fd97dbebf)) Update TODO ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @Syer10)
|
||||
- ([r1255](https://github.com/Suwayomi/Suwayomi-Server/commit/050ab170193517bf38823365e7f376fabe6e3a88)) Complete SourceQuery ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @Syer10)
|
||||
- ([r1254](https://github.com/Suwayomi/Suwayomi-Server/commit/c80f488a13a19903c0ceaf013b6d8443613ae6f4)) Complete ChapterQuery ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @Syer10)
|
||||
- ([r1253](https://github.com/Suwayomi/Suwayomi-Server/commit/cf73804c7162749cbf99045acd8570a8970e4875)) Complete ExtensionQuery ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @Syer10)
|
||||
- ([r1252](https://github.com/Suwayomi/Suwayomi-Server/commit/a90e5d13ea71310db9b2eb76f21aeecf9188a5b3)) Complete MetaQuery ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @Syer10)
|
||||
- ([r1251](https://github.com/Suwayomi/Suwayomi-Server/commit/891fb0b4794adc233273ce2286012decd646523f)) Simplify keyset pagination ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @Syer10)
|
||||
- ([r1250](https://github.com/Suwayomi/Suwayomi-Server/commit/58a623d44dc9e08a460246cba0c477f5790bf39d)) Fix keyset pagination for non-unique order by modes ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @Syer10)
|
||||
- ([r1249](https://github.com/Suwayomi/Suwayomi-Server/commit/0e84b8a1541663083e92ae7fde6eaace42c92a01)) Lint ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @Syer10)
|
||||
- ([r1248](https://github.com/Suwayomi/Suwayomi-Server/commit/a4dfcf80e4741180867cf523876989962ae3ef5a)) Implement manga status filter ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @Syer10)
|
||||
- ([r1247](https://github.com/Suwayomi/Suwayomi-Server/commit/d8567eadb2ef2355416a3a31c31c74124565a73a)) Simplify queries ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @Syer10)
|
||||
- ([r1246](https://github.com/Suwayomi/Suwayomi-Server/commit/0b88207ad524e8e34c96caf35a67252cd3c5fba2)) Fix empty results errors ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @Syer10)
|
||||
- ([r1245](https://github.com/Suwayomi/Suwayomi-Server/commit/671466a737ae47645bf4b0f93f01c6e26bf6900f)) Complete CategoryQuery ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @Syer10)
|
||||
- ([r1244](https://github.com/Suwayomi/Suwayomi-Server/commit/84881a0d52e82271c7bd616c080691fa1a27bcdf)) Complete MangaQuery ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @Syer10)
|
||||
- ([r1243](https://github.com/Suwayomi/Suwayomi-Server/commit/a589049cc7f5357cb41ead6eb79186c6ad00cec0)) Move things around and introduce Cursor type ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @Syer10)
|
||||
- ([r1242](https://github.com/Suwayomi/Suwayomi-Server/commit/17877e0f17f461ab0dffe606934ceb823f12bde7)) Fix case insensitive ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @Syer10)
|
||||
- ([r1241](https://github.com/Suwayomi/Suwayomi-Server/commit/1ed9bef2a1ede5646ca1829205af0e0e6a027e64)) Fix the playground explorer and add a updated default query ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @Syer10)
|
||||
- ([r1240](https://github.com/Suwayomi/Suwayomi-Server/commit/a6dddf311c4c6f89794305793983b02905279a74)) Basically finish MangaQuery, only paging left ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @Syer10)
|
||||
- ([r1239](https://github.com/Suwayomi/Suwayomi-Server/commit/e8c2bad18796f5c329e41a99cd650415bb2260e4)) Handle missing objects in graphql ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @Syer10)
|
||||
- ([r1238](https://github.com/Suwayomi/Suwayomi-Server/commit/52bda2c08051e187584073858e247657592f79e9)) Start working on graphql paging ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @Syer10)
|
||||
- ([r1237](https://github.com/Suwayomi/Suwayomi-Server/commit/607919f40f7ed008299ca71792f7171edbb9c895)) Implement more query parameters ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @Syer10)
|
||||
- ([r1236](https://github.com/Suwayomi/Suwayomi-Server/commit/d830638ee6e1ada9c4b80f6b9c308d7a42471c03)) Use actual MangaStatus enum ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @Syer10)
|
||||
- ([r1235](https://github.com/Suwayomi/Suwayomi-Server/commit/106bda20972d453a35b1aa964d83a6ae5b96703e)) Proper conversion Scalar for Long to String and back ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @Syer10)
|
||||
- ([r1234](https://github.com/Suwayomi/Suwayomi-Server/commit/7debb27374887a5bf6aa65ad55466ce7fedd5da9)) Might not need a updates query ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @Syer10)
|
||||
- ([r1233](https://github.com/Suwayomi/Suwayomi-Server/commit/05b5a7f598723f30fd5c8a0b783900a1aed6661d)) Add updates ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @Syer10)
|
||||
- ([r1232](https://github.com/Suwayomi/Suwayomi-Server/commit/3bbda7ba549e4c9eaa2314a22f5ba3a3915a9ee3)) More todos ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @Syer10)
|
||||
- ([r1231](https://github.com/Suwayomi/Suwayomi-Server/commit/9312f5fd14b9cb8e90be7e3ddb6cc35d6bb21e30)) Add global meta ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @Syer10)
|
||||
- ([r1230](https://github.com/Suwayomi/Suwayomi-Server/commit/399eb07e359fa7d87d4f566a936143eac78815ee)) Fix imports ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @Syer10)
|
||||
- ([r1229](https://github.com/Suwayomi/Suwayomi-Server/commit/eb197ebceef573defff5b3738a195c218bcb29ad)) Switch database logger to SLF4J ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @Syer10)
|
||||
- ([r1228](https://github.com/Suwayomi/Suwayomi-Server/commit/4c30d8ab05ccb59dee13b714c6a8e6df18096d03)) Some TODOs with ideas ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @Syer10)
|
||||
- ([r1227](https://github.com/Suwayomi/Suwayomi-Server/commit/3a67ddf0f697967e4dbc33f3ce84aac142f4b791)) Add Extensions to Graphql ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @Syer10)
|
||||
- ([r1226](https://github.com/Suwayomi/Suwayomi-Server/commit/6541c7b5b7a2219b14ac0524f77939e9b1ee36ef)) Serialize Long as String in graphql ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @Syer10)
|
||||
- ([r1225](https://github.com/Suwayomi/Suwayomi-Server/commit/37f41ade43827fed827a381f1e28bd27d04a1797)) Directly use the database for sources in graphql ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @Syer10)
|
||||
- ([r1224](https://github.com/Suwayomi/Suwayomi-Server/commit/007d20d41754efcf6eef269aa9947771a17c4e2a)) Add Sources to Graphql ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @Syer10)
|
||||
- ([r1223](https://github.com/Suwayomi/Suwayomi-Server/commit/00370a81fa38e7f63a48a759c32dd7acb1e80ce8)) Minor cleanup ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @Syer10)
|
||||
- ([r1222](https://github.com/Suwayomi/Suwayomi-Server/commit/d4599c3331cab595b810b870a48cf94a91a14751)) Use Graphiql with the Explorer plugin for the query builder ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @Syer10)
|
||||
- ([r1221](https://github.com/Suwayomi/Suwayomi-Server/commit/bce76bbcf36a21fc1916566a8e23303f7ccb5bca)) Use Kotlin Coroutines Flow instead of Project reactor ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @Syer10)
|
||||
- ([r1220](https://github.com/Suwayomi/Suwayomi-Server/commit/847a5fe71b088cf1a355653471ccd82a0ff12168)) Subscriptions! ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @martinek)
|
||||
- ([r1219](https://github.com/Suwayomi/Suwayomi-Server/commit/e2fa0032391191a2ed8536213f989bf9acc5824e)) Rewrite graphql controller execute as function without docs ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @martinek)
|
||||
- ([r1218](https://github.com/Suwayomi/Suwayomi-Server/commit/0c555e88d379b14161727237a09f4840a46bf1a2)) Update graphql-playground endpoint ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @martinek)
|
||||
- ([r1217](https://github.com/Suwayomi/Suwayomi-Server/commit/bf7f1a04b33f5d35080a01e1e8912f254b48345f)) Add categories to graphql ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @martinek)
|
||||
- ([r1216](https://github.com/Suwayomi/Suwayomi-Server/commit/623172af6d2d26ecb67b9ea2ea70a129d6ff1d35)) Add mutation for updating chapters ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @martinek)
|
||||
- ([r1215](https://github.com/Suwayomi/Suwayomi-Server/commit/4fb689d9e4ff54143a8685d77513cbe2265c6428)) Add chapter and manga meta field ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @martinek)
|
||||
- ([r1214](https://github.com/Suwayomi/Suwayomi-Server/commit/6054c489c6feda3e2d585ad1de97d51dba0fc126)) Add graphql playground ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @martinek)
|
||||
- ([r1213](https://github.com/Suwayomi/Suwayomi-Server/commit/21719f4408d73a22cae2f197b078b3af1f08cbd8)) Add basic graphql implementation with manga and chapters loading with data loaders ([#547](https://github.com/Suwayomi/Suwayomi-Server/pull/547) by @martinek)
|
||||
- ([r1212](https://github.com/Suwayomi/Suwayomi-Server/commit/f2a650ba02e1ffd25313e354a98bb72ead322fd9)) fix typo (by @AriaMoradi)
|
||||
- ([r1211](https://github.com/Suwayomi/Suwayomi-Server/commit/871c28b1ea51c2e03bf5754a949e467df555b90d)) cleanup notes (by @AriaMoradi)
|
||||
- ([r1210](https://github.com/Suwayomi/Suwayomi-Server/commit/d3aa32147a017113c1a4fecf437518133a116e12)) Add logic to only update specific categories ([#520](https://github.com/Suwayomi/Suwayomi-Server/pull/520) by @schroda)
|
||||
- ([r1209](https://github.com/Suwayomi/Suwayomi-Server/commit/9a50f2e4089c6766388adefc397f8fd59bcc063c)) Notify clients even if no manga gets updated ([#531](https://github.com/Suwayomi/Suwayomi-Server/pull/531) by @schroda)
|
||||
- ([r1208](https://github.com/Suwayomi/Suwayomi-Server/commit/dcde4947e83f4850a037184aff9c158233d23a5c)) Emit update to clients after adding all mangas to the queue ([#521](https://github.com/Suwayomi/Suwayomi-Server/pull/521) by @schroda)
|
||||
- ([r1207](https://github.com/Suwayomi/Suwayomi-Server/commit/5b61bdc3a81d5704ca283271b4ea25874fce778f)) add size field to Category data class ([#519](https://github.com/Suwayomi/Suwayomi-Server/pull/519) by @schroda)
|
||||
- ([r1206](https://github.com/Suwayomi/Suwayomi-Server/commit/ec1d65f4c3e3d74199ab5066870c7b531a66b76f)) update library grouped by source ([#511](https://github.com/Suwayomi/Suwayomi-Server/pull/511) by @schroda)
|
||||
- ([r1205](https://github.com/Suwayomi/Suwayomi-Server/commit/a0081dec07ed0fabd6fa8a18beb873563ac0af08)) fix manga unread and download count ([#509](https://github.com/Suwayomi/Suwayomi-Server/pull/509) by @akabhirav)
|
||||
- ([r1204](https://github.com/Suwayomi/Suwayomi-Server/commit/783787e5141ebc3e469d3a27c504ce7c210c0a7b)) Send last read chapter in Mangas in Category API ([#507](https://github.com/Suwayomi/Suwayomi-Server/pull/507) by @akabhirav)
|
||||
- ([r1203](https://github.com/Suwayomi/Suwayomi-Server/commit/ac99dd55a2991d49f62c3b388582b6a40ee1857d)) Fix random page sent when manga is downloaded ([#508](https://github.com/Suwayomi/Suwayomi-Server/pull/508) by @akabhirav)
|
||||
- ([r1202](https://github.com/Suwayomi/Suwayomi-Server/commit/c56f984952f6591cbf5d53be37a7d3868633b09b)) Fix SharedPreferences.Editor.clear and SharedPreferences.Editor.remove ([#505](https://github.com/Suwayomi/Suwayomi-Server/pull/505) by @Syer10)
|
||||
- ([r1201](https://github.com/Suwayomi/Suwayomi-Server/commit/9269ca726eabcf91aa041d37ff33fc805812c611)) It's not us, I swear ;;; (by @AriaMoradi)
|
||||
- ([r1200](https://github.com/Suwayomi/Suwayomi-Server/commit/eca3205dcf43a0e32e5f5976e2ef49205beff8a2)) Update winget.yml ([#500](https://github.com/Suwayomi/Suwayomi-Server/pull/500) by @DattatreyaReddy)
|
||||
- ([r1199](https://github.com/Suwayomi/Suwayomi-Server/commit/13f5486d0b929b3c2513ba4e0a2e01f20281fbbd)) Fix CBZ download bug for newly added mangas in Library ([#499](https://github.com/Suwayomi/Suwayomi-Server/pull/499) by @akabhirav)
|
||||
- ([r1198](https://github.com/Suwayomi/Suwayomi-Server/commit/d4e71274f94a066309cb4881042cf4673075a5d0)) update changelog (by @AriaMoradi)
|
||||
|
||||
## Suwayomi-WebUI Changelog
|
||||
- ([r1409](https://github.com/Suwayomi/Suwayomi-WebUI/commit/21bb931a74155a75b14fb4c307dcd18108a3de09)) Use full available width for reader component ([#618](https://github.com/Suwayomi/Suwayomi-WebUI/pull/618) by @schroda)
|
||||
- ([r1408](https://github.com/Suwayomi/Suwayomi-WebUI/commit/629b742140e21ab7cd51abd947f32b8d6dc54780)) Feature/settings add new socks proxy settings ([#617](https://github.com/Suwayomi/Suwayomi-WebUI/pull/617) by @schroda)
|
||||
- ([r1407](https://github.com/Suwayomi/Suwayomi-WebUI/commit/ab0ecf4da0a2511fd0a8d8885bce07e53dfd5840)) Center page number correctly ([#616](https://github.com/Suwayomi/Suwayomi-WebUI/pull/616) by @schroda)
|
||||
- ([r1406](https://github.com/Suwayomi/Suwayomi-WebUI/commit/ceec260d14d4c48d0c5f8b093af7039e849cd304)) Feature/reader setting add scale small pages ([#615](https://github.com/Suwayomi/Suwayomi-WebUI/pull/615) by @schroda)
|
||||
- ([r1405](https://github.com/Suwayomi/Suwayomi-WebUI/commit/9531825babc7f4ccc28c3a84022d6ef0aa01ff22)) Fix size of pages in continues reader mode ([#613](https://github.com/Suwayomi/Suwayomi-WebUI/pull/613) by @schroda)
|
||||
- ([r1404](https://github.com/Suwayomi/Suwayomi-WebUI/commit/d8ee676d550b677a11568773065e7254b2f12096)) Prevent invisible pages ([#614](https://github.com/Suwayomi/Suwayomi-WebUI/pull/614) by @schroda)
|
||||
- ([r1403](https://github.com/Suwayomi/Suwayomi-WebUI/commit/97ab4004a61b243cc3420818488e9685728315ce)) Do not update chapter in case it has not been loaded yet ([#612](https://github.com/Suwayomi/Suwayomi-WebUI/pull/612) by @schroda)
|
||||
- ([r1402](https://github.com/Suwayomi/Suwayomi-WebUI/commit/5a9618aefe6876b1b9cab8660ac05202cbb6bafa)) Correctly update function refs when state changes ([#611](https://github.com/Suwayomi/Suwayomi-WebUI/pull/611) by @schroda)
|
||||
- ([r1401](https://github.com/Suwayomi/Suwayomi-WebUI/commit/e8bfc28350328776f379cde1cb48e3c83d88bf87)) [VersionMapping] Require server version "r1487" for preview ([#610](https://github.com/Suwayomi/Suwayomi-WebUI/pull/610) by @schroda)
|
||||
- ([r1400](https://github.com/Suwayomi/Suwayomi-WebUI/commit/02dc9ca7365f96d0ab2ad61bfb5c1b0824ece151)) Add "thumbnailUrlLastFetched" to thumbnail url ([#607](https://github.com/Suwayomi/Suwayomi-WebUI/pull/607) by @schroda)
|
||||
- ([r1399](https://github.com/Suwayomi/Suwayomi-WebUI/commit/e5ffbb462c191a9f94e57bd837371d9d10374690)) Feature/gql remove download ahead limit ([#608](https://github.com/Suwayomi/Suwayomi-WebUI/pull/608) by @schroda)
|
||||
- ([r1398](https://github.com/Suwayomi/Suwayomi-WebUI/commit/fd651b61655315395775f6fdbe6aba672651111f)) Feature/add vui as webui flavor ([#609](https://github.com/Suwayomi/Suwayomi-WebUI/pull/609) by @schroda)
|
||||
- ([r1397](https://github.com/Suwayomi/Suwayomi-WebUI/commit/05077b4a2be05f10f7593b769bba77cc8eaf9ae1)) Correctly link to custom repos settings ([#603](https://github.com/Suwayomi/Suwayomi-WebUI/pull/603) by @schroda)
|
||||
- ([r1396](https://github.com/Suwayomi/Suwayomi-WebUI/commit/99c9d9d12f3c4a70066f4821d49be99815d3d2f9)) Use set reader width on small devices ([#602](https://github.com/Suwayomi/Suwayomi-WebUI/pull/602) by @schroda)
|
||||
- ([r1395](https://github.com/Suwayomi/Suwayomi-WebUI/commit/aea112e29298ba6a694e9af2375340220a6717b3)) Create correct manga thumbnail url ([#601](https://github.com/Suwayomi/Suwayomi-WebUI/pull/601) by @schroda)
|
||||
- ([r1394](https://github.com/Suwayomi/Suwayomi-WebUI/commit/4b54b0939576490948620f01996e593a73581985)) Rename "ExtensionSettings" to "BrowseSettings" ([#600](https://github.com/Suwayomi/Suwayomi-WebUI/pull/600) by @schroda)
|
||||
- ([r1393](https://github.com/Suwayomi/Suwayomi-WebUI/commit/e383cc9ea8d3a3f59c17551833e5438809ff1ed2)) Add info text to download ahead setting ([#599](https://github.com/Suwayomi/Suwayomi-WebUI/pull/599) by @schroda)
|
||||
- ([r1392](https://github.com/Suwayomi/Suwayomi-WebUI/commit/c099e01fb70b95412661a98e7669aceda8b2aa87)) Add manga fetch timestamp to thumbnail url ([#598](https://github.com/Suwayomi/Suwayomi-WebUI/pull/598) by @schroda)
|
||||
- ([r1391](https://github.com/Suwayomi/Suwayomi-WebUI/commit/2acf2b6d33742b9b09becce3912347f0d9f3f1f3)) Feature/download ahead trigger chapter downloads client side ([#597](https://github.com/Suwayomi/Suwayomi-WebUI/pull/597) by @schroda)
|
||||
- ([r1390](https://github.com/Suwayomi/Suwayomi-WebUI/commit/0edec685f9d96eb0cdf94d6e176b730fc743fb65)) Correctly select next chapter id for download ahead ([#596](https://github.com/Suwayomi/Suwayomi-WebUI/pull/596) by @schroda)
|
||||
- ([r1389](https://github.com/Suwayomi/Suwayomi-WebUI/commit/d244ba65381e68206229dcacc1e20b7c9409ae5a)) Fit double page reader pages correctly to windows width ([#595](https://github.com/Suwayomi/Suwayomi-WebUI/pull/595) by @schroda)
|
||||
- ([r1388](https://github.com/Suwayomi/Suwayomi-WebUI/commit/4ffd3584c6f2c70cda9fd6e2a1630ed91e1d0c74)) Handle RTL reading direction for double page reader ([#594](https://github.com/Suwayomi/Suwayomi-WebUI/pull/594) by @schroda)
|
||||
- ([r1387](https://github.com/Suwayomi/Suwayomi-WebUI/commit/444ebf80071c083588c79e31f18d98eb740b2564)) Fix/reader outdated chapter page count ([#593](https://github.com/Suwayomi/Suwayomi-WebUI/pull/593) by @schroda)
|
||||
- ([r1386](https://github.com/Suwayomi/Suwayomi-WebUI/commit/5017ba99328d3f200a92f2ffcfcdd50d2ec2bdaa)) Handle backup creation on same domain as server ([#592](https://github.com/Suwayomi/Suwayomi-WebUI/pull/592) by @schroda)
|
||||
- ([r1385](https://github.com/Suwayomi/Suwayomi-WebUI/commit/7caea4e5262ecbd42a06a37de57623a2cafa9b61)) Download ahead only in case current and next chapter are downloaded (by @schroda)
|
||||
- ([r1384](https://github.com/Suwayomi/Suwayomi-WebUI/commit/d5b633f5c97b4ba596703dc50a3150745ad621da)) Feature/improve create changelog script ([#591](https://github.com/Suwayomi/Suwayomi-WebUI/pull/591) by @schroda)
|
||||
- ([r1383](https://github.com/Suwayomi/Suwayomi-WebUI/commit/bf3815e3bd35aaf33d4e7039d7e9021419d062eb)) Correctly update cache after updating an extension ([#590](https://github.com/Suwayomi/Suwayomi-WebUI/pull/590) by @schroda)
|
||||
- ([r1382](https://github.com/Suwayomi/Suwayomi-WebUI/commit/7fb1dae9c4571586f5fc00a915a1119b4e670a47)) decrease reader's up and down arrows scrolling distance ([#588](https://github.com/Suwayomi/Suwayomi-WebUI/pull/588) by @JiPaix, @schroda)
|
||||
- ([r1381](https://github.com/Suwayomi/Suwayomi-WebUI/commit/3077824ca70ffe8e1949d559dd376f5c89a6782c)) Update dependencies ([#587](https://github.com/Suwayomi/Suwayomi-WebUI/pull/587) by @schroda)
|
||||
- ([r1380](https://github.com/Suwayomi/Suwayomi-WebUI/commit/76e5c674174c9f055a768cdfa9570f3e001b3cfc)) Translations update from Hosted Weblate ([#548](https://github.com/Suwayomi/Suwayomi-WebUI/pull/548) by @weblate, @jesusFx, @Yuhyeong, @a18ccms, @plum7x, @HiyoriTUK)
|
||||
- ([r1379](https://github.com/Suwayomi/Suwayomi-WebUI/commit/b074de26a23758b8900591f31200c8359fe6b5da)) Fix/install external extension does not update extension list ([#580](https://github.com/Suwayomi/Suwayomi-WebUI/pull/580) by @schroda)
|
||||
- ([r1378](https://github.com/Suwayomi/Suwayomi-WebUI/commit/506e0aa0e38c0bcb26931b85ef1d6d688b2d95ca)) Update extension list after removing an obsolete extension ([#579](https://github.com/Suwayomi/Suwayomi-WebUI/pull/579) by @schroda)
|
||||
- ([r1377](https://github.com/Suwayomi/Suwayomi-WebUI/commit/cee566d9b294d6499e938488fadb497c5dbc2b3f)) [Codegen] Update manga chapter total count on initial refresh ([#582](https://github.com/Suwayomi/Suwayomi-WebUI/pull/582) by @schroda)
|
||||
- ([r1376](https://github.com/Suwayomi/Suwayomi-WebUI/commit/c483e71bf1275c298979111ac244c2ff99794c20)) Remove automatic manga update ([#583](https://github.com/Suwayomi/Suwayomi-WebUI/pull/583) by @schroda)
|
||||
- ([r1375](https://github.com/Suwayomi/Suwayomi-WebUI/commit/d6468bca57e9cdfd69531fb1e994a4bbc16a42bc)) Add manga migrate option to menu on mobile devices ([#584](https://github.com/Suwayomi/Suwayomi-WebUI/pull/584) by @schroda)
|
||||
- ([r1374](https://github.com/Suwayomi/Suwayomi-WebUI/commit/6716339c0e754bd5aa3fc5bea4484354c6fb4697)) Set default value for resetting to 50% ([#585](https://github.com/Suwayomi/Suwayomi-WebUI/pull/585) by @schroda)
|
||||
- ([r1373](https://github.com/Suwayomi/Suwayomi-WebUI/commit/891d6f4165a18656e5fc6e040ead218f8675dde0)) Fix/manga migration opening search twice ([#586](https://github.com/Suwayomi/Suwayomi-WebUI/pull/586) by @schroda)
|
||||
- ([r1372](https://github.com/Suwayomi/Suwayomi-WebUI/commit/10ec7de628978eec481a829a119da80a8af6e86e)) Correctly calculate width ([#578](https://github.com/Suwayomi/Suwayomi-WebUI/pull/578) by @schroda)
|
||||
- ([r1371](https://github.com/Suwayomi/Suwayomi-WebUI/commit/f335b59dca5af5f82a62ab8db0b88734a0e53097)) Actually send library db cleanup mutation ([#577](https://github.com/Suwayomi/Suwayomi-WebUI/pull/577) by @schroda)
|
||||
- ([r1370](https://github.com/Suwayomi/Suwayomi-WebUI/commit/18ca66cb4046128cd24fd0c916d2c27a4ec62fdf)) Feature/settings add flaresolverr ([#568](https://github.com/Suwayomi/Suwayomi-WebUI/pull/568) by @schroda)
|
||||
- ([r1369](https://github.com/Suwayomi/Suwayomi-WebUI/commit/771d6beb18dbecc3a3723eace34fe1f121385396)) Add missing gap to VerticalReader mode with fit to window setting ([#574](https://github.com/Suwayomi/Suwayomi-WebUI/pull/574) by @schroda)
|
||||
- ([r1368](https://github.com/Suwayomi/Suwayomi-WebUI/commit/ab4cddfedde67aa3c287b10e8a81c300ffcc808f)) Force a reconnect in case a heartbeat is missing ([#569](https://github.com/Suwayomi/Suwayomi-WebUI/pull/569) by @schroda)
|
||||
- ([r1367](https://github.com/Suwayomi/Suwayomi-WebUI/commit/15825fbe530bf1de58565db63545341b84225da9)) Prevent pages from being bigger than the 100% in width ([#573](https://github.com/Suwayomi/Suwayomi-WebUI/pull/573) by @schroda)
|
||||
- ([r1366](https://github.com/Suwayomi/Suwayomi-WebUI/commit/2dab88eb7ef26eba29c96c9bbf6ef13fce95eea6)) Decrease default "reader width" to 50% ([#576](https://github.com/Suwayomi/Suwayomi-WebUI/pull/576) by @schroda)
|
||||
- ([r1365](https://github.com/Suwayomi/Suwayomi-WebUI/commit/4d75474b39b1fda4c3e33e1fd58fce6df1e8db30)) Fix reader width ([#567](https://github.com/Suwayomi/Suwayomi-WebUI/pull/567) by @chancez, @schroda)
|
||||
- ([r1364](https://github.com/Suwayomi/Suwayomi-WebUI/commit/e0c5e0521dbb56e3cfa2d7b3738908acf250feab)) Feature/manga migration ([#536](https://github.com/Suwayomi/Suwayomi-WebUI/pull/536) by @schroda)
|
||||
- ([r1363](https://github.com/Suwayomi/Suwayomi-WebUI/commit/5224ad139168220bf141dc3e9e513dd73f27722f)) Add missing id to request ([#571](https://github.com/Suwayomi/Suwayomi-WebUI/pull/571) by @schroda)
|
||||
- ([r1362](https://github.com/Suwayomi/Suwayomi-WebUI/commit/6edf3bad64b55c61bbd0f5d0f8baf6e18cd688a1)) [ESLint] Allow zero warnings ([#575](https://github.com/Suwayomi/Suwayomi-WebUI/pull/575) by @schroda)
|
||||
- ([r1361](https://github.com/Suwayomi/Suwayomi-WebUI/commit/5032a0ae94d32fdf9e6d4239df9e5e448a1719cf)) Make reader width configurable ([#565](https://github.com/Suwayomi/Suwayomi-WebUI/pull/565) by @chancez)
|
||||
- ([r1360](https://github.com/Suwayomi/Suwayomi-WebUI/commit/dcbf5a1899c415905be93b428faf93a4a87f7d0b)) Fix/library manga selection type error ([#566](https://github.com/Suwayomi/Suwayomi-WebUI/pull/566) by @schroda)
|
||||
- ([r1359](https://github.com/Suwayomi/Suwayomi-WebUI/commit/69a62b6c2cfd32033e9b546d2005af9cd4c961ca)) Add webUI settings again ([#564](https://github.com/Suwayomi/Suwayomi-WebUI/pull/564) by @schroda)
|
||||
- ([r1358](https://github.com/Suwayomi/Suwayomi-WebUI/commit/24379c1e46ce87aaa007b35529c80396d4d04af2)) Infinitely try to reconnect gql subscriptions ([#563](https://github.com/Suwayomi/Suwayomi-WebUI/pull/563) by @schroda)
|
||||
- ([r1357](https://github.com/Suwayomi/Suwayomi-WebUI/commit/05f57730db4901d3b20b1e33f6b41afcc13baebf)) Support configuring automatic downloads by category ([#562](https://github.com/Suwayomi/Suwayomi-WebUI/pull/562) by @chancez)
|
||||
- ([r1356](https://github.com/Suwayomi/Suwayomi-WebUI/commit/de596426bf705118a5f1d8d4da7d3d0942251d67)) [Codegen] Update generated files ([#561](https://github.com/Suwayomi/Suwayomi-WebUI/pull/561) by @schroda)
|
||||
- ([r1355](https://github.com/Suwayomi/Suwayomi-WebUI/commit/f8e1bfc401ac4869e8d5e790d526cfa059680a42)) Correctly change the category of a manga from the library ([#560](https://github.com/Suwayomi/Suwayomi-WebUI/pull/560) by @schroda)
|
||||
- ([r1354](https://github.com/Suwayomi/Suwayomi-WebUI/commit/1c2a196950553b365cacfdb7cb2974aac41558fe)) Fix/adding manga to library not updating category ([#559](https://github.com/Suwayomi/Suwayomi-WebUI/pull/559) by @schroda)
|
||||
- ([r1353](https://github.com/Suwayomi/Suwayomi-WebUI/commit/e10cbf9e8f0f133ce84a9b647a919afac361df38)) Add extension settings screen ([#557](https://github.com/Suwayomi/Suwayomi-WebUI/pull/557) by @schroda)
|
||||
- ([r1352](https://github.com/Suwayomi/Suwayomi-WebUI/commit/f0e1bd32493e6ea68bcd759a817f3200acc1dd2b)) Feature/extension list show info when no repo is defined ([#556](https://github.com/Suwayomi/Suwayomi-WebUI/pull/556) by @schroda)
|
||||
- ([r1351](https://github.com/Suwayomi/Suwayomi-WebUI/commit/42e3d64f80fc5de93a464c758ecbb46d75bf9e61)) Clear extensions cache after extension repos change ([#555](https://github.com/Suwayomi/Suwayomi-WebUI/pull/555) by @schroda)
|
||||
- ([r1350](https://github.com/Suwayomi/Suwayomi-WebUI/commit/bed586383f5b9f5720a91a8ac0ab5de987b8be8a)) Update extension repo regex to server changes ([#554](https://github.com/Suwayomi/Suwayomi-WebUI/pull/554) by @schroda)
|
||||
- ([r1349](https://github.com/Suwayomi/Suwayomi-WebUI/commit/6708a958cebcc7d94ae3f860d4ad00580db3e9f0)) Render selection fab in case only one category exists ([#553](https://github.com/Suwayomi/Suwayomi-WebUI/pull/553) by @schroda)
|
||||
- ([r1348](https://github.com/Suwayomi/Suwayomi-WebUI/commit/38b364bb550473697b2c6a7729a5942c44047f53)) Remove reader webtoon mode page gaps ([#552](https://github.com/Suwayomi/Suwayomi-WebUI/pull/552) by @schroda)
|
||||
- ([r1347](https://github.com/Suwayomi/Suwayomi-WebUI/commit/738a22233bc1cf7fd3bdeb542ac57b73814f3674)) Internationalize failed img retry text ([#551](https://github.com/Suwayomi/Suwayomi-WebUI/pull/551) by @schroda)
|
||||
- ([r1346](https://github.com/Suwayomi/Suwayomi-WebUI/commit/199b62341fb8c54ca17924b77ae56e21d61c2c33)) Feature/add retry button for failed image requests ([#550](https://github.com/Suwayomi/Suwayomi-WebUI/pull/550) by @schroda)
|
||||
- ([r1345](https://github.com/Suwayomi/Suwayomi-WebUI/commit/9962e0713d4753782dad65b56c34c3ea4a81977c)) Adding page loading with Double Page Mode. ([#480](https://github.com/Suwayomi/Suwayomi-WebUI/pull/480) by @rickymcmuffin, @schroda)
|
||||
- ([r1344](https://github.com/Suwayomi/Suwayomi-WebUI/commit/be5497af94a18336b52c892dbc714ecb24461969)) Add new library sort options ([#547](https://github.com/Suwayomi/Suwayomi-WebUI/pull/547) by @schroda)
|
||||
- ([r1343](https://github.com/Suwayomi/Suwayomi-WebUI/commit/3973eda0897e2a1f080d2280b606d069faa3b27d)) [ServerMapping][Codegen] Update to latest server gql MangaType changes ([#546](https://github.com/Suwayomi/Suwayomi-WebUI/pull/546) by @schroda)
|
||||
- ([r1342](https://github.com/Suwayomi/Suwayomi-WebUI/commit/955cc682fd7186587acd90b00fb929da0e1cdbba)) Apply filters when searching in SourceMangas ([#545](https://github.com/Suwayomi/Suwayomi-WebUI/pull/545) by @schroda)
|
||||
- ([r1341](https://github.com/Suwayomi/Suwayomi-WebUI/commit/89e8472ebc6edc50ea9af74429f378394687abda)) Add disclaimer to custom repositories setting ([#544](https://github.com/Suwayomi/Suwayomi-WebUI/pull/544) by @schroda)
|
||||
- ([r1340](https://github.com/Suwayomi/Suwayomi-WebUI/commit/fd1fa63064bd91b61de54947e68f50ae5aa8a59a)) Feature/show extension repo only in case more than one repo is set ([#543](https://github.com/Suwayomi/Suwayomi-WebUI/pull/543) by @schroda)
|
||||
- ([r1339](https://github.com/Suwayomi/Suwayomi-WebUI/commit/684ae69470af71b74c746bd59264ca6f99cdd568)) Update tokens ([#542](https://github.com/Suwayomi/Suwayomi-WebUI/pull/542) by @schroda)
|
||||
- ([r1338](https://github.com/Suwayomi/Suwayomi-WebUI/commit/1b3cc6bfe3857ba808f93089839c7e15851633e7)) Translations update from Hosted Weblate ([#541](https://github.com/Suwayomi/Suwayomi-WebUI/pull/541) by @weblate, @zmmx)
|
||||
- ([r1337](https://github.com/Suwayomi/Suwayomi-WebUI/commit/8faa756ab51ac6200424727998defba001803e40)) Feature/improve custom extension repos support ([#540](https://github.com/Suwayomi/Suwayomi-WebUI/pull/540) by @schroda)
|
||||
- ([r1336](https://github.com/Suwayomi/Suwayomi-WebUI/commit/39b79c6ebef0f71f64c51f69e9e1c1f22c09f1a7)) Feature/settings support custom extension repos ([#539](https://github.com/Suwayomi/Suwayomi-WebUI/pull/539) by @schroda)
|
||||
- ([r1335](https://github.com/Suwayomi/Suwayomi-WebUI/commit/b42c3103f1c62f2eb4151ded7f3025b25ff26c4c)) Feature/rebrand to suwayomi ([#500](https://github.com/Suwayomi/Suwayomi-WebUI/pull/500) by @schroda)
|
||||
- ([r1334](https://github.com/Suwayomi/Suwayomi-WebUI/commit/779dafe1dc04f1b6e9ee37e656f90e82542e58fd)) Pass correct group sizes to "GroupedVirtuoso" ([#537](https://github.com/Suwayomi/Suwayomi-WebUI/pull/537) by @schroda)
|
||||
- ([r1333](https://github.com/Suwayomi/Suwayomi-WebUI/commit/db65b9df44521cb78e76c64328a308e071b65aac)) Feature/merge source and extensions screen on desktop ([#535](https://github.com/Suwayomi/Suwayomi-WebUI/pull/535) by @schroda)
|
||||
- ([r1332](https://github.com/Suwayomi/Suwayomi-WebUI/commit/9caca6e753a8217e9928f0d4ca6d4fe5f893295a)) Update dependencies ([#534](https://github.com/Suwayomi/Suwayomi-WebUI/pull/534) by @schroda)
|
||||
- ([r1331](https://github.com/Suwayomi/Suwayomi-WebUI/commit/6c46c562522b443d5194496203752dc67e40348f)) Handle showing disabled state of automatic chapter deletion ([#533](https://github.com/Suwayomi/Suwayomi-WebUI/pull/533) by @schroda)
|
||||
- ([r1330](https://github.com/Suwayomi/Suwayomi-WebUI/commit/05fa3d0732d660b92ad125fd9bead7cab8a312d7)) Fix/chapter not getting deleted after being read ([#532](https://github.com/Suwayomi/Suwayomi-WebUI/pull/532) by @schroda)
|
||||
- ([r1329](https://github.com/Suwayomi/Suwayomi-WebUI/commit/7d3d82556a33109d7d823175189b6b5f43ad4b87)) Handle extension update failure ([#530](https://github.com/Suwayomi/Suwayomi-WebUI/pull/530) by @schroda)
|
||||
- ([r1328](https://github.com/Suwayomi/Suwayomi-WebUI/commit/37ce494fda66f0898f4fb7738e928b1f45bf0fd4)) Log promise failures instead of ignoring them ([#531](https://github.com/Suwayomi/Suwayomi-WebUI/pull/531) by @schroda)
|
||||
- ([r1327](https://github.com/Suwayomi/Suwayomi-WebUI/commit/37d6b84cf41ceae1a6679c3fdfdc89e53cd348dc)) Use correct titles for manga actions in selection mode ([#529](https://github.com/Suwayomi/Suwayomi-WebUI/pull/529) by @schroda)
|
||||
- ([r1326](https://github.com/Suwayomi/Suwayomi-WebUI/commit/edc2a62a7605209118eaaa778f0ef5fa21b29eb9)) Feature/cleanup files ([#528](https://github.com/Suwayomi/Suwayomi-WebUI/pull/528) by @schroda)
|
||||
- ([r1325](https://github.com/Suwayomi/Suwayomi-WebUI/commit/ecea80f41e8962fb5636cfbe9d1d878dd9f24b63)) Merge manga action menus ([#527](https://github.com/Suwayomi/Suwayomi-WebUI/pull/527) by @schroda)
|
||||
- ([r1324](https://github.com/Suwayomi/Suwayomi-WebUI/commit/a3a52064ccaac827ada5c64865de6acc95e6016e)) Feature/cleanup chapter actions ([#525](https://github.com/Suwayomi/Suwayomi-WebUI/pull/525) by @schroda)
|
||||
- ([r1323](https://github.com/Suwayomi/Suwayomi-WebUI/commit/d15be603b1afde980acd4d53ed3a03a3178f3e8e)) Use up-to-date manga data for selection fab actions ([#526](https://github.com/Suwayomi/Suwayomi-WebUI/pull/526) by @schroda)
|
||||
- ([r1322](https://github.com/Suwayomi/Suwayomi-WebUI/commit/356303ab5adf0f33f3dcfa5c24fb821268b82842)) Allow browser context menu for images in reader ([#524](https://github.com/Suwayomi/Suwayomi-WebUI/pull/524) by @schroda)
|
||||
- ([r1321](https://github.com/Suwayomi/Suwayomi-WebUI/commit/120e97e882a9a12552363b5ba97965c9c2700669)) Feature/restore backup inform about missing sources ([#523](https://github.com/Suwayomi/Suwayomi-WebUI/pull/523) by @schroda)
|
||||
- ([r1320](https://github.com/Suwayomi/Suwayomi-WebUI/commit/a1dca02feac1d0952e8bd4923c1539c9e9d268c9)) Add missing extension key field to mutation result ([#522](https://github.com/Suwayomi/Suwayomi-WebUI/pull/522) by @schroda)
|
||||
- ([r1319](https://github.com/Suwayomi/Suwayomi-WebUI/commit/f81b20b4fdec6956466e1adf60a999def4a2b174)) Fix/library continue read button causes page refresh ([#521](https://github.com/Suwayomi/Suwayomi-WebUI/pull/521) by @schroda)
|
||||
- ([r1318](https://github.com/Suwayomi/Suwayomi-WebUI/commit/f12f025e9d8850dd978cd9f8f482928581a63765)) Add option to remove non library mangas from categories ([#520](https://github.com/Suwayomi/Suwayomi-WebUI/pull/520) by @schroda)
|
||||
- ([r1317](https://github.com/Suwayomi/Suwayomi-WebUI/commit/e025efb9dc8a04d208b7549c121259d93832be92)) Feature/add manga to library category select dialog ([#519](https://github.com/Suwayomi/Suwayomi-WebUI/pull/519) by @schroda)
|
||||
- ([r1316](https://github.com/Suwayomi/Suwayomi-WebUI/commit/1345cfe62498d0e859f62fc09679295080250578)) Update manga category selection in case categories changed ([#518](https://github.com/Suwayomi/Suwayomi-WebUI/pull/518) by @schroda)
|
||||
- ([r1315](https://github.com/Suwayomi/Suwayomi-WebUI/commit/446deeae06b4c8f5f189685969105b185043f0a7)) Feature/library manga actions ([#506](https://github.com/Suwayomi/Suwayomi-WebUI/pull/506) by @schroda)
|
||||
- ([r1314](https://github.com/Suwayomi/Suwayomi-WebUI/commit/980da657d99dd8d01fed82b5c80be7e3b01d0429)) [Codegen] Check cache before executing query for a single item ([#513](https://github.com/Suwayomi/Suwayomi-WebUI/pull/513) by @schroda)
|
||||
- ([r1313](https://github.com/Suwayomi/Suwayomi-WebUI/commit/c115fcd5739ed17c3de26f5146c6d6e2a3381cda)) Feature/make selection logic reusable ([#515](https://github.com/Suwayomi/Suwayomi-WebUI/pull/515) by @schroda)
|
||||
- ([r1312](https://github.com/Suwayomi/Suwayomi-WebUI/commit/c8747d6db4bac16915b41e742f9f78a43919107c)) Use correct key to normalize extensions ([#512](https://github.com/Suwayomi/Suwayomi-WebUI/pull/512) by @schroda)
|
||||
- ([r1311](https://github.com/Suwayomi/Suwayomi-WebUI/commit/0615ef87ce03e4d9549c88cdedc052d1b164f762)) Add divider between library tabs and mangas ([#514](https://github.com/Suwayomi/Suwayomi-WebUI/pull/514) by @schroda)
|
||||
- ([r1310](https://github.com/Suwayomi/Suwayomi-WebUI/commit/2390361fb2b63fbe0726c93c4ac3a4afd04e29d2)) [Codegen] Request manga download count with chapter deletion mutation ([#516](https://github.com/Suwayomi/Suwayomi-WebUI/pull/516) by @schroda)
|
||||
- ([r1309](https://github.com/Suwayomi/Suwayomi-WebUI/commit/168e8f82f6f44c277f79467f2eb92aa3fc21a31b)) Use "Footer" to prevent fab overlapping the last item ([#517](https://github.com/Suwayomi/Suwayomi-WebUI/pull/517) by @schroda)
|
||||
- ([r1308](https://github.com/Suwayomi/Suwayomi-WebUI/commit/09d1cf91e002db1667881648d216a690a0d4f516)) Prevent navigation state update in case path already changed ([#511](https://github.com/Suwayomi/Suwayomi-WebUI/pull/511) by @schroda)
|
||||
- ([r1307](https://github.com/Suwayomi/Suwayomi-WebUI/commit/e4beafb11f1ee9dd4f464e7d627e40d58dad2115)) Cancel the navigation state update correctly ([#507](https://github.com/Suwayomi/Suwayomi-WebUI/pull/507) by @schroda)
|
||||
- ([r1306](https://github.com/Suwayomi/Suwayomi-WebUI/commit/e966b0328bbee4bd91bd6f9ea13881bdfd31cbb3)) Remove unnecessary query refetches with mutations ([#508](https://github.com/Suwayomi/Suwayomi-WebUI/pull/508) by @schroda)
|
||||
- ([r1305](https://github.com/Suwayomi/Suwayomi-WebUI/commit/3756b65256840e9bef6c277b73ddce977427800e)) Correctly check for dev env ([#509](https://github.com/Suwayomi/Suwayomi-WebUI/pull/509) by @schroda)
|
||||
- ([r1304](https://github.com/Suwayomi/Suwayomi-WebUI/commit/f4e84412389af1332bc374856d1b44904d7b6270)) Prevent ApolloError handling the manga category mutation result ([#510](https://github.com/Suwayomi/Suwayomi-WebUI/pull/510) by @schroda)
|
||||
- ([r1303](https://github.com/Suwayomi/Suwayomi-WebUI/commit/2f8c284c8b09298fb56182e8d7d7438a49180abf)) Add continue read button to library ([#505](https://github.com/Suwayomi/Suwayomi-WebUI/pull/505) by @schroda)
|
||||
- ([r1302](https://github.com/Suwayomi/Suwayomi-WebUI/commit/663e20fff9d81f381ce7b56a89b3b484a460de5c)) Visualize read chapters in the update list ([#504](https://github.com/Suwayomi/Suwayomi-WebUI/pull/504) by @schroda)
|
||||
- ([r1301](https://github.com/Suwayomi/Suwayomi-WebUI/commit/953ebbe5a639e737776ae3e4fc29581ce493f616)) Add button to mark all chapters as read ([#503](https://github.com/Suwayomi/Suwayomi-WebUI/pull/503) by @schroda)
|
||||
- ([r1300](https://github.com/Suwayomi/Suwayomi-WebUI/commit/5010b706fdce5746bf20b47c7bd5e77c88b5b15e)) Add button to download all chapters ([#503](https://github.com/Suwayomi/Suwayomi-WebUI/pull/503) by @schroda)
|
||||
- ([r1299](https://github.com/Suwayomi/Suwayomi-WebUI/commit/90cec353586f557337f2450f5067ab15a94a2275)) Add button to quickly select all chapters ([#503](https://github.com/Suwayomi/Suwayomi-WebUI/pull/503) by @schroda)
|
||||
- ([r1298](https://github.com/Suwayomi/Suwayomi-WebUI/commit/226f2e170f0c82dad81eedc22e00a8d166dc8906)) Handle line breaks in the manga description ([#502](https://github.com/Suwayomi/Suwayomi-WebUI/pull/502) by @schroda)
|
||||
- ([r1297](https://github.com/Suwayomi/Suwayomi-WebUI/commit/1300c10d83c62a05d36320e143f284c4a1263a25)) Remove unnecessary library refetch ([#499](https://github.com/Suwayomi/Suwayomi-WebUI/pull/499) by @schroda)
|
||||
- ([r1296](https://github.com/Suwayomi/Suwayomi-WebUI/commit/12b6700110abab6205349960fb3a8f6428a3ba6f)) [VersionMapping] Require server version "r1438" for preview ([#498](https://github.com/Suwayomi/Suwayomi-WebUI/pull/498) by @schroda)
|
||||
- ([r1295](https://github.com/Suwayomi/Suwayomi-WebUI/commit/f542c17fd682681bb5146ee5ef0e8629a83fa820)) Update download subscription to server changes ([#498](https://github.com/Suwayomi/Suwayomi-WebUI/pull/498) by @schroda)
|
||||
- ([r1294](https://github.com/Suwayomi/Suwayomi-WebUI/commit/62568ce76b82333132ee7ca567dabe86dbcba1f8)) Translations update from Hosted Weblate ([#424](https://github.com/Suwayomi/Suwayomi-WebUI/pull/424) by @weblate, @alexandrejournet, @ibaraki-douji, @nitezs, @misaka10843, @Becods)
|
||||
- ([r1293](https://github.com/Suwayomi/Suwayomi-WebUI/commit/214043fe9d77726641e7224705aaa7cace428c43)) Fix/script changelog creation ([#496](https://github.com/Suwayomi/Suwayomi-WebUI/pull/496) by @schroda)
|
||||
- ([r1292](https://github.com/Suwayomi/Suwayomi-WebUI/commit/1dc60af67c1df2b49b864a8ea6c93b6ad48150ba)) Add logic to reorder downloads ([#495](https://github.com/Suwayomi/Suwayomi-WebUI/pull/495) by @schroda)
|
||||
- ([r1291](https://github.com/Suwayomi/Suwayomi-WebUI/commit/b5f86ae6097d18048c5b4ef8fd5622460a32c31b)) Feature/virtualize download queue ([#494](https://github.com/Suwayomi/Suwayomi-WebUI/pull/494) by @schroda)
|
||||
- ([r1290](https://github.com/Suwayomi/Suwayomi-WebUI/commit/abee8c7c55c7d0ebc4b39685f4a41912de9a9f71)) Use virtuoso grid state to restore the previous scroll position ([#492](https://github.com/Suwayomi/Suwayomi-WebUI/pull/492) by @schroda)
|
||||
- ([r1289](https://github.com/Suwayomi/Suwayomi-WebUI/commit/b6b902797e2b9b82d2fd5d45c003dee887dd96a9)) Scroll to top when changing page ([#493](https://github.com/Suwayomi/Suwayomi-WebUI/pull/493) by @schroda)
|
||||
- ([r1288](https://github.com/Suwayomi/Suwayomi-WebUI/commit/c51897ed54f729470f3661e2f847aad998360368)) Feature/download queue clear queue ([#490](https://github.com/Suwayomi/Suwayomi-WebUI/pull/490) by @schroda)
|
||||
- ([r1287](https://github.com/Suwayomi/Suwayomi-WebUI/commit/7d574a29f6d01e9ce437c10480baf09104aac19e)) Correctly calculate the remaining time till the next update check ([#491](https://github.com/Suwayomi/Suwayomi-WebUI/pull/491) by @schroda)
|
||||
- ([r1286](https://github.com/Suwayomi/Suwayomi-WebUI/commit/9cb72243d6e1bdf464cf2f4e2c11d829622f50c6)) Automatically check for server updates ([#489](https://github.com/Suwayomi/Suwayomi-WebUI/pull/489) by @schroda)
|
||||
- ([r1285](https://github.com/Suwayomi/Suwayomi-WebUI/commit/78310496aa7483f65787b4022662bf662579d653)) Feature/about screen add option to check for and trigger updates ([#485](https://github.com/Suwayomi/Suwayomi-WebUI/pull/485) by @schroda)
|
||||
- ([r1284](https://github.com/Suwayomi/Suwayomi-WebUI/commit/2caf88ce51d071a8fff0c53d302b0309dff1abff)) Remove incorrect "ListItemSecondaryAction" usage ([#486](https://github.com/Suwayomi/Suwayomi-WebUI/pull/486) by @schroda)
|
||||
- ([r1283](https://github.com/Suwayomi/Suwayomi-WebUI/commit/fbf627b3aaea47ea88047b6c03804726f2983ec1)) Add option to clear the server cache ([#487](https://github.com/Suwayomi/Suwayomi-WebUI/pull/487) by @schroda)
|
||||
- ([r1282](https://github.com/Suwayomi/Suwayomi-WebUI/commit/db5d3ff7c0062257f7096df906bdcee84a98c17e)) Remove "directLink" prop ([#488](https://github.com/Suwayomi/Suwayomi-WebUI/pull/488) by @schroda)
|
||||
- ([r1281](https://github.com/Suwayomi/Suwayomi-WebUI/commit/ad0f0726517197000e2fcd90d81254a384897e0b)) Feature/automatic chapter deletion more options ([#484](https://github.com/Suwayomi/Suwayomi-WebUI/pull/484) by @schroda)
|
||||
- ([r1280](https://github.com/Suwayomi/Suwayomi-WebUI/commit/ee03c56684aafb60e82b835c9d11f7a9671daaf8)) Fix/mark previous as read action includes the selected chapter ([#483](https://github.com/Suwayomi/Suwayomi-WebUI/pull/483) by @schroda)
|
||||
- ([r1279](https://github.com/Suwayomi/Suwayomi-WebUI/commit/b2e6c040f154e7ae79bc41cf0e8b3e7d11113ae6)) [i18n] Format text to local lowercase ([#481](https://github.com/Suwayomi/Suwayomi-WebUI/pull/481) by @schroda)
|
||||
- ([r1278](https://github.com/Suwayomi/Suwayomi-WebUI/commit/259d1d87df38b617a58ae2e0a19df30d4dc85cd7)) Show info about hosted WebUI in "About" ([#482](https://github.com/Suwayomi/Suwayomi-WebUI/pull/482) by @schroda)
|
||||
- ([r1277](https://github.com/Suwayomi/Suwayomi-WebUI/commit/ee9811bf53d00a8117b760a392d769870f3efb38)) Correctly detect keyboard input "Enter" ([#479](https://github.com/Suwayomi/Suwayomi-WebUI/pull/479) by @schroda)
|
||||
- ([r1276](https://github.com/Suwayomi/Suwayomi-WebUI/commit/c6cc7c14dd42eaa6b95339f8d798f3fe028b1df1)) Update the "lastRunningState" in case update was triggered outside of app ([#478](https://github.com/Suwayomi/Suwayomi-WebUI/pull/478) by @schroda)
|
||||
- ([r1275](https://github.com/Suwayomi/Suwayomi-WebUI/commit/47b077cbf5f3d193f0f11b501f8244395c2dbf4e)) Feature/update dependencies ([#477](https://github.com/Suwayomi/Suwayomi-WebUI/pull/477) by @schroda)
|
||||
- ([r1274](https://github.com/Suwayomi/Suwayomi-WebUI/commit/5aa7fc0f9fcf381739266715f37a1bdb22627a1f)) Remove icon for library search filter setting ([#476](https://github.com/Suwayomi/Suwayomi-WebUI/pull/476) by @schroda)
|
||||
- ([r1273](https://github.com/Suwayomi/Suwayomi-WebUI/commit/2006ca910954f6b78d0f1cc4ad18a40dd75b7af1)) Feature/handle disabled download ahead limit by default ([#475](https://github.com/Suwayomi/Suwayomi-WebUI/pull/475) by @schroda)
|
||||
- ([r1272](https://github.com/Suwayomi/Suwayomi-WebUI/commit/6b2245d730eececc6e462b1b771cd676a45e1eaa)) Move the last update timestamp to the body ([#472](https://github.com/Suwayomi/Suwayomi-WebUI/pull/472) by @schroda)
|
||||
- ([r1271](https://github.com/Suwayomi/Suwayomi-WebUI/commit/7c69a4a5a1b2d49d23a00b17c6624f49222664fb)) Feature/global update last timestamp use stale data while fetching ([#473](https://github.com/Suwayomi/Suwayomi-WebUI/pull/473) by @schroda)
|
||||
- ([r1270](https://github.com/Suwayomi/Suwayomi-WebUI/commit/a4bd44de9ed4197366cbcf2360293ea41a01523f)) Correctly merge chapter requests ([#474](https://github.com/Suwayomi/Suwayomi-WebUI/pull/474) by @schroda)
|
||||
- ([r1269](https://github.com/Suwayomi/Suwayomi-WebUI/commit/aa01d6712b4ac4a66b3a134d77f0c1252a50dd16)) Update extensions list after extension update ([#471](https://github.com/Suwayomi/Suwayomi-WebUI/pull/471) by @schroda)
|
||||
- ([r1268](https://github.com/Suwayomi/Suwayomi-WebUI/commit/7adc28a35c074fb134c2d760f91601c2fc5546a6)) Feature/gql improve queries mutations subscriptions ([#470](https://github.com/Suwayomi/Suwayomi-WebUI/pull/470) by @schroda)
|
||||
- ([r1267](https://github.com/Suwayomi/Suwayomi-WebUI/commit/3b147b1b46a7b279ee961861fb1874867667602c)) Fix/update gql after server changes ([#469](https://github.com/Suwayomi/Suwayomi-WebUI/pull/469) by @schroda)
|
||||
- ([r1266](https://github.com/Suwayomi/Suwayomi-WebUI/commit/3766540ce59d19918cfa2f7b8e1454782087d71f)) Add WebUI settings ([#460](https://github.com/Suwayomi/Suwayomi-WebUI/pull/460) by @schroda)
|
||||
- ([r1265](https://github.com/Suwayomi/Suwayomi-WebUI/commit/cdf922945764eb26d7cdba5007f5fb8d1d44bbbd)) Feature/global update show last update time ([#468](https://github.com/Suwayomi/Suwayomi-WebUI/pull/468) by @schroda)
|
||||
- ([r1264](https://github.com/Suwayomi/Suwayomi-WebUI/commit/99ba45ecebb3f1c8bc2735cf8499bc73f38432f4)) Use mui tooltip for manga titles ([#467](https://github.com/Suwayomi/Suwayomi-WebUI/pull/467) by @schroda)
|
||||
- ([r1263](https://github.com/Suwayomi/Suwayomi-WebUI/commit/dbb4bf70af21ea74a4a92e54bdf2dc4a0495f2a9)) Use correct local source header in settings ([#466](https://github.com/Suwayomi/Suwayomi-WebUI/pull/466) by @schroda)
|
||||
- ([r1262](https://github.com/Suwayomi/Suwayomi-WebUI/commit/d12ac27b617d67c41e7ed5d3ba936ce4fca47761)) Feature/download ahead while reading ([#464](https://github.com/Suwayomi/Suwayomi-WebUI/pull/464) by @schroda)
|
||||
- ([r1261](https://github.com/Suwayomi/Suwayomi-WebUI/commit/e26907e2cea21ba113333e34c4433568aa0a8ecf)) Prevent TypeError when loading next chapter after last page ([#463](https://github.com/Suwayomi/Suwayomi-WebUI/pull/463) by @schroda)
|
||||
- ([r1260](https://github.com/Suwayomi/Suwayomi-WebUI/commit/7db5435967a3d41abc976605a501cc688be1d873)) Remove "useCache" query from image requests ([#465](https://github.com/Suwayomi/Suwayomi-WebUI/pull/465) by @schroda)
|
||||
- ([r1259](https://github.com/Suwayomi/Suwayomi-WebUI/commit/197ee5c94bb4153f8b4db2678a7f38c3a48c4cef)) Persist server settings when disabling them ([#462](https://github.com/Suwayomi/Suwayomi-WebUI/pull/462) by @schroda)
|
||||
- ([r1258](https://github.com/Suwayomi/Suwayomi-WebUI/commit/1fd9b4e74459a41ed8502700bf717262763670f6)) Disable disallowed settings ([#461](https://github.com/Suwayomi/Suwayomi-WebUI/pull/461) by @schroda)
|
||||
- ([r1257](https://github.com/Suwayomi/Suwayomi-WebUI/commit/eda682d9abe2bd74c7fcfeaf622880c99e4d5a94)) Remove deprecated cache setting ([#459](https://github.com/Suwayomi/Suwayomi-WebUI/pull/459) by @schroda)
|
||||
- ([r1256](https://github.com/Suwayomi/Suwayomi-WebUI/commit/cac60fed2c60c2ec90ba7dc2240897993df0a475)) Feature/server settings ([#458](https://github.com/Suwayomi/Suwayomi-WebUI/pull/458) by @schroda)
|
||||
- ([r1255](https://github.com/Suwayomi/Suwayomi-WebUI/commit/482db4626a7c13af4f769daa82807673a8bc4301)) Prevent infinite re-renders in extensions ([#457](https://github.com/Suwayomi/Suwayomi-WebUI/pull/457) by @schroda)
|
||||
- ([r1254](https://github.com/Suwayomi/Suwayomi-WebUI/commit/8d4687428fac1f078a1c2acab1abe4c0b13054a5)) Fix/app search ([#456](https://github.com/Suwayomi/Suwayomi-WebUI/pull/456) by @schroda)
|
||||
- ([r1253](https://github.com/Suwayomi/Suwayomi-WebUI/commit/35c34b6d1b7609acb59cce94a46ce2ca4dab7536)) Feature/search bar improvements ([#455](https://github.com/Suwayomi/Suwayomi-WebUI/pull/455) by @schroda)
|
||||
- ([r1252](https://github.com/Suwayomi/Suwayomi-WebUI/commit/0c7498dba61da0ff3faec41124f236834d90ba5d)) Prevent pages from getting selected while dragging ([#454](https://github.com/Suwayomi/Suwayomi-WebUI/pull/454) by @schroda)
|
||||
- ([r1251](https://github.com/Suwayomi/Suwayomi-WebUI/commit/dabe88385d0ff1017eea6e3df9d620cc6550e33f)) Feature/reader vertical pager keyboard bindings scrolling ([#452](https://github.com/Suwayomi/Suwayomi-WebUI/pull/452) by @schroda)
|
||||
- ([r1250](https://github.com/Suwayomi/Suwayomi-WebUI/commit/9b96560a17b4af243d9990c107c7de765a3a7a48)) Feature/settings backup ([#453](https://github.com/Suwayomi/Suwayomi-WebUI/pull/453) by @schroda)
|
||||
- ([r1249](https://github.com/Suwayomi/Suwayomi-WebUI/commit/b080286b81760eeb953f893b03e2b2df9cc0d84e)) Reduce chapter updates in the reader (by @schroda)
|
||||
- ([r1248](https://github.com/Suwayomi/Suwayomi-WebUI/commit/a0a52b11a1260efc4a1532b34291e22f6bef7933)) Fix/pagination of sources which require pages to be fetched in order ([#451](https://github.com/Suwayomi/Suwayomi-WebUI/pull/451) by @schroda)
|
||||
- ([r1247](https://github.com/Suwayomi/Suwayomi-WebUI/commit/345fbcb5c7322bd55b630489047085872840479b)) Fix/apollo client spamming infinite requets on failure ([#450](https://github.com/Suwayomi/Suwayomi-WebUI/pull/450) by @schroda)
|
||||
- ([r1246](https://github.com/Suwayomi/Suwayomi-WebUI/commit/291e1899f98a96a8c40ee079521fa425c752c7ec)) Remove re-fetching of manga query on chapter update ([#449](https://github.com/Suwayomi/Suwayomi-WebUI/pull/449) by @schroda)
|
||||
- ([r1245](https://github.com/Suwayomi/Suwayomi-WebUI/commit/c5f943d571649462167905b8957933f6def01a71)) Get latest manga data from the apollo cache ([#447](https://github.com/Suwayomi/Suwayomi-WebUI/pull/447) by @schroda)
|
||||
- ([r1244](https://github.com/Suwayomi/Suwayomi-WebUI/commit/593acc7f89e5a2125a13b884d93358bcef141ee0)) Refresh extension list after updating ([#446](https://github.com/Suwayomi/Suwayomi-WebUI/pull/446) by @schroda)
|
||||
- ([r1243](https://github.com/Suwayomi/Suwayomi-WebUI/commit/9284e46c45a8a97237c09cb887266eabd4e050dd)) Prevent SelectionFAB from being behind the "ChapterCard" checkbox ([#445](https://github.com/Suwayomi/Suwayomi-WebUI/pull/445) by @schroda)
|
||||
- ([r1242](https://github.com/Suwayomi/Suwayomi-WebUI/commit/4ab61723e7e3e6ebdc30970572ed81149002b8b9)) Add missing tooltips ([#444](https://github.com/Suwayomi/Suwayomi-WebUI/pull/444) by @schroda)
|
||||
- ([r1241](https://github.com/Suwayomi/Suwayomi-WebUI/commit/d9d92a75fa2bb3f8d45d34b4ae44da67296cdf7a)) Handle source not supporting browse "latest" ([#443](https://github.com/Suwayomi/Suwayomi-WebUI/pull/443) by @schroda)
|
||||
- ([r1240](https://github.com/Suwayomi/Suwayomi-WebUI/commit/134e47763faae9e62db4d4e3a8387a74e32e5568)) Feature/update dependencies ([#442](https://github.com/Suwayomi/Suwayomi-WebUI/pull/442) by @schroda)
|
||||
- ([r1239](https://github.com/Suwayomi/Suwayomi-WebUI/commit/7cb062d4534f568c96bdadbbf9066e6c9923d08d)) Feature/extensions always use fetch mutation to get list ([#440](https://github.com/Suwayomi/Suwayomi-WebUI/pull/440) by @schroda)
|
||||
- ([r1238](https://github.com/Suwayomi/Suwayomi-WebUI/commit/2c35808ee4490acc8bc2e272e29d54443f0b6fe2)) Fetch chapter pages everytime unless chapter is downloaded ([#439](https://github.com/Suwayomi/Suwayomi-WebUI/pull/439) by @schroda)
|
||||
- ([r1237](https://github.com/Suwayomi/Suwayomi-WebUI/commit/a6cc757d5044603e021a90ca89dfd0fa742c53e0)) Feature/global update settings update manga metadata ([#441](https://github.com/Suwayomi/Suwayomi-WebUI/pull/441) by @schroda)
|
||||
- ([r1236](https://github.com/Suwayomi/Suwayomi-WebUI/commit/14e7af9a4aff324f40b56ac864da6577d0af52f3)) Feature/modify download settings ([#429](https://github.com/Suwayomi/Suwayomi-WebUI/pull/429) by @schroda)
|
||||
- ([r1235](https://github.com/Suwayomi/Suwayomi-WebUI/commit/1acda66b1661d608a2b1deaaaa1c14ece344282d)) Feature/update backup restore to server changes ([#438](https://github.com/Suwayomi/Suwayomi-WebUI/pull/438) by @schroda)
|
||||
- ([r1234](https://github.com/Suwayomi/Suwayomi-WebUI/commit/cc59f88a1d87bf2d234a874ad75d1588c845368a)) Correct library error translations ([#437](https://github.com/Suwayomi/Suwayomi-WebUI/pull/437) by @schroda)
|
||||
- ([r1233](https://github.com/Suwayomi/Suwayomi-WebUI/commit/a638333684641dd755b0ed52371ffe176174edde)) Handle source browse when first page is also the last page ([#436](https://github.com/Suwayomi/Suwayomi-WebUI/pull/436) by @schroda)
|
||||
- ([r1232](https://github.com/Suwayomi/Suwayomi-WebUI/commit/529fcf2d258c5fda99c777575c900336b2d9c357)) Mark first chapter as read for "mark previous as read" ([#435](https://github.com/Suwayomi/Suwayomi-WebUI/pull/435) by @schroda)
|
||||
- ([r1231](https://github.com/Suwayomi/Suwayomi-WebUI/commit/4c6e9a8aa128f0a338ac48edc5456a018b5618cf)) Do not add mangas to the default category ([#433](https://github.com/Suwayomi/Suwayomi-WebUI/pull/433) by @schroda)
|
||||
- ([r1230](https://github.com/Suwayomi/Suwayomi-WebUI/commit/678df88068f34a7219b61cf7a4a4746ce4dbf106)) Feature/modify global update settings ([#432](https://github.com/Suwayomi/Suwayomi-WebUI/pull/432) by @schroda)
|
||||
- ([r1229](https://github.com/Suwayomi/Suwayomi-WebUI/commit/4c6d50740500d2823d7990c0192f9aebefea2575)) Feature/show backup restore progress ([#431](https://github.com/Suwayomi/Suwayomi-WebUI/pull/431) by @schroda)
|
||||
- ([r1228](https://github.com/Suwayomi/Suwayomi-WebUI/commit/30fd8b0fca65416db9ee4aa439a227d3d675bb16)) Feature/support new tachiyomi backup file extension ([#430](https://github.com/Suwayomi/Suwayomi-WebUI/pull/430) by @schroda)
|
||||
- ([r1227](https://github.com/Suwayomi/Suwayomi-WebUI/commit/5a5b12a9e10fb7d29622b6333013671efbf53011)) [ESLint] Prefer named exports ([#427](https://github.com/Suwayomi/Suwayomi-WebUI/pull/427) by @schroda)
|
||||
- ([r1226](https://github.com/Suwayomi/Suwayomi-WebUI/commit/68e7b4b16d8cf44d4d50bb77ddb9764c38b1e78d)) Feature/library global update exclude manga with state ([#281](https://github.com/Suwayomi/Suwayomi-WebUI/pull/281) by @schroda)
|
||||
- ([r1225](https://github.com/Suwayomi/Suwayomi-WebUI/commit/c8f02b3b8c70592cea6e9588118a304477e7e40c)) [ESLint] Add "no-unused-imports" plugin ([#426](https://github.com/Suwayomi/Suwayomi-WebUI/pull/426) by @schroda)
|
||||
- ([r1224](https://github.com/Suwayomi/Suwayomi-WebUI/commit/76d1ba835c2e6d976dbcee86aaa584d273115016)) Merge pull request #395 from schroda/feature/use_graphql ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1223](https://github.com/Suwayomi/Suwayomi-WebUI/commit/09c7e804ba2a6f76615ebd3136ab4b490c38e7cc)) Refresh library mangas after update ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1222](https://github.com/Suwayomi/Suwayomi-WebUI/commit/b1aaeb4d5d5ce3f54eb64616563b6a81fcbe84a1)) Show loading text for include/exclude categories setting ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1221](https://github.com/Suwayomi/Suwayomi-WebUI/commit/2ad9ccd9bd4d78a9e5aa7ffc592e748c5f55bfd4)) Use gql for loading default category mangas ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1220](https://github.com/Suwayomi/Suwayomi-WebUI/commit/b6382bb33af231ac37810ea338d44e0f79429d2c)) Load only category mangas that are in the library ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1219](https://github.com/Suwayomi/Suwayomi-WebUI/commit/2297f36efb9aed7cf234cec1ed79d730b0f81102)) Add manga to default categories when adding to library ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1218](https://github.com/Suwayomi/Suwayomi-WebUI/commit/e2fed69c903ac2ec2a1d4fd01261afc20e5d3020)) Optimistically refresh categories on reordering ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1217](https://github.com/Suwayomi/Suwayomi-WebUI/commit/b7e1afd4f445c1c72325f5c93740107a6e4a79ca)) Update refetching queries and evicting cache data ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1216](https://github.com/Suwayomi/Suwayomi-WebUI/commit/5263a1102ccfb07268ebd91037a5b677e832130a)) Rename "doRequestNew" to "doRequest" ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1215](https://github.com/Suwayomi/Suwayomi-WebUI/commit/b3a432ed2e459cad395a9bbef7f1963a0c168af3)) Update typings ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1214](https://github.com/Suwayomi/Suwayomi-WebUI/commit/16ebfcb51f50c20fcfc4b336bf9f1f4ce0966897)) Correctly update extensions language selection ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1213](https://github.com/Suwayomi/Suwayomi-WebUI/commit/fbcc1fb91a6c166e6c9e24d9015bc4ad82250ee3)) Add optional options arg to all requests ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1212](https://github.com/Suwayomi/Suwayomi-WebUI/commit/d3e15e9d3b36b94072b43b9374d14d9753ac0e76)) Use gql for "subscriptions" ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1211](https://github.com/Suwayomi/Suwayomi-WebUI/commit/b79fbcf71dfe9984ad27ff8ed14df84aaafcf041)) Setup graphql subscriptions ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1210](https://github.com/Suwayomi/Suwayomi-WebUI/commit/6a1c302e451e10c9cb67d5b895de8e6c32b8849f)) Remove SWR ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1209](https://github.com/Suwayomi/Suwayomi-WebUI/commit/09f48c2c1734da28ef849c084a41f171d1057077)) Use gql for "backups" ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1208](https://github.com/Suwayomi/Suwayomi-WebUI/commit/e2f34f1f479c067eefa60f9e210734be4040382b)) Use gql for "fetchMore" workaround ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1207](https://github.com/Suwayomi/Suwayomi-WebUI/commit/aad87463f34d8fcbf547d67be5e2d9968156f559)) [Codegen] Use gql for "loading chapters" ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1206](https://github.com/Suwayomi/Suwayomi-WebUI/commit/78049282a770f912d4f28515937a3b7d0cd62658)) Use gql for "sources" - preferences ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1205](https://github.com/Suwayomi/Suwayomi-WebUI/commit/dcead5a801cec5e7cba92ee5effb458a2940a6ba)) Preserve selected filters on browser back navigation ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1204](https://github.com/Suwayomi/Suwayomi-WebUI/commit/12dd973179e8599fd34782b7f958c09d9609343d)) Use gql for "mangas" VI - source mangas filter ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1203](https://github.com/Suwayomi/Suwayomi-WebUI/commit/3297c7d96c62de34335264925c1d8d80fe376de1)) Use gql for "mangas" V - update manga categories ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1202](https://github.com/Suwayomi/Suwayomi-WebUI/commit/87aef85683a680969fc0999a431c09fd21707e06)) Use gql for "mangas" IV - source mangas popular/latest ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1201](https://github.com/Suwayomi/Suwayomi-WebUI/commit/0a73496cbabfd5acb0fe08375948b5a368ab3c6a)) Use gql for "mangas" III - global search ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1200](https://github.com/Suwayomi/Suwayomi-WebUI/commit/af9d49e5e99fa52a6534307f46ec5a630619c9d7)) Use gql for "mangas" II - category mangas ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1199](https://github.com/Suwayomi/Suwayomi-WebUI/commit/89dc053f00f20832c09271b8ed3239078e03df16)) Use gql for "mangas" I - get manga ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1198](https://github.com/Suwayomi/Suwayomi-WebUI/commit/a5b4b95bce8861751a74bf3c121e32f9c19bff46)) Use gql for "updater" ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1197](https://github.com/Suwayomi/Suwayomi-WebUI/commit/222f8f5c9b2af91373fdfbf688dafa4d34eb96a0)) Use gql for "downloader" ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1196](https://github.com/Suwayomi/Suwayomi-WebUI/commit/ca18e750bf83e883714a39e1327d25b6349e5a6f)) Use gql for "categories" ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1195](https://github.com/Suwayomi/Suwayomi-WebUI/commit/f0d55c01c861821e20671155acd1966c4e98b671)) Use gql for "updating chapters" ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1194](https://github.com/Suwayomi/Suwayomi-WebUI/commit/c75e184c5741799c20a76df185762534a3743d06)) Use gql for "updating mangas" ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1193](https://github.com/Suwayomi/Suwayomi-WebUI/commit/e1f338d0132e2970d9e4467b94a133fae2aebf72)) Use gql for "sources" ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1192](https://github.com/Suwayomi/Suwayomi-WebUI/commit/4a80e18cd0df8e9fc231af46afc8c2d6ddb1d5f6)) Use gql for "extensions" ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1191](https://github.com/Suwayomi/Suwayomi-WebUI/commit/81cfeedbba59037998e938ab031f6055f5cbaca6)) Use gql for "checkForUpdate" ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1190](https://github.com/Suwayomi/Suwayomi-WebUI/commit/59468bc5685d872600299f5d2f3997230fe57f2c)) Use gql for "about" ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1189](https://github.com/Suwayomi/Suwayomi-WebUI/commit/80cb06dfd5ecba58873ef7d029af67571585e188)) Use gql for "global metadata" ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1188](https://github.com/Suwayomi/Suwayomi-WebUI/commit/b9480736c31e742fbef78182419efcf2e203085d)) Log apollo errors ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1187](https://github.com/Suwayomi/Suwayomi-WebUI/commit/181eb6c811d2a149b7680935a6968abd73264c68)) Add graphql logic to RequestManager ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1186](https://github.com/Suwayomi/Suwayomi-WebUI/commit/0ee47c872b781e8f80c385f668bb982fd3244e85)) Introduce "BaseClient" ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1185](https://github.com/Suwayomi/Suwayomi-WebUI/commit/a0b686497f7b408ede50354f62541272caf0835b)) Move "RestClient" in sub folder ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1184](https://github.com/Suwayomi/Suwayomi-WebUI/commit/bffc1669e211fb9b7a082fa5661bb40bdc59a53b)) Move "RequestManager" in sub folder ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1183](https://github.com/Suwayomi/Suwayomi-WebUI/commit/1c794c24b5e9cc02d9998945091e9cf1783c84a6)) Setup intellij "GraphQL" plugin ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1182](https://github.com/Suwayomi/Suwayomi-WebUI/commit/87ddf2f8443140882f4e9d9334652ab6571c58ec)) [Codegen] Generate files ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1181](https://github.com/Suwayomi/Suwayomi-WebUI/commit/0087d3a87b13fffd9ad50e8d66fa98f38ade38d9)) [Tool][Codegen] Add script to post format the generated graphql file ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1180](https://github.com/Suwayomi/Suwayomi-WebUI/commit/10a57d3388660d479773bdd2d007e923a986d916)) Change "moduleResolution" to "node" ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1179](https://github.com/Suwayomi/Suwayomi-WebUI/commit/1b4497db46f9fa6756dc7c93311cc4e74c54670a)) Setup "graphql-codgen" ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1178](https://github.com/Suwayomi/Suwayomi-WebUI/commit/55c1c51edd03f3e7559b32face8dd3e78301cd92)) Create queries, mutations and subscriptions ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1177](https://github.com/Suwayomi/Suwayomi-WebUI/commit/ae5d2525f18912a91d0bf3fa9dfe0ab68740fe92)) Add "apollo-client" dependencies ([#395](https://github.com/Suwayomi/Suwayomi-WebUI/pull/395) by @schroda)
|
||||
- ([r1176](https://github.com/Suwayomi/Suwayomi-WebUI/commit/5cf07acc1cc87834f6d091e71432c919f4c28e25)) Update BUILDING.md ([#420](https://github.com/Suwayomi/Suwayomi-WebUI/pull/420) by @skrewde)
|
||||
- ([r1175](https://github.com/Suwayomi/Suwayomi-WebUI/commit/3155c1d602e1ec715f956fc0af2cd1c3361c0f8a)) Add option to offset first page in double page reader ([#418](https://github.com/Suwayomi/Suwayomi-WebUI/pull/418) by @rickymcmuffin)
|
||||
- ([r1174](https://github.com/Suwayomi/Suwayomi-WebUI/commit/9daf71d85cbe79726af2b87422ee67360876724f)) Translations update from Hosted Weblate ([#411](https://github.com/Suwayomi/Suwayomi-WebUI/pull/411) by @weblate)
|
||||
- ([r1173](https://github.com/Suwayomi/Suwayomi-WebUI/commit/4e134768ef028798efd3fc8b9ff7666c2e81daa2)) Feature/update dependencies ([#419](https://github.com/Suwayomi/Suwayomi-WebUI/pull/419) by @schroda)
|
||||
- ([r1172](https://github.com/Suwayomi/Suwayomi-WebUI/commit/686f9d605f332b3bd1ecbe5a253f58f8c23fa434)) Improvements on double page ([#417](https://github.com/Suwayomi/Suwayomi-WebUI/pull/417) by @rickymcmuffin)
|
||||
- ([r1171](https://github.com/Suwayomi/Suwayomi-WebUI/commit/6afd12b0bbf2938826a0f796888a42a1c8b35d3e)) Update required server version for preview to r1353 ([#415](https://github.com/Suwayomi/Suwayomi-WebUI/pull/415) by @schroda)
|
||||
- ([r1170](https://github.com/Suwayomi/Suwayomi-WebUI/commit/a8f25f58bf2bcee878465545d3bd89cff21405cc)) Update dependencies ([#414](https://github.com/Suwayomi/Suwayomi-WebUI/pull/414) by @schroda)
|
||||
- ([r1169](https://github.com/Suwayomi/Suwayomi-WebUI/commit/c23799f0e0714b54966cf14ead8567ba32429406)) Update "UpdateStatus" type to server changes ([#413](https://github.com/Suwayomi/Suwayomi-WebUI/pull/413) by @schroda)
|
||||
- ([r1168](https://github.com/Suwayomi/Suwayomi-WebUI/commit/bde16d04112d3666a9f8f29979a99aa33bc08070)) Feature/update dependencies ([#410](https://github.com/Suwayomi/Suwayomi-WebUI/pull/410) by @schroda)
|
||||
- ([r1167](https://github.com/Suwayomi/Suwayomi-WebUI/commit/38b69297f49b8d315fad4c27458d5d8117644da1)) Add new languages to resources ([#409](https://github.com/Suwayomi/Suwayomi-WebUI/pull/409) by @schroda)
|
||||
- ([r1166](https://github.com/Suwayomi/Suwayomi-WebUI/commit/5d032d3748d0654bc1f9d05820a2908a954ea712)) Translations update from Hosted Weblate ([#403](https://github.com/Suwayomi/Suwayomi-WebUI/pull/403) by @weblate, @xconkhi9x)
|
||||
- ([r1165](https://github.com/Suwayomi/Suwayomi-WebUI/commit/dd0ab4cb8604ceff9a52d94c4659743ec6b01d8a)) Show "inLibraryIndicator" in "VerticalGrid" ([#408](https://github.com/Suwayomi/Suwayomi-WebUI/pull/408) by @schroda)
|
||||
- ([r1164](https://github.com/Suwayomi/Suwayomi-WebUI/commit/2f795b9d38d9594f655788cc9e3041206a9f1072)) Fix/tools scripts tsconfig and linting ([#406](https://github.com/Suwayomi/Suwayomi-WebUI/pull/406) by @schroda)
|
||||
- ([r1163](https://github.com/Suwayomi/Suwayomi-WebUI/commit/ebf6c99dee61e3500a4acb7770315bd8b24a1719)) Fix contributing readme ([#405](https://github.com/Suwayomi/Suwayomi-WebUI/pull/405) by @schroda)
|
||||
- ([r1162](https://github.com/Suwayomi/Suwayomi-WebUI/commit/48fe8d23e9d4b5e59e4ef70f8e082e098f0959da)) Fix/vite tsconfig setup ([#404](https://github.com/Suwayomi/Suwayomi-WebUI/pull/404) by @schroda)
|
||||
- ([r1161](https://github.com/Suwayomi/Suwayomi-WebUI/commit/637118ad7a2562255bad507c4d78dcaa373d133c)) Add new languages to resources ([#402](https://github.com/Suwayomi/Suwayomi-WebUI/pull/402) by @schroda)
|
||||
- ([r1160](https://github.com/Suwayomi/Suwayomi-WebUI/commit/e061fc348c13e4aa14c54a4726e2093a3881907a)) Translations update from Hosted Weblate ([#396](https://github.com/Suwayomi/Suwayomi-WebUI/pull/396) by @weblate, @cnmorocho, @Wip-Sama, @Becods)
|
||||
- ([r1159](https://github.com/Suwayomi/Suwayomi-WebUI/commit/31dca431e4bfae922ffa9820dc87f6a55370be7a)) Feature/use vite with swc ([#400](https://github.com/Suwayomi/Suwayomi-WebUI/pull/400) by @schroda)
|
||||
- ([r1158](https://github.com/Suwayomi/Suwayomi-WebUI/commit/a3d36bdb1719b34bc9fe3f5e6655b421363821f0)) Feature/introduce script to create changelog ([#401](https://github.com/Suwayomi/Suwayomi-WebUI/pull/401) by @schroda)
|
||||
- ([r1157](https://github.com/Suwayomi/Suwayomi-WebUI/commit/a487e47260317bc5bf69238c114e23d59efd93bb)) Feature/update dependencies ([#399](https://github.com/Suwayomi/Suwayomi-WebUI/pull/399) by @schroda)
|
||||
- ([r1156](https://github.com/Suwayomi/Suwayomi-WebUI/commit/f42aead32b7815c9bc9d0f01b89435030b60a1fa)) Add ui version to server version mapping file ([#398](https://github.com/Suwayomi/Suwayomi-WebUI/pull/398) by @schroda)
|
||||
- ([r1155](https://github.com/Suwayomi/Suwayomi-WebUI/commit/e4745ee8123e2fc197768f9a11a2e7d0fda30105)) [ESLint] Fix issues ([#397](https://github.com/Suwayomi/Suwayomi-WebUI/pull/397) by @schroda)
|
||||
- ([r1154](https://github.com/Suwayomi/Suwayomi-WebUI/commit/04f7831dc49aa44d055fcc5112358f6bef7df821)) Use proper button radius ([#393](https://github.com/Suwayomi/Suwayomi-WebUI/pull/393) by @schroda)
|
||||
- ([r1153](https://github.com/Suwayomi/Suwayomi-WebUI/commit/83c68513576d60e58f033e08d25771b7feadc227)) Update "react-i18next" to v13.x ([#392](https://github.com/Suwayomi/Suwayomi-WebUI/pull/392) by @schroda)
|
||||
- ([r1152](https://github.com/Suwayomi/Suwayomi-WebUI/commit/5279339b86f2fc0d1946f0ef53d0842a922117fd)) Feature/update i18next to v23.x ([#391](https://github.com/Suwayomi/Suwayomi-WebUI/pull/391) by @schroda)
|
||||
- ([r1151](https://github.com/Suwayomi/Suwayomi-WebUI/commit/46ccf20168b9aeb595b0303876fb01e22de90bec)) Update dependencies with non-breaking changes ([#390](https://github.com/Suwayomi/Suwayomi-WebUI/pull/390) by @schroda)
|
||||
- ([r1150](https://github.com/Suwayomi/Suwayomi-WebUI/commit/09b10cd5abba260741084fad71afdfe976d3e372)) Fix/back button not working without browser history ([#389](https://github.com/Suwayomi/Suwayomi-WebUI/pull/389) by @schroda)
|
||||
- ([r1149](https://github.com/Suwayomi/Suwayomi-WebUI/commit/1d76e990ae58b27739a3802e42af98c1c6dd4913)) Move "@types/node" to dev-dependencies ([#388](https://github.com/Suwayomi/Suwayomi-WebUI/pull/388) by @schroda)
|
||||
- ([r1148](https://github.com/Suwayomi/Suwayomi-WebUI/commit/7857645800d6973f52ccfa105957d3900156f14d)) Enable changing include/exclude state of "default" category ([#387](https://github.com/Suwayomi/Suwayomi-WebUI/pull/387) by @schroda)
|
||||
- ([r1147](https://github.com/Suwayomi/Suwayomi-WebUI/commit/67b4bbbdcf2cc8d133e6f365f6e9cbdb49a8d6cf)) Do not use and mutate global array ([#386](https://github.com/Suwayomi/Suwayomi-WebUI/pull/386) by @schroda)
|
||||
- ([r1146](https://github.com/Suwayomi/Suwayomi-WebUI/commit/5c0dcf1f6ccd69504d3e8617d6e6eebc976612f9)) Rename function ([#386](https://github.com/Suwayomi/Suwayomi-WebUI/pull/386) by @schroda)
|
||||
- ([r1145](https://github.com/Suwayomi/Suwayomi-WebUI/commit/084308a8331402e9b1af517ccd263712b74649fb)) Fix typo ([#385](https://github.com/Suwayomi/Suwayomi-WebUI/pull/385) by @schroda)
|
||||
- ([r1144](https://github.com/Suwayomi/Suwayomi-WebUI/commit/95ca2fabaec4b15255f071ea5b47f984a0d9df28)) Group obsolete extensions ([#385](https://github.com/Suwayomi/Suwayomi-WebUI/pull/385) by @schroda)
|
||||
- ([r1143](https://github.com/Suwayomi/Suwayomi-WebUI/commit/2bbfb5272fa8eb019f01e5e2300af05ad733b265)) Add new languages to resources ([#384](https://github.com/Suwayomi/Suwayomi-WebUI/pull/384) by @schroda)
|
||||
- ([r1142](https://github.com/Suwayomi/Suwayomi-WebUI/commit/750d2f246462bb276bdcf6f28822bb2c4dab6771)) Translated using Weblate (Ukrainian) ([#292](https://github.com/Suwayomi/Suwayomi-WebUI/pull/292) by @weblate, @Kefir2105, @RafieHardinur, @SuperMario229, @misaka10843, @schroda, @Becods)
|
||||
- ([r1141](https://github.com/Suwayomi/Suwayomi-WebUI/commit/5d9bc5474c3f0c7d282bb75ab1dbb2ef38952a09)) Prevent white screen in Updates page ([#383](https://github.com/Suwayomi/Suwayomi-WebUI/pull/383) by @Becods)
|
||||
- ([r1140](https://github.com/Suwayomi/Suwayomi-WebUI/commit/ed9c51cd37afbb9f84682ff48378f722b847cc15)) Reset scroll position when changing the search term ([#382](https://github.com/Suwayomi/Suwayomi-WebUI/pull/382) by @schroda)
|
||||
- ([r1139](https://github.com/Suwayomi/Suwayomi-WebUI/commit/322cf8c91588d28c3c7f4a1b52e7144b0e94bd8a)) Move Library "search settings" to LibrarySettings ([#381](https://github.com/Suwayomi/Suwayomi-WebUI/pull/381) by @schroda)
|
||||
- ([r1138](https://github.com/Suwayomi/Suwayomi-WebUI/commit/f5a8c8d35c113aca2643f3e6e1b54611bb1a8db7)) Update reset scroll position flag after doing the reset ([#380](https://github.com/Suwayomi/Suwayomi-WebUI/pull/380) by @schroda)
|
||||
- ([r1137](https://github.com/Suwayomi/Suwayomi-WebUI/commit/79b5e696b65b1281496a0ee184171054ad47175f)) Fix/setting buttons unclickable area ([#379](https://github.com/Suwayomi/Suwayomi-WebUI/pull/379) by @schroda)
|
||||
- ([r1136](https://github.com/Suwayomi/Suwayomi-WebUI/commit/0f029cb625bd69e0806aefcc8ad76c7d84147969)) Fix/library manga grid infinite item size on category switch ([#378](https://github.com/Suwayomi/Suwayomi-WebUI/pull/378) by @schroda)
|
||||
- ([r1135](https://github.com/Suwayomi/Suwayomi-WebUI/commit/cdacc4b2a96668dd6d954aed367fbf1fcf486680)) Always use available width for grid items ([#375](https://github.com/Suwayomi/Suwayomi-WebUI/pull/375) by @schroda)
|
||||
- ([r1134](https://github.com/Suwayomi/Suwayomi-WebUI/commit/ad3864161691f21751f5f97e116728d5fa3ebf4b)) Only show scrollbar when necessary ([#377](https://github.com/Suwayomi/Suwayomi-WebUI/pull/377) by @schroda)
|
||||
- ([r1133](https://github.com/Suwayomi/Suwayomi-WebUI/commit/48d559ec190fc9496c40139187431b7d754dcb4f)) Fix/manga grid infinite item width ([#376](https://github.com/Suwayomi/Suwayomi-WebUI/pull/376) by @schroda)
|
||||
- ([r1132](https://github.com/Suwayomi/Suwayomi-WebUI/commit/cc423932b05e9375c724ed3aad05affe3db09025)) Properly resolve alias paths in vite ([#374](https://github.com/Suwayomi/Suwayomi-WebUI/pull/374) by @schroda)
|
||||
- ([r1131](https://github.com/Suwayomi/Suwayomi-WebUI/commit/4dcf6a3ad90dddbecef98f0695ac5a0a3b447df7)) Make library tabs menu position fixed ([#369](https://github.com/Suwayomi/Suwayomi-WebUI/pull/369) by @schroda)
|
||||
- ([r1130](https://github.com/Suwayomi/Suwayomi-WebUI/commit/19d27fdf2621c7271294486d835124a274bb3d1f)) Feature/virtualize manga grid ([#363](https://github.com/Suwayomi/Suwayomi-WebUI/pull/363) by @schroda)
|
||||
- ([r1129](https://github.com/Suwayomi/Suwayomi-WebUI/commit/3ad33b0a15d0f5e5c34a83f0db2a1f553d76f5b4)) Reset scroll position when changing searchTerm ([#373](https://github.com/Suwayomi/Suwayomi-WebUI/pull/373) by @schroda)
|
||||
- ([r1128](https://github.com/Suwayomi/Suwayomi-WebUI/commit/938b5166d9df2f431589d77b08770ad17368390a)) Fix Library tab change animation ([#372](https://github.com/Suwayomi/Suwayomi-WebUI/pull/372) by @schroda)
|
||||
- ([r1127](https://github.com/Suwayomi/Suwayomi-WebUI/commit/05fa6d8fa952443a013f45c264e33d378efa165d)) Never pass "searchTerm" for "filter" source content type request ([#371](https://github.com/Suwayomi/Suwayomi-WebUI/pull/371) by @schroda)
|
||||
- ([r1126](https://github.com/Suwayomi/Suwayomi-WebUI/commit/0495a932deda341a6035147a93d2ba1043cc65b8)) Use the "disableCache" flag for the "filters" source content type request ([#370](https://github.com/Suwayomi/Suwayomi-WebUI/pull/370) by @schroda)
|
||||
- ([r1125](https://github.com/Suwayomi/Suwayomi-WebUI/commit/64eb420ba6d88f85704b3a479b4b309c59dab550)) Use same endpoint for search in SearchAll and SourceMangas ([#368](https://github.com/Suwayomi/Suwayomi-WebUI/pull/368) by @schroda)
|
||||
- ([r1124](https://github.com/Suwayomi/Suwayomi-WebUI/commit/97c77027f22033ffbcef67a5941c007ea74de381)) Scroll to top when changing source manga content type ([#365](https://github.com/Suwayomi/Suwayomi-WebUI/pull/365) by @schroda)
|
||||
- ([r1123](https://github.com/Suwayomi/Suwayomi-WebUI/commit/5187d4c8428434c1fc3e5036e1c5e7821856a04d)) Fix "hasNextPage" calculation for Library grid ([#366](https://github.com/Suwayomi/Suwayomi-WebUI/pull/366) by @schroda)
|
||||
- ([r1122](https://github.com/Suwayomi/Suwayomi-WebUI/commit/cc4bf7bb325f986f39c68fe9fd610dd5051df592)) Fix initial infinite swr request for pages > 1 ([#367](https://github.com/Suwayomi/Suwayomi-WebUI/pull/367) by @schroda)
|
||||
- ([r1121](https://github.com/Suwayomi/Suwayomi-WebUI/commit/dcd5302a2650b17581dd53df6ab873a2f83cc0c3)) Fix/source mangas white screen when directly open page via url ([#362](https://github.com/Suwayomi/Suwayomi-WebUI/pull/362) by @schroda)
|
||||
- ([r1120](https://github.com/Suwayomi/Suwayomi-WebUI/commit/26b48f15c5887ac72b585f9fd4dcfe11d1f7b59a)) Fix/library settings global update categories empty dialog after updating ([#361](https://github.com/Suwayomi/Suwayomi-WebUI/pull/361) by @schroda)
|
||||
- ([r1119](https://github.com/Suwayomi/Suwayomi-WebUI/commit/63c1ac95eb25d7b80e5c7328159d1f896ab42146)) Feature/remove use back to util ([#360](https://github.com/Suwayomi/Suwayomi-WebUI/pull/360) by @schroda)
|
||||
- ([r1118](https://github.com/Suwayomi/Suwayomi-WebUI/commit/87de016d6f50bf4a819bb539ab06583b66fd48d9)) Prevent chapter revalidation on focus event in the Reader ([#359](https://github.com/Suwayomi/Suwayomi-WebUI/pull/359) by @schroda)
|
||||
- ([r1117](https://github.com/Suwayomi/Suwayomi-WebUI/commit/bbdbccf235bd54af35b6b0de1de228ef986202ea)) Prevent chapter revalidation on focus event in the Reader (#359) (by @schroda)
|
||||
- ([r1116](https://github.com/Suwayomi/Suwayomi-WebUI/commit/7f83bb9a0a9413d5b469025d82baf7198e36451f)) Do not use stale chapter data for the reader ([#358](https://github.com/Suwayomi/Suwayomi-WebUI/pull/358) by @schroda)
|
||||
- ([r1115](https://github.com/Suwayomi/Suwayomi-WebUI/commit/67e554ede6ec3691245ab236bc044466d18f47f6)) Prevent updating "lastPageRead" of chapter to the initial chapters "lastPageRead" ([#357](https://github.com/Suwayomi/Suwayomi-WebUI/pull/357) by @schroda)
|
||||
- ([r1114](https://github.com/Suwayomi/Suwayomi-WebUI/commit/21174dc04ab392cca5ae00f45f2b75a45f37840f)) Add the option to ignore SWR stale data ([#356](https://github.com/Suwayomi/Suwayomi-WebUI/pull/356) by @schroda)
|
||||
- ([r1113](https://github.com/Suwayomi/Suwayomi-WebUI/commit/f979d207e87da5dd33ba2a0a4e9e93a190f0f5bb)) Open reader via the correct url when using resume FAB ([#355](https://github.com/Suwayomi/Suwayomi-WebUI/pull/355) by @schroda)
|
||||
- ([r1112](https://github.com/Suwayomi/Suwayomi-WebUI/commit/cce6ec0113f0d88f02fb748b7c297438f4ce12e5)) Preserve "SourceMangas" location state ([#354](https://github.com/Suwayomi/Suwayomi-WebUI/pull/354) by @schroda)
|
||||
- ([r1111](https://github.com/Suwayomi/Suwayomi-WebUI/commit/0446a01e85e80cb1ebd3d29d88b20a78dffec748)) Remove unused dependency "web-vitals" ([#353](https://github.com/Suwayomi/Suwayomi-WebUI/pull/353) by @schroda)
|
||||
- ([r1110](https://github.com/Suwayomi/Suwayomi-WebUI/commit/4b83ccc88902135f1801858c4d189f6ff228a1a7)) Feature/use alias for imports ([#352](https://github.com/Suwayomi/Suwayomi-WebUI/pull/352) by @schroda)
|
||||
- ([r1109](https://github.com/Suwayomi/Suwayomi-WebUI/commit/1ec6d77598788890beea16245d59b5b65410440e)) Fix/library title size info initial render flickering ([#350](https://github.com/Suwayomi/Suwayomi-WebUI/pull/350) by @schroda)
|
||||
- ([r1108](https://github.com/Suwayomi/Suwayomi-WebUI/commit/00e403ce09f1514db6064732571cab596e154c43)) Use correct endpoint for deleting downloaded chapter ([#351](https://github.com/Suwayomi/Suwayomi-WebUI/pull/351) by @schroda)
|
||||
- ([r1107](https://github.com/Suwayomi/Suwayomi-WebUI/commit/bc99728dcd9e7b36c8d3495d80bcbd6b3326f072)) Feature/migrate to vite ([#349](https://github.com/Suwayomi/Suwayomi-WebUI/pull/349) by @schroda)
|
||||
- ([r1106](https://github.com/Suwayomi/Suwayomi-WebUI/commit/fa614a19d257fd9c993ccedebb7da9f2443f1510)) Update dependency "typescript" to v5.x ([#348](https://github.com/Suwayomi/Suwayomi-WebUI/pull/348) by @schroda)
|
||||
- ([r1105](https://github.com/Suwayomi/Suwayomi-WebUI/commit/57b1c0f680eb33d3849278beb4e14b7428e58e96)) Update dependency "eslint" to v8.42.0 ([#347](https://github.com/Suwayomi/Suwayomi-WebUI/pull/347) by @schroda)
|
||||
- ([r1104](https://github.com/Suwayomi/Suwayomi-WebUI/commit/61975b5c4935b6c919f4560a82ba95fdd7d3e1cb)) Feature/update dependency react to v18.x ([#346](https://github.com/Suwayomi/Suwayomi-WebUI/pull/346) by @schroda)
|
||||
- ([r1103](https://github.com/Suwayomi/Suwayomi-WebUI/commit/62bf6d66dccfe3b26caef0018d6d965488c63657)) Update dependency "react-virtuoso" to v4.x ([#345](https://github.com/Suwayomi/Suwayomi-WebUI/pull/345) by @schroda)
|
||||
- ([r1102](https://github.com/Suwayomi/Suwayomi-WebUI/commit/f2ac73891ca3c524a568035d0d6d581f88c7c3b1)) Update dependency "file-selector" to v0.6.0 ([#344](https://github.com/Suwayomi/Suwayomi-WebUI/pull/344) by @schroda)
|
||||
- ([r1101](https://github.com/Suwayomi/Suwayomi-WebUI/commit/c6f7cd7731e5e8b0a3fc7b5916d0f2de760da51b)) Feature/update dependency web vitals to v3.x ([#343](https://github.com/Suwayomi/Suwayomi-WebUI/pull/343) by @schroda)
|
||||
- ([r1100](https://github.com/Suwayomi/Suwayomi-WebUI/commit/548b746ee42e3f4eff6370d62d909514a5538961)) Update dependency "@fontsource/roboto" to v5.x ([#342](https://github.com/Suwayomi/Suwayomi-WebUI/pull/342) by @schroda)
|
||||
- ([r1099](https://github.com/Suwayomi/Suwayomi-WebUI/commit/7f4ec7a30c600415fd61acd25f6aea52f68935d9)) Update dependency "@mui/icons-material" to v5.11.16 ([#341](https://github.com/Suwayomi/Suwayomi-WebUI/pull/341) by @schroda)
|
||||
- ([r1098](https://github.com/Suwayomi/Suwayomi-WebUI/commit/4e8813b526fab197a61e22d9a855514f1f9bd203)) Feature/update dependency react router dom to v6.x ([#340](https://github.com/Suwayomi/Suwayomi-WebUI/pull/340) by @schroda)
|
||||
- ([r1097](https://github.com/Suwayomi/Suwayomi-WebUI/commit/fd5a1e240a51bf19483546551e6a5509b9ca7d0c)) Remove unused dependency "query-string" ([#339](https://github.com/Suwayomi/Suwayomi-WebUI/pull/339) by @schroda)
|
||||
- ([r1096](https://github.com/Suwayomi/Suwayomi-WebUI/commit/6b05083d950263897ffbaf72b62de5abf795a2c8)) Feature/update dependency use query params to v2.x ([#338](https://github.com/Suwayomi/Suwayomi-WebUI/pull/338) by @schroda)
|
||||
- ([r1095](https://github.com/Suwayomi/Suwayomi-WebUI/commit/ce02802888d20d968a6abdfbc18562faa59e0902)) Update dependency "@emotion" to v11.11.0 ([#337](https://github.com/Suwayomi/Suwayomi-WebUI/pull/337) by @schroda)
|
||||
- ([r1094](https://github.com/Suwayomi/Suwayomi-WebUI/commit/c63b4acdf826c4e8a586e48f2e7c005a1275c2cd)) Update dependency "i18n" to v22.5.0 ([#336](https://github.com/Suwayomi/Suwayomi-WebUI/pull/336) by @schroda)
|
||||
- ([r1093](https://github.com/Suwayomi/Suwayomi-WebUI/commit/31f2e0f839740dcfae3fc0c475f24b3c98128950)) Update dependency "react-beautiful-dnd" to v13.1.1 ([#335](https://github.com/Suwayomi/Suwayomi-WebUI/pull/335) by @schroda)
|
||||
- ([r1092](https://github.com/Suwayomi/Suwayomi-WebUI/commit/8655fe1f6dfc7466811db36bcd345842264060a6)) Update dependency "@typescript-eslint" to v5.59.8 ([#334](https://github.com/Suwayomi/Suwayomi-WebUI/pull/334) by @schroda)
|
||||
- ([r1091](https://github.com/Suwayomi/Suwayomi-WebUI/commit/647260f18c708ff2edd7006e2aecd10658835aff)) Update dependency "prettier" to v2.8.8 ([#333](https://github.com/Suwayomi/Suwayomi-WebUI/pull/333) by @schroda)
|
||||
- ([r1090](https://github.com/Suwayomi/Suwayomi-WebUI/commit/1ce27ea939db46ad0b53e60901b9aa02ea628011)) Feature/update dependency mui to v5.x ([#332](https://github.com/Suwayomi/Suwayomi-WebUI/pull/332) by @schroda)
|
||||
- ([r1089](https://github.com/Suwayomi/Suwayomi-WebUI/commit/6911f60eb4fa43b83e48420a4108780072c2d096)) Feature/remove dependency mui system ([#331](https://github.com/Suwayomi/Suwayomi-WebUI/pull/331) by @schroda)
|
||||
- ([r1088](https://github.com/Suwayomi/Suwayomi-WebUI/commit/2a6950b015f75b070a89132d54adb0e999662101)) Feature/remove dependency mui styles ([#330](https://github.com/Suwayomi/Suwayomi-WebUI/pull/330) by @schroda)
|
||||
- ([r1087](https://github.com/Suwayomi/Suwayomi-WebUI/commit/3a82fd0abb63b8408435963369a72bb3c7d0815b)) Remove unused dependency "react-lazyload" ([#329](https://github.com/Suwayomi/Suwayomi-WebUI/pull/329) by @schroda)
|
||||
- ([r1086](https://github.com/Suwayomi/Suwayomi-WebUI/commit/9e8c4254bd4f77feb6e6aea3bb4197fa98c223a0)) Remove unused dependency "p-queue" ([#328](https://github.com/Suwayomi/Suwayomi-WebUI/pull/328) by @schroda)
|
||||
- ([r1085](https://github.com/Suwayomi/Suwayomi-WebUI/commit/9ae7b2e5922a4fe5a548208e729849df8baa59a3)) Remove console log ([#327](https://github.com/Suwayomi/Suwayomi-WebUI/pull/327) by @schroda)
|
||||
- ([r1084](https://github.com/Suwayomi/Suwayomi-WebUI/commit/0fb204f1419d4d903c5ac7710e6a4ee3c89b260d)) Feature/request manager remove get client usage ([#325](https://github.com/Suwayomi/Suwayomi-WebUI/pull/325) by @schroda)
|
||||
- ([r1083](https://github.com/Suwayomi/Suwayomi-WebUI/commit/207a87f3e98875aeef0aad6963c8ccb86ad6a789)) Fix import backup file request ([#326](https://github.com/Suwayomi/Suwayomi-WebUI/pull/326) by @schroda)
|
||||
- ([r1082](https://github.com/Suwayomi/Suwayomi-WebUI/commit/eaa83927898cf3249d443f42ea4621ef89695cf9)) Fix axios requests ([#324](https://github.com/Suwayomi/Suwayomi-WebUI/pull/324) by @schroda)
|
||||
- ([r1081](https://github.com/Suwayomi/Suwayomi-WebUI/commit/d60ed0c0558c4d9895ab681143f374959709c42f)) Add fit page to window reader setting ([#323](https://github.com/Suwayomi/Suwayomi-WebUI/pull/323) by @Alexandre-P-J)
|
||||
- ([r1080](https://github.com/Suwayomi/Suwayomi-WebUI/commit/46a7ff6e3bc28fd4e459920a9640ab04a851ce4d)) Feature/refactor source mangas screen ([#314](https://github.com/Suwayomi/Suwayomi-WebUI/pull/314) by @schroda)
|
||||
- ([r1079](https://github.com/Suwayomi/Suwayomi-WebUI/commit/1480507c353009d2d7238c97f6eaf09fcfddb3ab)) Fix/source options filters state ([#320](https://github.com/Suwayomi/Suwayomi-WebUI/pull/320) by @schroda)
|
||||
- ([r1078](https://github.com/Suwayomi/Suwayomi-WebUI/commit/f852255d8260e5f19ac5ba81328371f025c30a7e)) Add additional info about SWR infinite load to response ([#321](https://github.com/Suwayomi/Suwayomi-WebUI/pull/321) by @schroda)
|
||||
- ([r1077](https://github.com/Suwayomi/Suwayomi-WebUI/commit/606ee9de2d8b99e86264cf95d549ec2244908ebf)) RequestManager make requests abortable - Fix missed usages ([#318](https://github.com/Suwayomi/Suwayomi-WebUI/pull/318) by @schroda)
|
||||
- ([r1076](https://github.com/Suwayomi/Suwayomi-WebUI/commit/c32cb535920da8ac3653890a76a413e225fc8f29)) Feature/improve refactored global search performance ([#317](https://github.com/Suwayomi/Suwayomi-WebUI/pull/317) by @schroda)
|
||||
- ([r1075](https://github.com/Suwayomi/Suwayomi-WebUI/commit/0d36d2dcfb41709c3515abbb0f4cf3b6d49115d4)) Feature/request manager make requests abortable ([#316](https://github.com/Suwayomi/Suwayomi-WebUI/pull/316) by @schroda)
|
||||
- ([r1074](https://github.com/Suwayomi/Suwayomi-WebUI/commit/95ceac335b6749530b2d895f40be715ef278e738)) Update to axios v1.x ([#315](https://github.com/Suwayomi/Suwayomi-WebUI/pull/315) by @schroda)
|
||||
- ([r1073](https://github.com/Suwayomi/Suwayomi-WebUI/commit/84433ffff826e1faf2a110c4b7c7183a1816179c)) Support SWR infinite requests via POST ([#313](https://github.com/Suwayomi/Suwayomi-WebUI/pull/313) by @schroda)
|
||||
- ([r1072](https://github.com/Suwayomi/Suwayomi-WebUI/commit/6fd0dd0daddd6f3aa141a35a1d27e796df23f440)) Fix "setSourceFilters" request ([#312](https://github.com/Suwayomi/Suwayomi-WebUI/pull/312) by @schroda)
|
||||
- ([r1071](https://github.com/Suwayomi/Suwayomi-WebUI/commit/a95937f2536e25ce5b3988477306f31300956b3f)) Fix/global search not showing request error ([#310](https://github.com/Suwayomi/Suwayomi-WebUI/pull/310) by @schroda)
|
||||
- ([r1070](https://github.com/Suwayomi/Suwayomi-WebUI/commit/474e568a05a1f58846323e94f9d821dc440ed36e)) Refactor SearchAll screen ([#308](https://github.com/Suwayomi/Suwayomi-WebUI/pull/308) by @schroda)
|
||||
- ([r1069](https://github.com/Suwayomi/Suwayomi-WebUI/commit/836b4ea4d21f4b6976ee2678863b3e683326bcb9)) Set the manga ref for the "list style" ([#311](https://github.com/Suwayomi/Suwayomi-WebUI/pull/311) by @schroda)
|
||||
- ([r1068](https://github.com/Suwayomi/Suwayomi-WebUI/commit/f908195b0982d12c498b019cb956c91ded07dc2b)) Feature/cleanup search all ([#307](https://github.com/Suwayomi/Suwayomi-WebUI/pull/307) by @schroda)
|
||||
- ([r1067](https://github.com/Suwayomi/Suwayomi-WebUI/commit/0cd5720f54d06d70e15a57afa1ee1f28d52ae5da)) Fix/request manager infinite swr requests ([#306](https://github.com/Suwayomi/Suwayomi-WebUI/pull/306) by @schroda)
|
||||
- ([r1066](https://github.com/Suwayomi/Suwayomi-WebUI/commit/feac34ba83028b59798d69f55f4cd03b7fc13d16)) Trigger global search request ([#305](https://github.com/Suwayomi/Suwayomi-WebUI/pull/305) by @schroda)
|
||||
- ([r1065](https://github.com/Suwayomi/Suwayomi-WebUI/commit/aa801a57ee61fb52a544ed77fdfddfe4c9843ffe)) Feature/updates screen use infinite swr hook ([#303](https://github.com/Suwayomi/Suwayomi-WebUI/pull/303) by @schroda)
|
||||
- ([r1064](https://github.com/Suwayomi/Suwayomi-WebUI/commit/a457d80c446960df5644513fdf4d3a11974243c7)) Feature/streamline backend requests ([#297](https://github.com/Suwayomi/Suwayomi-WebUI/pull/297) by @schroda)
|
||||
- ([r1063](https://github.com/Suwayomi/Suwayomi-WebUI/commit/f86c7ea08ae0c6a40e628f0d92f8d7f31537c668)) Prevent showing "empty library" message on first load ([#302](https://github.com/Suwayomi/Suwayomi-WebUI/pull/302) by @schroda)
|
||||
- ([r1062](https://github.com/Suwayomi/Suwayomi-WebUI/commit/58eb2fead3e8bed2b42843293882b5e9d1563ae3)) Feature/enforce license notice in each file via eslint rule ([#304](https://github.com/Suwayomi/Suwayomi-WebUI/pull/304) by @schroda)
|
||||
- ([r1061](https://github.com/Suwayomi/Suwayomi-WebUI/commit/13dbb8faf4f3353cc342c91cbebb557d83ebf2ab)) Prevent add category FAB from overlaying last category ([#301](https://github.com/Suwayomi/Suwayomi-WebUI/pull/301) by @schroda)
|
||||
- ([r1060](https://github.com/Suwayomi/Suwayomi-WebUI/commit/14ac872876922128de3e141d1f3c05b5d11da7ad)) Prevent manga page FAB from changing position when selecting chapters ([#300](https://github.com/Suwayomi/Suwayomi-WebUI/pull/300) by @schroda)
|
||||
- ([r1059](https://github.com/Suwayomi/Suwayomi-WebUI/commit/0a0d902bd28d5eb6b99ef56c53c6abd69151dc37)) Fix/manga screen prevent fab from overlaying last chapter in list ([#298](https://github.com/Suwayomi/Suwayomi-WebUI/pull/298) by @schroda)
|
||||
- ([r1058](https://github.com/Suwayomi/Suwayomi-WebUI/commit/5aaf0854fea41c4a18e66f53140946fa3e70694c)) Fix/download queue staying stopped when removing download ([#299](https://github.com/Suwayomi/Suwayomi-WebUI/pull/299) by @schroda)
|
||||
- ([r1057](https://github.com/Suwayomi/Suwayomi-WebUI/commit/8dae72604f8417e4afddc634acfddb4a0f8a8197)) Update to SWR version 2.x ([#296](https://github.com/Suwayomi/Suwayomi-WebUI/pull/296) by @schroda)
|
||||
- ([r1056](https://github.com/Suwayomi/Suwayomi-WebUI/commit/4aff22079a136c2dc48a34fdfa7e34b17ddfea9b)) Settings language add description ([#294](https://github.com/Suwayomi/Suwayomi-WebUI/pull/294) by @schroda)
|
||||
- ([r1055](https://github.com/Suwayomi/Suwayomi-WebUI/commit/c8b0c64a8d53808e104672f2b86538bc5ce46e0b)) Add new languages to resources ([#293](https://github.com/Suwayomi/Suwayomi-WebUI/pull/293) by @schroda)
|
||||
- ([r1054](https://github.com/Suwayomi/Suwayomi-WebUI/commit/b6c87dd630554325e80e2369f10c1b2e7171d18d)) Translations update from Hosted Weblate ([#276](https://github.com/Suwayomi/Suwayomi-WebUI/pull/276) by @weblate, @AriaMoradi, @NathanBnm, @misaka10843, @FumoVite, @JoHena, @bandysharif, @DevCoz)
|
||||
- ([r1053](https://github.com/Suwayomi/Suwayomi-WebUI/commit/b4b3dc54a7f09ca95e29aab5a277032afc625f7f)) App strings reworked ([#277](https://github.com/Suwayomi/Suwayomi-WebUI/pull/277) by @comradekingu, @schroda)
|
||||
- ([r1052](https://github.com/Suwayomi/Suwayomi-WebUI/commit/bd91510227d36d4a15a1feddd681b0105bc37ca6)) Remove eslint rule deactivations ([#290](https://github.com/Suwayomi/Suwayomi-WebUI/pull/290) by @schroda)
|
||||
- ([r1051](https://github.com/Suwayomi/Suwayomi-WebUI/commit/d92dc83ddaca8a7ac45b781761ea7c0ce0ded123)) Display strings in uppercase ([#291](https://github.com/Suwayomi/Suwayomi-WebUI/pull/291) by @schroda)
|
||||
- ([r1050](https://github.com/Suwayomi/Suwayomi-WebUI/commit/2e05e7d35c0c25c0a1d134f146ebf8ec645a6ed0)) fix/manga_screen_missing_source_toast ([#288](https://github.com/Suwayomi/Suwayomi-WebUI/pull/288) by @schroda)
|
||||
- ([r1049](https://github.com/Suwayomi/Suwayomi-WebUI/commit/548b22d21151390fbfb599a977d7d2cbb4a732f6)) fix/library_settings_screen_title_update_on_language_change ([#287](https://github.com/Suwayomi/Suwayomi-WebUI/pull/287) by @schroda)
|
||||
- ([r1048](https://github.com/Suwayomi/Suwayomi-WebUI/commit/b906509f9f7cc9eec8ce5ed7b474a2aae8a5d718)) Add missing licence text ([#286](https://github.com/Suwayomi/Suwayomi-WebUI/pull/286) by @schroda)
|
||||
- ([r1047](https://github.com/Suwayomi/Suwayomi-WebUI/commit/f5a961e82fb6ddb7b79b3789eaa9102b7af83dd7)) Fix/manga white screen missing extension ([#285](https://github.com/Suwayomi/Suwayomi-WebUI/pull/285) by @schroda)
|
||||
- ([r1046](https://github.com/Suwayomi/Suwayomi-WebUI/commit/9a27335760a049e53acb9347d78690e7967ccdc5)) Add option to include and exclude categories from the global update ([#265](https://github.com/Suwayomi/Suwayomi-WebUI/pull/265) by @schroda)
|
||||
- ([r1045](https://github.com/Suwayomi/Suwayomi-WebUI/commit/79b2305e540fa85346d47b598d05bd12004b3742)) Feature/library show number of mangas in category ([#269](https://github.com/Suwayomi/Suwayomi-WebUI/pull/269) by @schroda)
|
||||
- ([r1044](https://github.com/Suwayomi/Suwayomi-WebUI/commit/4157611d83bd6143a204f4226c719d5b46280dc5)) Feature/improve typing of metadata related logic ([#268](https://github.com/Suwayomi/Suwayomi-WebUI/pull/268) by @schroda)
|
||||
- ([r1043](https://github.com/Suwayomi/Suwayomi-WebUI/commit/0a56a2f6d48b13bd414b5e4a5327f41b7a0a11f8)) Extensions cleanup ([#257](https://github.com/Suwayomi/Suwayomi-WebUI/pull/257) by @schroda)
|
||||
- ([r1042](https://github.com/Suwayomi/Suwayomi-WebUI/commit/c81189a9ed35c1144466aa15fea0fcd14f5f464a)) Fix/chapter mark as unread not resetting last page read ([#282](https://github.com/Suwayomi/Suwayomi-WebUI/pull/282) by @schroda)
|
||||
- ([r1041](https://github.com/Suwayomi/Suwayomi-WebUI/commit/22b3437dcdbad22bffe49955187dfda2e606e874)) Keep "add category" fab position fixed ([#283](https://github.com/Suwayomi/Suwayomi-WebUI/pull/283) by @schroda)
|
||||
- ([r1040](https://github.com/Suwayomi/Suwayomi-WebUI/commit/d51150b7848cf7a6596bbba7c015328a578dfd16)) Feature/reader skip duplicate chapters ([#262](https://github.com/Suwayomi/Suwayomi-WebUI/pull/262) by @schroda)
|
||||
- ([r1039](https://github.com/Suwayomi/Suwayomi-WebUI/commit/b1dc13cd30bfdc374123019a9fc51c07e5824633)) Update browser and nav bar title on language change ([#275](https://github.com/Suwayomi/Suwayomi-WebUI/pull/275) by @schroda)
|
||||
- ([r1038](https://github.com/Suwayomi/Suwayomi-WebUI/commit/92506ccf78ecdfe11fbc71b95ca99267b6c0b621)) Revert "Translated using Weblate (Portuguese)" (by @AriaMoradi)
|
||||
- ([r1037](https://github.com/Suwayomi/Suwayomi-WebUI/commit/31d3656697c6719de62b0bc424c6f57bdcadbbd3)) Revert "Translated using Weblate (German)" (by @AriaMoradi)
|
||||
- ([r1036](https://github.com/Suwayomi/Suwayomi-WebUI/commit/14bdb6d9ac6c6766cb02a9eac8ff4aa75a46752e)) Revert "Translated using Weblate (Arabic)" (by @AriaMoradi)
|
||||
- ([r1035](https://github.com/Suwayomi/Suwayomi-WebUI/commit/2e14b71d18e10f09bc4f9748b7e809eddeaa93a4)) Revert "Translated using Weblate (Spanish)" (by @AriaMoradi)
|
||||
- ([r1034](https://github.com/Suwayomi/Suwayomi-WebUI/commit/ff79d9e964a8b7e236d65956926f00404baf3bb1)) Revert "Translated using Weblate (French)" (by @AriaMoradi)
|
||||
- ([r1033](https://github.com/Suwayomi/Suwayomi-WebUI/commit/2050875d6ffc5f8f74be565bf7812d4a79009069)) Merge pull request #274 from weblate/weblate-suwayomi-tachidesk-webui ([#274](https://github.com/Suwayomi/Suwayomi-WebUI/pull/274) by @AriaMoradi)
|
||||
- ([r1032](https://github.com/Suwayomi/Suwayomi-WebUI/commit/d04d33c658ef885a5b050ed17ab192bc96948cba)) Translated using Weblate (French) ([#274](https://github.com/Suwayomi/Suwayomi-WebUI/pull/274) by @weblate)
|
||||
- ([r1031](https://github.com/Suwayomi/Suwayomi-WebUI/commit/f9e5e6cf7a04767705095903b31cc46df0cd8c93)) Translated using Weblate (Spanish) ([#274](https://github.com/Suwayomi/Suwayomi-WebUI/pull/274) by @weblate)
|
||||
- ([r1030](https://github.com/Suwayomi/Suwayomi-WebUI/commit/c34e55ce80a8e98be047cbeae31df6036d79784f)) Translated using Weblate (Arabic) ([#274](https://github.com/Suwayomi/Suwayomi-WebUI/pull/274) by @weblate)
|
||||
- ([r1029](https://github.com/Suwayomi/Suwayomi-WebUI/commit/fe5edb1c4b2a1af27a185021ff2d7f09abcd144f)) Translated using Weblate (German) ([#274](https://github.com/Suwayomi/Suwayomi-WebUI/pull/274) by @weblate)
|
||||
- ([r1028](https://github.com/Suwayomi/Suwayomi-WebUI/commit/14d23c3d29050a843a8a2304bd4030c37d269d69)) Translated using Weblate (Portuguese) ([#274](https://github.com/Suwayomi/Suwayomi-WebUI/pull/274) by @weblate)
|
||||
- ([r1027](https://github.com/Suwayomi/Suwayomi-WebUI/commit/43f367a5460a819b685c7429675d4d5eecd748a5)) Merge remote-tracking branch 'origin/master' ([#274](https://github.com/Suwayomi/Suwayomi-WebUI/pull/274) by @weblate)
|
||||
- ([r1026](https://github.com/Suwayomi/Suwayomi-WebUI/commit/07c0e83f8fbf7a01f46dc19a9858ee78e867fa49)) fix translation files ([#273](https://github.com/Suwayomi/Suwayomi-WebUI/pull/273) by @AriaMoradi)
|
||||
- ([r1025](https://github.com/Suwayomi/Suwayomi-WebUI/commit/6f75837b81e078577c49ad67f31cb5501aa8b9ef)) Translations update from Hosted Weblate ([#272](https://github.com/Suwayomi/Suwayomi-WebUI/pull/272) by @weblate, @AriaMoradi)
|
||||
- ([r1024](https://github.com/Suwayomi/Suwayomi-WebUI/commit/8497a0b12e4ce0d5c7f3d435f4ce3654fcd38a41)) add trnalation policy (by @AriaMoradi)
|
||||
- ([r1023](https://github.com/Suwayomi/Suwayomi-WebUI/commit/aedbccf83e3f6e9e00fe0070b0ff964d483a3132)) Translated using Weblate (German) ([#272](https://github.com/Suwayomi/Suwayomi-WebUI/pull/272) by @AriaMoradi)
|
||||
- ([r1022](https://github.com/Suwayomi/Suwayomi-WebUI/commit/8fcbe29031bd4bd57ca965b4b3ce5723038623f3)) Added translation using Weblate (German) ([#272](https://github.com/Suwayomi/Suwayomi-WebUI/pull/272) by @AriaMoradi)
|
||||
- ([r1021](https://github.com/Suwayomi/Suwayomi-WebUI/commit/ffe0d5160df0293454ef98c683165c07ba7875e2)) Translations update from Hosted Weblate ([#270](https://github.com/Suwayomi/Suwayomi-WebUI/pull/270) by @weblate, @comradekingu, @AriaMoradi, @Zereef)
|
||||
- ([r1020](https://github.com/Suwayomi/Suwayomi-WebUI/commit/fdb44bb46bca1b5f9c4462754716ef61e05f5c66)) Added translation using Weblate (Portuguese) (by @Zereef)
|
||||
- ([r1019](https://github.com/Suwayomi/Suwayomi-WebUI/commit/42b80b439b242ac9269bc58d117b348b9e5f009e)) Translated using Weblate (German) (by @J. Lavoie)
|
||||
- ([r1018](https://github.com/Suwayomi/Suwayomi-WebUI/commit/c11a057a420822206bc5c99398bfd0c4b95ae8a8)) Added translation using Weblate (French) (by @J. Lavoie)
|
||||
- ([r1017](https://github.com/Suwayomi/Suwayomi-WebUI/commit/f71a29fa93d0155f97157d0407d18597ca2828b4)) Translated using Weblate (Arabic) (by @Shippo)
|
||||
- ([r1016](https://github.com/Suwayomi/Suwayomi-WebUI/commit/dce6f5259ffba2fd73b52a2d32d8c00f38599334)) Added translation using Weblate (Spanish) (by @PedroJLR)
|
||||
- ([r1015](https://github.com/Suwayomi/Suwayomi-WebUI/commit/ce06220b9c2c78863196d5c396c76a979856e11f)) Translated using Weblate (German) (by @AriaMoradi)
|
||||
- ([r1014](https://github.com/Suwayomi/Suwayomi-WebUI/commit/75e021588127f03270acd53ed5754c66222b89a7)) Added translation using Weblate (Arabic) (by @Shippo)
|
||||
- ([r1013](https://github.com/Suwayomi/Suwayomi-WebUI/commit/70511aa1c8fc6f650293f4cfcb88e6c9785fba68)) Added translation using Weblate (German) (by @AriaMoradi)
|
||||
- ([r1012](https://github.com/Suwayomi/Suwayomi-WebUI/commit/52b08cfeb11be6e3301cdfc4808722b8cf6234d0)) Deleted translation using Weblate (Norwegian Bokmål) (by @AriaMoradi)
|
||||
- ([r1011](https://github.com/Suwayomi/Suwayomi-WebUI/commit/226a0cc249e6e0bacb28120d6885738f3bf5a5d1)) Translated using Weblate (Norwegian Bokmål) (by @comradekingu)
|
||||
- ([r1010](https://github.com/Suwayomi/Suwayomi-WebUI/commit/9fef2bf488ef7466046d384fdf8e6504c603e68a)) Translated using Weblate (French) (by @AriaMoradi)
|
||||
- ([r1009](https://github.com/Suwayomi/Suwayomi-WebUI/commit/0126cd266b8c55893469c34f14dad102961f8742)) Added translation using Weblate (Norwegian Bokmål) (by @comradekingu)
|
||||
- ([r1008](https://github.com/Suwayomi/Suwayomi-WebUI/commit/16c0f854fe7fd5911f4f14c4ea9cebe29ba59e9c)) Fix discord and github links ([#267](https://github.com/Suwayomi/Suwayomi-WebUI/pull/267) by @JoHena)
|
||||
- ([r1007](https://github.com/Suwayomi/Suwayomi-WebUI/commit/39780df0e0504601d0ba1250ddea7ee6155a895b)) add language selection to settings ([#260](https://github.com/Suwayomi/Suwayomi-WebUI/pull/260) by @schroda)
|
||||
- ([r1006](https://github.com/Suwayomi/Suwayomi-WebUI/commit/d58bd6cd92a74faeaa13a7713efb11e838d73fc2)) add translation keys ([#246](https://github.com/Suwayomi/Suwayomi-WebUI/pull/246) by @schroda)
|
||||
- ([r1005](https://github.com/Suwayomi/Suwayomi-WebUI/commit/8c129f2e08e8c807b57c7eef802556faa2edcf1b)) Disable "SSR" option in "useMediaQuery" ([#263](https://github.com/Suwayomi/Suwayomi-WebUI/pull/263) by @schroda)
|
||||
- ([r1004](https://github.com/Suwayomi/Suwayomi-WebUI/commit/8cd1afd2c725e015f8480f51eb0a2965585053d2)) Ignore filters only while searching ([#256](https://github.com/Suwayomi/Suwayomi-WebUI/pull/256) by @schroda)
|
||||
- ([r1003](https://github.com/Suwayomi/Suwayomi-WebUI/commit/57c8ee2cdc1f6e52a26ed436d0ac438fa390b218)) Add build step to pr workflow ([#259](https://github.com/Suwayomi/Suwayomi-WebUI/pull/259) by @schroda)
|
||||
- ([r1002](https://github.com/Suwayomi/Suwayomi-WebUI/commit/398626250e71b85029288167ab47f25f297c0914)) Remove console log ([#258](https://github.com/Suwayomi/Suwayomi-WebUI/pull/258) by @schroda)
|
||||
- ([r1001](https://github.com/Suwayomi/Suwayomi-WebUI/commit/23001a42989f0231e946bd6251b45c6eeda1818e)) update react scripts dependency ([#255](https://github.com/Suwayomi/Suwayomi-WebUI/pull/255) by @schroda)
|
||||
- ([r1000](https://github.com/Suwayomi/Suwayomi-WebUI/commit/91e7bd2b27beae0c83b8158757600cf396694381)) Added sort by last read ([#254](https://github.com/Suwayomi/Suwayomi-WebUI/pull/254) by @akabhirav)
|
||||
- ([r999](https://github.com/Suwayomi/Suwayomi-WebUI/commit/e32078917d82991ecdb70f4a9c23e941ff147a23)) Replace Sort by ID with Date Added ([#253](https://github.com/Suwayomi/Suwayomi-WebUI/pull/253) by @akabhirav)
|
||||
- ([r998](https://github.com/Suwayomi/Suwayomi-WebUI/commit/a67370be62ee4b47c0ad78f338998c82bbf11218)) Introduce override filters while searching setting ([#242](https://github.com/Suwayomi/Suwayomi-WebUI/pull/242) by @akabhirav)
|
||||
- ([r997](https://github.com/Suwayomi/Suwayomi-WebUI/commit/dcc18bba0083b684190992e252ed1c6e4dc4203d)) extension card cleanup ([#252](https://github.com/Suwayomi/Suwayomi-WebUI/pull/252) by @schroda)
|
||||
- ([r996](https://github.com/Suwayomi/Suwayomi-WebUI/commit/96254365182cf02425c17649dece4bb7554f9985)) get first unread chapter from original chapter list ([#250](https://github.com/Suwayomi/Suwayomi-WebUI/pull/250) by @schroda)
|
||||
- ([r995](https://github.com/Suwayomi/Suwayomi-WebUI/commit/c6257acdf11370109a7fca93be5efc536103fa3d)) Show empty library in case search doesn't match anything ([#251](https://github.com/Suwayomi/Suwayomi-WebUI/pull/251) by @schroda)
|
||||
- ([r994](https://github.com/Suwayomi/Suwayomi-WebUI/commit/96fd1cf73ba0ad18158e9ff99f9dca9944ea1ff4)) remove manually created typing d ts file ([#249](https://github.com/Suwayomi/Suwayomi-WebUI/pull/249) by @schroda)
|
||||
- ([r993](https://github.com/Suwayomi/Suwayomi-WebUI/commit/1914a61eddc1033eabd2844fd80b8909cf30b6fe)) Add GitHub Action to run tsc on pull request events ([#232](https://github.com/Suwayomi/Suwayomi-WebUI/pull/232) by @schroda)
|
||||
- ([r992](https://github.com/Suwayomi/Suwayomi-WebUI/commit/56fe90d0499fa674b6833d3ccd99a8b17bd7a0f0)) Translations update from Hosted Weblate ([#241](https://github.com/Suwayomi/Suwayomi-WebUI/pull/241) by @weblate, @comradekingu, @AriaMoradi)
|
||||
- ([r991](https://github.com/Suwayomi/Suwayomi-WebUI/commit/cd1d24ada7a81f5df43235c310eb90d10eb44103)) Add logic to migrate metadata values ([#227](https://github.com/Suwayomi/Suwayomi-WebUI/pull/227) by @schroda)
|
||||
- ([r990](https://github.com/Suwayomi/Suwayomi-WebUI/commit/5473d14eaeb8d21aea36f90f2f28833c0cd2817e)) Adds search by genre to WebUI ([#238](https://github.com/Suwayomi/Suwayomi-WebUI/pull/238) by @akabhirav)
|
||||
- ([r989](https://github.com/Suwayomi/Suwayomi-WebUI/commit/94e45c21333be735cd3e2d76815db4b1962958c2)) add translation keys (by @AriaMoradi)
|
||||
- ([r988](https://github.com/Suwayomi/Suwayomi-WebUI/commit/688358f67391cadbbb9246f2cf3dc2cfffe9f21d)) add translation keys (by @AriaMoradi)
|
||||
- ([r987](https://github.com/Suwayomi/Suwayomi-WebUI/commit/76d44bd657a11357d0617f317628ab5dbaf0d0fa)) clean up translations (by @AriaMoradi)
|
||||
- ([r986](https://github.com/Suwayomi/Suwayomi-WebUI/commit/6f3bc1bc4edac94a4828ca58b2388e51382870b2)) add translation notice (by @AriaMoradi)
|
||||
- ([r985](https://github.com/Suwayomi/Suwayomi-WebUI/commit/ce839145993ab21d4b42e2245232774205a9d3d2)) add translation files (by @AriaMoradi)
|
||||
- ([r984](https://github.com/Suwayomi/Suwayomi-WebUI/commit/1c7c3e566c780ae457d376b525ffb8613a903110)) add i18n (#239) (by @AriaMoradi)
|
||||
|
||||
|
||||
# Server: v0.7.0 + WebUI: r983
|
||||
## TL;DR
|
||||
- CBZ downloads support
|
||||
|
||||
21
README.md
21
README.md
@@ -45,13 +45,13 @@ Here's a list of known clients/user interfaces for Suwayomi-Server:
|
||||
##### Actively Developed Clients
|
||||
- [Suwayomi-WebUI](https://github.com/Suwayomi/Suwayomi-WebUI): The web/ElectronJS front-end that Suwayomi-Server ships with by default.
|
||||
- [Tachidesk-JUI](https://github.com/Suwayomi/Tachidesk-JUI): The native desktop front-end for Suwayomi-Server. Currently, the most advanced.
|
||||
- [Tachidesk-qtui](https://github.com/Suwayomi/Tachidesk-qtui): A C++/Qt front-end for mobile devices(Android/linux), feature support is basic.
|
||||
- [Tachidesk-Sorayomi](https://github.com/Suwayomi/Tachidesk-Sorayomi): A Flutter front-end for Desktop(Linux, windows, etc.), Web and Android with a User Interface inspired by Tachiyomi.
|
||||
- [Tachidesk-VaadinUI](https://github.com/Suwayomi/Tachidesk-VaadinUI): A Web front-end for Suwayomi-Server built with Vaadin.
|
||||
- [Suwayomi-VUI](https://github.com/Suwayomi/Suwayomi-VUI): A preview focused web frontend built with svelte with some features the other UIs might not have (migration)
|
||||
##### Inctive/Abandoned Clients
|
||||
- [Equinox](https://github.com/Suwayomi/Equinox): A web user interface made with Vue.js.
|
||||
##### Inactive/Abandoned Clients
|
||||
- [Tachidesk-qtui](https://github.com/Suwayomi/Tachidesk-qtui): A C++/Qt front-end for mobile devices(Android/linux), feature support is basic.
|
||||
- [Tachidesk-GTK](https://github.com/mahor1221/Tachidesk-GTK): A native Rust/GTK desktop client.
|
||||
- [Equinox](https://github.com/Suwayomi/Equinox): A web user interface made with Vue.js.
|
||||
|
||||
## Is this application usable? Should I test it?
|
||||
Here is a list of current features:
|
||||
@@ -108,6 +108,21 @@ sudo apt update
|
||||
sudo apt install suwayomi-server
|
||||
```
|
||||
|
||||
### NixOS
|
||||
You can deploy Suwayomi on NixOS using the module `services.suwayomi-server` in your configuration:
|
||||
|
||||
```
|
||||
{
|
||||
services.suwayomi-server = {
|
||||
enable = true;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
For more information, see [the NixOS manual](https://nixos.org/manual/nixos/stable/#module-services-suwayomi-server).
|
||||
|
||||
You can also directly use the package from [nixpkgs](https://search.nixos.org/packages?channel=unstable&type=packages&query=suwayomi-server).
|
||||
|
||||
### Docker
|
||||
Check our Official Docker release [Suwayomi Container](https://github.com/orgs/Suwayomi/packages/container/package/tachidesk) for running Suwayomi Server in a docker container. Source code for our container is available at [docker-tachidesk](https://github.com/Suwayomi/docker-tachidesk). By default, the server will be running on http://localhost:4567 open this url in your browser.
|
||||
|
||||
|
||||
@@ -10,9 +10,9 @@ import java.io.BufferedReader
|
||||
const val MainClass = "suwayomi.tachidesk.MainKt"
|
||||
|
||||
// should be bumped with each stable release
|
||||
val tachideskVersion = System.getenv("ProductVersion") ?: "v0.7.0"
|
||||
val tachideskVersion = System.getenv("ProductVersion") ?: "v1.1.0"
|
||||
|
||||
val webUIRevisionTag = System.getenv("WebUIRevision") ?: "r1397"
|
||||
val webUIRevisionTag = System.getenv("WebUIRevision") ?: "r1689"
|
||||
|
||||
// counts commits on the current checked out branch
|
||||
val getTachideskRevision = {
|
||||
|
||||
@@ -19,6 +19,11 @@ import android.app.Application
|
||||
import eu.kanade.tachiyomi.network.JavaScriptEngine
|
||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.protobuf.ProtoBuf
|
||||
import nl.adaptivity.xmlutil.serialization.XML
|
||||
import org.kodein.di.DI
|
||||
import org.kodein.di.conf.global
|
||||
import org.kodein.di.instance
|
||||
import rx.Observable
|
||||
import rx.schedulers.Schedulers
|
||||
import uy.kohesive.injekt.api.InjektModule
|
||||
@@ -53,7 +58,20 @@ class AppModule(val app: Application) : InjektModule {
|
||||
//
|
||||
// addSingletonFactory { LibrarySyncManager(app) }
|
||||
|
||||
addSingletonFactory { Json { ignoreUnknownKeys = true } }
|
||||
addSingletonFactory {
|
||||
val json by DI.global.instance<Json>()
|
||||
json
|
||||
}
|
||||
|
||||
addSingletonFactory {
|
||||
val xml by DI.global.instance<XML>()
|
||||
xml
|
||||
}
|
||||
|
||||
addSingletonFactory {
|
||||
val protobuf by DI.global.instance<ProtoBuf>()
|
||||
protobuf
|
||||
}
|
||||
|
||||
// Asynchronously init expensive components for a faster cold start
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package suwayomi.tachidesk.graphql
|
||||
|
||||
import com.expediagroup.graphql.server.extensions.toGraphQLError
|
||||
import graphql.execution.DataFetcherResult
|
||||
import mu.KotlinLogging
|
||||
|
||||
val logger = KotlinLogging.logger { }
|
||||
|
||||
inline fun <T> asDataFetcherResult(block: () -> T): DataFetcherResult<T?> {
|
||||
val result =
|
||||
runCatching {
|
||||
block()
|
||||
}
|
||||
|
||||
if (result.isFailure) {
|
||||
logger.error(result.exceptionOrNull()) { "asDataFetcherResult: failed due to" }
|
||||
return DataFetcherResult.newResult<T?>()
|
||||
.error(result.exceptionOrNull()?.toGraphQLError())
|
||||
.build()
|
||||
}
|
||||
|
||||
return DataFetcherResult.newResult<T?>()
|
||||
.data(result.getOrNull())
|
||||
.build()
|
||||
}
|
||||
46
server/src/main/kotlin/suwayomi/tachidesk/graphql/cache/CustomCacheMap.kt
vendored
Normal file
46
server/src/main/kotlin/suwayomi/tachidesk/graphql/cache/CustomCacheMap.kt
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
package suwayomi.tachidesk.graphql.cache
|
||||
|
||||
import org.dataloader.CacheMap
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class CustomCacheMap<K, V> : CacheMap<K, V> {
|
||||
private val cache: MutableMap<K, CompletableFuture<V>>
|
||||
|
||||
init {
|
||||
cache = HashMap()
|
||||
}
|
||||
|
||||
override fun containsKey(key: K): Boolean {
|
||||
return cache.containsKey(key)
|
||||
}
|
||||
|
||||
override fun get(key: K): CompletableFuture<V> {
|
||||
return cache[key]!!
|
||||
}
|
||||
|
||||
fun getKeys(): Collection<K> {
|
||||
return cache.keys.toSet()
|
||||
}
|
||||
|
||||
override fun getAll(): Collection<CompletableFuture<V>> {
|
||||
return cache.values
|
||||
}
|
||||
|
||||
override fun set(
|
||||
key: K,
|
||||
value: CompletableFuture<V>,
|
||||
): CacheMap<K, V> {
|
||||
cache[key] = value
|
||||
return this
|
||||
}
|
||||
|
||||
override fun delete(key: K): CacheMap<K, V> {
|
||||
cache.remove(key)
|
||||
return this
|
||||
}
|
||||
|
||||
override fun clear(): CacheMap<K, V> {
|
||||
cache.clear()
|
||||
return this
|
||||
}
|
||||
}
|
||||
@@ -99,6 +99,26 @@ class UnreadChapterCountForMangaDataLoader : KotlinDataLoader<Int, Int> {
|
||||
}
|
||||
}
|
||||
|
||||
class BookmarkedChapterCountForMangaDataLoader : KotlinDataLoader<Int, Int> {
|
||||
override val dataLoaderName = "BookmarkedChapterCountForMangaDataLoader"
|
||||
|
||||
override fun getDataLoader(): DataLoader<Int, Int> =
|
||||
DataLoaderFactory.newDataLoader<Int, Int> { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val bookmarkedChapterCountByMangaId =
|
||||
ChapterTable
|
||||
.slice(ChapterTable.manga, ChapterTable.isBookmarked.count())
|
||||
.select { (ChapterTable.manga inList ids) and (ChapterTable.isBookmarked eq true) }
|
||||
.groupBy(ChapterTable.manga)
|
||||
.associate { it[ChapterTable.manga].value to it[ChapterTable.isBookmarked.count()] }
|
||||
ids.map { bookmarkedChapterCountByMangaId[it]?.toInt() ?: 0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class LastReadChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType?> {
|
||||
override val dataLoaderName = "LastReadChapterForMangaDataLoader"
|
||||
|
||||
@@ -174,3 +194,22 @@ class LatestUploadedChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterTyp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FirstUnreadChapterForMangaDataLoader : KotlinDataLoader<Int, ChapterType?> {
|
||||
override val dataLoaderName = "FirstUnreadChapterForMangaDataLoader"
|
||||
|
||||
override fun getDataLoader(): DataLoader<Int, ChapterType?> =
|
||||
DataLoaderFactory.newDataLoader<Int, ChapterType?> { ids ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val firstUnreadChaptersByMangaId =
|
||||
ChapterTable
|
||||
.select { (ChapterTable.manga inList ids) and (ChapterTable.isRead eq false) }
|
||||
.orderBy(ChapterTable.sourceOrder to SortOrder.ASC)
|
||||
.groupBy { it[ChapterTable.manga].value }
|
||||
ids.map { id -> firstUnreadChaptersByMangaId[id]?.let { chapters -> ChapterType(chapters.first()) } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,11 +10,13 @@ package suwayomi.tachidesk.graphql.dataLoaders
|
||||
import com.expediagroup.graphql.dataloader.KotlinDataLoader
|
||||
import org.dataloader.DataLoader
|
||||
import org.dataloader.DataLoaderFactory
|
||||
import org.dataloader.DataLoaderOptions
|
||||
import org.jetbrains.exposed.sql.Slf4jSqlDebugLogger
|
||||
import org.jetbrains.exposed.sql.addLogger
|
||||
import org.jetbrains.exposed.sql.andWhere
|
||||
import org.jetbrains.exposed.sql.select
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import suwayomi.tachidesk.graphql.cache.CustomCacheMap
|
||||
import suwayomi.tachidesk.graphql.types.MangaNodeList
|
||||
import suwayomi.tachidesk.graphql.types.MangaNodeList.Companion.toNodeList
|
||||
import suwayomi.tachidesk.graphql.types.MangaType
|
||||
@@ -95,18 +97,21 @@ class MangaForIdsDataLoader : KotlinDataLoader<List<Int>, MangaNodeList> {
|
||||
override val dataLoaderName = "MangaForIdsDataLoader"
|
||||
|
||||
override fun getDataLoader(): DataLoader<List<Int>, MangaNodeList> =
|
||||
DataLoaderFactory.newDataLoader { mangaIds ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val ids = mangaIds.flatten().distinct()
|
||||
val manga =
|
||||
MangaTable.select { MangaTable.id inList ids }
|
||||
.map { MangaType(it) }
|
||||
mangaIds.map { mangaIds ->
|
||||
manga.filter { it.id in mangaIds }.toNodeList()
|
||||
DataLoaderFactory.newDataLoader(
|
||||
{ mangaIds ->
|
||||
future {
|
||||
transaction {
|
||||
addLogger(Slf4jSqlDebugLogger)
|
||||
val ids = mangaIds.flatten().distinct()
|
||||
val manga =
|
||||
MangaTable.select { MangaTable.id inList ids }
|
||||
.map { MangaType(it) }
|
||||
mangaIds.map { mangaIds ->
|
||||
manga.filter { it.id in mangaIds }.toNodeList()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
DataLoaderOptions.newOptions().setCacheMap(CustomCacheMap<List<Int>, MangaNodeList>()),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -67,6 +67,19 @@ class TrackerScoresDataLoader : KotlinDataLoader<Int, List<String>> {
|
||||
}
|
||||
}
|
||||
|
||||
class TrackerTokenExpiredDataLoader : KotlinDataLoader<Int, Boolean> {
|
||||
override val dataLoaderName = "TrackerTokenExpiredDataLoader"
|
||||
|
||||
override fun getDataLoader(): DataLoader<Int, Boolean> =
|
||||
DataLoaderFactory.newDataLoader { ids ->
|
||||
future {
|
||||
ids.map { id ->
|
||||
TrackerManager.getTracker(id)?.getIfAuthExpired()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TrackRecordsForMangaIdDataLoader : KotlinDataLoader<Int, TrackRecordNodeList> {
|
||||
override val dataLoaderName = "TrackRecordsForMangaIdDataLoader"
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package suwayomi.tachidesk.graphql.mutations
|
||||
|
||||
import graphql.execution.DataFetcherResult
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.inList
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.minus
|
||||
@@ -12,6 +13,7 @@ import org.jetbrains.exposed.sql.select
|
||||
import org.jetbrains.exposed.sql.selectAll
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import org.jetbrains.exposed.sql.update
|
||||
import suwayomi.tachidesk.graphql.asDataFetcherResult
|
||||
import suwayomi.tachidesk.graphql.types.CategoryMetaType
|
||||
import suwayomi.tachidesk.graphql.types.CategoryType
|
||||
import suwayomi.tachidesk.graphql.types.MangaType
|
||||
@@ -36,12 +38,14 @@ class CategoryMutation {
|
||||
val meta: CategoryMetaType,
|
||||
)
|
||||
|
||||
fun setCategoryMeta(input: SetCategoryMetaInput): SetCategoryMetaPayload {
|
||||
val (clientMutationId, meta) = input
|
||||
fun setCategoryMeta(input: SetCategoryMetaInput): DataFetcherResult<SetCategoryMetaPayload?> {
|
||||
return asDataFetcherResult {
|
||||
val (clientMutationId, meta) = input
|
||||
|
||||
Category.modifyMeta(meta.categoryId, meta.key, meta.value)
|
||||
Category.modifyMeta(meta.categoryId, meta.key, meta.value)
|
||||
|
||||
return SetCategoryMetaPayload(clientMutationId, meta)
|
||||
SetCategoryMetaPayload(clientMutationId, meta)
|
||||
}
|
||||
}
|
||||
|
||||
data class DeleteCategoryMetaInput(
|
||||
@@ -56,30 +60,32 @@ class CategoryMutation {
|
||||
val category: CategoryType,
|
||||
)
|
||||
|
||||
fun deleteCategoryMeta(input: DeleteCategoryMetaInput): DeleteCategoryMetaPayload {
|
||||
val (clientMutationId, categoryId, key) = input
|
||||
fun deleteCategoryMeta(input: DeleteCategoryMetaInput): DataFetcherResult<DeleteCategoryMetaPayload?> {
|
||||
return asDataFetcherResult {
|
||||
val (clientMutationId, categoryId, key) = input
|
||||
|
||||
val (meta, category) =
|
||||
transaction {
|
||||
val meta =
|
||||
CategoryMetaTable.select { (CategoryMetaTable.ref eq categoryId) and (CategoryMetaTable.key eq key) }
|
||||
.firstOrNull()
|
||||
val (meta, category) =
|
||||
transaction {
|
||||
val meta =
|
||||
CategoryMetaTable.select { (CategoryMetaTable.ref eq categoryId) and (CategoryMetaTable.key eq key) }
|
||||
.firstOrNull()
|
||||
|
||||
CategoryMetaTable.deleteWhere { (CategoryMetaTable.ref eq categoryId) and (CategoryMetaTable.key eq key) }
|
||||
CategoryMetaTable.deleteWhere { (CategoryMetaTable.ref eq categoryId) and (CategoryMetaTable.key eq key) }
|
||||
|
||||
val category =
|
||||
transaction {
|
||||
CategoryType(CategoryTable.select { CategoryTable.id eq categoryId }.first())
|
||||
}
|
||||
val category =
|
||||
transaction {
|
||||
CategoryType(CategoryTable.select { CategoryTable.id eq categoryId }.first())
|
||||
}
|
||||
|
||||
if (meta != null) {
|
||||
CategoryMetaType(meta)
|
||||
} else {
|
||||
null
|
||||
} to category
|
||||
}
|
||||
if (meta != null) {
|
||||
CategoryMetaType(meta)
|
||||
} else {
|
||||
null
|
||||
} to category
|
||||
}
|
||||
|
||||
return DeleteCategoryMetaPayload(clientMutationId, meta, category)
|
||||
DeleteCategoryMetaPayload(clientMutationId, meta, category)
|
||||
}
|
||||
}
|
||||
|
||||
data class UpdateCategoryPatch(
|
||||
@@ -147,36 +153,40 @@ class CategoryMutation {
|
||||
}
|
||||
}
|
||||
|
||||
fun updateCategory(input: UpdateCategoryInput): UpdateCategoryPayload {
|
||||
val (clientMutationId, id, patch) = input
|
||||
fun updateCategory(input: UpdateCategoryInput): DataFetcherResult<UpdateCategoryPayload?> {
|
||||
return asDataFetcherResult {
|
||||
val (clientMutationId, id, patch) = input
|
||||
|
||||
updateCategories(listOf(id), patch)
|
||||
updateCategories(listOf(id), patch)
|
||||
|
||||
val category =
|
||||
transaction {
|
||||
CategoryType(CategoryTable.select { CategoryTable.id eq id }.first())
|
||||
}
|
||||
val category =
|
||||
transaction {
|
||||
CategoryType(CategoryTable.select { CategoryTable.id eq id }.first())
|
||||
}
|
||||
|
||||
return UpdateCategoryPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
category = category,
|
||||
)
|
||||
UpdateCategoryPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
category = category,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateCategories(input: UpdateCategoriesInput): UpdateCategoriesPayload {
|
||||
val (clientMutationId, ids, patch) = input
|
||||
fun updateCategories(input: UpdateCategoriesInput): DataFetcherResult<UpdateCategoriesPayload?> {
|
||||
return asDataFetcherResult {
|
||||
val (clientMutationId, ids, patch) = input
|
||||
|
||||
updateCategories(ids, patch)
|
||||
updateCategories(ids, patch)
|
||||
|
||||
val categories =
|
||||
transaction {
|
||||
CategoryTable.select { CategoryTable.id inList ids }.map { CategoryType(it) }
|
||||
}
|
||||
val categories =
|
||||
transaction {
|
||||
CategoryTable.select { CategoryTable.id inList ids }.map { CategoryType(it) }
|
||||
}
|
||||
|
||||
return UpdateCategoriesPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
categories = categories,
|
||||
)
|
||||
UpdateCategoriesPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
categories = categories,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class UpdateCategoryOrderPayload(
|
||||
@@ -190,46 +200,48 @@ class CategoryMutation {
|
||||
val position: Int,
|
||||
)
|
||||
|
||||
fun updateCategoryOrder(input: UpdateCategoryOrderInput): UpdateCategoryOrderPayload {
|
||||
val (clientMutationId, categoryId, position) = input
|
||||
require(position > 0) {
|
||||
"'order' must not be <= 0"
|
||||
}
|
||||
|
||||
transaction {
|
||||
val currentOrder =
|
||||
CategoryTable
|
||||
.select { CategoryTable.id eq categoryId }
|
||||
.first()[CategoryTable.order]
|
||||
|
||||
if (currentOrder != position) {
|
||||
if (position < currentOrder) {
|
||||
CategoryTable.update({ CategoryTable.order greaterEq position }) {
|
||||
it[CategoryTable.order] = CategoryTable.order + 1
|
||||
}
|
||||
} else {
|
||||
CategoryTable.update({ CategoryTable.order lessEq position }) {
|
||||
it[CategoryTable.order] = CategoryTable.order - 1
|
||||
}
|
||||
}
|
||||
|
||||
CategoryTable.update({ CategoryTable.id eq categoryId }) {
|
||||
it[CategoryTable.order] = position
|
||||
}
|
||||
fun updateCategoryOrder(input: UpdateCategoryOrderInput): DataFetcherResult<UpdateCategoryOrderPayload?> {
|
||||
return asDataFetcherResult {
|
||||
val (clientMutationId, categoryId, position) = input
|
||||
require(position > 0) {
|
||||
"'order' must not be <= 0"
|
||||
}
|
||||
}
|
||||
|
||||
Category.normalizeCategories()
|
||||
|
||||
val categories =
|
||||
transaction {
|
||||
CategoryTable.selectAll().orderBy(CategoryTable.order).map { CategoryType(it) }
|
||||
val currentOrder =
|
||||
CategoryTable
|
||||
.select { CategoryTable.id eq categoryId }
|
||||
.first()[CategoryTable.order]
|
||||
|
||||
if (currentOrder != position) {
|
||||
if (position < currentOrder) {
|
||||
CategoryTable.update({ CategoryTable.order greaterEq position }) {
|
||||
it[CategoryTable.order] = CategoryTable.order + 1
|
||||
}
|
||||
} else {
|
||||
CategoryTable.update({ CategoryTable.order lessEq position }) {
|
||||
it[CategoryTable.order] = CategoryTable.order - 1
|
||||
}
|
||||
}
|
||||
|
||||
CategoryTable.update({ CategoryTable.id eq categoryId }) {
|
||||
it[CategoryTable.order] = position
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return UpdateCategoryOrderPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
categories = categories,
|
||||
)
|
||||
Category.normalizeCategories()
|
||||
|
||||
val categories =
|
||||
transaction {
|
||||
CategoryTable.selectAll().orderBy(CategoryTable.order).map { CategoryType(it) }
|
||||
}
|
||||
|
||||
UpdateCategoryOrderPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
categories = categories,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class CreateCategoryInput(
|
||||
@@ -246,51 +258,53 @@ class CategoryMutation {
|
||||
val category: CategoryType,
|
||||
)
|
||||
|
||||
fun createCategory(input: CreateCategoryInput): CreateCategoryPayload {
|
||||
val (clientMutationId, name, order, default, includeInUpdate, includeInDownload) = input
|
||||
transaction {
|
||||
require(CategoryTable.select { CategoryTable.name eq input.name }.isEmpty()) {
|
||||
"'name' must be unique"
|
||||
}
|
||||
}
|
||||
require(!name.equals(Category.DEFAULT_CATEGORY_NAME, ignoreCase = true)) {
|
||||
"'name' must not be ${Category.DEFAULT_CATEGORY_NAME}"
|
||||
}
|
||||
if (order != null) {
|
||||
require(order > 0) {
|
||||
"'order' must not be <= 0"
|
||||
}
|
||||
}
|
||||
|
||||
val category =
|
||||
fun createCategory(input: CreateCategoryInput): DataFetcherResult<CreateCategoryPayload?> {
|
||||
return asDataFetcherResult {
|
||||
val (clientMutationId, name, order, default, includeInUpdate, includeInDownload) = input
|
||||
transaction {
|
||||
if (order != null) {
|
||||
CategoryTable.update({ CategoryTable.order greaterEq order }) {
|
||||
it[CategoryTable.order] = CategoryTable.order + 1
|
||||
require(CategoryTable.select { CategoryTable.name eq input.name }.isEmpty()) {
|
||||
"'name' must be unique"
|
||||
}
|
||||
}
|
||||
require(!name.equals(Category.DEFAULT_CATEGORY_NAME, ignoreCase = true)) {
|
||||
"'name' must not be ${Category.DEFAULT_CATEGORY_NAME}"
|
||||
}
|
||||
if (order != null) {
|
||||
require(order > 0) {
|
||||
"'order' must not be <= 0"
|
||||
}
|
||||
}
|
||||
|
||||
val category =
|
||||
transaction {
|
||||
if (order != null) {
|
||||
CategoryTable.update({ CategoryTable.order greaterEq order }) {
|
||||
it[CategoryTable.order] = CategoryTable.order + 1
|
||||
}
|
||||
}
|
||||
|
||||
val id =
|
||||
CategoryTable.insertAndGetId {
|
||||
it[CategoryTable.name] = input.name
|
||||
it[CategoryTable.order] = order ?: Int.MAX_VALUE
|
||||
if (default != null) {
|
||||
it[CategoryTable.isDefault] = default
|
||||
}
|
||||
if (includeInUpdate != null) {
|
||||
it[CategoryTable.includeInUpdate] = includeInUpdate.value
|
||||
}
|
||||
if (includeInDownload != null) {
|
||||
it[CategoryTable.includeInDownload] = includeInDownload.value
|
||||
}
|
||||
}
|
||||
|
||||
Category.normalizeCategories()
|
||||
|
||||
CategoryType(CategoryTable.select { CategoryTable.id eq id }.first())
|
||||
}
|
||||
|
||||
val id =
|
||||
CategoryTable.insertAndGetId {
|
||||
it[CategoryTable.name] = input.name
|
||||
it[CategoryTable.order] = order ?: Int.MAX_VALUE
|
||||
if (default != null) {
|
||||
it[CategoryTable.isDefault] = default
|
||||
}
|
||||
if (includeInUpdate != null) {
|
||||
it[CategoryTable.includeInUpdate] = includeInUpdate.value
|
||||
}
|
||||
if (includeInDownload != null) {
|
||||
it[CategoryTable.includeInDownload] = includeInDownload.value
|
||||
}
|
||||
}
|
||||
|
||||
Category.normalizeCategories()
|
||||
|
||||
CategoryType(CategoryTable.select { CategoryTable.id eq id }.first())
|
||||
}
|
||||
|
||||
return CreateCategoryPayload(clientMutationId, category)
|
||||
CreateCategoryPayload(clientMutationId, category)
|
||||
}
|
||||
}
|
||||
|
||||
data class DeleteCategoryInput(
|
||||
@@ -304,41 +318,43 @@ class CategoryMutation {
|
||||
val mangas: List<MangaType>,
|
||||
)
|
||||
|
||||
fun deleteCategory(input: DeleteCategoryInput): DeleteCategoryPayload {
|
||||
val (clientMutationId, categoryId) = input
|
||||
if (categoryId == 0) { // Don't delete default category
|
||||
return DeleteCategoryPayload(
|
||||
clientMutationId,
|
||||
null,
|
||||
emptyList(),
|
||||
)
|
||||
}
|
||||
|
||||
val (category, mangas) =
|
||||
transaction {
|
||||
val category =
|
||||
CategoryTable.select { CategoryTable.id eq categoryId }
|
||||
.firstOrNull()
|
||||
|
||||
val mangas =
|
||||
transaction {
|
||||
MangaTable.innerJoin(CategoryMangaTable)
|
||||
.select { CategoryMangaTable.category eq categoryId }
|
||||
.map { MangaType(it) }
|
||||
}
|
||||
|
||||
CategoryTable.deleteWhere { CategoryTable.id eq categoryId }
|
||||
|
||||
Category.normalizeCategories()
|
||||
|
||||
if (category != null) {
|
||||
CategoryType(category)
|
||||
} else {
|
||||
null
|
||||
} to mangas
|
||||
fun deleteCategory(input: DeleteCategoryInput): DataFetcherResult<DeleteCategoryPayload?> {
|
||||
return asDataFetcherResult {
|
||||
val (clientMutationId, categoryId) = input
|
||||
if (categoryId == 0) { // Don't delete default category
|
||||
return@asDataFetcherResult DeleteCategoryPayload(
|
||||
clientMutationId,
|
||||
null,
|
||||
emptyList(),
|
||||
)
|
||||
}
|
||||
|
||||
return DeleteCategoryPayload(clientMutationId, category, mangas)
|
||||
val (category, mangas) =
|
||||
transaction {
|
||||
val category =
|
||||
CategoryTable.select { CategoryTable.id eq categoryId }
|
||||
.firstOrNull()
|
||||
|
||||
val mangas =
|
||||
transaction {
|
||||
MangaTable.innerJoin(CategoryMangaTable)
|
||||
.select { CategoryMangaTable.category eq categoryId }
|
||||
.map { MangaType(it) }
|
||||
}
|
||||
|
||||
CategoryTable.deleteWhere { CategoryTable.id eq categoryId }
|
||||
|
||||
Category.normalizeCategories()
|
||||
|
||||
if (category != null) {
|
||||
CategoryType(category)
|
||||
} else {
|
||||
null
|
||||
} to mangas
|
||||
}
|
||||
|
||||
DeleteCategoryPayload(clientMutationId, category, mangas)
|
||||
}
|
||||
}
|
||||
|
||||
data class UpdateMangaCategoriesPatch(
|
||||
@@ -406,35 +422,39 @@ class CategoryMutation {
|
||||
}
|
||||
}
|
||||
|
||||
fun updateMangaCategories(input: UpdateMangaCategoriesInput): UpdateMangaCategoriesPayload {
|
||||
val (clientMutationId, id, patch) = input
|
||||
fun updateMangaCategories(input: UpdateMangaCategoriesInput): DataFetcherResult<UpdateMangaCategoriesPayload?> {
|
||||
return asDataFetcherResult {
|
||||
val (clientMutationId, id, patch) = input
|
||||
|
||||
updateMangas(listOf(id), patch)
|
||||
updateMangas(listOf(id), patch)
|
||||
|
||||
val manga =
|
||||
transaction {
|
||||
MangaType(MangaTable.select { MangaTable.id eq id }.first())
|
||||
}
|
||||
val manga =
|
||||
transaction {
|
||||
MangaType(MangaTable.select { MangaTable.id eq id }.first())
|
||||
}
|
||||
|
||||
return UpdateMangaCategoriesPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
manga = manga,
|
||||
)
|
||||
UpdateMangaCategoriesPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
manga = manga,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateMangasCategories(input: UpdateMangasCategoriesInput): UpdateMangasCategoriesPayload {
|
||||
val (clientMutationId, ids, patch) = input
|
||||
fun updateMangasCategories(input: UpdateMangasCategoriesInput): DataFetcherResult<UpdateMangasCategoriesPayload?> {
|
||||
return asDataFetcherResult {
|
||||
val (clientMutationId, ids, patch) = input
|
||||
|
||||
updateMangas(ids, patch)
|
||||
updateMangas(ids, patch)
|
||||
|
||||
val mangas =
|
||||
transaction {
|
||||
MangaTable.select { MangaTable.id inList ids }.map { MangaType(it) }
|
||||
}
|
||||
val mangas =
|
||||
transaction {
|
||||
MangaTable.select { MangaTable.id inList ids }.map { MangaType(it) }
|
||||
}
|
||||
|
||||
return UpdateMangasCategoriesPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
mangas = mangas,
|
||||
)
|
||||
UpdateMangasCategoriesPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
mangas = mangas,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
package suwayomi.tachidesk.graphql.mutations
|
||||
|
||||
import graphql.execution.DataFetcherResult
|
||||
import org.jetbrains.exposed.dao.id.EntityID
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||
import org.jetbrains.exposed.sql.and
|
||||
import org.jetbrains.exposed.sql.deleteWhere
|
||||
import org.jetbrains.exposed.sql.select
|
||||
import org.jetbrains.exposed.sql.statements.BatchUpdateStatement
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import org.jetbrains.exposed.sql.update
|
||||
import suwayomi.tachidesk.graphql.asDataFetcherResult
|
||||
import suwayomi.tachidesk.graphql.types.ChapterMetaType
|
||||
import suwayomi.tachidesk.graphql.types.ChapterType
|
||||
import suwayomi.tachidesk.manga.impl.Chapter
|
||||
import suwayomi.tachidesk.manga.impl.chapter.getChapterDownloadReadyById
|
||||
import suwayomi.tachidesk.manga.impl.track.Track
|
||||
import suwayomi.tachidesk.manga.model.table.ChapterMetaTable
|
||||
import suwayomi.tachidesk.manga.model.table.ChapterTable
|
||||
import suwayomi.tachidesk.server.JavalinSetup.future
|
||||
@@ -56,61 +58,73 @@ class ChapterMutation {
|
||||
patch: UpdateChapterPatch,
|
||||
) {
|
||||
transaction {
|
||||
val chapterIdToPageCount =
|
||||
if (patch.lastPageRead != null) {
|
||||
ChapterTable
|
||||
.slice(ChapterTable.id, ChapterTable.pageCount)
|
||||
.select { ChapterTable.id inList ids }
|
||||
.groupBy { it[ChapterTable.id].value }
|
||||
.mapValues { it.value.firstOrNull()?.let { it[ChapterTable.pageCount] } }
|
||||
} else {
|
||||
emptyMap()
|
||||
}
|
||||
if (patch.isRead != null || patch.isBookmarked != null || patch.lastPageRead != null) {
|
||||
val now = Instant.now().epochSecond
|
||||
ChapterTable.update({ ChapterTable.id inList ids }) { update ->
|
||||
patch.isRead?.also {
|
||||
update[isRead] = it
|
||||
|
||||
BatchUpdateStatement(ChapterTable).apply {
|
||||
ids.forEach { chapterId ->
|
||||
addBatch(EntityID(chapterId, ChapterTable))
|
||||
patch.isRead?.also {
|
||||
this[ChapterTable.isRead] = it
|
||||
}
|
||||
patch.isBookmarked?.also {
|
||||
this[ChapterTable.isBookmarked] = it
|
||||
}
|
||||
patch.lastPageRead?.also {
|
||||
this[ChapterTable.lastPageRead] = it.coerceAtMost(chapterIdToPageCount[chapterId] ?: 0).coerceAtLeast(0)
|
||||
this[ChapterTable.lastReadAt] = now
|
||||
}
|
||||
}
|
||||
patch.isBookmarked?.also {
|
||||
update[isBookmarked] = it
|
||||
}
|
||||
patch.lastPageRead?.also {
|
||||
update[lastPageRead] = it
|
||||
update[lastReadAt] = now
|
||||
}
|
||||
}
|
||||
if (patch.isRead == true) {
|
||||
val mangaIds =
|
||||
ChapterTable.slice(ChapterTable.manga).select { ChapterTable.id inList ids }
|
||||
.map { it[ChapterTable.manga].value }
|
||||
.toSet()
|
||||
Track.asyncTrackChapter(mangaIds)
|
||||
execute(this@transaction)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateChapter(input: UpdateChapterInput): UpdateChapterPayload {
|
||||
val (clientMutationId, id, patch) = input
|
||||
fun updateChapter(input: UpdateChapterInput): DataFetcherResult<UpdateChapterPayload?> {
|
||||
return asDataFetcherResult {
|
||||
val (clientMutationId, id, patch) = input
|
||||
|
||||
updateChapters(listOf(id), patch)
|
||||
updateChapters(listOf(id), patch)
|
||||
|
||||
val chapter =
|
||||
transaction {
|
||||
ChapterType(ChapterTable.select { ChapterTable.id eq id }.first())
|
||||
}
|
||||
val chapter =
|
||||
transaction {
|
||||
ChapterType(ChapterTable.select { ChapterTable.id eq id }.first())
|
||||
}
|
||||
|
||||
return UpdateChapterPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
chapter = chapter,
|
||||
)
|
||||
UpdateChapterPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
chapter = chapter,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateChapters(input: UpdateChaptersInput): UpdateChaptersPayload {
|
||||
val (clientMutationId, ids, patch) = input
|
||||
fun updateChapters(input: UpdateChaptersInput): DataFetcherResult<UpdateChaptersPayload?> {
|
||||
return asDataFetcherResult {
|
||||
val (clientMutationId, ids, patch) = input
|
||||
|
||||
updateChapters(ids, patch)
|
||||
updateChapters(ids, patch)
|
||||
|
||||
val chapters =
|
||||
transaction {
|
||||
ChapterTable.select { ChapterTable.id inList ids }.map { ChapterType(it) }
|
||||
}
|
||||
val chapters =
|
||||
transaction {
|
||||
ChapterTable.select { ChapterTable.id inList ids }.map { ChapterType(it) }
|
||||
}
|
||||
|
||||
return UpdateChaptersPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
chapters = chapters,
|
||||
)
|
||||
UpdateChaptersPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
chapters = chapters,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class FetchChaptersInput(
|
||||
@@ -123,23 +137,25 @@ class ChapterMutation {
|
||||
val chapters: List<ChapterType>,
|
||||
)
|
||||
|
||||
fun fetchChapters(input: FetchChaptersInput): CompletableFuture<FetchChaptersPayload> {
|
||||
fun fetchChapters(input: FetchChaptersInput): CompletableFuture<DataFetcherResult<FetchChaptersPayload?>> {
|
||||
val (clientMutationId, mangaId) = input
|
||||
|
||||
return future {
|
||||
Chapter.fetchChapterList(mangaId)
|
||||
}.thenApply {
|
||||
val chapters =
|
||||
transaction {
|
||||
ChapterTable.select { ChapterTable.manga eq mangaId }
|
||||
.orderBy(ChapterTable.sourceOrder)
|
||||
.map { ChapterType(it) }
|
||||
}
|
||||
asDataFetcherResult {
|
||||
Chapter.fetchChapterList(mangaId)
|
||||
|
||||
FetchChaptersPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
chapters = chapters,
|
||||
)
|
||||
val chapters =
|
||||
transaction {
|
||||
ChapterTable.select { ChapterTable.manga eq mangaId }
|
||||
.orderBy(ChapterTable.sourceOrder)
|
||||
.map { ChapterType(it) }
|
||||
}
|
||||
|
||||
FetchChaptersPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
chapters = chapters,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,12 +169,14 @@ class ChapterMutation {
|
||||
val meta: ChapterMetaType,
|
||||
)
|
||||
|
||||
fun setChapterMeta(input: SetChapterMetaInput): SetChapterMetaPayload {
|
||||
val (clientMutationId, meta) = input
|
||||
fun setChapterMeta(input: SetChapterMetaInput): DataFetcherResult<SetChapterMetaPayload?> {
|
||||
return asDataFetcherResult {
|
||||
val (clientMutationId, meta) = input
|
||||
|
||||
Chapter.modifyChapterMeta(meta.chapterId, meta.key, meta.value)
|
||||
Chapter.modifyChapterMeta(meta.chapterId, meta.key, meta.value)
|
||||
|
||||
return SetChapterMetaPayload(clientMutationId, meta)
|
||||
SetChapterMetaPayload(clientMutationId, meta)
|
||||
}
|
||||
}
|
||||
|
||||
data class DeleteChapterMetaInput(
|
||||
@@ -173,30 +191,32 @@ class ChapterMutation {
|
||||
val chapter: ChapterType,
|
||||
)
|
||||
|
||||
fun deleteChapterMeta(input: DeleteChapterMetaInput): DeleteChapterMetaPayload {
|
||||
val (clientMutationId, chapterId, key) = input
|
||||
fun deleteChapterMeta(input: DeleteChapterMetaInput): DataFetcherResult<DeleteChapterMetaPayload?> {
|
||||
return asDataFetcherResult {
|
||||
val (clientMutationId, chapterId, key) = input
|
||||
|
||||
val (meta, chapter) =
|
||||
transaction {
|
||||
val meta =
|
||||
ChapterMetaTable.select { (ChapterMetaTable.ref eq chapterId) and (ChapterMetaTable.key eq key) }
|
||||
.firstOrNull()
|
||||
val (meta, chapter) =
|
||||
transaction {
|
||||
val meta =
|
||||
ChapterMetaTable.select { (ChapterMetaTable.ref eq chapterId) and (ChapterMetaTable.key eq key) }
|
||||
.firstOrNull()
|
||||
|
||||
ChapterMetaTable.deleteWhere { (ChapterMetaTable.ref eq chapterId) and (ChapterMetaTable.key eq key) }
|
||||
ChapterMetaTable.deleteWhere { (ChapterMetaTable.ref eq chapterId) and (ChapterMetaTable.key eq key) }
|
||||
|
||||
val chapter =
|
||||
transaction {
|
||||
ChapterType(ChapterTable.select { ChapterTable.id eq chapterId }.first())
|
||||
}
|
||||
val chapter =
|
||||
transaction {
|
||||
ChapterType(ChapterTable.select { ChapterTable.id eq chapterId }.first())
|
||||
}
|
||||
|
||||
if (meta != null) {
|
||||
ChapterMetaType(meta)
|
||||
} else {
|
||||
null
|
||||
} to chapter
|
||||
}
|
||||
if (meta != null) {
|
||||
ChapterMetaType(meta)
|
||||
} else {
|
||||
null
|
||||
} to chapter
|
||||
}
|
||||
|
||||
return DeleteChapterMetaPayload(clientMutationId, meta, chapter)
|
||||
DeleteChapterMetaPayload(clientMutationId, meta, chapter)
|
||||
}
|
||||
}
|
||||
|
||||
data class FetchChapterPagesInput(
|
||||
@@ -210,20 +230,22 @@ class ChapterMutation {
|
||||
val chapter: ChapterType,
|
||||
)
|
||||
|
||||
fun fetchChapterPages(input: FetchChapterPagesInput): CompletableFuture<FetchChapterPagesPayload> {
|
||||
fun fetchChapterPages(input: FetchChapterPagesInput): CompletableFuture<DataFetcherResult<FetchChapterPagesPayload?>> {
|
||||
val (clientMutationId, chapterId) = input
|
||||
|
||||
return future {
|
||||
getChapterDownloadReadyById(chapterId)
|
||||
}.thenApply { chapter ->
|
||||
FetchChapterPagesPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
pages =
|
||||
List(chapter.pageCount) { index ->
|
||||
"/api/v1/manga/${chapter.mangaId}/chapter/${chapter.index}/page/$index"
|
||||
},
|
||||
chapter = ChapterType(chapter),
|
||||
)
|
||||
asDataFetcherResult {
|
||||
val chapter = getChapterDownloadReadyById(chapterId)
|
||||
|
||||
FetchChapterPagesPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
pages =
|
||||
List(chapter.pageCount) { index ->
|
||||
"/api/v1/manga/${chapter.mangaId}/chapter/${chapter.index}/page/$index"
|
||||
},
|
||||
chapter = ChapterType(chapter),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package suwayomi.tachidesk.graphql.mutations
|
||||
|
||||
import graphql.execution.DataFetcherResult
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import org.jetbrains.exposed.sql.select
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import suwayomi.tachidesk.graphql.asDataFetcherResult
|
||||
import suwayomi.tachidesk.graphql.types.ChapterType
|
||||
import suwayomi.tachidesk.graphql.types.DownloadStatus
|
||||
import suwayomi.tachidesk.manga.impl.Chapter
|
||||
@@ -25,19 +27,21 @@ class DownloadMutation {
|
||||
val chapters: List<ChapterType>,
|
||||
)
|
||||
|
||||
fun deleteDownloadedChapters(input: DeleteDownloadedChaptersInput): DeleteDownloadedChaptersPayload {
|
||||
fun deleteDownloadedChapters(input: DeleteDownloadedChaptersInput): DataFetcherResult<DeleteDownloadedChaptersPayload?> {
|
||||
val (clientMutationId, chapters) = input
|
||||
|
||||
Chapter.deleteChapters(chapters)
|
||||
return asDataFetcherResult {
|
||||
Chapter.deleteChapters(chapters)
|
||||
|
||||
return DeleteDownloadedChaptersPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
chapters =
|
||||
transaction {
|
||||
ChapterTable.select { ChapterTable.id inList chapters }
|
||||
.map { ChapterType(it) }
|
||||
},
|
||||
)
|
||||
DeleteDownloadedChaptersPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
chapters =
|
||||
transaction {
|
||||
ChapterTable.select { ChapterTable.id inList chapters }
|
||||
.map { ChapterType(it) }
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class DeleteDownloadedChapterInput(
|
||||
@@ -50,18 +54,20 @@ class DownloadMutation {
|
||||
val chapters: ChapterType,
|
||||
)
|
||||
|
||||
fun deleteDownloadedChapter(input: DeleteDownloadedChapterInput): DeleteDownloadedChapterPayload {
|
||||
fun deleteDownloadedChapter(input: DeleteDownloadedChapterInput): DataFetcherResult<DeleteDownloadedChapterPayload?> {
|
||||
val (clientMutationId, chapter) = input
|
||||
|
||||
Chapter.deleteChapters(listOf(chapter))
|
||||
return asDataFetcherResult {
|
||||
Chapter.deleteChapters(listOf(chapter))
|
||||
|
||||
return DeleteDownloadedChapterPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
chapters =
|
||||
transaction {
|
||||
ChapterType(ChapterTable.select { ChapterTable.id eq chapter }.first())
|
||||
},
|
||||
)
|
||||
DeleteDownloadedChapterPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
chapters =
|
||||
transaction {
|
||||
ChapterType(ChapterTable.select { ChapterTable.id eq chapter }.first())
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class EnqueueChapterDownloadsInput(
|
||||
@@ -74,19 +80,23 @@ class DownloadMutation {
|
||||
val downloadStatus: DownloadStatus,
|
||||
)
|
||||
|
||||
fun enqueueChapterDownloads(input: EnqueueChapterDownloadsInput): CompletableFuture<EnqueueChapterDownloadsPayload> {
|
||||
fun enqueueChapterDownloads(
|
||||
input: EnqueueChapterDownloadsInput,
|
||||
): CompletableFuture<DataFetcherResult<EnqueueChapterDownloadsPayload?>> {
|
||||
val (clientMutationId, chapters) = input
|
||||
|
||||
DownloadManager.enqueue(DownloadManager.EnqueueInput(chapters))
|
||||
|
||||
return future {
|
||||
EnqueueChapterDownloadsPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
downloadStatus =
|
||||
withTimeout(30.seconds) {
|
||||
DownloadStatus(DownloadManager.status.first { it.queue.any { it.chapter.id in chapters } })
|
||||
},
|
||||
)
|
||||
asDataFetcherResult {
|
||||
DownloadManager.enqueue(DownloadManager.EnqueueInput(chapters))
|
||||
|
||||
EnqueueChapterDownloadsPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
downloadStatus =
|
||||
withTimeout(30.seconds) {
|
||||
DownloadStatus(DownloadManager.status.first { it.queue.any { it.chapter.id in chapters } })
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,19 +110,21 @@ class DownloadMutation {
|
||||
val downloadStatus: DownloadStatus,
|
||||
)
|
||||
|
||||
fun enqueueChapterDownload(input: EnqueueChapterDownloadInput): CompletableFuture<EnqueueChapterDownloadPayload> {
|
||||
fun enqueueChapterDownload(input: EnqueueChapterDownloadInput): CompletableFuture<DataFetcherResult<EnqueueChapterDownloadPayload?>> {
|
||||
val (clientMutationId, chapter) = input
|
||||
|
||||
DownloadManager.enqueue(DownloadManager.EnqueueInput(listOf(chapter)))
|
||||
|
||||
return future {
|
||||
EnqueueChapterDownloadPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
downloadStatus =
|
||||
withTimeout(30.seconds) {
|
||||
DownloadStatus(DownloadManager.status.first { it.queue.any { it.chapter.id == chapter } })
|
||||
},
|
||||
)
|
||||
asDataFetcherResult {
|
||||
DownloadManager.enqueue(DownloadManager.EnqueueInput(listOf(chapter)))
|
||||
|
||||
EnqueueChapterDownloadPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
downloadStatus =
|
||||
withTimeout(30.seconds) {
|
||||
DownloadStatus(DownloadManager.status.first { it.queue.any { it.chapter.id == chapter } })
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,19 +138,23 @@ class DownloadMutation {
|
||||
val downloadStatus: DownloadStatus,
|
||||
)
|
||||
|
||||
fun dequeueChapterDownloads(input: DequeueChapterDownloadsInput): CompletableFuture<DequeueChapterDownloadsPayload> {
|
||||
fun dequeueChapterDownloads(
|
||||
input: DequeueChapterDownloadsInput,
|
||||
): CompletableFuture<DataFetcherResult<DequeueChapterDownloadsPayload?>> {
|
||||
val (clientMutationId, chapters) = input
|
||||
|
||||
DownloadManager.dequeue(DownloadManager.EnqueueInput(chapters))
|
||||
|
||||
return future {
|
||||
DequeueChapterDownloadsPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
downloadStatus =
|
||||
withTimeout(30.seconds) {
|
||||
DownloadStatus(DownloadManager.status.first { it.queue.none { it.chapter.id in chapters } })
|
||||
},
|
||||
)
|
||||
asDataFetcherResult {
|
||||
DownloadManager.dequeue(DownloadManager.EnqueueInput(chapters))
|
||||
|
||||
DequeueChapterDownloadsPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
downloadStatus =
|
||||
withTimeout(30.seconds) {
|
||||
DownloadStatus(DownloadManager.status.first { it.queue.none { it.chapter.id in chapters } })
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,19 +168,21 @@ class DownloadMutation {
|
||||
val downloadStatus: DownloadStatus,
|
||||
)
|
||||
|
||||
fun dequeueChapterDownload(input: DequeueChapterDownloadInput): CompletableFuture<DequeueChapterDownloadPayload> {
|
||||
fun dequeueChapterDownload(input: DequeueChapterDownloadInput): CompletableFuture<DataFetcherResult<DequeueChapterDownloadPayload?>> {
|
||||
val (clientMutationId, chapter) = input
|
||||
|
||||
DownloadManager.dequeue(DownloadManager.EnqueueInput(listOf(chapter)))
|
||||
|
||||
return future {
|
||||
DequeueChapterDownloadPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
downloadStatus =
|
||||
withTimeout(30.seconds) {
|
||||
DownloadStatus(DownloadManager.status.first { it.queue.none { it.chapter.id == chapter } })
|
||||
},
|
||||
)
|
||||
asDataFetcherResult {
|
||||
DownloadManager.dequeue(DownloadManager.EnqueueInput(listOf(chapter)))
|
||||
|
||||
DequeueChapterDownloadPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
downloadStatus =
|
||||
withTimeout(30.seconds) {
|
||||
DownloadStatus(DownloadManager.status.first { it.queue.none { it.chapter.id == chapter } })
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -177,19 +195,21 @@ class DownloadMutation {
|
||||
val downloadStatus: DownloadStatus,
|
||||
)
|
||||
|
||||
fun startDownloader(input: StartDownloaderInput): CompletableFuture<StartDownloaderPayload> {
|
||||
DownloadManager.start()
|
||||
|
||||
fun startDownloader(input: StartDownloaderInput): CompletableFuture<DataFetcherResult<StartDownloaderPayload?>> {
|
||||
return future {
|
||||
StartDownloaderPayload(
|
||||
input.clientMutationId,
|
||||
downloadStatus =
|
||||
withTimeout(30.seconds) {
|
||||
DownloadStatus(
|
||||
DownloadManager.status.first { it.status == Status.Started },
|
||||
)
|
||||
},
|
||||
)
|
||||
asDataFetcherResult {
|
||||
DownloadManager.start()
|
||||
|
||||
StartDownloaderPayload(
|
||||
input.clientMutationId,
|
||||
downloadStatus =
|
||||
withTimeout(30.seconds) {
|
||||
DownloadStatus(
|
||||
DownloadManager.status.first { it.status == Status.Started },
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,18 +222,21 @@ class DownloadMutation {
|
||||
val downloadStatus: DownloadStatus,
|
||||
)
|
||||
|
||||
fun stopDownloader(input: StopDownloaderInput): CompletableFuture<StopDownloaderPayload> {
|
||||
fun stopDownloader(input: StopDownloaderInput): CompletableFuture<DataFetcherResult<StopDownloaderPayload?>> {
|
||||
return future {
|
||||
DownloadManager.stop()
|
||||
StopDownloaderPayload(
|
||||
input.clientMutationId,
|
||||
downloadStatus =
|
||||
withTimeout(30.seconds) {
|
||||
DownloadStatus(
|
||||
DownloadManager.status.first { it.status == Status.Stopped },
|
||||
)
|
||||
},
|
||||
)
|
||||
asDataFetcherResult {
|
||||
DownloadManager.stop()
|
||||
|
||||
StopDownloaderPayload(
|
||||
input.clientMutationId,
|
||||
downloadStatus =
|
||||
withTimeout(30.seconds) {
|
||||
DownloadStatus(
|
||||
DownloadManager.status.first { it.status == Status.Stopped },
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,18 +249,21 @@ class DownloadMutation {
|
||||
val downloadStatus: DownloadStatus,
|
||||
)
|
||||
|
||||
fun clearDownloader(input: ClearDownloaderInput): CompletableFuture<ClearDownloaderPayload> {
|
||||
fun clearDownloader(input: ClearDownloaderInput): CompletableFuture<DataFetcherResult<ClearDownloaderPayload?>> {
|
||||
return future {
|
||||
DownloadManager.clear()
|
||||
ClearDownloaderPayload(
|
||||
input.clientMutationId,
|
||||
downloadStatus =
|
||||
withTimeout(30.seconds) {
|
||||
DownloadStatus(
|
||||
DownloadManager.status.first { it.status == Status.Stopped && it.queue.isEmpty() },
|
||||
)
|
||||
},
|
||||
)
|
||||
asDataFetcherResult {
|
||||
DownloadManager.clear()
|
||||
|
||||
ClearDownloaderPayload(
|
||||
input.clientMutationId,
|
||||
downloadStatus =
|
||||
withTimeout(30.seconds) {
|
||||
DownloadStatus(
|
||||
DownloadManager.status.first { it.status == Status.Stopped && it.queue.isEmpty() },
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,20 +278,23 @@ class DownloadMutation {
|
||||
val downloadStatus: DownloadStatus,
|
||||
)
|
||||
|
||||
fun reorderChapterDownload(input: ReorderChapterDownloadInput): CompletableFuture<ReorderChapterDownloadPayload> {
|
||||
fun reorderChapterDownload(input: ReorderChapterDownloadInput): CompletableFuture<DataFetcherResult<ReorderChapterDownloadPayload?>> {
|
||||
val (clientMutationId, chapter, to) = input
|
||||
DownloadManager.reorder(chapter, to)
|
||||
|
||||
return future {
|
||||
ReorderChapterDownloadPayload(
|
||||
clientMutationId,
|
||||
downloadStatus =
|
||||
withTimeout(30.seconds) {
|
||||
DownloadStatus(
|
||||
DownloadManager.status.first { it.queue.indexOfFirst { it.chapter.id == chapter } <= to },
|
||||
)
|
||||
},
|
||||
)
|
||||
asDataFetcherResult {
|
||||
DownloadManager.reorder(chapter, to)
|
||||
|
||||
ReorderChapterDownloadPayload(
|
||||
clientMutationId,
|
||||
downloadStatus =
|
||||
withTimeout(30.seconds) {
|
||||
DownloadStatus(
|
||||
DownloadManager.status.first { it.queue.indexOfFirst { it.chapter.id == chapter } <= to },
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package suwayomi.tachidesk.graphql.mutations
|
||||
|
||||
import eu.kanade.tachiyomi.source.local.LocalSource
|
||||
import graphql.execution.DataFetcherResult
|
||||
import io.javalin.http.UploadedFile
|
||||
import org.jetbrains.exposed.sql.select
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import suwayomi.tachidesk.graphql.asDataFetcherResult
|
||||
import suwayomi.tachidesk.graphql.types.ExtensionType
|
||||
import suwayomi.tachidesk.manga.impl.extension.Extension
|
||||
import suwayomi.tachidesk.manga.impl.extension.ExtensionsList
|
||||
@@ -69,41 +71,45 @@ class ExtensionMutation {
|
||||
}
|
||||
}
|
||||
|
||||
fun updateExtension(input: UpdateExtensionInput): CompletableFuture<UpdateExtensionPayload> {
|
||||
fun updateExtension(input: UpdateExtensionInput): CompletableFuture<DataFetcherResult<UpdateExtensionPayload?>> {
|
||||
val (clientMutationId, id, patch) = input
|
||||
|
||||
return future {
|
||||
updateExtensions(listOf(id), patch)
|
||||
}.thenApply {
|
||||
val extension =
|
||||
transaction {
|
||||
ExtensionTable.select { ExtensionTable.pkgName eq id }.firstOrNull()
|
||||
?.let { ExtensionType(it) }
|
||||
}
|
||||
asDataFetcherResult {
|
||||
updateExtensions(listOf(id), patch)
|
||||
|
||||
UpdateExtensionPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
extension = extension,
|
||||
)
|
||||
val extension =
|
||||
transaction {
|
||||
ExtensionTable.select { ExtensionTable.pkgName eq id }.firstOrNull()
|
||||
?.let { ExtensionType(it) }
|
||||
}
|
||||
|
||||
UpdateExtensionPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
extension = extension,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateExtensions(input: UpdateExtensionsInput): CompletableFuture<UpdateExtensionsPayload> {
|
||||
fun updateExtensions(input: UpdateExtensionsInput): CompletableFuture<DataFetcherResult<UpdateExtensionsPayload?>> {
|
||||
val (clientMutationId, ids, patch) = input
|
||||
|
||||
return future {
|
||||
updateExtensions(ids, patch)
|
||||
}.thenApply {
|
||||
val extensions =
|
||||
transaction {
|
||||
ExtensionTable.select { ExtensionTable.pkgName inList ids }
|
||||
.map { ExtensionType(it) }
|
||||
}
|
||||
asDataFetcherResult {
|
||||
updateExtensions(ids, patch)
|
||||
|
||||
UpdateExtensionsPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
extensions = extensions,
|
||||
)
|
||||
val extensions =
|
||||
transaction {
|
||||
ExtensionTable.select { ExtensionTable.pkgName inList ids }
|
||||
.map { ExtensionType(it) }
|
||||
}
|
||||
|
||||
UpdateExtensionsPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
extensions = extensions,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,22 +122,24 @@ class ExtensionMutation {
|
||||
val extensions: List<ExtensionType>,
|
||||
)
|
||||
|
||||
fun fetchExtensions(input: FetchExtensionsInput): CompletableFuture<FetchExtensionsPayload> {
|
||||
fun fetchExtensions(input: FetchExtensionsInput): CompletableFuture<DataFetcherResult<FetchExtensionsPayload?>> {
|
||||
val (clientMutationId) = input
|
||||
|
||||
return future {
|
||||
ExtensionsList.fetchExtensions()
|
||||
}.thenApply {
|
||||
val extensions =
|
||||
transaction {
|
||||
ExtensionTable.select { ExtensionTable.name neq LocalSource.EXTENSION_NAME }
|
||||
.map { ExtensionType(it) }
|
||||
}
|
||||
asDataFetcherResult {
|
||||
ExtensionsList.fetchExtensions()
|
||||
|
||||
FetchExtensionsPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
extensions = extensions,
|
||||
)
|
||||
val extensions =
|
||||
transaction {
|
||||
ExtensionTable.select { ExtensionTable.name neq LocalSource.EXTENSION_NAME }
|
||||
.map { ExtensionType(it) }
|
||||
}
|
||||
|
||||
FetchExtensionsPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
extensions = extensions,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,18 +153,22 @@ class ExtensionMutation {
|
||||
val extension: ExtensionType,
|
||||
)
|
||||
|
||||
fun installExternalExtension(input: InstallExternalExtensionInput): CompletableFuture<InstallExternalExtensionPayload> {
|
||||
fun installExternalExtension(
|
||||
input: InstallExternalExtensionInput,
|
||||
): CompletableFuture<DataFetcherResult<InstallExternalExtensionPayload?>> {
|
||||
val (clientMutationId, extensionFile) = input
|
||||
|
||||
return future {
|
||||
Extension.installExternalExtension(extensionFile.content, extensionFile.filename)
|
||||
}.thenApply {
|
||||
val dbExtension = transaction { ExtensionTable.select { ExtensionTable.apkName eq extensionFile.filename }.first() }
|
||||
asDataFetcherResult {
|
||||
Extension.installExternalExtension(extensionFile.content, extensionFile.filename)
|
||||
|
||||
InstallExternalExtensionPayload(
|
||||
clientMutationId,
|
||||
extension = ExtensionType(dbExtension),
|
||||
)
|
||||
val dbExtension = transaction { ExtensionTable.select { ExtensionTable.apkName eq extensionFile.filename }.first() }
|
||||
|
||||
InstallExternalExtensionPayload(
|
||||
clientMutationId,
|
||||
extension = ExtensionType(dbExtension),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package suwayomi.tachidesk.graphql.mutations
|
||||
|
||||
import graphql.execution.DataFetcherResult
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import suwayomi.tachidesk.graphql.asDataFetcherResult
|
||||
import suwayomi.tachidesk.graphql.types.UpdateState.DOWNLOADING
|
||||
import suwayomi.tachidesk.graphql.types.UpdateState.ERROR
|
||||
import suwayomi.tachidesk.graphql.types.UpdateState.IDLE
|
||||
@@ -22,50 +24,54 @@ class InfoMutation {
|
||||
val updateStatus: WebUIUpdateStatus,
|
||||
)
|
||||
|
||||
fun updateWebUI(input: WebUIUpdateInput): CompletableFuture<WebUIUpdatePayload> {
|
||||
fun updateWebUI(input: WebUIUpdateInput): CompletableFuture<DataFetcherResult<WebUIUpdatePayload?>> {
|
||||
return future {
|
||||
withTimeout(30.seconds) {
|
||||
if (WebInterfaceManager.status.value.state === DOWNLOADING) {
|
||||
return@withTimeout WebUIUpdatePayload(input.clientMutationId, WebInterfaceManager.status.value)
|
||||
}
|
||||
asDataFetcherResult {
|
||||
withTimeout(30.seconds) {
|
||||
if (WebInterfaceManager.status.value.state === DOWNLOADING) {
|
||||
return@withTimeout WebUIUpdatePayload(input.clientMutationId, WebInterfaceManager.status.value)
|
||||
}
|
||||
|
||||
val flavor = WebUIFlavor.current
|
||||
val flavor = WebUIFlavor.current
|
||||
|
||||
val (version, updateAvailable) = WebInterfaceManager.isUpdateAvailable(flavor)
|
||||
val (version, updateAvailable) = WebInterfaceManager.isUpdateAvailable(flavor)
|
||||
|
||||
if (!updateAvailable) {
|
||||
val didUpdateCheckFail = version.isEmpty()
|
||||
if (!updateAvailable) {
|
||||
val didUpdateCheckFail = version.isEmpty()
|
||||
|
||||
return@withTimeout WebUIUpdatePayload(
|
||||
return@withTimeout WebUIUpdatePayload(
|
||||
input.clientMutationId,
|
||||
WebInterfaceManager.getStatus(version, if (didUpdateCheckFail) ERROR else IDLE),
|
||||
)
|
||||
}
|
||||
try {
|
||||
WebInterfaceManager.startDownloadInScope(flavor, version)
|
||||
} catch (e: Exception) {
|
||||
// ignore since we use the status anyway
|
||||
}
|
||||
|
||||
WebUIUpdatePayload(
|
||||
input.clientMutationId,
|
||||
WebInterfaceManager.getStatus(version, if (didUpdateCheckFail) ERROR else IDLE),
|
||||
updateStatus = WebInterfaceManager.status.first { it.state == DOWNLOADING },
|
||||
)
|
||||
}
|
||||
try {
|
||||
WebInterfaceManager.startDownloadInScope(flavor, version)
|
||||
} catch (e: Exception) {
|
||||
// ignore since we use the status anyway
|
||||
}
|
||||
|
||||
WebUIUpdatePayload(
|
||||
input.clientMutationId,
|
||||
updateStatus = WebInterfaceManager.status.first { it.state == DOWNLOADING },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun resetWebUIUpdateStatus(): CompletableFuture<WebUIUpdateStatus> {
|
||||
fun resetWebUIUpdateStatus(): CompletableFuture<DataFetcherResult<WebUIUpdateStatus?>> {
|
||||
return future {
|
||||
withTimeout(30.seconds) {
|
||||
val isUpdateFinished = WebInterfaceManager.status.value.state != DOWNLOADING
|
||||
if (!isUpdateFinished) {
|
||||
throw Exception("Status reset is not allowed during status \"$DOWNLOADING\"")
|
||||
asDataFetcherResult {
|
||||
withTimeout(30.seconds) {
|
||||
val isUpdateFinished = WebInterfaceManager.status.value.state != DOWNLOADING
|
||||
if (!isUpdateFinished) {
|
||||
throw Exception("Status reset is not allowed during status \"$DOWNLOADING\"")
|
||||
}
|
||||
|
||||
WebInterfaceManager.resetStatus()
|
||||
|
||||
WebInterfaceManager.status.first { it.state == IDLE }
|
||||
}
|
||||
|
||||
WebInterfaceManager.resetStatus()
|
||||
|
||||
WebInterfaceManager.status.first { it.state == IDLE }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package suwayomi.tachidesk.graphql.mutations
|
||||
|
||||
import graphql.execution.DataFetcherResult
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||
import org.jetbrains.exposed.sql.and
|
||||
import org.jetbrains.exposed.sql.deleteWhere
|
||||
import org.jetbrains.exposed.sql.select
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import org.jetbrains.exposed.sql.update
|
||||
import suwayomi.tachidesk.graphql.asDataFetcherResult
|
||||
import suwayomi.tachidesk.graphql.types.MangaMetaType
|
||||
import suwayomi.tachidesk.graphql.types.MangaType
|
||||
import suwayomi.tachidesk.manga.impl.Library
|
||||
@@ -13,6 +15,7 @@ import suwayomi.tachidesk.manga.impl.Manga
|
||||
import suwayomi.tachidesk.manga.model.table.MangaMetaTable
|
||||
import suwayomi.tachidesk.manga.model.table.MangaTable
|
||||
import suwayomi.tachidesk.server.JavalinSetup.future
|
||||
import java.time.Instant
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
/**
|
||||
@@ -56,6 +59,7 @@ class MangaMutation {
|
||||
MangaTable.update({ MangaTable.id inList ids }) { update ->
|
||||
patch.inLibrary.also {
|
||||
update[inLibrary] = it
|
||||
if (it) update[inLibraryAt] = Instant.now().epochSecond
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,39 +72,43 @@ class MangaMutation {
|
||||
}
|
||||
}
|
||||
|
||||
fun updateManga(input: UpdateMangaInput): CompletableFuture<UpdateMangaPayload> {
|
||||
fun updateManga(input: UpdateMangaInput): CompletableFuture<DataFetcherResult<UpdateMangaPayload?>> {
|
||||
val (clientMutationId, id, patch) = input
|
||||
|
||||
return future {
|
||||
updateMangas(listOf(id), patch)
|
||||
}.thenApply {
|
||||
val manga =
|
||||
transaction {
|
||||
MangaType(MangaTable.select { MangaTable.id eq id }.first())
|
||||
}
|
||||
asDataFetcherResult {
|
||||
updateMangas(listOf(id), patch)
|
||||
|
||||
UpdateMangaPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
manga = manga,
|
||||
)
|
||||
val manga =
|
||||
transaction {
|
||||
MangaType(MangaTable.select { MangaTable.id eq id }.first())
|
||||
}
|
||||
|
||||
UpdateMangaPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
manga = manga,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateMangas(input: UpdateMangasInput): CompletableFuture<UpdateMangasPayload> {
|
||||
fun updateMangas(input: UpdateMangasInput): CompletableFuture<DataFetcherResult<UpdateMangasPayload?>> {
|
||||
val (clientMutationId, ids, patch) = input
|
||||
|
||||
return future {
|
||||
updateMangas(ids, patch)
|
||||
}.thenApply {
|
||||
val mangas =
|
||||
transaction {
|
||||
MangaTable.select { MangaTable.id inList ids }.map { MangaType(it) }
|
||||
}
|
||||
asDataFetcherResult {
|
||||
updateMangas(ids, patch)
|
||||
|
||||
UpdateMangasPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
mangas = mangas,
|
||||
)
|
||||
val mangas =
|
||||
transaction {
|
||||
MangaTable.select { MangaTable.id inList ids }.map { MangaType(it) }
|
||||
}
|
||||
|
||||
UpdateMangasPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
mangas = mangas,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,20 +122,22 @@ class MangaMutation {
|
||||
val manga: MangaType,
|
||||
)
|
||||
|
||||
fun fetchManga(input: FetchMangaInput): CompletableFuture<FetchMangaPayload> {
|
||||
fun fetchManga(input: FetchMangaInput): CompletableFuture<DataFetcherResult<FetchMangaPayload?>> {
|
||||
val (clientMutationId, id) = input
|
||||
|
||||
return future {
|
||||
Manga.fetchManga(id)
|
||||
}.thenApply {
|
||||
val manga =
|
||||
transaction {
|
||||
MangaTable.select { MangaTable.id eq id }.first()
|
||||
}
|
||||
FetchMangaPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
manga = MangaType(manga),
|
||||
)
|
||||
asDataFetcherResult {
|
||||
Manga.fetchManga(id)
|
||||
|
||||
val manga =
|
||||
transaction {
|
||||
MangaTable.select { MangaTable.id eq id }.first()
|
||||
}
|
||||
FetchMangaPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
manga = MangaType(manga),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,12 +151,14 @@ class MangaMutation {
|
||||
val meta: MangaMetaType,
|
||||
)
|
||||
|
||||
fun setMangaMeta(input: SetMangaMetaInput): SetMangaMetaPayload {
|
||||
fun setMangaMeta(input: SetMangaMetaInput): DataFetcherResult<SetMangaMetaPayload?> {
|
||||
val (clientMutationId, meta) = input
|
||||
|
||||
Manga.modifyMangaMeta(meta.mangaId, meta.key, meta.value)
|
||||
return asDataFetcherResult {
|
||||
Manga.modifyMangaMeta(meta.mangaId, meta.key, meta.value)
|
||||
|
||||
return SetMangaMetaPayload(clientMutationId, meta)
|
||||
SetMangaMetaPayload(clientMutationId, meta)
|
||||
}
|
||||
}
|
||||
|
||||
data class DeleteMangaMetaInput(
|
||||
@@ -161,29 +173,31 @@ class MangaMutation {
|
||||
val manga: MangaType,
|
||||
)
|
||||
|
||||
fun deleteMangaMeta(input: DeleteMangaMetaInput): DeleteMangaMetaPayload {
|
||||
fun deleteMangaMeta(input: DeleteMangaMetaInput): DataFetcherResult<DeleteMangaMetaPayload?> {
|
||||
val (clientMutationId, mangaId, key) = input
|
||||
|
||||
val (meta, manga) =
|
||||
transaction {
|
||||
val meta =
|
||||
MangaMetaTable.select { (MangaMetaTable.ref eq mangaId) and (MangaMetaTable.key eq key) }
|
||||
.firstOrNull()
|
||||
return asDataFetcherResult {
|
||||
val (meta, manga) =
|
||||
transaction {
|
||||
val meta =
|
||||
MangaMetaTable.select { (MangaMetaTable.ref eq mangaId) and (MangaMetaTable.key eq key) }
|
||||
.firstOrNull()
|
||||
|
||||
MangaMetaTable.deleteWhere { (MangaMetaTable.ref eq mangaId) and (MangaMetaTable.key eq key) }
|
||||
MangaMetaTable.deleteWhere { (MangaMetaTable.ref eq mangaId) and (MangaMetaTable.key eq key) }
|
||||
|
||||
val manga =
|
||||
transaction {
|
||||
MangaType(MangaTable.select { MangaTable.id eq mangaId }.first())
|
||||
}
|
||||
val manga =
|
||||
transaction {
|
||||
MangaType(MangaTable.select { MangaTable.id eq mangaId }.first())
|
||||
}
|
||||
|
||||
if (meta != null) {
|
||||
MangaMetaType(meta)
|
||||
} else {
|
||||
null
|
||||
} to manga
|
||||
}
|
||||
if (meta != null) {
|
||||
MangaMetaType(meta)
|
||||
} else {
|
||||
null
|
||||
} to manga
|
||||
}
|
||||
|
||||
return DeleteMangaMetaPayload(clientMutationId, meta, manga)
|
||||
DeleteMangaMetaPayload(clientMutationId, meta, manga)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package suwayomi.tachidesk.graphql.mutations
|
||||
|
||||
import graphql.execution.DataFetcherResult
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||
import org.jetbrains.exposed.sql.deleteWhere
|
||||
import org.jetbrains.exposed.sql.select
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import suwayomi.tachidesk.global.impl.GlobalMeta
|
||||
import suwayomi.tachidesk.global.model.table.GlobalMetaTable
|
||||
import suwayomi.tachidesk.graphql.asDataFetcherResult
|
||||
import suwayomi.tachidesk.graphql.types.GlobalMetaType
|
||||
|
||||
class MetaMutation {
|
||||
@@ -19,12 +21,14 @@ class MetaMutation {
|
||||
val meta: GlobalMetaType,
|
||||
)
|
||||
|
||||
fun setGlobalMeta(input: SetGlobalMetaInput): SetGlobalMetaPayload {
|
||||
fun setGlobalMeta(input: SetGlobalMetaInput): DataFetcherResult<SetGlobalMetaPayload?> {
|
||||
val (clientMutationId, meta) = input
|
||||
|
||||
GlobalMeta.modifyMeta(meta.key, meta.value)
|
||||
return asDataFetcherResult {
|
||||
GlobalMeta.modifyMeta(meta.key, meta.value)
|
||||
|
||||
return SetGlobalMetaPayload(clientMutationId, meta)
|
||||
SetGlobalMetaPayload(clientMutationId, meta)
|
||||
}
|
||||
}
|
||||
|
||||
data class DeleteGlobalMetaInput(
|
||||
@@ -37,24 +41,26 @@ class MetaMutation {
|
||||
val meta: GlobalMetaType?,
|
||||
)
|
||||
|
||||
fun deleteGlobalMeta(input: DeleteGlobalMetaInput): DeleteGlobalMetaPayload {
|
||||
fun deleteGlobalMeta(input: DeleteGlobalMetaInput): DataFetcherResult<DeleteGlobalMetaPayload?> {
|
||||
val (clientMutationId, key) = input
|
||||
|
||||
val meta =
|
||||
transaction {
|
||||
val meta =
|
||||
GlobalMetaTable.select { GlobalMetaTable.key eq key }
|
||||
.firstOrNull()
|
||||
return asDataFetcherResult {
|
||||
val meta =
|
||||
transaction {
|
||||
val meta =
|
||||
GlobalMetaTable.select { GlobalMetaTable.key eq key }
|
||||
.firstOrNull()
|
||||
|
||||
GlobalMetaTable.deleteWhere { GlobalMetaTable.key eq key }
|
||||
GlobalMetaTable.deleteWhere { GlobalMetaTable.key eq key }
|
||||
|
||||
if (meta != null) {
|
||||
GlobalMetaType(meta)
|
||||
} else {
|
||||
null
|
||||
if (meta != null) {
|
||||
GlobalMetaType(meta)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return DeleteGlobalMetaPayload(clientMutationId, meta)
|
||||
DeleteGlobalMetaPayload(clientMutationId, meta)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ class SettingsMutation {
|
||||
updateSetting(settings.excludeEntryWithUnreadChapters, serverConfig.excludeEntryWithUnreadChapters)
|
||||
updateSetting(settings.autoDownloadAheadLimit, serverConfig.autoDownloadNewChaptersLimit) // deprecated
|
||||
updateSetting(settings.autoDownloadNewChaptersLimit, serverConfig.autoDownloadNewChaptersLimit)
|
||||
updateSetting(settings.autoDownloadIgnoreReUploads, serverConfig.autoDownloadIgnoreReUploads)
|
||||
|
||||
// extension
|
||||
updateSetting(settings.extensionRepos, serverConfig.extensionRepos)
|
||||
|
||||
@@ -5,11 +5,13 @@ import androidx.preference.EditTextPreference
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.MultiSelectListPreference
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import graphql.execution.DataFetcherResult
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
|
||||
import org.jetbrains.exposed.sql.and
|
||||
import org.jetbrains.exposed.sql.deleteWhere
|
||||
import org.jetbrains.exposed.sql.select
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import suwayomi.tachidesk.graphql.asDataFetcherResult
|
||||
import suwayomi.tachidesk.graphql.types.FilterChange
|
||||
import suwayomi.tachidesk.graphql.types.MangaType
|
||||
import suwayomi.tachidesk.graphql.types.Preference
|
||||
@@ -17,7 +19,7 @@ import suwayomi.tachidesk.graphql.types.SourceMetaType
|
||||
import suwayomi.tachidesk.graphql.types.SourceType
|
||||
import suwayomi.tachidesk.graphql.types.preferenceOf
|
||||
import suwayomi.tachidesk.graphql.types.updateFilterList
|
||||
import suwayomi.tachidesk.manga.impl.MangaList.insertOrGet
|
||||
import suwayomi.tachidesk.manga.impl.MangaList.insertOrUpdate
|
||||
import suwayomi.tachidesk.manga.impl.Source
|
||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource
|
||||
import suwayomi.tachidesk.manga.model.table.MangaTable
|
||||
@@ -37,12 +39,14 @@ class SourceMutation {
|
||||
val meta: SourceMetaType,
|
||||
)
|
||||
|
||||
fun setSourceMeta(input: SetSourceMetaInput): SetSourceMetaPayload {
|
||||
fun setSourceMeta(input: SetSourceMetaInput): DataFetcherResult<SetSourceMetaPayload?> {
|
||||
val (clientMutationId, meta) = input
|
||||
|
||||
Source.modifyMeta(meta.sourceId, meta.key, meta.value)
|
||||
return asDataFetcherResult {
|
||||
Source.modifyMeta(meta.sourceId, meta.key, meta.value)
|
||||
|
||||
return SetSourceMetaPayload(clientMutationId, meta)
|
||||
SetSourceMetaPayload(clientMutationId, meta)
|
||||
}
|
||||
}
|
||||
|
||||
data class DeleteSourceMetaInput(
|
||||
@@ -57,31 +61,33 @@ class SourceMutation {
|
||||
val source: SourceType?,
|
||||
)
|
||||
|
||||
fun deleteSourceMeta(input: DeleteSourceMetaInput): DeleteSourceMetaPayload {
|
||||
fun deleteSourceMeta(input: DeleteSourceMetaInput): DataFetcherResult<DeleteSourceMetaPayload?> {
|
||||
val (clientMutationId, sourceId, key) = input
|
||||
|
||||
val (meta, source) =
|
||||
transaction {
|
||||
val meta =
|
||||
SourceMetaTable.select { (SourceMetaTable.ref eq sourceId) and (SourceMetaTable.key eq key) }
|
||||
.firstOrNull()
|
||||
return asDataFetcherResult {
|
||||
val (meta, source) =
|
||||
transaction {
|
||||
val meta =
|
||||
SourceMetaTable.select { (SourceMetaTable.ref eq sourceId) and (SourceMetaTable.key eq key) }
|
||||
.firstOrNull()
|
||||
|
||||
SourceMetaTable.deleteWhere { (SourceMetaTable.ref eq sourceId) and (SourceMetaTable.key eq key) }
|
||||
SourceMetaTable.deleteWhere { (SourceMetaTable.ref eq sourceId) and (SourceMetaTable.key eq key) }
|
||||
|
||||
val source =
|
||||
transaction {
|
||||
SourceTable.select { SourceTable.id eq sourceId }.firstOrNull()
|
||||
?.let { SourceType(it) }
|
||||
}
|
||||
val source =
|
||||
transaction {
|
||||
SourceTable.select { SourceTable.id eq sourceId }.firstOrNull()
|
||||
?.let { SourceType(it) }
|
||||
}
|
||||
|
||||
if (meta != null) {
|
||||
SourceMetaType(meta)
|
||||
} else {
|
||||
null
|
||||
} to source
|
||||
}
|
||||
if (meta != null) {
|
||||
SourceMetaType(meta)
|
||||
} else {
|
||||
null
|
||||
} to source
|
||||
}
|
||||
|
||||
return DeleteSourceMetaPayload(clientMutationId, meta, source)
|
||||
DeleteSourceMetaPayload(clientMutationId, meta, source)
|
||||
}
|
||||
}
|
||||
|
||||
enum class FetchSourceMangaType {
|
||||
@@ -105,44 +111,46 @@ class SourceMutation {
|
||||
val hasNextPage: Boolean,
|
||||
)
|
||||
|
||||
fun fetchSourceManga(input: FetchSourceMangaInput): CompletableFuture<FetchSourceMangaPayload> {
|
||||
fun fetchSourceManga(input: FetchSourceMangaInput): CompletableFuture<DataFetcherResult<FetchSourceMangaPayload?>> {
|
||||
val (clientMutationId, sourceId, type, page, query, filters) = input
|
||||
|
||||
return future {
|
||||
val source = GetCatalogueSource.getCatalogueSourceOrNull(sourceId)!!
|
||||
val mangasPage =
|
||||
when (type) {
|
||||
FetchSourceMangaType.SEARCH -> {
|
||||
source.getSearchManga(
|
||||
page = page,
|
||||
query = query.orEmpty(),
|
||||
filters = updateFilterList(source, filters),
|
||||
)
|
||||
asDataFetcherResult {
|
||||
val source = GetCatalogueSource.getCatalogueSourceOrNull(sourceId)!!
|
||||
val mangasPage =
|
||||
when (type) {
|
||||
FetchSourceMangaType.SEARCH -> {
|
||||
source.getSearchManga(
|
||||
page = page,
|
||||
query = query.orEmpty(),
|
||||
filters = updateFilterList(source, filters),
|
||||
)
|
||||
}
|
||||
FetchSourceMangaType.POPULAR -> {
|
||||
source.getPopularManga(page)
|
||||
}
|
||||
FetchSourceMangaType.LATEST -> {
|
||||
if (!source.supportsLatest) throw Exception("Source does not support latest")
|
||||
source.getLatestUpdates(page)
|
||||
}
|
||||
}
|
||||
FetchSourceMangaType.POPULAR -> {
|
||||
source.getPopularManga(page)
|
||||
|
||||
val mangaIds = mangasPage.insertOrUpdate(sourceId)
|
||||
|
||||
val mangas =
|
||||
transaction {
|
||||
MangaTable.select { MangaTable.id inList mangaIds }
|
||||
.map { MangaType(it) }
|
||||
}.sortedBy {
|
||||
mangaIds.indexOf(it.id)
|
||||
}
|
||||
FetchSourceMangaType.LATEST -> {
|
||||
if (!source.supportsLatest) throw Exception("Source does not support latest")
|
||||
source.getLatestUpdates(page)
|
||||
}
|
||||
}
|
||||
|
||||
val mangaIds = mangasPage.insertOrGet(sourceId)
|
||||
|
||||
val mangas =
|
||||
transaction {
|
||||
MangaTable.select { MangaTable.id inList mangaIds }
|
||||
.map { MangaType(it) }
|
||||
}.sortedBy {
|
||||
mangaIds.indexOf(it.id)
|
||||
}
|
||||
|
||||
FetchSourceMangaPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
mangas = mangas,
|
||||
hasNextPage = mangasPage.hasNextPage,
|
||||
)
|
||||
FetchSourceMangaPayload(
|
||||
clientMutationId = clientMutationId,
|
||||
mangas = mangas,
|
||||
hasNextPage = mangasPage.hasNextPage,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,27 +175,29 @@ class SourceMutation {
|
||||
val source: SourceType,
|
||||
)
|
||||
|
||||
fun updateSourcePreference(input: UpdateSourcePreferenceInput): UpdateSourcePreferencePayload {
|
||||
fun updateSourcePreference(input: UpdateSourcePreferenceInput): DataFetcherResult<UpdateSourcePreferencePayload?> {
|
||||
val (clientMutationId, sourceId, change) = input
|
||||
|
||||
Source.setSourcePreference(sourceId, change.position, "") { preference ->
|
||||
when (preference) {
|
||||
is SwitchPreferenceCompat -> change.switchState
|
||||
is CheckBoxPreference -> change.checkBoxState
|
||||
is EditTextPreference -> change.editTextState
|
||||
is ListPreference -> change.listState
|
||||
is MultiSelectListPreference -> change.multiSelectState?.toSet()
|
||||
else -> throw RuntimeException("sealed class cannot have more subtypes!")
|
||||
} ?: throw Exception("Expected change to ${preference::class.simpleName}")
|
||||
}
|
||||
return asDataFetcherResult {
|
||||
Source.setSourcePreference(sourceId, change.position, "") { preference ->
|
||||
when (preference) {
|
||||
is SwitchPreferenceCompat -> change.switchState
|
||||
is CheckBoxPreference -> change.checkBoxState
|
||||
is EditTextPreference -> change.editTextState
|
||||
is ListPreference -> change.listState
|
||||
is MultiSelectListPreference -> change.multiSelectState?.toSet()
|
||||
else -> throw RuntimeException("sealed class cannot have more subtypes!")
|
||||
} ?: throw Exception("Expected change to ${preference::class.simpleName}")
|
||||
}
|
||||
|
||||
return UpdateSourcePreferencePayload(
|
||||
clientMutationId = clientMutationId,
|
||||
preferences = Source.getSourcePreferencesRaw(sourceId).map { preferenceOf(it) },
|
||||
source =
|
||||
transaction {
|
||||
SourceType(SourceTable.select { SourceTable.id eq sourceId }.first())!!
|
||||
},
|
||||
)
|
||||
UpdateSourcePreferencePayload(
|
||||
clientMutationId = clientMutationId,
|
||||
preferences = Source.getSourcePreferencesRaw(sourceId).map { preferenceOf(it) },
|
||||
source =
|
||||
transaction {
|
||||
SourceType(SourceTable.select { SourceTable.id eq sourceId }.first())!!
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
package suwayomi.tachidesk.graphql.mutations
|
||||
|
||||
import com.expediagroup.graphql.generator.annotations.GraphQLDeprecated
|
||||
import com.expediagroup.graphql.generator.annotations.GraphQLDescription
|
||||
import graphql.execution.DataFetcherResult
|
||||
import org.jetbrains.exposed.sql.and
|
||||
import org.jetbrains.exposed.sql.select
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import suwayomi.tachidesk.graphql.asDataFetcherResult
|
||||
import suwayomi.tachidesk.graphql.types.TrackRecordType
|
||||
import suwayomi.tachidesk.graphql.types.TrackerType
|
||||
import suwayomi.tachidesk.manga.impl.track.Track
|
||||
@@ -133,6 +137,93 @@ class TrackMutation {
|
||||
}
|
||||
}
|
||||
|
||||
data class FetchTrackInput(
|
||||
val clientMutationId: String? = null,
|
||||
val recordId: Int,
|
||||
)
|
||||
|
||||
data class FetchTrackPayload(
|
||||
val clientMutationId: String?,
|
||||
val trackRecord: TrackRecordType,
|
||||
)
|
||||
|
||||
fun fetchTrack(input: FetchTrackInput): CompletableFuture<FetchTrackPayload> {
|
||||
val (clientMutationId, recordId) = input
|
||||
|
||||
return future {
|
||||
Track.refresh(recordId)
|
||||
val trackRecord =
|
||||
transaction {
|
||||
TrackRecordTable.select {
|
||||
TrackRecordTable.id eq recordId
|
||||
}.first()
|
||||
}
|
||||
FetchTrackPayload(
|
||||
clientMutationId,
|
||||
TrackRecordType(trackRecord),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class UnbindTrackInput(
|
||||
val clientMutationId: String? = null,
|
||||
val recordId: Int,
|
||||
@GraphQLDescription("This will only work if the tracker of the track record supports deleting tracks")
|
||||
val deleteRemoteTrack: Boolean? = null,
|
||||
)
|
||||
|
||||
data class UnbindTrackPayload(
|
||||
val clientMutationId: String?,
|
||||
val trackRecord: TrackRecordType?,
|
||||
)
|
||||
|
||||
fun unbindTrack(input: UnbindTrackInput): CompletableFuture<UnbindTrackPayload> {
|
||||
val (clientMutationId, recordId, deleteRemoteTrack) = input
|
||||
|
||||
return future {
|
||||
Track.unbind(recordId, deleteRemoteTrack)
|
||||
val trackRecord =
|
||||
transaction {
|
||||
TrackRecordTable.select {
|
||||
TrackRecordTable.id eq recordId
|
||||
}.firstOrNull()
|
||||
}
|
||||
UnbindTrackPayload(
|
||||
clientMutationId,
|
||||
trackRecord?.let { TrackRecordType(it) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
data class TrackProgressInput(
|
||||
val clientMutationId: String? = null,
|
||||
val mangaId: Int,
|
||||
)
|
||||
|
||||
data class TrackProgressPayload(
|
||||
val clientMutationId: String?,
|
||||
val trackRecords: List<TrackRecordType>,
|
||||
)
|
||||
|
||||
fun trackProgress(input: TrackProgressInput): CompletableFuture<DataFetcherResult<TrackProgressPayload?>> {
|
||||
val (clientMutationId, mangaId) = input
|
||||
|
||||
return future {
|
||||
asDataFetcherResult {
|
||||
Track.trackChapter(mangaId)
|
||||
val trackRecords =
|
||||
transaction {
|
||||
TrackRecordTable.select { TrackRecordTable.mangaId eq mangaId }
|
||||
.toList()
|
||||
}
|
||||
TrackProgressPayload(
|
||||
clientMutationId,
|
||||
trackRecords.map { TrackRecordType(it) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class UpdateTrackInput(
|
||||
val clientMutationId: String? = null,
|
||||
val recordId: Int,
|
||||
@@ -141,6 +232,7 @@ class TrackMutation {
|
||||
val scoreString: String? = null,
|
||||
val startDate: Long? = null,
|
||||
val finishDate: Long? = null,
|
||||
@GraphQLDeprecated("Replaced with \"unbindTrack\" mutation", replaceWith = ReplaceWith("unbindTrack"))
|
||||
val unbind: Boolean? = null,
|
||||
)
|
||||
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
package suwayomi.tachidesk.graphql.mutations
|
||||
|
||||
import graphql.execution.DataFetcherResult
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import org.jetbrains.exposed.sql.select
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import org.kodein.di.DI
|
||||
import org.kodein.di.conf.global
|
||||
import org.kodein.di.instance
|
||||
import suwayomi.tachidesk.graphql.asDataFetcherResult
|
||||
import suwayomi.tachidesk.graphql.types.UpdateStatus
|
||||
import suwayomi.tachidesk.manga.impl.Category
|
||||
import suwayomi.tachidesk.manga.impl.update.IUpdater
|
||||
import suwayomi.tachidesk.manga.model.table.CategoryTable
|
||||
import suwayomi.tachidesk.manga.model.table.toDataClass
|
||||
import suwayomi.tachidesk.server.JavalinSetup.future
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
class UpdateMutation {
|
||||
private val updater by DI.global.instance<IUpdater>()
|
||||
@@ -23,14 +30,24 @@ class UpdateMutation {
|
||||
val updateStatus: UpdateStatus,
|
||||
)
|
||||
|
||||
fun updateLibraryManga(input: UpdateLibraryMangaInput): UpdateLibraryMangaPayload {
|
||||
fun updateLibraryManga(input: UpdateLibraryMangaInput): CompletableFuture<DataFetcherResult<UpdateLibraryMangaPayload?>> {
|
||||
updater.addCategoriesToUpdateQueue(
|
||||
Category.getCategoryList(),
|
||||
clear = true,
|
||||
forceAll = false,
|
||||
)
|
||||
|
||||
return UpdateLibraryMangaPayload(input.clientMutationId, UpdateStatus(updater.status.value))
|
||||
return future {
|
||||
asDataFetcherResult {
|
||||
UpdateLibraryMangaPayload(
|
||||
input.clientMutationId,
|
||||
updateStatus =
|
||||
withTimeout(30.seconds) {
|
||||
UpdateStatus(updater.status.first())
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class UpdateCategoryMangaInput(
|
||||
@@ -43,7 +60,7 @@ class UpdateMutation {
|
||||
val updateStatus: UpdateStatus,
|
||||
)
|
||||
|
||||
fun updateCategoryManga(input: UpdateCategoryMangaInput): UpdateCategoryMangaPayload {
|
||||
fun updateCategoryManga(input: UpdateCategoryMangaInput): CompletableFuture<DataFetcherResult<UpdateCategoryMangaPayload?>> {
|
||||
val categories =
|
||||
transaction {
|
||||
CategoryTable.select { CategoryTable.id inList input.categories }.map {
|
||||
@@ -52,10 +69,17 @@ class UpdateMutation {
|
||||
}
|
||||
updater.addCategoriesToUpdateQueue(categories, clear = true, forceAll = true)
|
||||
|
||||
return UpdateCategoryMangaPayload(
|
||||
clientMutationId = input.clientMutationId,
|
||||
updateStatus = UpdateStatus(updater.status.value),
|
||||
)
|
||||
return future {
|
||||
asDataFetcherResult {
|
||||
UpdateCategoryMangaPayload(
|
||||
input.clientMutationId,
|
||||
updateStatus =
|
||||
withTimeout(30.seconds) {
|
||||
UpdateStatus(updater.status.first())
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class UpdateStopInput(
|
||||
|
||||
@@ -16,14 +16,20 @@ class BackupQuery {
|
||||
val name: String,
|
||||
)
|
||||
|
||||
data class ValidateBackupTracker(
|
||||
val name: String,
|
||||
)
|
||||
|
||||
data class ValidateBackupResult(
|
||||
val missingSources: List<ValidateBackupSource>,
|
||||
val missingTrackers: List<ValidateBackupTracker>,
|
||||
)
|
||||
|
||||
fun validateBackup(input: ValidateBackupInput): ValidateBackupResult {
|
||||
val result = ProtoBackupValidator.validate(input.backup.content)
|
||||
return ValidateBackupResult(
|
||||
result.missingSourceIds.map { ValidateBackupSource(it.first, it.second) },
|
||||
result.missingTrackers.map { ValidateBackupTracker(it) },
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ import suwayomi.tachidesk.graphql.queries.filter.andFilterWithCompare
|
||||
import suwayomi.tachidesk.graphql.queries.filter.andFilterWithCompareEntity
|
||||
import suwayomi.tachidesk.graphql.queries.filter.andFilterWithCompareString
|
||||
import suwayomi.tachidesk.graphql.queries.filter.applyOps
|
||||
import suwayomi.tachidesk.graphql.queries.util.distinctOn
|
||||
import suwayomi.tachidesk.graphql.server.primitives.Cursor
|
||||
import suwayomi.tachidesk.graphql.server.primitives.OrderBy
|
||||
import suwayomi.tachidesk.graphql.server.primitives.PageInfo
|
||||
@@ -217,7 +218,12 @@ class MangaQuery {
|
||||
): MangaNodeList {
|
||||
val queryResults =
|
||||
transaction {
|
||||
val res = MangaTable.leftJoin(CategoryMangaTable).selectAll()
|
||||
val res =
|
||||
MangaTable.leftJoin(CategoryMangaTable).slice(
|
||||
distinctOn(MangaTable.id),
|
||||
*(MangaTable.columns).toTypedArray(),
|
||||
*(CategoryMangaTable.columns).toTypedArray(),
|
||||
).selectAll()
|
||||
|
||||
res.applyOps(condition, filter)
|
||||
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
package suwayomi.tachidesk.graphql.queries
|
||||
|
||||
import kotlinx.coroutines.flow.first
|
||||
import org.kodein.di.DI
|
||||
import org.kodein.di.conf.global
|
||||
import org.kodein.di.instance
|
||||
import suwayomi.tachidesk.graphql.types.UpdateStatus
|
||||
import suwayomi.tachidesk.manga.impl.update.IUpdater
|
||||
import suwayomi.tachidesk.server.JavalinSetup.future
|
||||
import java.util.concurrent.CompletableFuture
|
||||
|
||||
class UpdateQuery {
|
||||
private val updater by DI.global.instance<IUpdater>()
|
||||
|
||||
fun updateStatus(): UpdateStatus {
|
||||
return UpdateStatus(updater.status.value)
|
||||
fun updateStatus(): CompletableFuture<UpdateStatus> {
|
||||
return future { UpdateStatus(updater.status.first()) }
|
||||
}
|
||||
|
||||
data class LastUpdateTimestampPayload(val timestamp: Long)
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package suwayomi.tachidesk.graphql.queries.util
|
||||
|
||||
import org.jetbrains.exposed.sql.BooleanColumnType
|
||||
import org.jetbrains.exposed.sql.CustomFunction
|
||||
import org.jetbrains.exposed.sql.Expression
|
||||
import org.jetbrains.exposed.sql.QueryBuilder
|
||||
|
||||
/**
|
||||
* src: https://github.com/JetBrains/Exposed/issues/500#issuecomment-543574151 (2024-04-02 02:20)
|
||||
*/
|
||||
|
||||
fun distinctOn(vararg expressions: Expression<*>): CustomFunction<Boolean?> =
|
||||
customBooleanFunction(
|
||||
functionName = "DISTINCT ON",
|
||||
postfix = " TRUE",
|
||||
params = expressions,
|
||||
)
|
||||
|
||||
fun customBooleanFunction(
|
||||
functionName: String,
|
||||
postfix: String = "",
|
||||
vararg params: Expression<*>,
|
||||
): CustomFunction<Boolean?> =
|
||||
object : CustomFunction<Boolean?>(functionName, BooleanColumnType(), *params) {
|
||||
override fun toQueryBuilder(queryBuilder: QueryBuilder) {
|
||||
super.toQueryBuilder(queryBuilder)
|
||||
if (postfix.isNotEmpty()) {
|
||||
queryBuilder.append(postfix)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
package suwayomi.tachidesk.graphql.server
|
||||
|
||||
import com.expediagroup.graphql.dataloader.KotlinDataLoaderRegistryFactory
|
||||
import suwayomi.tachidesk.graphql.dataLoaders.BookmarkedChapterCountForMangaDataLoader
|
||||
import suwayomi.tachidesk.graphql.dataLoaders.CategoriesForMangaDataLoader
|
||||
import suwayomi.tachidesk.graphql.dataLoaders.CategoryDataLoader
|
||||
import suwayomi.tachidesk.graphql.dataLoaders.CategoryForIdsDataLoader
|
||||
@@ -19,6 +20,7 @@ import suwayomi.tachidesk.graphql.dataLoaders.DisplayScoreForTrackRecordDataLoad
|
||||
import suwayomi.tachidesk.graphql.dataLoaders.DownloadedChapterCountForMangaDataLoader
|
||||
import suwayomi.tachidesk.graphql.dataLoaders.ExtensionDataLoader
|
||||
import suwayomi.tachidesk.graphql.dataLoaders.ExtensionForSourceDataLoader
|
||||
import suwayomi.tachidesk.graphql.dataLoaders.FirstUnreadChapterForMangaDataLoader
|
||||
import suwayomi.tachidesk.graphql.dataLoaders.GlobalMetaDataLoader
|
||||
import suwayomi.tachidesk.graphql.dataLoaders.LastReadChapterForMangaDataLoader
|
||||
import suwayomi.tachidesk.graphql.dataLoaders.LatestFetchedChapterForMangaDataLoader
|
||||
@@ -38,6 +40,7 @@ import suwayomi.tachidesk.graphql.dataLoaders.TrackRecordsForTrackerIdDataLoader
|
||||
import suwayomi.tachidesk.graphql.dataLoaders.TrackerDataLoader
|
||||
import suwayomi.tachidesk.graphql.dataLoaders.TrackerScoresDataLoader
|
||||
import suwayomi.tachidesk.graphql.dataLoaders.TrackerStatusesDataLoader
|
||||
import suwayomi.tachidesk.graphql.dataLoaders.TrackerTokenExpiredDataLoader
|
||||
import suwayomi.tachidesk.graphql.dataLoaders.UnreadChapterCountForMangaDataLoader
|
||||
|
||||
class TachideskDataLoaderRegistryFactory {
|
||||
@@ -49,10 +52,12 @@ class TachideskDataLoaderRegistryFactory {
|
||||
ChaptersForMangaDataLoader(),
|
||||
DownloadedChapterCountForMangaDataLoader(),
|
||||
UnreadChapterCountForMangaDataLoader(),
|
||||
BookmarkedChapterCountForMangaDataLoader(),
|
||||
LastReadChapterForMangaDataLoader(),
|
||||
LatestReadChapterForMangaDataLoader(),
|
||||
LatestFetchedChapterForMangaDataLoader(),
|
||||
LatestUploadedChapterForMangaDataLoader(),
|
||||
FirstUnreadChapterForMangaDataLoader(),
|
||||
GlobalMetaDataLoader(),
|
||||
ChapterMetaDataLoader(),
|
||||
MangaMetaDataLoader(),
|
||||
@@ -71,6 +76,7 @@ class TachideskDataLoaderRegistryFactory {
|
||||
TrackerDataLoader(),
|
||||
TrackerStatusesDataLoader(),
|
||||
TrackerScoresDataLoader(),
|
||||
TrackerTokenExpiredDataLoader(),
|
||||
TrackRecordsForMangaIdDataLoader(),
|
||||
DisplayScoreForTrackRecordDataLoader(),
|
||||
TrackRecordsForTrackerIdDataLoader(),
|
||||
|
||||
@@ -12,6 +12,7 @@ import com.expediagroup.graphql.server.execution.GraphQLRequestHandler
|
||||
import com.expediagroup.graphql.server.execution.GraphQLServer
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import graphql.GraphQL
|
||||
import graphql.execution.AsyncExecutionStrategy
|
||||
import io.javalin.http.Context
|
||||
import io.javalin.websocket.WsCloseContext
|
||||
import io.javalin.websocket.WsMessageContext
|
||||
@@ -47,6 +48,7 @@ class TachideskGraphQLServer(
|
||||
private fun getGraphQLObject(): GraphQL =
|
||||
GraphQL.newGraphQL(schema)
|
||||
.subscriptionExecutionStrategy(FlowSubscriptionExecutionStrategy())
|
||||
.mutationExecutionStrategy(AsyncExecutionStrategy())
|
||||
.build()
|
||||
|
||||
fun create(): TachideskGraphQLServer {
|
||||
|
||||
@@ -11,6 +11,7 @@ import com.expediagroup.graphql.server.extensions.getValueFromDataLoader
|
||||
import eu.kanade.tachiyomi.source.model.UpdateStrategy
|
||||
import graphql.schema.DataFetchingEnvironment
|
||||
import org.jetbrains.exposed.sql.ResultRow
|
||||
import suwayomi.tachidesk.graphql.cache.CustomCacheMap
|
||||
import suwayomi.tachidesk.graphql.server.primitives.Cursor
|
||||
import suwayomi.tachidesk.graphql.server.primitives.Edge
|
||||
import suwayomi.tachidesk.graphql.server.primitives.Node
|
||||
@@ -45,18 +46,33 @@ class MangaType(
|
||||
var chaptersLastFetchedAt: Long?, // todo
|
||||
) : Node {
|
||||
companion object {
|
||||
fun clearCacheFor(
|
||||
mangaIds: List<Int>,
|
||||
dataFetchingEnvironment: DataFetchingEnvironment,
|
||||
) {
|
||||
mangaIds.forEach { clearCacheFor(it, dataFetchingEnvironment) }
|
||||
}
|
||||
|
||||
fun clearCacheFor(
|
||||
mangaId: Int,
|
||||
dataFetchingEnvironment: DataFetchingEnvironment,
|
||||
) {
|
||||
dataFetchingEnvironment.getDataLoader<Int, MangaType>("MangaDataLoader").clear(mangaId)
|
||||
dataFetchingEnvironment.getDataLoader<Int, MangaNodeList>("MangaForIdsDataLoader").clear(mangaId)
|
||||
|
||||
val mangaForIdsDataLoader =
|
||||
dataFetchingEnvironment.getDataLoader<List<Int>, MangaNodeList>("MangaForIdsDataLoader")
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
(mangaForIdsDataLoader.cacheMap as CustomCacheMap<List<Int>, MangaNodeList>).getKeys()
|
||||
.filter { it.contains(mangaId) }.forEach { mangaForIdsDataLoader.clear(it) }
|
||||
|
||||
dataFetchingEnvironment.getDataLoader<Int, Int>("DownloadedChapterCountForMangaDataLoader").clear(mangaId)
|
||||
dataFetchingEnvironment.getDataLoader<Int, Int>("UnreadChapterCountForMangaDataLoader").clear(mangaId)
|
||||
dataFetchingEnvironment.getDataLoader<Int, Int>("BookmarkedChapterCountForMangaDataLoader").clear(mangaId)
|
||||
dataFetchingEnvironment.getDataLoader<Int, ChapterType>("LastReadChapterForMangaDataLoader").clear(mangaId)
|
||||
dataFetchingEnvironment.getDataLoader<Int, ChapterType>("LatestReadChapterForMangaDataLoader").clear(mangaId)
|
||||
dataFetchingEnvironment.getDataLoader<Int, ChapterType>("LatestFetchedChapterForMangaDataLoader").clear(mangaId)
|
||||
dataFetchingEnvironment.getDataLoader<Int, ChapterType>("LatestUploadedChapterForMangaDataLoader").clear(mangaId)
|
||||
dataFetchingEnvironment.getDataLoader<Int, ChapterType>("FirstUnreadChapterForMangaDataLoader").clear(mangaId)
|
||||
dataFetchingEnvironment.getDataLoader<Int, ChapterNodeList>(
|
||||
"ChaptersForMangaDataLoader",
|
||||
).clear(mangaId)
|
||||
@@ -115,6 +131,10 @@ class MangaType(
|
||||
return dataFetchingEnvironment.getValueFromDataLoader("UnreadChapterCountForMangaDataLoader", id)
|
||||
}
|
||||
|
||||
fun bookmarkCount(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture<Int> {
|
||||
return dataFetchingEnvironment.getValueFromDataLoader("BookmarkedChapterCountForMangaDataLoader", id)
|
||||
}
|
||||
|
||||
fun lastReadChapter(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture<ChapterType?> {
|
||||
return dataFetchingEnvironment.getValueFromDataLoader("LastReadChapterForMangaDataLoader", id)
|
||||
}
|
||||
@@ -131,6 +151,10 @@ class MangaType(
|
||||
return dataFetchingEnvironment.getValueFromDataLoader("LatestUploadedChapterForMangaDataLoader", id)
|
||||
}
|
||||
|
||||
fun firstUnreadChapter(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture<ChapterType?> {
|
||||
return dataFetchingEnvironment.getValueFromDataLoader("FirstUnreadChapterForMangaDataLoader", id)
|
||||
}
|
||||
|
||||
fun chapters(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture<ChapterNodeList> {
|
||||
return dataFetchingEnvironment.getValueFromDataLoader<Int, ChapterNodeList>("ChaptersForMangaDataLoader", id)
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ interface Settings : Node {
|
||||
)
|
||||
val autoDownloadAheadLimit: Int?
|
||||
val autoDownloadNewChaptersLimit: Int?
|
||||
val autoDownloadIgnoreReUploads: Boolean?
|
||||
|
||||
// extension
|
||||
val extensionRepos: List<String>?
|
||||
@@ -118,6 +119,7 @@ data class PartialSettingsType(
|
||||
)
|
||||
override val autoDownloadAheadLimit: Int?,
|
||||
override val autoDownloadNewChaptersLimit: Int?,
|
||||
override val autoDownloadIgnoreReUploads: Boolean?,
|
||||
// extension
|
||||
override val extensionRepos: List<String>?,
|
||||
// requests
|
||||
@@ -179,6 +181,7 @@ class SettingsType(
|
||||
)
|
||||
override val autoDownloadAheadLimit: Int,
|
||||
override val autoDownloadNewChaptersLimit: Int,
|
||||
override val autoDownloadIgnoreReUploads: Boolean?,
|
||||
// extension
|
||||
override val extensionRepos: List<String>,
|
||||
// requests
|
||||
@@ -235,6 +238,7 @@ class SettingsType(
|
||||
config.excludeEntryWithUnreadChapters.value,
|
||||
config.autoDownloadNewChaptersLimit.value, // deprecated
|
||||
config.autoDownloadNewChaptersLimit.value,
|
||||
config.autoDownloadIgnoreReUploads.value,
|
||||
// extension
|
||||
config.extensionRepos.value,
|
||||
// requests
|
||||
|
||||
@@ -20,6 +20,7 @@ class TrackerType(
|
||||
val icon: String,
|
||||
val isLoggedIn: Boolean,
|
||||
val authUrl: String?,
|
||||
val supportsTrackDeletion: Boolean?,
|
||||
) : Node {
|
||||
constructor(tracker: Tracker) : this(
|
||||
tracker.isLoggedIn,
|
||||
@@ -36,6 +37,7 @@ class TrackerType(
|
||||
} else {
|
||||
tracker.authUrl()
|
||||
},
|
||||
tracker.supportsTrackDeletion,
|
||||
)
|
||||
|
||||
fun statuses(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture<List<TrackStatusType>> {
|
||||
@@ -49,6 +51,10 @@ class TrackerType(
|
||||
fun trackRecords(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture<TrackRecordNodeList> {
|
||||
return dataFetchingEnvironment.getValueFromDataLoader<Int, TrackRecordNodeList>("TrackRecordsForTrackerIdDataLoader", id)
|
||||
}
|
||||
|
||||
fun isTokenExpired(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture<Boolean> {
|
||||
return dataFetchingEnvironment.getValueFromDataLoader<Int, Boolean>("TrackerTokenExpiredDataLoader", id)
|
||||
}
|
||||
}
|
||||
|
||||
class TrackStatusType(
|
||||
|
||||
@@ -117,7 +117,7 @@ object UpdateController {
|
||||
},
|
||||
behaviorOf = { ctx ->
|
||||
val updater by DI.global.instance<IUpdater>()
|
||||
ctx.json(updater.status.value)
|
||||
ctx.json(updater.statusDeprecated.value)
|
||||
},
|
||||
withResults = {
|
||||
json<UpdateStatus>(HttpCode.OK)
|
||||
|
||||
@@ -20,7 +20,6 @@ import kotlinx.serialization.Serializable
|
||||
import mu.KotlinLogging
|
||||
import org.jetbrains.exposed.dao.id.EntityID
|
||||
import org.jetbrains.exposed.sql.Op
|
||||
import org.jetbrains.exposed.sql.ResultRow
|
||||
import org.jetbrains.exposed.sql.SortOrder
|
||||
import org.jetbrains.exposed.sql.SqlExpressionBuilder.inList
|
||||
import org.jetbrains.exposed.sql.and
|
||||
@@ -37,7 +36,6 @@ import suwayomi.tachidesk.manga.impl.download.DownloadManager.EnqueueInput
|
||||
import suwayomi.tachidesk.manga.impl.track.Track
|
||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub
|
||||
import suwayomi.tachidesk.manga.model.dataclass.ChapterDataClass
|
||||
import suwayomi.tachidesk.manga.model.dataclass.IncludeOrExclude
|
||||
import suwayomi.tachidesk.manga.model.dataclass.MangaChapterDataClass
|
||||
import suwayomi.tachidesk.manga.model.dataclass.PaginatedList
|
||||
import suwayomi.tachidesk.manga.model.dataclass.paginatedFrom
|
||||
@@ -52,6 +50,15 @@ import java.util.TreeSet
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.math.max
|
||||
|
||||
private fun List<ChapterDataClass>.removeDuplicates(currentChapter: ChapterDataClass): List<ChapterDataClass> {
|
||||
return groupBy { it.chapterNumber }
|
||||
.map { (_, chapters) ->
|
||||
chapters.find { it.id == currentChapter.id }
|
||||
?: chapters.find { it.scanlator == currentChapter.scanlator }
|
||||
?: chapters.first()
|
||||
}
|
||||
}
|
||||
|
||||
object Chapter {
|
||||
private val logger = KotlinLogging.logger { }
|
||||
|
||||
@@ -136,6 +143,7 @@ object Chapter {
|
||||
url = manga.url
|
||||
}
|
||||
|
||||
val currentLatestChapterNumber = Manga.getLatestChapter(mangaId)?.chapterNumber ?: 0f
|
||||
val numberOfCurrentChapters = getCountOfMangaChapters(mangaId)
|
||||
val chapterList = source.getChapterList(sManga)
|
||||
|
||||
@@ -164,7 +172,10 @@ object Chapter {
|
||||
.toList()
|
||||
}
|
||||
|
||||
val chaptersToInsert = mutableListOf<ChapterDataClass>()
|
||||
// new chapters after they have been added to the database for auto downloads
|
||||
val insertedChapters = mutableListOf<ChapterDataClass>()
|
||||
|
||||
val chaptersToInsert = mutableListOf<ChapterDataClass>() // do not yet have an ID from the database
|
||||
val chaptersToUpdate = mutableListOf<ChapterDataClass>()
|
||||
|
||||
chapterList.reversed().forEachIndexed { index, fetchedChapter ->
|
||||
@@ -260,7 +271,7 @@ object Chapter {
|
||||
this[ChapterTable.fetchedAt] = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}.forEach { insertedChapters.add(ChapterTable.toDataClass(it)) }
|
||||
}
|
||||
|
||||
if (chaptersToUpdate.isNotEmpty()) {
|
||||
@@ -283,14 +294,8 @@ object Chapter {
|
||||
}
|
||||
}
|
||||
|
||||
val newChapters =
|
||||
transaction {
|
||||
ChapterTable.select { ChapterTable.manga eq mangaId }
|
||||
.orderBy(ChapterTable.sourceOrder to SortOrder.DESC).toList()
|
||||
}
|
||||
|
||||
if (manga.inLibrary) {
|
||||
downloadNewChapters(mangaId, numberOfCurrentChapters, newChapters)
|
||||
downloadNewChapters(mangaId, currentLatestChapterNumber, numberOfCurrentChapters, insertedChapters)
|
||||
}
|
||||
|
||||
chapterList
|
||||
@@ -301,16 +306,19 @@ object Chapter {
|
||||
|
||||
private fun downloadNewChapters(
|
||||
mangaId: Int,
|
||||
prevLatestChapterNumber: Float,
|
||||
prevNumberOfChapters: Int,
|
||||
updatedChapterList: List<ResultRow>,
|
||||
newChapters: List<ChapterDataClass>,
|
||||
) {
|
||||
val log =
|
||||
KotlinLogging.logger(
|
||||
"${logger.name}::downloadNewChapters(" +
|
||||
"mangaId= $mangaId, " +
|
||||
"prevLatestChapterNumber= $prevLatestChapterNumber, " +
|
||||
"prevNumberOfChapters= $prevNumberOfChapters, " +
|
||||
"updatedChapterList= ${updatedChapterList.size}, " +
|
||||
"autoDownloadNewChaptersLimit= ${serverConfig.autoDownloadNewChaptersLimit.value}" +
|
||||
"newChapters= ${newChapters.size}, " +
|
||||
"autoDownloadNewChaptersLimit= ${serverConfig.autoDownloadNewChaptersLimit.value}, " +
|
||||
"autoDownloadIgnoreReUploads= ${serverConfig.autoDownloadIgnoreReUploads.value}" +
|
||||
")",
|
||||
)
|
||||
|
||||
@@ -319,68 +327,22 @@ object Chapter {
|
||||
return
|
||||
}
|
||||
|
||||
// Only download if there are new chapters, or if this is the first fetch
|
||||
val newNumberOfChapters = updatedChapterList.size
|
||||
val numberOfNewChapters = newNumberOfChapters - prevNumberOfChapters
|
||||
|
||||
val areNewChaptersAvailable = numberOfNewChapters > 0
|
||||
val wasInitialFetch = prevNumberOfChapters == 0
|
||||
|
||||
if (!areNewChaptersAvailable) {
|
||||
if (newChapters.isEmpty()) {
|
||||
log.debug { "no new chapters available" }
|
||||
return
|
||||
}
|
||||
|
||||
val wasInitialFetch = prevNumberOfChapters == 0
|
||||
if (wasInitialFetch) {
|
||||
log.debug { "skipping download on initial fetch" }
|
||||
return
|
||||
}
|
||||
|
||||
// Verify the manga is configured to be downloaded based on it's categories.
|
||||
var mangaCategories = CategoryManga.getMangaCategories(mangaId).toSet()
|
||||
// if the manga has no categories, then it's implicitly in the default category
|
||||
if (mangaCategories.isEmpty()) {
|
||||
val defaultCategory = Category.getCategoryById(Category.DEFAULT_CATEGORY_ID)
|
||||
if (defaultCategory != null) {
|
||||
mangaCategories = setOf(defaultCategory)
|
||||
} else {
|
||||
log.warn { "missing default category" }
|
||||
}
|
||||
if (!Manga.isInIncludedDownloadCategory(log, mangaId)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (mangaCategories.isNotEmpty()) {
|
||||
val downloadCategoriesMap = Category.getCategoryList().groupBy { it.includeInDownload }
|
||||
val unsetCategories = downloadCategoriesMap[IncludeOrExclude.UNSET].orEmpty()
|
||||
// We only download if it's in the include list, and not in the exclude list.
|
||||
// Use the unset categories as the included categories if the included categories is
|
||||
// empty
|
||||
val includedCategories = downloadCategoriesMap[IncludeOrExclude.INCLUDE].orEmpty().ifEmpty { unsetCategories }
|
||||
val excludedCategories = downloadCategoriesMap[IncludeOrExclude.EXCLUDE].orEmpty()
|
||||
// Only download manga that aren't in any excluded categories
|
||||
val mangaExcludeCategories = mangaCategories.intersect(excludedCategories.toSet())
|
||||
if (mangaExcludeCategories.isNotEmpty()) {
|
||||
log.debug { "download excluded by categories: '${mangaExcludeCategories.joinToString("', '") { it.name }}'" }
|
||||
return
|
||||
}
|
||||
val mangaDownloadCategories = mangaCategories.intersect(includedCategories.toSet())
|
||||
if (mangaDownloadCategories.isNotEmpty()) {
|
||||
log.debug { "download inluded by categories: '${mangaDownloadCategories.joinToString("', '") { it.name }}'" }
|
||||
} else {
|
||||
log.debug { "skipping download due to download categories configuration" }
|
||||
return
|
||||
}
|
||||
} else {
|
||||
log.debug { "no categories configured, skipping check for category download include/excludes" }
|
||||
}
|
||||
|
||||
val newChapters = updatedChapterList.subList(0, numberOfNewChapters)
|
||||
|
||||
// make sure to only consider the latest chapters. e.g. old unread chapters should be ignored
|
||||
val latestReadChapterIndex =
|
||||
updatedChapterList.indexOfFirst { it[ChapterTable.isRead] }.takeIf { it > -1 } ?: (updatedChapterList.size)
|
||||
val unreadChapters =
|
||||
updatedChapterList.subList(numberOfNewChapters, latestReadChapterIndex)
|
||||
.filter { !it[ChapterTable.isRead] }
|
||||
val unreadChapters = Manga.getUnreadChapters(mangaId).subtract(newChapters.toSet())
|
||||
|
||||
val skipDueToUnreadChapters = serverConfig.excludeEntryWithUnreadChapters.value && unreadChapters.isNotEmpty()
|
||||
if (skipDueToUnreadChapters) {
|
||||
@@ -388,17 +350,7 @@ object Chapter {
|
||||
return
|
||||
}
|
||||
|
||||
val firstChapterToDownloadIndex =
|
||||
if (serverConfig.autoDownloadNewChaptersLimit.value > 0) {
|
||||
(numberOfNewChapters - serverConfig.autoDownloadNewChaptersLimit.value).coerceAtLeast(0)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
|
||||
val chapterIdsToDownload =
|
||||
newChapters.subList(firstChapterToDownloadIndex, numberOfNewChapters)
|
||||
.filter { !it[ChapterTable.isRead] && !it[ChapterTable.isDownloaded] }
|
||||
.map { it[ChapterTable.id].value }
|
||||
val chapterIdsToDownload = getNewChapterIdsToDownload(newChapters, prevLatestChapterNumber)
|
||||
|
||||
if (chapterIdsToDownload.isEmpty()) {
|
||||
log.debug { "no chapters available for download" }
|
||||
@@ -410,6 +362,37 @@ object Chapter {
|
||||
DownloadManager.enqueue(EnqueueInput(chapterIdsToDownload))
|
||||
}
|
||||
|
||||
private fun getNewChapterIdsToDownload(
|
||||
newChapters: List<ChapterDataClass>,
|
||||
prevLatestChapterNumber: Float,
|
||||
): List<Int> {
|
||||
val reUploadedChapters = newChapters.filter { it.chapterNumber < prevLatestChapterNumber }
|
||||
val actualNewChapters = newChapters.subtract(reUploadedChapters.toSet()).toList()
|
||||
val chaptersToConsiderForDownloadLimit =
|
||||
if (serverConfig.autoDownloadIgnoreReUploads.value) {
|
||||
if (actualNewChapters.isNotEmpty()) actualNewChapters.removeDuplicates(actualNewChapters[0]) else emptyList()
|
||||
} else {
|
||||
newChapters.removeDuplicates(newChapters[0])
|
||||
}.sortedBy { it.index }
|
||||
|
||||
val latestChapterToDownloadIndex =
|
||||
if (serverConfig.autoDownloadNewChaptersLimit.value == 0) {
|
||||
chaptersToConsiderForDownloadLimit.size
|
||||
} else {
|
||||
serverConfig.autoDownloadNewChaptersLimit.value.coerceAtMost(chaptersToConsiderForDownloadLimit.size)
|
||||
}
|
||||
val limitedChaptersToDownload = chaptersToConsiderForDownloadLimit.subList(0, latestChapterToDownloadIndex)
|
||||
val limitedChaptersToDownloadWithDuplicates =
|
||||
(
|
||||
limitedChaptersToDownload +
|
||||
newChapters.filter { newChapter ->
|
||||
limitedChaptersToDownload.find { it.chapterNumber == newChapter.chapterNumber } != null
|
||||
}
|
||||
).toSet()
|
||||
|
||||
return limitedChaptersToDownloadWithDuplicates.map { it.id }
|
||||
}
|
||||
|
||||
fun modifyChapter(
|
||||
mangaId: Int,
|
||||
chapterIndex: Int,
|
||||
|
||||
@@ -8,13 +8,18 @@ package suwayomi.tachidesk.manga.impl
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.HttpException
|
||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||
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 mu.KLogger
|
||||
import mu.KotlinLogging
|
||||
import okhttp3.CacheControl
|
||||
import okhttp3.Response
|
||||
import org.jetbrains.exposed.sql.ResultRow
|
||||
import org.jetbrains.exposed.sql.SortOrder
|
||||
import org.jetbrains.exposed.sql.and
|
||||
@@ -37,6 +42,8 @@ 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.ImageUtil
|
||||
import suwayomi.tachidesk.manga.impl.util.updateMangaDownloadDir
|
||||
import suwayomi.tachidesk.manga.model.dataclass.ChapterDataClass
|
||||
import suwayomi.tachidesk.manga.model.dataclass.IncludeOrExclude
|
||||
import suwayomi.tachidesk.manga.model.dataclass.MangaDataClass
|
||||
import suwayomi.tachidesk.manga.model.dataclass.toGenreList
|
||||
import suwayomi.tachidesk.manga.model.table.ChapterTable
|
||||
@@ -251,9 +258,55 @@ object Manga {
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun fetchThumbnailUrl(mangaId: Int): String? {
|
||||
getManga(mangaId, true)
|
||||
return transaction {
|
||||
MangaTable.select { MangaTable.id eq mangaId }.first()
|
||||
}[MangaTable.thumbnail_url]
|
||||
}
|
||||
|
||||
private val applicationDirs by DI.global.instance<ApplicationDirs>()
|
||||
private val network: NetworkHelper by injectLazy()
|
||||
|
||||
private suspend fun fetchHttpSourceMangaThumbnail(
|
||||
source: HttpSource,
|
||||
mangaEntry: ResultRow,
|
||||
refreshUrl: Boolean = false,
|
||||
): Response {
|
||||
val mangaId = mangaEntry[MangaTable.id].value
|
||||
|
||||
val requiresInitialization = mangaEntry[MangaTable.thumbnail_url] == null && !mangaEntry[MangaTable.initialized]
|
||||
val refreshThumbnailUrl = refreshUrl || requiresInitialization
|
||||
|
||||
val thumbnailUrl =
|
||||
if (refreshThumbnailUrl) {
|
||||
fetchThumbnailUrl(mangaId)
|
||||
} else {
|
||||
mangaEntry[MangaTable.thumbnail_url]
|
||||
} ?: throw NullPointerException("No thumbnail found")
|
||||
|
||||
return try {
|
||||
source.client.newCall(
|
||||
GET(thumbnailUrl, source.headers, cache = CacheControl.FORCE_NETWORK),
|
||||
).awaitSuccess()
|
||||
} catch (e: HttpException) {
|
||||
val tryToRefreshUrl =
|
||||
!refreshUrl &&
|
||||
listOf(
|
||||
HttpCode.GONE.status,
|
||||
HttpCode.MOVED_PERMANENTLY.status,
|
||||
HttpCode.NOT_FOUND.status,
|
||||
523, // (Cloudflare) Origin Is Unreachable
|
||||
522, // (Cloudflare) Connection timed out
|
||||
).contains(e.code)
|
||||
if (!tryToRefreshUrl) {
|
||||
throw e
|
||||
}
|
||||
|
||||
fetchHttpSourceMangaThumbnail(source, mangaEntry, refreshUrl = true)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun fetchMangaThumbnail(mangaId: Int): Pair<InputStream, String> {
|
||||
val cacheSaveDir = applicationDirs.tempThumbnailCacheRoot
|
||||
val fileName = mangaId.toString()
|
||||
@@ -264,22 +317,7 @@ object Manga {
|
||||
return when (val source = getCatalogueSourceOrStub(sourceId)) {
|
||||
is HttpSource ->
|
||||
getImageResponse(cacheSaveDir, fileName) {
|
||||
val thumbnailUrl =
|
||||
mangaEntry[MangaTable.thumbnail_url]
|
||||
?: if (!mangaEntry[MangaTable.initialized]) {
|
||||
// initialize then try again
|
||||
getManga(mangaId)
|
||||
transaction {
|
||||
MangaTable.select { MangaTable.id eq mangaId }.first()
|
||||
}[MangaTable.thumbnail_url]!!
|
||||
} else {
|
||||
// source provides no thumbnail url for this manga
|
||||
throw NullPointerException("No thumbnail found")
|
||||
}
|
||||
|
||||
source.client.newCall(
|
||||
GET(thumbnailUrl, source.headers, cache = CacheControl.FORCE_NETWORK),
|
||||
).await()
|
||||
fetchHttpSourceMangaThumbnail(source, mangaEntry)
|
||||
}
|
||||
|
||||
is LocalSource -> {
|
||||
@@ -315,7 +353,7 @@ object Manga {
|
||||
suspend fun getMangaThumbnail(mangaId: Int): Pair<InputStream, String> {
|
||||
val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() }
|
||||
|
||||
if (mangaEntry[MangaTable.inLibrary]) {
|
||||
if (mangaEntry[MangaTable.inLibrary] && mangaEntry[MangaTable.sourceReference] != LocalSource.ID) {
|
||||
return try {
|
||||
ThumbnailDownloadHelper.getImage(mangaId)
|
||||
} catch (_: MissingThumbnailException) {
|
||||
@@ -333,4 +371,56 @@ object Manga {
|
||||
clearCachedImage(applicationDirs.tempThumbnailCacheRoot, fileName)
|
||||
clearCachedImage(applicationDirs.thumbnailDownloadsRoot, fileName)
|
||||
}
|
||||
|
||||
fun getLatestChapter(mangaId: Int): ChapterDataClass? {
|
||||
return transaction {
|
||||
ChapterTable.select { ChapterTable.manga eq mangaId }.maxByOrNull { it[ChapterTable.sourceOrder] }
|
||||
}?.let { ChapterTable.toDataClass(it) }
|
||||
}
|
||||
|
||||
fun getUnreadChapters(mangaId: Int): List<ChapterDataClass> {
|
||||
return transaction {
|
||||
ChapterTable.select { (ChapterTable.manga eq mangaId) and (ChapterTable.isRead eq false) }
|
||||
.orderBy(ChapterTable.sourceOrder to SortOrder.DESC)
|
||||
.map { ChapterTable.toDataClass(it) }
|
||||
}
|
||||
}
|
||||
|
||||
fun isInIncludedDownloadCategory(
|
||||
logContext: KLogger = logger,
|
||||
mangaId: Int,
|
||||
): Boolean {
|
||||
val log = KotlinLogging.logger("${logContext.name}::isInExcludedDownloadCategory($mangaId)")
|
||||
|
||||
// Verify the manga is configured to be downloaded based on it's categories.
|
||||
var mangaCategories = CategoryManga.getMangaCategories(mangaId).toSet()
|
||||
// if the manga has no categories, then it's implicitly in the default category
|
||||
if (mangaCategories.isEmpty()) {
|
||||
val defaultCategory = Category.getCategoryById(Category.DEFAULT_CATEGORY_ID)!!
|
||||
mangaCategories = setOf(defaultCategory)
|
||||
}
|
||||
|
||||
val downloadCategoriesMap = Category.getCategoryList().groupBy { it.includeInDownload }
|
||||
val unsetCategories = downloadCategoriesMap[IncludeOrExclude.UNSET].orEmpty()
|
||||
// We only download if it's in the include list, and not in the exclude list.
|
||||
// Use the unset categories as the included categories if the included categories is
|
||||
// empty
|
||||
val includedCategories = downloadCategoriesMap[IncludeOrExclude.INCLUDE].orEmpty().ifEmpty { unsetCategories }
|
||||
val excludedCategories = downloadCategoriesMap[IncludeOrExclude.EXCLUDE].orEmpty()
|
||||
// Only download manga that aren't in any excluded categories
|
||||
val mangaExcludeCategories = mangaCategories.intersect(excludedCategories.toSet())
|
||||
if (mangaExcludeCategories.isNotEmpty()) {
|
||||
log.debug { "download excluded by categories: '${mangaExcludeCategories.joinToString("', '") { it.name }}'" }
|
||||
return false
|
||||
}
|
||||
val mangaDownloadCategories = mangaCategories.intersect(includedCategories.toSet())
|
||||
if (mangaDownloadCategories.isNotEmpty()) {
|
||||
log.debug { "download inluded by categories: '${mangaDownloadCategories.joinToString("', '") { it.name }}'" }
|
||||
} else {
|
||||
log.debug { "skipping download due to download categories configuration" }
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,14 +8,17 @@ package suwayomi.tachidesk.manga.impl
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||
import org.jetbrains.exposed.dao.id.EntityID
|
||||
import org.jetbrains.exposed.sql.and
|
||||
import org.jetbrains.exposed.sql.batchInsert
|
||||
import org.jetbrains.exposed.sql.select
|
||||
import org.jetbrains.exposed.sql.statements.BatchUpdateStatement
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub
|
||||
import suwayomi.tachidesk.manga.model.dataclass.PagedMangaListDataClass
|
||||
import suwayomi.tachidesk.manga.model.table.MangaTable
|
||||
import suwayomi.tachidesk.manga.model.table.toDataClass
|
||||
import java.time.Instant
|
||||
|
||||
object MangaList {
|
||||
fun proxyThumbnailUrl(mangaId: Int): String {
|
||||
@@ -44,12 +47,12 @@ object MangaList {
|
||||
return mangasPage.processEntries(sourceId)
|
||||
}
|
||||
|
||||
fun MangasPage.insertOrGet(sourceId: Long): List<Int> {
|
||||
fun MangasPage.insertOrUpdate(sourceId: Long): List<Int> {
|
||||
return transaction {
|
||||
val existingMangaUrlsToId =
|
||||
MangaTable.slice(MangaTable.url, MangaTable.id).select {
|
||||
MangaTable.select {
|
||||
(MangaTable.sourceReference eq sourceId) and (MangaTable.url inList mangas.map { it.url })
|
||||
}.associate { Pair(it[MangaTable.url], it[MangaTable.id].value) }
|
||||
}.associateBy { it[MangaTable.url] }
|
||||
val existingMangaUrls = existingMangaUrlsToId.map { it.key }
|
||||
|
||||
val mangasToInsert = mangas.filter { !existingMangaUrls.contains(it.url) }
|
||||
@@ -73,7 +76,40 @@ object MangaList {
|
||||
// delete thumbnail in case cached data still exists
|
||||
insertedMangaUrlsToId.forEach { (_, id) -> Manga.clearThumbnail(id) }
|
||||
|
||||
val mangaUrlsToId = existingMangaUrlsToId + insertedMangaUrlsToId
|
||||
val mangaToUpdate =
|
||||
mangas.mapNotNull { sManga ->
|
||||
existingMangaUrlsToId[sManga.url]?.let { sManga to it }
|
||||
}.filterNot { (_, resultRow) ->
|
||||
resultRow[MangaTable.inLibrary]
|
||||
}
|
||||
|
||||
if (mangaToUpdate.isNotEmpty()) {
|
||||
BatchUpdateStatement(MangaTable).apply {
|
||||
mangaToUpdate.forEach { (sManga, manga) ->
|
||||
addBatch(EntityID(manga[MangaTable.id].value, MangaTable))
|
||||
this[MangaTable.title] = sManga.title
|
||||
this[MangaTable.artist] = sManga.artist ?: manga[MangaTable.artist]
|
||||
this[MangaTable.author] = sManga.author ?: manga[MangaTable.author]
|
||||
this[MangaTable.description] = sManga.description ?: manga[MangaTable.description]
|
||||
this[MangaTable.genre] = sManga.genre ?: manga[MangaTable.genre]
|
||||
this[MangaTable.status] = sManga.status
|
||||
this[MangaTable.thumbnail_url] = sManga.thumbnail_url ?: manga[MangaTable.thumbnail_url]
|
||||
this[MangaTable.updateStrategy] = sManga.update_strategy.name
|
||||
if (!sManga.thumbnail_url.isNullOrEmpty() && manga[MangaTable.thumbnail_url] != sManga.thumbnail_url) {
|
||||
this[MangaTable.thumbnailUrlLastFetched] = Instant.now().epochSecond
|
||||
Manga.clearThumbnail(manga[MangaTable.id].value)
|
||||
} else {
|
||||
this[MangaTable.thumbnailUrlLastFetched] =
|
||||
manga[MangaTable.thumbnailUrlLastFetched]
|
||||
}
|
||||
}
|
||||
execute(this@transaction)
|
||||
}
|
||||
}
|
||||
|
||||
val mangaUrlsToId =
|
||||
existingMangaUrlsToId
|
||||
.mapValues { it.value[MangaTable.id].value } + insertedMangaUrlsToId
|
||||
|
||||
mangas.map { manga ->
|
||||
mangaUrlsToId[manga.url]
|
||||
@@ -86,7 +122,7 @@ object MangaList {
|
||||
val mangasPage = this
|
||||
val mangaList =
|
||||
transaction {
|
||||
val mangaIds = insertOrGet(sourceId)
|
||||
val mangaIds = insertOrUpdate(sourceId)
|
||||
return@transaction MangaTable.select { MangaTable.id inList mangaIds }.map { MangaTable.toDataClass(it) }
|
||||
}
|
||||
return PagedMangaListDataClass(
|
||||
|
||||
@@ -11,7 +11,7 @@ interface Track : Serializable {
|
||||
|
||||
var sync_id: Int
|
||||
|
||||
var media_id: Int
|
||||
var media_id: Long
|
||||
|
||||
var library_id: Long?
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ class TrackImpl : Track {
|
||||
|
||||
override var sync_id: Int = 0
|
||||
|
||||
override var media_id: Int = 0
|
||||
override var media_id: Long = 0L
|
||||
|
||||
override var library_id: Long? = null
|
||||
|
||||
@@ -43,7 +43,7 @@ class TrackImpl : Track {
|
||||
override fun hashCode(): Int {
|
||||
var result = (manga_id xor manga_id.ushr(32)).toInt()
|
||||
result = 31 * result + sync_id
|
||||
result = 31 * result + media_id
|
||||
result = (31 * result + media_id).toInt()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ import suwayomi.tachidesk.manga.impl.backup.proto.models.BackupChapter
|
||||
import suwayomi.tachidesk.manga.impl.backup.proto.models.BackupManga
|
||||
import suwayomi.tachidesk.manga.impl.backup.proto.models.BackupSerializer
|
||||
import suwayomi.tachidesk.manga.impl.backup.proto.models.BackupSource
|
||||
import suwayomi.tachidesk.manga.impl.backup.proto.models.BackupTracking
|
||||
import suwayomi.tachidesk.manga.impl.track.Track
|
||||
import suwayomi.tachidesk.manga.model.table.CategoryTable
|
||||
import suwayomi.tachidesk.manga.model.table.ChapterTable
|
||||
import suwayomi.tachidesk.manga.model.table.MangaStatus
|
||||
@@ -230,9 +232,32 @@ object ProtoBackupExport : ProtoBackupBase() {
|
||||
backupManga.categories = CategoryManga.getMangaCategories(mangaId).map { it.order }
|
||||
}
|
||||
|
||||
// if(flags.includeTracking) {
|
||||
// backupManga.tracking = TODO()
|
||||
// }
|
||||
if (flags.includeTracking) {
|
||||
val tracks =
|
||||
Track.getTrackRecordsByMangaId(mangaRow[MangaTable.id].value).mapNotNull {
|
||||
if (it.record == null) {
|
||||
null
|
||||
} else {
|
||||
BackupTracking(
|
||||
syncId = it.record.trackerId,
|
||||
// forced not null so its compatible with 1.x backup system
|
||||
libraryId = it.record.libraryId ?: 0,
|
||||
mediaId = it.record.remoteId,
|
||||
title = it.record.title,
|
||||
lastChapterRead = it.record.lastChapterRead.toFloat(),
|
||||
totalChapters = it.record.totalChapters,
|
||||
score = it.record.score.toFloat(),
|
||||
status = it.record.status,
|
||||
startedReadingDate = it.record.startDate,
|
||||
finishedReadingDate = it.record.finishDate,
|
||||
trackingUrl = it.record.remoteUrl,
|
||||
)
|
||||
}
|
||||
}
|
||||
if (tracks.isNotEmpty()) {
|
||||
backupManga.tracking = tracks
|
||||
}
|
||||
}
|
||||
|
||||
// if (flags.includeHistory) {
|
||||
// backupManga.history = TODO()
|
||||
|
||||
@@ -34,22 +34,27 @@ import suwayomi.tachidesk.manga.impl.CategoryManga
|
||||
import suwayomi.tachidesk.manga.impl.Manga.clearThumbnail
|
||||
import suwayomi.tachidesk.manga.impl.backup.models.Chapter
|
||||
import suwayomi.tachidesk.manga.impl.backup.models.Manga
|
||||
import suwayomi.tachidesk.manga.impl.backup.models.Track
|
||||
import suwayomi.tachidesk.manga.impl.backup.proto.ProtoBackupValidator.ValidationResult
|
||||
import suwayomi.tachidesk.manga.impl.backup.proto.ProtoBackupValidator.validate
|
||||
import suwayomi.tachidesk.manga.impl.backup.proto.models.BackupCategory
|
||||
import suwayomi.tachidesk.manga.impl.backup.proto.models.BackupHistory
|
||||
import suwayomi.tachidesk.manga.impl.backup.proto.models.BackupManga
|
||||
import suwayomi.tachidesk.manga.impl.backup.proto.models.BackupSerializer
|
||||
import suwayomi.tachidesk.manga.impl.backup.proto.models.BackupTracking
|
||||
import suwayomi.tachidesk.manga.impl.track.tracker.TrackerManager
|
||||
import suwayomi.tachidesk.manga.impl.track.tracker.model.toTrack
|
||||
import suwayomi.tachidesk.manga.impl.track.tracker.model.toTrackRecordDataClass
|
||||
import suwayomi.tachidesk.manga.model.dataclass.TrackRecordDataClass
|
||||
import suwayomi.tachidesk.manga.model.table.CategoryTable
|
||||
import suwayomi.tachidesk.manga.model.table.ChapterTable
|
||||
import suwayomi.tachidesk.manga.model.table.MangaTable
|
||||
import java.io.InputStream
|
||||
import java.lang.Integer.max
|
||||
import java.util.Date
|
||||
import java.util.Timer
|
||||
import java.util.TimerTask
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.math.max
|
||||
import suwayomi.tachidesk.manga.impl.track.Track as Tracker
|
||||
|
||||
object ProtoBackupImport : ProtoBackupBase() {
|
||||
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
|
||||
@@ -239,10 +244,9 @@ object ProtoBackupImport : ProtoBackupBase() {
|
||||
val chapters = backupManga.getChaptersImpl()
|
||||
val categories = backupManga.categories
|
||||
val history = backupManga.brokenHistory.map { BackupHistory(it.url, it.lastRead) } + backupManga.history
|
||||
val tracks = backupManga.getTrackingImpl()
|
||||
|
||||
try {
|
||||
restoreMangaData(manga, chapters, categories, history, tracks, backupCategories, categoryMapping)
|
||||
restoreMangaData(manga, chapters, categories, history, backupManga.tracking, backupCategories, categoryMapping)
|
||||
} catch (e: Exception) {
|
||||
val sourceName = sourceMapping[manga.source] ?: manga.source.toString()
|
||||
errors.add(Date() to "${manga.title} [$sourceName]: ${e.message}")
|
||||
@@ -255,7 +259,7 @@ object ProtoBackupImport : ProtoBackupBase() {
|
||||
chapters: List<Chapter>,
|
||||
categories: List<Int>,
|
||||
history: List<BackupHistory>,
|
||||
tracks: List<Track>,
|
||||
tracks: List<BackupTracking>,
|
||||
backupCategories: List<BackupCategory>,
|
||||
categoryMapping: Map<Int, Int>,
|
||||
) {
|
||||
@@ -265,127 +269,165 @@ object ProtoBackupImport : ProtoBackupBase() {
|
||||
.firstOrNull()
|
||||
}
|
||||
|
||||
if (dbManga == null) { // Manga not in database
|
||||
transaction {
|
||||
// insert manga to database
|
||||
val mangaId =
|
||||
MangaTable.insertAndGetId {
|
||||
it[url] = manga.url
|
||||
it[title] = manga.title
|
||||
val mangaId =
|
||||
if (dbManga == null) { // Manga not in database
|
||||
transaction {
|
||||
// insert manga to database
|
||||
val mangaId =
|
||||
MangaTable.insertAndGetId {
|
||||
it[url] = manga.url
|
||||
it[title] = manga.title
|
||||
|
||||
it[artist] = manga.artist
|
||||
it[author] = manga.author
|
||||
it[description] = manga.description
|
||||
it[genre] = manga.genre
|
||||
it[artist] = manga.artist
|
||||
it[author] = manga.author
|
||||
it[description] = manga.description
|
||||
it[genre] = manga.genre
|
||||
it[status] = manga.status
|
||||
it[thumbnail_url] = manga.thumbnail_url
|
||||
it[updateStrategy] = manga.update_strategy.name
|
||||
|
||||
it[sourceReference] = manga.source
|
||||
|
||||
it[initialized] = manga.description != null
|
||||
|
||||
it[inLibrary] = manga.favorite
|
||||
|
||||
it[inLibraryAt] = TimeUnit.MILLISECONDS.toSeconds(manga.date_added)
|
||||
}.value
|
||||
|
||||
// delete thumbnail in case cached data still exists
|
||||
clearThumbnail(mangaId)
|
||||
|
||||
// insert chapter data
|
||||
val chaptersLength = chapters.size
|
||||
ChapterTable.batchInsert(chapters) { chapter ->
|
||||
this[ChapterTable.url] = chapter.url
|
||||
this[ChapterTable.name] = chapter.name
|
||||
if (chapter.date_upload == 0L) {
|
||||
this[ChapterTable.date_upload] = chapter.date_fetch
|
||||
} else {
|
||||
this[ChapterTable.date_upload] = chapter.date_upload
|
||||
}
|
||||
this[ChapterTable.chapter_number] = chapter.chapter_number
|
||||
this[ChapterTable.scanlator] = chapter.scanlator
|
||||
|
||||
this[ChapterTable.sourceOrder] = chaptersLength - chapter.source_order
|
||||
this[ChapterTable.manga] = mangaId
|
||||
|
||||
this[ChapterTable.isRead] = chapter.read
|
||||
this[ChapterTable.lastPageRead] = chapter.last_page_read
|
||||
this[ChapterTable.isBookmarked] = chapter.bookmark
|
||||
|
||||
this[ChapterTable.fetchedAt] = TimeUnit.MILLISECONDS.toSeconds(chapter.date_fetch)
|
||||
}
|
||||
|
||||
// insert categories
|
||||
categories.forEach { backupCategoryOrder ->
|
||||
CategoryManga.addMangaToCategory(mangaId, categoryMapping[backupCategoryOrder]!!)
|
||||
}
|
||||
|
||||
mangaId
|
||||
}
|
||||
} else { // Manga in database
|
||||
transaction {
|
||||
val mangaId = dbManga[MangaTable.id].value
|
||||
|
||||
// Merge manga data
|
||||
MangaTable.update({ MangaTable.id eq mangaId }) {
|
||||
it[artist] = manga.artist ?: dbManga[artist]
|
||||
it[author] = manga.author ?: dbManga[author]
|
||||
it[description] = manga.description ?: dbManga[description]
|
||||
it[genre] = manga.genre ?: dbManga[genre]
|
||||
it[status] = manga.status
|
||||
it[thumbnail_url] = manga.thumbnail_url
|
||||
it[thumbnail_url] = manga.thumbnail_url ?: dbManga[thumbnail_url]
|
||||
it[updateStrategy] = manga.update_strategy.name
|
||||
|
||||
it[sourceReference] = manga.source
|
||||
it[initialized] = dbManga[initialized] || manga.description != null
|
||||
|
||||
it[initialized] = manga.description != null
|
||||
|
||||
it[inLibrary] = manga.favorite
|
||||
it[inLibrary] = manga.favorite || dbManga[inLibrary]
|
||||
|
||||
it[inLibraryAt] = TimeUnit.MILLISECONDS.toSeconds(manga.date_added)
|
||||
}.value
|
||||
|
||||
// delete thumbnail in case cached data still exists
|
||||
clearThumbnail(mangaId)
|
||||
|
||||
// insert chapter data
|
||||
val chaptersLength = chapters.size
|
||||
ChapterTable.batchInsert(chapters) { chapter ->
|
||||
this[ChapterTable.url] = chapter.url
|
||||
this[ChapterTable.name] = chapter.name
|
||||
if (chapter.date_upload == 0L) {
|
||||
this[ChapterTable.date_upload] = chapter.date_fetch
|
||||
} else {
|
||||
this[ChapterTable.date_upload] = chapter.date_upload
|
||||
}
|
||||
this[ChapterTable.chapter_number] = chapter.chapter_number
|
||||
this[ChapterTable.scanlator] = chapter.scanlator
|
||||
|
||||
this[ChapterTable.sourceOrder] = chaptersLength - chapter.source_order
|
||||
this[ChapterTable.manga] = mangaId
|
||||
// merge chapter data
|
||||
val chaptersLength = chapters.size
|
||||
val dbChapters = ChapterTable.select { ChapterTable.manga eq mangaId }
|
||||
|
||||
this[ChapterTable.isRead] = chapter.read
|
||||
this[ChapterTable.lastPageRead] = chapter.last_page_read
|
||||
this[ChapterTable.isBookmarked] = chapter.bookmark
|
||||
chapters.forEach { chapter ->
|
||||
val dbChapter = dbChapters.find { it[ChapterTable.url] == chapter.url }
|
||||
|
||||
this[ChapterTable.fetchedAt] = TimeUnit.MILLISECONDS.toSeconds(chapter.date_fetch)
|
||||
}
|
||||
if (dbChapter == null) {
|
||||
ChapterTable.insert {
|
||||
it[url] = chapter.url
|
||||
it[name] = chapter.name
|
||||
if (chapter.date_upload == 0L) {
|
||||
it[date_upload] = chapter.date_fetch
|
||||
} else {
|
||||
it[date_upload] = chapter.date_upload
|
||||
}
|
||||
it[chapter_number] = chapter.chapter_number
|
||||
it[scanlator] = chapter.scanlator
|
||||
|
||||
// insert categories
|
||||
categories.forEach { backupCategoryOrder ->
|
||||
CategoryManga.addMangaToCategory(mangaId, categoryMapping[backupCategoryOrder]!!)
|
||||
}
|
||||
}
|
||||
} else { // Manga in database
|
||||
transaction {
|
||||
val mangaId = dbManga[MangaTable.id].value
|
||||
it[sourceOrder] = chaptersLength - chapter.source_order
|
||||
it[ChapterTable.manga] = mangaId
|
||||
|
||||
// Merge manga data
|
||||
MangaTable.update({ MangaTable.id eq mangaId }) {
|
||||
it[artist] = manga.artist ?: dbManga[artist]
|
||||
it[author] = manga.author ?: dbManga[author]
|
||||
it[description] = manga.description ?: dbManga[description]
|
||||
it[genre] = manga.genre ?: dbManga[genre]
|
||||
it[status] = manga.status
|
||||
it[thumbnail_url] = manga.thumbnail_url ?: dbManga[thumbnail_url]
|
||||
it[updateStrategy] = manga.update_strategy.name
|
||||
|
||||
it[initialized] = dbManga[initialized] || manga.description != null
|
||||
|
||||
it[inLibrary] = manga.favorite || dbManga[inLibrary]
|
||||
|
||||
it[inLibraryAt] = TimeUnit.MILLISECONDS.toSeconds(manga.date_added)
|
||||
}
|
||||
|
||||
// merge chapter data
|
||||
val chaptersLength = chapters.size
|
||||
val dbChapters = ChapterTable.select { ChapterTable.manga eq mangaId }
|
||||
|
||||
chapters.forEach { chapter ->
|
||||
val dbChapter = dbChapters.find { it[ChapterTable.url] == chapter.url }
|
||||
|
||||
if (dbChapter == null) {
|
||||
ChapterTable.insert {
|
||||
it[url] = chapter.url
|
||||
it[name] = chapter.name
|
||||
if (chapter.date_upload == 0L) {
|
||||
it[date_upload] = chapter.date_fetch
|
||||
} else {
|
||||
it[date_upload] = chapter.date_upload
|
||||
it[isRead] = chapter.read
|
||||
it[lastPageRead] = chapter.last_page_read
|
||||
it[isBookmarked] = chapter.bookmark
|
||||
}
|
||||
} else {
|
||||
ChapterTable.update({ (ChapterTable.url eq dbChapter[ChapterTable.url]) and (ChapterTable.manga eq mangaId) }) {
|
||||
it[isRead] = chapter.read || dbChapter[isRead]
|
||||
it[lastPageRead] = max(chapter.last_page_read, dbChapter[lastPageRead])
|
||||
it[isBookmarked] = chapter.bookmark || dbChapter[isBookmarked]
|
||||
}
|
||||
it[chapter_number] = chapter.chapter_number
|
||||
it[scanlator] = chapter.scanlator
|
||||
|
||||
it[sourceOrder] = chaptersLength - chapter.source_order
|
||||
it[ChapterTable.manga] = mangaId
|
||||
|
||||
it[isRead] = chapter.read
|
||||
it[lastPageRead] = chapter.last_page_read
|
||||
it[isBookmarked] = chapter.bookmark
|
||||
}
|
||||
} else {
|
||||
ChapterTable.update({ (ChapterTable.url eq dbChapter[ChapterTable.url]) and (ChapterTable.manga eq mangaId) }) {
|
||||
it[isRead] = chapter.read || dbChapter[isRead]
|
||||
it[lastPageRead] = max(chapter.last_page_read, dbChapter[lastPageRead])
|
||||
it[isBookmarked] = chapter.bookmark || dbChapter[isBookmarked]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// merge categories
|
||||
categories.forEach { backupCategoryOrder ->
|
||||
CategoryManga.addMangaToCategory(mangaId, categoryMapping[backupCategoryOrder]!!)
|
||||
// merge categories
|
||||
categories.forEach { backupCategoryOrder ->
|
||||
CategoryManga.addMangaToCategory(mangaId, categoryMapping[backupCategoryOrder]!!)
|
||||
}
|
||||
|
||||
mangaId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val dbTrackRecordsByTrackerId =
|
||||
Tracker.getTrackRecordsByMangaId(mangaId)
|
||||
.mapNotNull { it.record?.toTrack() }
|
||||
.associateBy { it.sync_id }
|
||||
|
||||
val (existingTracks, newTracks) =
|
||||
tracks.mapNotNull { backupTrack ->
|
||||
val track = backupTrack.toTrack(mangaId)
|
||||
|
||||
val isUnsupportedTracker = TrackerManager.getTracker(track.sync_id) == null
|
||||
if (isUnsupportedTracker) {
|
||||
return@mapNotNull null
|
||||
}
|
||||
|
||||
val dbTrack =
|
||||
dbTrackRecordsByTrackerId[backupTrack.syncId]
|
||||
?: // new track
|
||||
return@mapNotNull track
|
||||
|
||||
if (track.toTrackRecordDataClass().forComparison() == dbTrack.toTrackRecordDataClass().forComparison()) {
|
||||
return@mapNotNull null
|
||||
}
|
||||
|
||||
dbTrack.also {
|
||||
it.media_id = track.media_id
|
||||
it.library_id = track.library_id
|
||||
it.last_chapter_read = max(dbTrack.last_chapter_read, track.last_chapter_read)
|
||||
}
|
||||
}.partition { (it.id ?: -1) > 0 }
|
||||
|
||||
existingTracks.forEach(Tracker::updateTrackRecord)
|
||||
newTracks.forEach(Tracker::insertTrackRecord)
|
||||
|
||||
// TODO: insert/merge history
|
||||
|
||||
// TODO: insert/merge tracking
|
||||
}
|
||||
|
||||
private fun TrackRecordDataClass.forComparison() = this.copy(id = 0, mangaId = 0)
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import org.jetbrains.exposed.sql.select
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import suwayomi.tachidesk.manga.impl.backup.proto.models.Backup
|
||||
import suwayomi.tachidesk.manga.impl.backup.proto.models.BackupSerializer
|
||||
import suwayomi.tachidesk.manga.impl.track.tracker.TrackerManager
|
||||
import suwayomi.tachidesk.manga.model.table.SourceTable
|
||||
import java.io.InputStream
|
||||
|
||||
@@ -39,17 +40,18 @@ object ProtoBackupValidator {
|
||||
sources.filter { SourceTable.select { SourceTable.id eq it.key }.firstOrNull() == null }
|
||||
}
|
||||
|
||||
// val trackers = backup.backupManga
|
||||
// .flatMap { it.tracking }
|
||||
// .map { it.syncId }
|
||||
// .distinct()
|
||||
val trackers =
|
||||
backup.backupManga
|
||||
.flatMap { it.tracking }
|
||||
.map { it.syncId }
|
||||
.distinct()
|
||||
|
||||
val missingTrackers = listOf("")
|
||||
// val missingTrackers = trackers
|
||||
// .mapNotNull { trackManager.getService(it) }
|
||||
// .filter { !it.isLogged }
|
||||
// .map { context.getString(it.nameRes()) }
|
||||
// .sorted()
|
||||
val missingTrackers =
|
||||
trackers
|
||||
.mapNotNull { TrackerManager.getTracker(it) }
|
||||
.filter { !it.isLoggedIn }
|
||||
.map { it.name }
|
||||
.sorted()
|
||||
|
||||
return ValidationResult(
|
||||
missingSources
|
||||
|
||||
@@ -8,11 +8,12 @@ import suwayomi.tachidesk.manga.impl.backup.models.TrackImpl
|
||||
@Serializable
|
||||
data class BackupTracking(
|
||||
// in 1.x some of these values have different types or names
|
||||
// syncId is called siteId in 1,x
|
||||
@ProtoNumber(1) var syncId: Int,
|
||||
// LibraryId is not null in 1.x
|
||||
@ProtoNumber(2) var libraryId: Long,
|
||||
@ProtoNumber(3) var mediaId: Int = 0,
|
||||
@Deprecated("Use mediaId instead", level = DeprecationLevel.WARNING)
|
||||
@ProtoNumber(3)
|
||||
var mediaIdInt: Int = 0,
|
||||
// trackingUrl is called mediaUrl in 1.x
|
||||
@ProtoNumber(4) var trackingUrl: String = "",
|
||||
@ProtoNumber(5) var title: String = "",
|
||||
@@ -25,11 +26,17 @@ data class BackupTracking(
|
||||
@ProtoNumber(10) var startedReadingDate: Long = 0,
|
||||
// finishedReadingDate is called endReadTime in 1.x
|
||||
@ProtoNumber(11) var finishedReadingDate: Long = 0,
|
||||
@ProtoNumber(100) var mediaId: Long = 0,
|
||||
) {
|
||||
fun getTrackingImpl(): TrackImpl {
|
||||
return TrackImpl().apply {
|
||||
sync_id = this@BackupTracking.syncId
|
||||
media_id = this@BackupTracking.mediaId
|
||||
media_id =
|
||||
if (this@BackupTracking.mediaIdInt != 0) {
|
||||
this@BackupTracking.mediaIdInt.toLong()
|
||||
} else {
|
||||
this@BackupTracking.mediaId
|
||||
}
|
||||
library_id = this@BackupTracking.libraryId
|
||||
title = this@BackupTracking.title
|
||||
// convert from float to int because of 1.x types
|
||||
|
||||
@@ -7,9 +7,16 @@ import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.sample
|
||||
import org.jetbrains.exposed.sql.select
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import suwayomi.tachidesk.manga.impl.Page
|
||||
import suwayomi.tachidesk.manga.impl.download.model.DownloadChapter
|
||||
import suwayomi.tachidesk.manga.impl.util.createComicInfoFile
|
||||
import suwayomi.tachidesk.manga.impl.util.getChapterCachePath
|
||||
import suwayomi.tachidesk.manga.impl.util.getChapterDownloadPath
|
||||
import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse
|
||||
import suwayomi.tachidesk.manga.model.table.ChapterTable
|
||||
import suwayomi.tachidesk.manga.model.table.MangaTable
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
|
||||
@@ -23,21 +30,40 @@ abstract class ChaptersFilesProvider(val mangaId: Int, val chapterId: Int) : Dow
|
||||
return RetrieveFile1Args(::getImageImpl)
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the existing download to the base download folder (see [getChapterDownloadPath])
|
||||
*/
|
||||
protected abstract fun extractExistingDownload()
|
||||
|
||||
protected abstract suspend fun handleSuccessfulDownload()
|
||||
|
||||
@OptIn(FlowPreview::class)
|
||||
open suspend fun downloadImpl(
|
||||
private suspend fun downloadImpl(
|
||||
download: DownloadChapter,
|
||||
scope: CoroutineScope,
|
||||
step: suspend (DownloadChapter?, Boolean) -> Unit,
|
||||
): Boolean {
|
||||
val pageCount = download.chapter.pageCount
|
||||
val chapterDir = getChapterCachePath(mangaId, chapterId)
|
||||
val folder = File(chapterDir)
|
||||
folder.mkdirs()
|
||||
extractExistingDownload()
|
||||
|
||||
val finalDownloadFolder = getChapterDownloadPath(mangaId, chapterId)
|
||||
|
||||
val cacheChapterDir = getChapterCachePath(mangaId, chapterId)
|
||||
val downloadCacheFolder = File(cacheChapterDir)
|
||||
downloadCacheFolder.mkdirs()
|
||||
|
||||
val pageCount = download.chapter.pageCount
|
||||
for (pageNum in 0 until pageCount) {
|
||||
var pageProgressJob: Job? = null
|
||||
val fileName = Page.getPageName(pageNum) // might have to change this to index stored in database
|
||||
if (File(folder, fileName).exists()) continue
|
||||
|
||||
val pageExistsInFinalDownloadFolder = ImageResponse.findFileNameStartingWith(finalDownloadFolder, fileName) != null
|
||||
val pageExistsInCacheDownloadFolder = ImageResponse.findFileNameStartingWith(cacheChapterDir, fileName) != null
|
||||
|
||||
val doesPageAlreadyExist = pageExistsInFinalDownloadFolder || pageExistsInCacheDownloadFolder
|
||||
if (doesPageAlreadyExist) {
|
||||
continue
|
||||
}
|
||||
|
||||
try {
|
||||
Page.getPageImage(
|
||||
mangaId = download.mangaId,
|
||||
@@ -53,7 +79,7 @@ abstract class ChaptersFilesProvider(val mangaId: Int, val chapterId: Int) : Dow
|
||||
step(null, false) // don't throw on canceled download here since we can't do anything
|
||||
}
|
||||
.launchIn(scope)
|
||||
}
|
||||
}.first.close()
|
||||
} finally {
|
||||
// always cancel the page progress job even if it throws an exception to avoid memory leaks
|
||||
pageProgressJob?.cancel()
|
||||
@@ -62,6 +88,21 @@ abstract class ChaptersFilesProvider(val mangaId: Int, val chapterId: Int) : Dow
|
||||
download.progress = ((pageNum + 1).toFloat()) / pageCount
|
||||
step(download, false)
|
||||
}
|
||||
|
||||
createComicInfoFile(
|
||||
downloadCacheFolder.toPath(),
|
||||
transaction {
|
||||
MangaTable.select { MangaTable.id eq mangaId }.first()
|
||||
},
|
||||
transaction {
|
||||
ChapterTable.select { ChapterTable.id eq chapterId }.first()
|
||||
},
|
||||
)
|
||||
|
||||
handleSuccessfulDownload()
|
||||
|
||||
File(cacheChapterDir).deleteRecursively()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package suwayomi.tachidesk.manga.impl.download.fileProvider.impl
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry
|
||||
@@ -11,7 +10,6 @@ import org.kodein.di.DI
|
||||
import org.kodein.di.conf.global
|
||||
import org.kodein.di.instance
|
||||
import suwayomi.tachidesk.manga.impl.download.fileProvider.ChaptersFilesProvider
|
||||
import suwayomi.tachidesk.manga.impl.download.model.DownloadChapter
|
||||
import suwayomi.tachidesk.manga.impl.util.getChapterCachePath
|
||||
import suwayomi.tachidesk.manga.impl.util.getChapterCbzPath
|
||||
import suwayomi.tachidesk.manga.impl.util.getMangaDownloadDir
|
||||
@@ -32,17 +30,21 @@ class ArchiveProvider(mangaId: Int, chapterId: Int) : ChaptersFilesProvider(mang
|
||||
return Pair(inputStream.buffered(), "image/$fileType")
|
||||
}
|
||||
|
||||
override suspend fun downloadImpl(
|
||||
download: DownloadChapter,
|
||||
scope: CoroutineScope,
|
||||
step: suspend (DownloadChapter?, Boolean) -> Unit,
|
||||
): Boolean {
|
||||
override fun extractExistingDownload() {
|
||||
val outputFile = File(getChapterCbzPath(mangaId, chapterId))
|
||||
val chapterCacheFolder = File(getChapterCachePath(mangaId, chapterId))
|
||||
|
||||
if (!outputFile.exists()) {
|
||||
return
|
||||
}
|
||||
|
||||
extractCbzFile(outputFile, chapterCacheFolder)
|
||||
}
|
||||
|
||||
override suspend fun handleSuccessfulDownload() {
|
||||
val mangaDownloadFolder = File(getMangaDownloadDir(mangaId))
|
||||
val outputFile = File(getChapterCbzPath(mangaId, chapterId))
|
||||
val chapterCacheFolder = File(getChapterCachePath(mangaId, chapterId))
|
||||
if (outputFile.exists()) handleExistingCbzFile(outputFile, chapterCacheFolder)
|
||||
|
||||
super.downloadImpl(download, scope, step)
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
mangaDownloadFolder.mkdirs()
|
||||
@@ -68,8 +70,6 @@ class ArchiveProvider(mangaId: Int, chapterId: Int) : ChaptersFilesProvider(mang
|
||||
if (chapterCacheFolder.exists() && chapterCacheFolder.isDirectory) {
|
||||
chapterCacheFolder.deleteRecursively()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun delete(): Boolean {
|
||||
@@ -83,7 +83,7 @@ class ArchiveProvider(mangaId: Int, chapterId: Int) : ChaptersFilesProvider(mang
|
||||
return cbzDeleted
|
||||
}
|
||||
|
||||
private fun handleExistingCbzFile(
|
||||
private fun extractCbzFile(
|
||||
cbzFile: File,
|
||||
chapterFolder: File,
|
||||
) {
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
package suwayomi.tachidesk.manga.impl.download.fileProvider.impl
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import org.kodein.di.DI
|
||||
import org.kodein.di.conf.global
|
||||
import org.kodein.di.instance
|
||||
import suwayomi.tachidesk.manga.impl.download.fileProvider.ChaptersFilesProvider
|
||||
import suwayomi.tachidesk.manga.impl.download.model.DownloadChapter
|
||||
import suwayomi.tachidesk.manga.impl.util.getChapterCachePath
|
||||
import suwayomi.tachidesk.manga.impl.util.getChapterDownloadPath
|
||||
import suwayomi.tachidesk.manga.impl.util.storage.FileDeletionHelper
|
||||
@@ -29,23 +27,16 @@ class FolderProvider(mangaId: Int, chapterId: Int) : ChaptersFilesProvider(manga
|
||||
return Pair(FileInputStream(file).buffered(), "image/$fileType")
|
||||
}
|
||||
|
||||
override suspend fun downloadImpl(
|
||||
download: DownloadChapter,
|
||||
scope: CoroutineScope,
|
||||
step: suspend (DownloadChapter?, Boolean) -> Unit,
|
||||
): Boolean {
|
||||
override fun extractExistingDownload() {
|
||||
// nothing to do
|
||||
}
|
||||
|
||||
override suspend fun handleSuccessfulDownload() {
|
||||
val chapterDir = getChapterDownloadPath(mangaId, chapterId)
|
||||
val folder = File(chapterDir)
|
||||
|
||||
val downloadSucceeded = super.downloadImpl(download, scope, step)
|
||||
if (!downloadSucceeded) {
|
||||
return false
|
||||
}
|
||||
|
||||
val cacheChapterDir = getChapterCachePath(mangaId, chapterId)
|
||||
File(cacheChapterDir).copyRecursively(folder, true)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun delete(): Boolean {
|
||||
|
||||
@@ -15,6 +15,7 @@ import org.jetbrains.exposed.sql.insertAndGetId
|
||||
import org.jetbrains.exposed.sql.select
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import org.jetbrains.exposed.sql.update
|
||||
import suwayomi.tachidesk.manga.impl.track.tracker.DeletableTrackService
|
||||
import suwayomi.tachidesk.manga.impl.track.tracker.TrackerManager
|
||||
import suwayomi.tachidesk.manga.impl.track.tracker.model.Track
|
||||
import suwayomi.tachidesk.manga.impl.track.tracker.model.toTrack
|
||||
@@ -73,9 +74,6 @@ object Track {
|
||||
}
|
||||
|
||||
fun getTrackRecordsByMangaId(mangaId: Int): List<MangaTrackerDataClass> {
|
||||
if (!TrackerManager.hasLoggedTracker()) {
|
||||
return emptyList()
|
||||
}
|
||||
val recordMap =
|
||||
transaction {
|
||||
TrackRecordTable.select { TrackRecordTable.mangaId eq mangaId }
|
||||
@@ -83,27 +81,25 @@ object Track {
|
||||
}.associateBy { it.trackerId }
|
||||
|
||||
val trackers = TrackerManager.services
|
||||
return trackers
|
||||
.filter { it.isLoggedIn }
|
||||
.map {
|
||||
val record = recordMap[it.id]
|
||||
if (record != null) {
|
||||
val track =
|
||||
Track.create(it.id).also { t ->
|
||||
t.score = record.score.toFloat()
|
||||
}
|
||||
record.scoreString = it.displayScore(track)
|
||||
}
|
||||
MangaTrackerDataClass(
|
||||
id = it.id,
|
||||
name = it.name,
|
||||
icon = proxyThumbnailUrl(it.id),
|
||||
statusList = it.getStatusList(),
|
||||
statusTextMap = it.getStatusList().associateWith { k -> it.getStatus(k).orEmpty() },
|
||||
scoreList = it.getScoreList(),
|
||||
record = record,
|
||||
)
|
||||
return trackers.map {
|
||||
val record = recordMap[it.id]
|
||||
if (record != null) {
|
||||
val track =
|
||||
Track.create(it.id).also { t ->
|
||||
t.score = record.score.toFloat()
|
||||
}
|
||||
record.scoreString = it.displayScore(track)
|
||||
}
|
||||
MangaTrackerDataClass(
|
||||
id = it.id,
|
||||
name = it.name,
|
||||
icon = proxyThumbnailUrl(it.id),
|
||||
statusList = it.getStatusList(),
|
||||
statusTextMap = it.getStatusList().associateWith { k -> it.getStatus(k).orEmpty() },
|
||||
scoreList = it.getScoreList(),
|
||||
record = record,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun search(input: SearchInput): List<TrackSearchDataClass> {
|
||||
@@ -158,7 +154,7 @@ object Track {
|
||||
|
||||
var lastChapterRead: Double? = null
|
||||
var startDate: Long? = null
|
||||
if (chapterNumber != null && chapterNumber > 0) {
|
||||
if (chapterNumber != null && chapterNumber > 0 && chapterNumber > track.last_chapter_read) {
|
||||
lastChapterRead = chapterNumber.toDouble()
|
||||
}
|
||||
if (track.started_reading_date <= 0) {
|
||||
@@ -186,11 +182,42 @@ object Track {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun refresh(recordId: Int) {
|
||||
val recordDb =
|
||||
transaction {
|
||||
TrackRecordTable.select { TrackRecordTable.id eq recordId }.first()
|
||||
}
|
||||
|
||||
val tracker = TrackerManager.getTracker(recordDb[TrackRecordTable.trackerId])!!
|
||||
|
||||
val track = recordDb.toTrack()
|
||||
tracker.refresh(track)
|
||||
upsertTrackRecord(track)
|
||||
}
|
||||
|
||||
suspend fun unbind(
|
||||
recordId: Int,
|
||||
deleteRemoteTrack: Boolean? = false,
|
||||
) {
|
||||
val recordDb =
|
||||
transaction {
|
||||
TrackRecordTable.select { TrackRecordTable.id eq recordId }.first()
|
||||
}
|
||||
|
||||
val tracker = TrackerManager.getTracker(recordDb[TrackRecordTable.trackerId])
|
||||
|
||||
if (deleteRemoteTrack == true && tracker is DeletableTrackService) {
|
||||
tracker.delete(recordDb.toTrack())
|
||||
}
|
||||
|
||||
transaction {
|
||||
TrackRecordTable.deleteWhere { TrackRecordTable.id eq recordId }
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun update(input: UpdateInput) {
|
||||
if (input.unbind == true) {
|
||||
transaction {
|
||||
TrackRecordTable.deleteWhere { TrackRecordTable.id eq input.recordId }
|
||||
}
|
||||
unbind(input.recordId)
|
||||
return
|
||||
}
|
||||
val recordDb =
|
||||
@@ -250,13 +277,14 @@ object Track {
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun trackChapter(mangaId: Int) {
|
||||
suspend fun trackChapter(mangaId: Int) {
|
||||
val chapter = queryMaxReadChapter(mangaId)
|
||||
val chapterNumber = chapter?.get(ChapterTable.chapter_number)
|
||||
logger.debug {
|
||||
"[Tracker]mangaId $mangaId chapter:${chapter?.get(ChapterTable.name)} " +
|
||||
"chapterNumber:$chapterNumber"
|
||||
|
||||
logger.info {
|
||||
"trackChapter(mangaId= $mangaId): maxReadChapter= #$chapterNumber ${chapter?.get(ChapterTable.name)}"
|
||||
}
|
||||
|
||||
if (chapterNumber != null && chapterNumber > 0) {
|
||||
trackChapter(mangaId, chapterNumber.toDouble())
|
||||
}
|
||||
@@ -282,23 +310,55 @@ object Track {
|
||||
}
|
||||
|
||||
records.forEach {
|
||||
val tracker = TrackerManager.getTracker(it[TrackRecordTable.trackerId])
|
||||
val lastChapterRead = it[TrackRecordTable.lastChapterRead]
|
||||
val isLogin = tracker?.isLoggedIn == true
|
||||
logger.debug {
|
||||
"[Tracker]trackChapter id:${tracker?.id} login:$isLogin " +
|
||||
"mangaId:$mangaId dbChapter:$lastChapterRead toChapter:$chapterNumber"
|
||||
}
|
||||
if (isLogin && chapterNumber > lastChapterRead) {
|
||||
it[TrackRecordTable.lastChapterRead] = chapterNumber
|
||||
val track = it.toTrack()
|
||||
tracker?.update(track, true)
|
||||
upsertTrackRecord(track)
|
||||
try {
|
||||
trackChapterForTracker(it, chapterNumber)
|
||||
} catch (e: Exception) {
|
||||
KotlinLogging.logger("${logger.name}::trackChapter(mangaId= $mangaId, chapterNumber= $chapterNumber)")
|
||||
.error(e) { "failed due to" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun upsertTrackRecord(track: Track): Int {
|
||||
private suspend fun trackChapterForTracker(
|
||||
it: ResultRow,
|
||||
chapterNumber: Double,
|
||||
) {
|
||||
val tracker = TrackerManager.getTracker(it[TrackRecordTable.trackerId]) ?: return
|
||||
val track = it.toTrack()
|
||||
|
||||
val log =
|
||||
KotlinLogging.logger {
|
||||
"${logger.name}::trackChapterForTracker(chapterNumber= $chapterNumber, tracker= ${tracker.id}, recordId= ${track.id})"
|
||||
}
|
||||
log.debug { "called for $tracker, ${track.title} (recordId= ${track.id}, mangaId= ${track.manga_id})" }
|
||||
|
||||
val localLastReadChapter = it[TrackRecordTable.lastChapterRead]
|
||||
|
||||
if (localLastReadChapter == chapterNumber) {
|
||||
log.debug { "new chapter is the same as the local last read chapter" }
|
||||
return
|
||||
}
|
||||
|
||||
if (!tracker.isLoggedIn) {
|
||||
upsertTrackRecord(track)
|
||||
return
|
||||
}
|
||||
|
||||
tracker.refresh(track)
|
||||
upsertTrackRecord(track)
|
||||
|
||||
val lastChapterRead = track.last_chapter_read
|
||||
|
||||
log.debug { "remoteLastReadChapter= $lastChapterRead" }
|
||||
|
||||
if (chapterNumber > lastChapterRead) {
|
||||
track.last_chapter_read = chapterNumber.toFloat()
|
||||
tracker.update(track, true)
|
||||
upsertTrackRecord(track)
|
||||
}
|
||||
}
|
||||
|
||||
fun upsertTrackRecord(track: Track): Int {
|
||||
return transaction {
|
||||
val existingRecord =
|
||||
TrackRecordTable.select {
|
||||
@@ -308,41 +368,53 @@ object Track {
|
||||
.singleOrNull()
|
||||
|
||||
if (existingRecord != null) {
|
||||
TrackRecordTable.update({
|
||||
(TrackRecordTable.mangaId eq track.manga_id) and
|
||||
(TrackRecordTable.trackerId eq track.sync_id)
|
||||
}) {
|
||||
it[remoteId] = track.media_id
|
||||
it[libraryId] = track.library_id
|
||||
it[title] = track.title
|
||||
it[lastChapterRead] = track.last_chapter_read.toDouble()
|
||||
it[totalChapters] = track.total_chapters
|
||||
it[status] = track.status
|
||||
it[score] = track.score.toDouble()
|
||||
it[remoteUrl] = track.tracking_url
|
||||
it[startDate] = track.started_reading_date
|
||||
it[finishDate] = track.finished_reading_date
|
||||
}
|
||||
updateTrackRecord(track)
|
||||
existingRecord[TrackRecordTable.id].value
|
||||
} else {
|
||||
TrackRecordTable.insertAndGetId {
|
||||
it[mangaId] = track.manga_id
|
||||
it[trackerId] = track.sync_id
|
||||
it[remoteId] = track.media_id
|
||||
it[libraryId] = track.library_id
|
||||
it[title] = track.title
|
||||
it[lastChapterRead] = track.last_chapter_read.toDouble()
|
||||
it[totalChapters] = track.total_chapters
|
||||
it[status] = track.status
|
||||
it[score] = track.score.toDouble()
|
||||
it[remoteUrl] = track.tracking_url
|
||||
it[startDate] = track.started_reading_date
|
||||
it[finishDate] = track.finished_reading_date
|
||||
}.value
|
||||
insertTrackRecord(track)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updateTrackRecord(track: Track): Int =
|
||||
transaction {
|
||||
TrackRecordTable.update(
|
||||
{
|
||||
(TrackRecordTable.mangaId eq track.manga_id) and
|
||||
(TrackRecordTable.trackerId eq track.sync_id)
|
||||
},
|
||||
) {
|
||||
it[remoteId] = track.media_id
|
||||
it[libraryId] = track.library_id
|
||||
it[title] = track.title
|
||||
it[lastChapterRead] = track.last_chapter_read.toDouble()
|
||||
it[totalChapters] = track.total_chapters
|
||||
it[status] = track.status
|
||||
it[score] = track.score.toDouble()
|
||||
it[remoteUrl] = track.tracking_url
|
||||
it[startDate] = track.started_reading_date
|
||||
it[finishDate] = track.finished_reading_date
|
||||
}
|
||||
}
|
||||
|
||||
fun insertTrackRecord(track: Track): Int =
|
||||
transaction {
|
||||
TrackRecordTable.insertAndGetId {
|
||||
it[mangaId] = track.manga_id
|
||||
it[trackerId] = track.sync_id
|
||||
it[remoteId] = track.media_id
|
||||
it[libraryId] = track.library_id
|
||||
it[title] = track.title
|
||||
it[lastChapterRead] = track.last_chapter_read.toDouble()
|
||||
it[totalChapters] = track.total_chapters
|
||||
it[status] = track.status
|
||||
it[score] = track.score.toDouble()
|
||||
it[remoteUrl] = track.tracking_url
|
||||
it[startDate] = track.started_reading_date
|
||||
it[finishDate] = track.finished_reading_date
|
||||
}.value
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class LoginInput(
|
||||
val trackerId: Int,
|
||||
|
||||
@@ -6,5 +6,5 @@ import suwayomi.tachidesk.manga.impl.track.tracker.model.Track
|
||||
* For track services api that support deleting a manga entry for a user's list
|
||||
*/
|
||||
interface DeletableTrackService {
|
||||
suspend fun delete(track: Track): Track
|
||||
suspend fun delete(track: Track)
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import okhttp3.OkHttpClient
|
||||
import suwayomi.tachidesk.manga.impl.track.tracker.model.Track
|
||||
import suwayomi.tachidesk.manga.impl.track.tracker.model.TrackSearch
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.IOException
|
||||
|
||||
abstract class Tracker(val id: Int, val name: String) {
|
||||
val trackPreferences = TrackerPreferences
|
||||
@@ -16,6 +17,10 @@ abstract class Tracker(val id: Int, val name: String) {
|
||||
// Application and remote support for reading dates
|
||||
open val supportsReadingDates: Boolean = false
|
||||
|
||||
abstract val supportsTrackDeletion: Boolean
|
||||
|
||||
override fun toString() = "$name ($id) (isLoggedIn= $isLoggedIn, isAuthExpired= ${getIfAuthExpired()})"
|
||||
|
||||
abstract fun getLogo(): String
|
||||
|
||||
abstract fun getStatusList(): List<Int>
|
||||
@@ -81,6 +86,14 @@ abstract class Tracker(val id: Int, val name: String) {
|
||||
) {
|
||||
trackPreferences.setTrackCredentials(this, username, password)
|
||||
}
|
||||
|
||||
fun getIfAuthExpired(): Boolean {
|
||||
return trackPreferences.trackAuthExpired(this)
|
||||
}
|
||||
|
||||
fun setAuthExpired() {
|
||||
trackPreferences.setTrackTokenExpired(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun String.extractToken(key: String): String? {
|
||||
@@ -93,3 +106,7 @@ fun String.extractToken(key: String): String? {
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
class TokenExpired : IOException("Token is expired, re-logging required")
|
||||
|
||||
class TokenRefreshFailed : IOException("Token refresh failed")
|
||||
|
||||
@@ -16,6 +16,12 @@ object TrackerPreferences {
|
||||
|
||||
fun getTrackPassword(sync: Tracker) = preferenceStore.getString(trackPassword(sync.id), "")
|
||||
|
||||
fun trackAuthExpired(tracker: Tracker) =
|
||||
preferenceStore.getBoolean(
|
||||
trackTokenExpired(tracker.id),
|
||||
false,
|
||||
)
|
||||
|
||||
fun setTrackCredentials(
|
||||
sync: Tracker,
|
||||
username: String,
|
||||
@@ -25,6 +31,7 @@ object TrackerPreferences {
|
||||
preferenceStore.edit()
|
||||
.putString(trackUsername(sync.id), username)
|
||||
.putString(trackPassword(sync.id), password)
|
||||
.putBoolean(trackTokenExpired(sync.id), false)
|
||||
.apply()
|
||||
}
|
||||
|
||||
@@ -38,14 +45,22 @@ object TrackerPreferences {
|
||||
if (token == null) {
|
||||
preferenceStore.edit()
|
||||
.remove(trackToken(sync.id))
|
||||
.putBoolean(trackTokenExpired(sync.id), false)
|
||||
.apply()
|
||||
} else {
|
||||
preferenceStore.edit()
|
||||
.putString(trackToken(sync.id), token)
|
||||
.putBoolean(trackTokenExpired(sync.id), false)
|
||||
.apply()
|
||||
}
|
||||
}
|
||||
|
||||
fun setTrackTokenExpired(sync: Tracker) {
|
||||
preferenceStore.edit()
|
||||
.putBoolean(trackTokenExpired(sync.id), true)
|
||||
.apply()
|
||||
}
|
||||
|
||||
fun getScoreType(sync: Tracker) = preferenceStore.getString(scoreType(sync.id), Anilist.POINT_10)
|
||||
|
||||
fun setScoreType(
|
||||
@@ -63,5 +78,7 @@ object TrackerPreferences {
|
||||
|
||||
private fun trackToken(trackerId: Int) = "track_token_$trackerId"
|
||||
|
||||
private fun trackTokenExpired(trackerId: Int) = "track_token_expired_$trackerId"
|
||||
|
||||
private fun scoreType(trackerId: Int) = "score_type_$trackerId"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package suwayomi.tachidesk.manga.impl.track.tracker.anilist
|
||||
|
||||
import android.annotation.StringRes
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import mu.KotlinLogging
|
||||
@@ -29,9 +28,11 @@ class Anilist(id: Int) : Tracker(id, "AniList"), DeletableTrackService {
|
||||
const val POINT_3 = "POINT_3"
|
||||
}
|
||||
|
||||
override val supportsTrackDeletion: Boolean = true
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
private val interceptor by lazy { AnilistInterceptor(this, getPassword()) }
|
||||
private val interceptor by lazy { AnilistInterceptor(this) }
|
||||
|
||||
private val api by lazy { AnilistApi(client, interceptor) }
|
||||
|
||||
@@ -157,13 +158,13 @@ class Anilist(id: Int) : Tracker(id, "AniList"), DeletableTrackService {
|
||||
return api.updateLibManga(track)
|
||||
}
|
||||
|
||||
override suspend fun delete(track: Track): Track {
|
||||
override suspend fun delete(track: Track) {
|
||||
if (track.library_id == null || track.library_id!! == 0L) {
|
||||
val libManga = api.findLibManga(track, getUsername().toInt()) ?: return track
|
||||
val libManga = api.findLibManga(track, getUsername().toInt()) ?: return
|
||||
track.library_id = libManga.library_id
|
||||
}
|
||||
|
||||
return api.deleteLibManga(track)
|
||||
api.deleteLibManga(track)
|
||||
}
|
||||
|
||||
override suspend fun bind(
|
||||
|
||||
@@ -115,7 +115,7 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun deleteLibManga(track: Track): Track {
|
||||
suspend fun deleteLibManga(track: Track) {
|
||||
return withIOContext {
|
||||
val query =
|
||||
"""
|
||||
@@ -135,7 +135,6 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
||||
}
|
||||
authClient.newCall(POST(API_URL, body = payload.toString().toRequestBody(jsonMime)))
|
||||
.awaitSuccess()
|
||||
track
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,10 @@ package suwayomi.tachidesk.manga.impl.track.tracker.anilist
|
||||
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
import suwayomi.tachidesk.manga.impl.track.tracker.TokenExpired
|
||||
import java.io.IOException
|
||||
|
||||
class AnilistInterceptor(val anilist: Anilist, private var token: String?) : Interceptor {
|
||||
class AnilistInterceptor(private val anilist: Anilist) : Interceptor {
|
||||
/**
|
||||
* OAuth object used for authenticated requests.
|
||||
*
|
||||
@@ -16,24 +17,25 @@ class AnilistInterceptor(val anilist: Anilist, private var token: String?) : Int
|
||||
field = value?.copy(expires = value.expires * 1000 - 60 * 1000)
|
||||
}
|
||||
|
||||
init {
|
||||
oauth = anilist.loadOAuth()
|
||||
}
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
if (anilist.getIfAuthExpired()) {
|
||||
throw TokenExpired()
|
||||
}
|
||||
val originalRequest = chain.request()
|
||||
|
||||
if (token.isNullOrEmpty()) {
|
||||
throw Exception("Not authenticated with Anilist")
|
||||
}
|
||||
if (oauth == null) {
|
||||
oauth = anilist.loadOAuth()
|
||||
}
|
||||
// Refresh access token if null or expired.
|
||||
if (oauth!!.isExpired()) {
|
||||
anilist.logout()
|
||||
throw IOException("Token expired")
|
||||
if (oauth?.isExpired() == true) {
|
||||
anilist.setAuthExpired()
|
||||
throw TokenExpired()
|
||||
}
|
||||
|
||||
// Throw on null auth.
|
||||
if (oauth == null) {
|
||||
throw IOException("No authentication token")
|
||||
throw IOException("Anilist: User is not authenticated")
|
||||
}
|
||||
|
||||
// Add the authorization header to the original request.
|
||||
@@ -50,7 +52,6 @@ class AnilistInterceptor(val anilist: Anilist, private var token: String?) : Int
|
||||
* and the oauth object.
|
||||
*/
|
||||
fun setAuth(oauth: OAuth?) {
|
||||
token = oauth?.access_token
|
||||
this.oauth = oauth
|
||||
anilist.saveOAuth(oauth)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package suwayomi.tachidesk.manga.impl.track.tracker.mangaupdates
|
||||
|
||||
import suwayomi.tachidesk.manga.impl.track.tracker.DeletableTrackService
|
||||
import suwayomi.tachidesk.manga.impl.track.tracker.Tracker
|
||||
import suwayomi.tachidesk.manga.impl.track.tracker.mangaupdates.dto.ListItem
|
||||
import suwayomi.tachidesk.manga.impl.track.tracker.mangaupdates.dto.Rating
|
||||
@@ -8,8 +9,7 @@ import suwayomi.tachidesk.manga.impl.track.tracker.mangaupdates.dto.toTrackSearc
|
||||
import suwayomi.tachidesk.manga.impl.track.tracker.model.Track
|
||||
import suwayomi.tachidesk.manga.impl.track.tracker.model.TrackSearch
|
||||
|
||||
class MangaUpdates(id: Int) : Tracker(id, "MangaUpdates") {
|
||||
// , DeletableTracker
|
||||
class MangaUpdates(id: Int) : Tracker(id, "MangaUpdates"), DeletableTrackService {
|
||||
companion object {
|
||||
const val READING_LIST = 0
|
||||
const val WISH_LIST = 1
|
||||
@@ -31,6 +31,8 @@ class MangaUpdates(id: Int) : Tracker(id, "MangaUpdates") {
|
||||
}
|
||||
}
|
||||
|
||||
override val supportsTrackDeletion: Boolean = true
|
||||
|
||||
private val interceptor by lazy { MangaUpdatesInterceptor(this) }
|
||||
|
||||
private val api by lazy { MangaUpdatesApi(interceptor, client) }
|
||||
@@ -74,9 +76,9 @@ class MangaUpdates(id: Int) : Tracker(id, "MangaUpdates") {
|
||||
return track
|
||||
}
|
||||
|
||||
// override suspend fun delete(track: Track) {
|
||||
// api.deleteSeriesFromList(track)
|
||||
// }
|
||||
override suspend fun delete(track: Track) {
|
||||
api.deleteSeriesFromList(track)
|
||||
}
|
||||
|
||||
override suspend fun bind(
|
||||
track: Track,
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package suwayomi.tachidesk.manga.impl.track.tracker.model
|
||||
|
||||
import org.jetbrains.exposed.sql.ResultRow
|
||||
import suwayomi.tachidesk.manga.impl.backup.proto.models.BackupTracking
|
||||
import suwayomi.tachidesk.manga.model.dataclass.TrackRecordDataClass
|
||||
import suwayomi.tachidesk.manga.model.table.TrackRecordTable
|
||||
import suwayomi.tachidesk.manga.model.table.TrackRecordTable.lastChapterRead
|
||||
import suwayomi.tachidesk.manga.model.table.TrackRecordTable.remoteUrl
|
||||
|
||||
fun ResultRow.toTrackRecordDataClass(): TrackRecordDataClass =
|
||||
TrackRecordDataClass(
|
||||
@@ -36,3 +39,52 @@ fun ResultRow.toTrack(): Track =
|
||||
it.started_reading_date = this[TrackRecordTable.startDate]
|
||||
it.finished_reading_date = this[TrackRecordTable.finishDate]
|
||||
}
|
||||
|
||||
fun BackupTracking.toTrack(mangaId: Int): Track =
|
||||
Track.create(syncId).also {
|
||||
it.id = -1
|
||||
it.manga_id = mangaId
|
||||
it.media_id = mediaId
|
||||
it.library_id = libraryId
|
||||
it.title = title
|
||||
it.last_chapter_read = lastChapterRead
|
||||
it.total_chapters = totalChapters
|
||||
it.status = status
|
||||
it.score = score
|
||||
it.tracking_url = trackingUrl
|
||||
it.started_reading_date = startedReadingDate
|
||||
it.finished_reading_date = finishedReadingDate
|
||||
}
|
||||
|
||||
fun TrackRecordDataClass.toTrack(): Track =
|
||||
Track.create(trackerId).also {
|
||||
it.id = id
|
||||
it.manga_id = mangaId
|
||||
it.media_id = remoteId
|
||||
it.library_id = libraryId
|
||||
it.title = title
|
||||
it.last_chapter_read = lastChapterRead.toFloat()
|
||||
it.total_chapters = totalChapters
|
||||
it.status = status
|
||||
it.score = score.toFloat()
|
||||
it.tracking_url = remoteUrl
|
||||
it.started_reading_date = startDate
|
||||
it.finished_reading_date = finishDate
|
||||
}
|
||||
|
||||
fun Track.toTrackRecordDataClass(): TrackRecordDataClass =
|
||||
TrackRecordDataClass(
|
||||
id = id ?: -1,
|
||||
mangaId = manga_id,
|
||||
trackerId = sync_id,
|
||||
remoteId = media_id,
|
||||
libraryId = library_id,
|
||||
title = title,
|
||||
lastChapterRead = last_chapter_read.toDouble(),
|
||||
totalChapters = total_chapters,
|
||||
status = status,
|
||||
score = score.toDouble(),
|
||||
remoteUrl = tracking_url,
|
||||
startDate = started_reading_date,
|
||||
finishDate = finished_reading_date,
|
||||
)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package suwayomi.tachidesk.manga.impl.track.tracker.myanimelist
|
||||
|
||||
import android.annotation.StringRes
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import mu.KotlinLogging
|
||||
@@ -26,9 +25,11 @@ class MyAnimeList(id: Int) : Tracker(id, "MyAnimeList"), DeletableTrackService {
|
||||
private const val SEARCH_LIST_PREFIX = "my:"
|
||||
}
|
||||
|
||||
override val supportsTrackDeletion: Boolean = true
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
private val interceptor by lazy { MyAnimeListInterceptor(this, getPassword()) }
|
||||
private val interceptor by lazy { MyAnimeListInterceptor(this) }
|
||||
private val api by lazy { MyAnimeListApi(client, interceptor) }
|
||||
|
||||
override val supportsReadingDates: Boolean = true
|
||||
@@ -94,8 +95,8 @@ class MyAnimeList(id: Int) : Tracker(id, "MyAnimeList"), DeletableTrackService {
|
||||
return api.updateItem(track)
|
||||
}
|
||||
|
||||
override suspend fun delete(track: Track): Track {
|
||||
return api.deleteItem(track)
|
||||
override suspend fun delete(track: Track) {
|
||||
api.deleteItem(track)
|
||||
}
|
||||
|
||||
override suspend fun bind(
|
||||
|
||||
@@ -164,18 +164,15 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun deleteItem(track: Track): Track {
|
||||
suspend fun deleteItem(track: Track) {
|
||||
return withIOContext {
|
||||
val request =
|
||||
Request.Builder()
|
||||
.url(mangaUrl(track.media_id).toString())
|
||||
.delete()
|
||||
.build()
|
||||
with(json) {
|
||||
authClient.newCall(request)
|
||||
.awaitSuccess()
|
||||
track
|
||||
}
|
||||
authClient.newCall(request)
|
||||
.awaitSuccess()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,63 +1,42 @@
|
||||
package suwayomi.tachidesk.manga.impl.track.tracker.myanimelist
|
||||
|
||||
import eu.kanade.tachiyomi.AppInfo
|
||||
import eu.kanade.tachiyomi.network.parseAs
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
import suwayomi.tachidesk.manga.impl.track.tracker.TokenExpired
|
||||
import suwayomi.tachidesk.manga.impl.track.tracker.TokenRefreshFailed
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.IOException
|
||||
|
||||
class MyAnimeListInterceptor(private val myanimelist: MyAnimeList, private var token: String?) : Interceptor {
|
||||
class MyAnimeListInterceptor(private val myanimelist: MyAnimeList) : Interceptor {
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
private var oauth: OAuth? = null
|
||||
private var oauth: OAuth? = myanimelist.loadOAuth()
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
if (myanimelist.getIfAuthExpired()) {
|
||||
throw TokenExpired()
|
||||
}
|
||||
val originalRequest = chain.request()
|
||||
|
||||
if (token.isNullOrEmpty()) {
|
||||
throw IOException("Not authenticated with MyAnimeList")
|
||||
}
|
||||
if (oauth == null) {
|
||||
oauth = myanimelist.loadOAuth()
|
||||
}
|
||||
// Refresh access token if expired
|
||||
if (oauth != null && oauth!!.isExpired()) {
|
||||
setAuth(refreshToken(chain))
|
||||
if (oauth?.isExpired() == true) {
|
||||
refreshToken(chain)
|
||||
}
|
||||
|
||||
if (oauth == null) {
|
||||
throw IOException("No authentication token")
|
||||
throw IOException("MAL: User is not authenticated")
|
||||
}
|
||||
|
||||
// Add the authorization header to the original request
|
||||
val authRequest =
|
||||
originalRequest.newBuilder()
|
||||
.addHeader("Authorization", "Bearer ${oauth!!.access_token}")
|
||||
.header("User-Agent", "Suwayomi v${AppInfo.getVersionName()}")
|
||||
.build()
|
||||
|
||||
val response = chain.proceed(authRequest)
|
||||
val tokenIsExpired =
|
||||
response.headers["www-authenticate"]
|
||||
?.contains("The access token expired") ?: false
|
||||
|
||||
// Retry the request once with a new token in case it was not already refreshed
|
||||
// by the is expired check before.
|
||||
if (response.code == 401 && tokenIsExpired) {
|
||||
response.close()
|
||||
|
||||
val newToken = refreshToken(chain)
|
||||
setAuth(newToken)
|
||||
|
||||
val newRequest =
|
||||
originalRequest.newBuilder()
|
||||
.addHeader("Authorization", "Bearer ${newToken.access_token}")
|
||||
.build()
|
||||
|
||||
return chain.proceed(newRequest)
|
||||
}
|
||||
|
||||
return response
|
||||
return chain.proceed(authRequest)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,28 +44,37 @@ class MyAnimeListInterceptor(private val myanimelist: MyAnimeList, private var t
|
||||
* and the oauth object.
|
||||
*/
|
||||
fun setAuth(oauth: OAuth?) {
|
||||
token = oauth?.access_token
|
||||
this.oauth = oauth
|
||||
myanimelist.saveOAuth(oauth)
|
||||
}
|
||||
|
||||
private fun refreshToken(chain: Interceptor.Chain): OAuth {
|
||||
val newOauth =
|
||||
runCatching {
|
||||
val oauthResponse = chain.proceed(MyAnimeListApi.refreshTokenRequest(oauth!!))
|
||||
private fun refreshToken(chain: Interceptor.Chain): OAuth =
|
||||
synchronized(this) {
|
||||
if (myanimelist.getIfAuthExpired()) throw TokenExpired()
|
||||
oauth?.takeUnless { it.isExpired() }?.let { return@synchronized it }
|
||||
|
||||
if (oauthResponse.isSuccessful) {
|
||||
with(json) { oauthResponse.parseAs<OAuth>() }
|
||||
val response =
|
||||
try {
|
||||
chain.proceed(MyAnimeListApi.refreshTokenRequest(oauth!!))
|
||||
} catch (_: Throwable) {
|
||||
throw TokenRefreshFailed()
|
||||
}
|
||||
|
||||
if (response.code == 401) {
|
||||
myanimelist.setAuthExpired()
|
||||
throw TokenExpired()
|
||||
}
|
||||
|
||||
return runCatching {
|
||||
if (response.isSuccessful) {
|
||||
with(json) { response.parseAs<OAuth>() }
|
||||
} else {
|
||||
oauthResponse.close()
|
||||
response.close()
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
if (newOauth.getOrNull() == null) {
|
||||
throw IOException("Failed to refresh the access token")
|
||||
.getOrNull()
|
||||
?.also(::setAuth)
|
||||
?: throw TokenRefreshFailed()
|
||||
}
|
||||
|
||||
return newOauth.getOrNull()!!
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package suwayomi.tachidesk.manga.impl.update
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import suwayomi.tachidesk.manga.model.dataclass.CategoryDataClass
|
||||
|
||||
@@ -12,7 +13,9 @@ interface IUpdater {
|
||||
forceAll: Boolean,
|
||||
)
|
||||
|
||||
val status: StateFlow<UpdateStatus>
|
||||
val status: Flow<UpdateStatus>
|
||||
|
||||
val statusDeprecated: StateFlow<UpdateStatus>
|
||||
|
||||
fun reset()
|
||||
}
|
||||
|
||||
@@ -6,9 +6,12 @@ import eu.kanade.tachiyomi.source.model.UpdateStrategy
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancelChildren
|
||||
import kotlinx.coroutines.channels.BufferOverflow
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
@@ -16,6 +19,8 @@ import kotlinx.coroutines.flow.consumeAsFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onCompletion
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.flow.sample
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Semaphore
|
||||
@@ -37,14 +42,33 @@ import java.util.Date
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.time.Duration.Companion.hours
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@OptIn(FlowPreview::class)
|
||||
class Updater : IUpdater {
|
||||
private val logger = KotlinLogging.logger {}
|
||||
private val notifyFlowScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
|
||||
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
|
||||
|
||||
private val _status = MutableStateFlow(UpdateStatus())
|
||||
override val status = _status.asStateFlow()
|
||||
private val notifyFlow = MutableSharedFlow<Unit>(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
|
||||
|
||||
private val statusFlow = MutableSharedFlow<UpdateStatus>()
|
||||
override val status = statusFlow.onStart { emit(getStatus()) }
|
||||
|
||||
init {
|
||||
// has to be in its own scope (notifyFlowScope), otherwise, the collection gets canceled due to canceling the scopes (scope) children in the reset function
|
||||
notifyFlowScope.launch {
|
||||
notifyFlow.sample(1.seconds).collect {
|
||||
updateStatus(immediate = true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val _status = MutableStateFlow(UpdateStatus())
|
||||
override val statusDeprecated = _status.asStateFlow()
|
||||
|
||||
private var updateStatusCategories: Map<CategoryUpdateStatus, List<CategoryDataClass>> = emptyMap()
|
||||
private var updateStatusSkippedMangas: List<MangaDataClass> = emptyList()
|
||||
private val tracker = ConcurrentHashMap<Int, UpdateJob>()
|
||||
private val updateChannels = ConcurrentHashMap<String, Channel<UpdateJob>>()
|
||||
|
||||
@@ -87,7 +111,7 @@ class Updater : IUpdater {
|
||||
val lastAutomatedUpdate = preferences.getLong(lastAutomatedUpdateKey, 0)
|
||||
preferences.edit().putLong(lastAutomatedUpdateKey, System.currentTimeMillis()).apply()
|
||||
|
||||
if (status.value.running) {
|
||||
if (getStatus().running) {
|
||||
logger.debug { "Global update is already in progress" }
|
||||
return
|
||||
}
|
||||
@@ -123,23 +147,33 @@ class Updater : IUpdater {
|
||||
HAScheduler.schedule(::autoUpdateTask, updateInterval, timeToNextExecution, "global-update")
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the status and sustains the "skippedMangas"
|
||||
*/
|
||||
private fun updateStatus(
|
||||
jobs: List<UpdateJob>,
|
||||
running: Boolean? = null,
|
||||
categories: Map<CategoryUpdateStatus, List<CategoryDataClass>>? = null,
|
||||
skippedMangas: List<MangaDataClass>? = null,
|
||||
) {
|
||||
private fun getStatus(running: Boolean? = null): UpdateStatus {
|
||||
val jobs = tracker.values.toList()
|
||||
val isRunning =
|
||||
running
|
||||
?: jobs.any { job ->
|
||||
job.status == JobStatus.PENDING || job.status == JobStatus.RUNNING
|
||||
}
|
||||
val updateStatusCategories = categories ?: _status.value.categoryStatusMap
|
||||
val tmpSkippedMangas = skippedMangas ?: _status.value.mangaStatusMap[JobStatus.SKIPPED] ?: emptyList()
|
||||
_status.update { UpdateStatus(updateStatusCategories, jobs, tmpSkippedMangas, isRunning) }
|
||||
return UpdateStatus(this.updateStatusCategories, jobs, this.updateStatusSkippedMangas, isRunning)
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass "isRunning" to force a specific running state
|
||||
*/
|
||||
private suspend fun updateStatus(
|
||||
immediate: Boolean = false,
|
||||
isRunning: Boolean? = null,
|
||||
) {
|
||||
if (immediate) {
|
||||
val status = getStatus(running = isRunning)
|
||||
|
||||
statusFlow.emit(status)
|
||||
_status.update { status }
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
notifyFlow.emit(Unit)
|
||||
}
|
||||
|
||||
private fun getOrCreateUpdateChannelFor(source: String): Channel<UpdateJob> {
|
||||
@@ -166,7 +200,7 @@ class Updater : IUpdater {
|
||||
return channel
|
||||
}
|
||||
|
||||
private fun handleChannelUpdateFailure(source: String) {
|
||||
private suspend fun handleChannelUpdateFailure(source: String) {
|
||||
val isFailedSourceUpdate = { job: UpdateJob ->
|
||||
val isForSource = job.manga.sourceId == source
|
||||
val hasFailed = job.status == JobStatus.FAILED
|
||||
@@ -181,17 +215,12 @@ class Updater : IUpdater {
|
||||
tracker[mangaId] = job.copy(status = JobStatus.FAILED)
|
||||
}
|
||||
|
||||
updateStatus(
|
||||
tracker.values.toList(),
|
||||
tracker.any { (_, job) ->
|
||||
job.status == JobStatus.PENDING || job.status == JobStatus.RUNNING
|
||||
},
|
||||
)
|
||||
updateStatus()
|
||||
}
|
||||
|
||||
private suspend fun process(job: UpdateJob) {
|
||||
tracker[job.manga.id] = job.copy(status = JobStatus.RUNNING)
|
||||
updateStatus(tracker.values.toList(), true)
|
||||
updateStatus()
|
||||
|
||||
tracker[job.manga.id] =
|
||||
try {
|
||||
@@ -207,7 +236,15 @@ class Updater : IUpdater {
|
||||
job.copy(status = JobStatus.FAILED)
|
||||
}
|
||||
|
||||
updateStatus(tracker.values.toList())
|
||||
val wasLastJob = tracker.values.none { it.status == JobStatus.PENDING || it.status == JobStatus.RUNNING }
|
||||
|
||||
// in case this is the last update job, the running flag has to be true, before it gets set to false, to be able
|
||||
// to properly clear the dataloader store in UpdateType
|
||||
updateStatus(immediate = wasLastJob, isRunning = true)
|
||||
|
||||
if (wasLastJob) {
|
||||
updateStatus(isRunning = false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun addCategoriesToUpdateQueue(
|
||||
@@ -274,10 +311,15 @@ class Updater : IUpdater {
|
||||
.toList()
|
||||
val skippedMangas = categoriesToUpdateMangas.subtract(mangasToUpdate.toSet()).toList()
|
||||
|
||||
// In case no manga gets updated and no update job was running before, the client would never receive an info about its update request
|
||||
updateStatus(emptyList(), mangasToUpdate.isNotEmpty(), updateStatusCategories, skippedMangas)
|
||||
this.updateStatusCategories = updateStatusCategories
|
||||
this.updateStatusSkippedMangas = skippedMangas
|
||||
|
||||
if (mangasToUpdate.isEmpty()) {
|
||||
// In case no manga gets updated and no update job was running before, the client would never receive an info
|
||||
// about its update request
|
||||
scope.launch {
|
||||
updateStatus(immediate = true)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -288,8 +330,9 @@ class Updater : IUpdater {
|
||||
}
|
||||
|
||||
private fun addMangasToQueue(mangasToUpdate: List<MangaDataClass>) {
|
||||
// create all manga update jobs before adding them to the queue so that the client is able to calculate the
|
||||
// progress properly right form the start
|
||||
mangasToUpdate.forEach { tracker[it.id] = UpdateJob(it) }
|
||||
updateStatus(tracker.values.toList(), mangasToUpdate.isNotEmpty())
|
||||
mangasToUpdate.forEach { addMangaToQueue(it) }
|
||||
}
|
||||
|
||||
@@ -303,7 +346,11 @@ class Updater : IUpdater {
|
||||
override fun reset() {
|
||||
scope.coroutineContext.cancelChildren()
|
||||
tracker.clear()
|
||||
updateStatus(emptyList(), false)
|
||||
this.updateStatusCategories = emptyMap()
|
||||
this.updateStatusSkippedMangas = emptyList()
|
||||
scope.launch {
|
||||
updateStatus(immediate = true, isRunning = false)
|
||||
}
|
||||
updateChannels.forEach { (_, channel) -> channel.cancel() }
|
||||
updateChannels.clear()
|
||||
}
|
||||
|
||||
@@ -23,12 +23,12 @@ object UpdaterSocket : Websocket<UpdateStatus>() {
|
||||
ctx: WsContext,
|
||||
value: UpdateStatus?,
|
||||
) {
|
||||
ctx.send(value ?: updater.status.value)
|
||||
ctx.send(value ?: updater.statusDeprecated.value)
|
||||
}
|
||||
|
||||
override fun handleRequest(ctx: WsMessageContext) {
|
||||
when (ctx.message()) {
|
||||
"STATUS" -> notifyClient(ctx, updater.status.value)
|
||||
"STATUS" -> notifyClient(ctx, updater.statusDeprecated.value)
|
||||
else ->
|
||||
ctx.send(
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package suwayomi.tachidesk.manga.impl.util
|
||||
|
||||
import eu.kanade.tachiyomi.source.local.metadata.COMIC_INFO_FILE
|
||||
import eu.kanade.tachiyomi.source.local.metadata.ComicInfo
|
||||
import eu.kanade.tachiyomi.source.local.metadata.ComicInfoPublishingStatus
|
||||
import nl.adaptivity.xmlutil.serialization.XML
|
||||
import org.jetbrains.exposed.sql.ResultRow
|
||||
import org.jetbrains.exposed.sql.SortOrder
|
||||
import org.jetbrains.exposed.sql.select
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import suwayomi.tachidesk.manga.model.table.CategoryMangaTable
|
||||
import suwayomi.tachidesk.manga.model.table.CategoryTable
|
||||
import suwayomi.tachidesk.manga.model.table.ChapterTable
|
||||
import suwayomi.tachidesk.manga.model.table.MangaTable
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.nio.file.Path
|
||||
import kotlin.io.path.deleteIfExists
|
||||
import kotlin.io.path.div
|
||||
import kotlin.io.path.outputStream
|
||||
|
||||
/**
|
||||
* Creates a ComicInfo instance based on the manga and chapter metadata.
|
||||
*/
|
||||
fun getComicInfo(
|
||||
manga: ResultRow,
|
||||
chapter: ResultRow,
|
||||
chapterUrl: String,
|
||||
categories: List<String>?,
|
||||
) = ComicInfo(
|
||||
title = ComicInfo.Title(chapter[ChapterTable.name]),
|
||||
series = ComicInfo.Series(manga[MangaTable.title]),
|
||||
number =
|
||||
chapter[ChapterTable.chapter_number].takeIf { it >= 0 }?.let {
|
||||
if ((it.rem(1) == 0.0f)) {
|
||||
ComicInfo.Number(it.toInt().toString())
|
||||
} else {
|
||||
ComicInfo.Number(it.toString())
|
||||
}
|
||||
},
|
||||
web = ComicInfo.Web(chapterUrl),
|
||||
summary = manga[MangaTable.description]?.let { ComicInfo.Summary(it) },
|
||||
writer = manga[MangaTable.author]?.let { ComicInfo.Writer(it) },
|
||||
penciller = manga[MangaTable.artist]?.let { ComicInfo.Penciller(it) },
|
||||
translator = chapter[ChapterTable.scanlator]?.let { ComicInfo.Translator(it) },
|
||||
genre = manga[MangaTable.genre]?.let { ComicInfo.Genre(it) },
|
||||
publishingStatus =
|
||||
ComicInfo.PublishingStatusTachiyomi(
|
||||
ComicInfoPublishingStatus.toComicInfoValue(manga[MangaTable.status].toLong()),
|
||||
),
|
||||
categories = categories?.let { ComicInfo.CategoriesTachiyomi(it.joinToString()) },
|
||||
inker = null,
|
||||
colorist = null,
|
||||
letterer = null,
|
||||
coverArtist = null,
|
||||
tags = null,
|
||||
)
|
||||
|
||||
/**
|
||||
* Creates a ComicInfo.xml file inside the given directory.
|
||||
*/
|
||||
fun createComicInfoFile(
|
||||
dir: Path,
|
||||
manga: ResultRow,
|
||||
chapter: ResultRow,
|
||||
) {
|
||||
val chapterUrl = chapter[ChapterTable.realUrl].orEmpty()
|
||||
val categories =
|
||||
transaction {
|
||||
CategoryMangaTable.innerJoin(CategoryTable).select {
|
||||
CategoryMangaTable.manga eq manga[MangaTable.id]
|
||||
}.orderBy(CategoryTable.order to SortOrder.ASC).map {
|
||||
it[CategoryTable.name]
|
||||
}
|
||||
}.takeUnless { it.isEmpty() }
|
||||
val comicInfo = getComicInfo(manga, chapter, chapterUrl, categories)
|
||||
// Remove the old file
|
||||
(dir / COMIC_INFO_FILE).deleteIfExists()
|
||||
(dir / COMIC_INFO_FILE).outputStream().use {
|
||||
val comicInfoString = Injekt.get<XML>().encodeToString(ComicInfo.serializer(), comicInfo)
|
||||
it.write(comicInfoString.toByteArray())
|
||||
}
|
||||
}
|
||||
@@ -106,13 +106,15 @@ object JavalinSetup {
|
||||
handler.handle(ctx)
|
||||
}
|
||||
}
|
||||
}.events { event ->
|
||||
event.serverStarted {
|
||||
if (serverConfig.initialOpenInBrowserEnabled.value) {
|
||||
Browser.openInBrowser()
|
||||
}
|
||||
}
|
||||
|
||||
app.events { event ->
|
||||
event.serverStarted {
|
||||
if (serverConfig.initialOpenInBrowserEnabled.value) {
|
||||
Browser.openInBrowser()
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
}
|
||||
|
||||
// when JVM is prompted to shutdown, stop javalin gracefully
|
||||
Runtime.getRuntime().addShutdownHook(
|
||||
@@ -150,6 +152,8 @@ object JavalinSetup {
|
||||
GraphQL.defineEndpoints()
|
||||
}
|
||||
}
|
||||
|
||||
app.start()
|
||||
}
|
||||
|
||||
private fun getOpenApiOptions(): OpenApiOptions {
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
package suwayomi.tachidesk.server
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import mu.KotlinLogging
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.io.File
|
||||
import java.util.prefs.Preferences
|
||||
|
||||
private fun migratePreferences(
|
||||
parent: String?,
|
||||
rootNode: Preferences,
|
||||
) {
|
||||
val subNodes = rootNode.childrenNames()
|
||||
|
||||
for (subNodeName in subNodes) {
|
||||
val subNode = rootNode.node(subNodeName)
|
||||
val key =
|
||||
if (parent != null) {
|
||||
"$parent/$subNodeName"
|
||||
} else {
|
||||
subNodeName
|
||||
}
|
||||
val preferences = Injekt.get<Application>().getSharedPreferences(key, Context.MODE_PRIVATE)
|
||||
|
||||
val items: Map<String, String?> =
|
||||
subNode.keys().associateWith {
|
||||
subNode[it, null]?.ifBlank { null }
|
||||
}
|
||||
|
||||
preferences.edit().apply {
|
||||
items.forEach { (key, value) ->
|
||||
if (value != null) {
|
||||
putString(key, value)
|
||||
}
|
||||
}
|
||||
}.apply()
|
||||
|
||||
migratePreferences(key, subNode) // Recursively migrate sub-level nodes
|
||||
}
|
||||
}
|
||||
|
||||
const val MIGRATION_VERSION = 1
|
||||
|
||||
fun runMigrations(applicationDirs: ApplicationDirs) {
|
||||
val migrationPreferences =
|
||||
Injekt.get<Application>()
|
||||
.getSharedPreferences(
|
||||
"migrations",
|
||||
Context.MODE_PRIVATE,
|
||||
)
|
||||
val version = migrationPreferences.getInt("version", 0)
|
||||
val logger = KotlinLogging.logger("Migration")
|
||||
logger.info { "Running migrations, previous version $version, target version $MIGRATION_VERSION" }
|
||||
|
||||
if (version < 1) {
|
||||
logger.info { "Running migration for version: 1" }
|
||||
val oldMangaDownloadDir = File(applicationDirs.downloadsRoot)
|
||||
val newMangaDownloadDir = File(applicationDirs.mangaDownloadsRoot)
|
||||
val downloadDirs = oldMangaDownloadDir.listFiles().orEmpty()
|
||||
|
||||
val moveDownloadsToNewFolder = !newMangaDownloadDir.exists() && downloadDirs.isNotEmpty()
|
||||
if (moveDownloadsToNewFolder) {
|
||||
newMangaDownloadDir.mkdirs()
|
||||
|
||||
for (downloadDir in downloadDirs) {
|
||||
if (downloadDir == File(applicationDirs.thumbnailDownloadsRoot)) {
|
||||
continue
|
||||
}
|
||||
|
||||
downloadDir.renameTo(File(newMangaDownloadDir, downloadDir.name))
|
||||
}
|
||||
}
|
||||
|
||||
// Migrate from old preferences api
|
||||
val prefRootNode = "suwayomi/tachidesk"
|
||||
val isMigrationRequired = Preferences.userRoot().nodeExists(prefRootNode)
|
||||
if (isMigrationRequired) {
|
||||
val preferences = Preferences.userRoot().node(prefRootNode)
|
||||
migratePreferences(null, preferences)
|
||||
preferences.removeNode()
|
||||
}
|
||||
}
|
||||
migrationPreferences.edit().putInt("version", MIGRATION_VERSION).apply()
|
||||
}
|
||||
@@ -101,6 +101,7 @@ class ServerConfig(getConfig: () -> Config, val moduleName: String = SERVER_CONF
|
||||
val autoDownloadNewChapters: MutableStateFlow<Boolean> by OverrideConfigValue(BooleanConfigAdapter)
|
||||
val excludeEntryWithUnreadChapters: MutableStateFlow<Boolean> by OverrideConfigValue(BooleanConfigAdapter)
|
||||
val autoDownloadNewChaptersLimit: MutableStateFlow<Int> by OverrideConfigValue(IntConfigAdapter)
|
||||
val autoDownloadIgnoreReUploads: MutableStateFlow<Boolean> by OverrideConfigValue(BooleanConfigAdapter)
|
||||
|
||||
// extensions
|
||||
val extensionRepos: MutableStateFlow<List<String>> by OverrideConfigValues(StringConfigAdapter)
|
||||
|
||||
@@ -7,8 +7,6 @@ package suwayomi.tachidesk.server
|
||||
* 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 android.app.Application
|
||||
import android.content.Context
|
||||
import ch.qos.logback.classic.Level
|
||||
import com.typesafe.config.ConfigRenderOptions
|
||||
import eu.kanade.tachiyomi.App
|
||||
@@ -19,7 +17,11 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.protobuf.ProtoBuf
|
||||
import mu.KotlinLogging
|
||||
import nl.adaptivity.xmlutil.XmlDeclMode
|
||||
import nl.adaptivity.xmlutil.core.XmlVersion
|
||||
import nl.adaptivity.xmlutil.serialization.XML
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||
import org.kodein.di.DI
|
||||
import org.kodein.di.bind
|
||||
@@ -35,8 +37,6 @@ import suwayomi.tachidesk.server.database.databaseUp
|
||||
import suwayomi.tachidesk.server.generated.BuildConfig
|
||||
import suwayomi.tachidesk.server.util.AppMutex.handleAppMutex
|
||||
import suwayomi.tachidesk.server.util.SystemTray
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import xyz.nulldev.androidcompat.AndroidCompat
|
||||
import xyz.nulldev.androidcompat.AndroidCompatInitializer
|
||||
import xyz.nulldev.ts.config.ApplicationRootDir
|
||||
@@ -48,7 +48,6 @@ import xyz.nulldev.ts.config.setLogLevelFor
|
||||
import java.io.File
|
||||
import java.security.Security
|
||||
import java.util.Locale
|
||||
import java.util.prefs.Preferences
|
||||
|
||||
private val logger = KotlinLogging.logger {}
|
||||
|
||||
@@ -116,7 +115,29 @@ fun applicationSetup() {
|
||||
bind<ApplicationDirs>() with singleton { applicationDirs }
|
||||
bind<IUpdater>() with singleton { Updater() }
|
||||
bind<JsonMapper>() with singleton { JavalinJackson() }
|
||||
bind<Json>() with singleton { Json { ignoreUnknownKeys = true } }
|
||||
bind<Json>() with
|
||||
singleton {
|
||||
Json {
|
||||
ignoreUnknownKeys = true
|
||||
explicitNulls = false
|
||||
}
|
||||
}
|
||||
bind<XML>() with
|
||||
singleton {
|
||||
XML {
|
||||
defaultPolicy {
|
||||
ignoreUnknownChildren()
|
||||
}
|
||||
autoPolymorphic = true
|
||||
xmlDeclMode = XmlDeclMode.Charset
|
||||
indent = 2
|
||||
xmlVersion = XmlVersion.XML10
|
||||
}
|
||||
}
|
||||
bind<ProtoBuf>() with
|
||||
singleton {
|
||||
ProtoBuf
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -127,23 +148,6 @@ fun applicationSetup() {
|
||||
File("$ApplicationRootDir/manga-local").renameTo(applicationDirs.localMangaRoot)
|
||||
File("$ApplicationRootDir/anime-thumbnails").delete()
|
||||
|
||||
val oldMangaDownloadDir = File(applicationDirs.downloadsRoot)
|
||||
val newMangaDownloadDir = File(applicationDirs.mangaDownloadsRoot)
|
||||
val downloadDirs = oldMangaDownloadDir.listFiles().orEmpty()
|
||||
|
||||
val moveDownloadsToNewFolder = !newMangaDownloadDir.exists() && downloadDirs.isNotEmpty()
|
||||
if (moveDownloadsToNewFolder) {
|
||||
newMangaDownloadDir.mkdirs()
|
||||
|
||||
for (downloadDir in downloadDirs) {
|
||||
if (downloadDir == File(applicationDirs.thumbnailDownloadsRoot)) {
|
||||
continue
|
||||
}
|
||||
|
||||
downloadDir.renameTo(File(newMangaDownloadDir, downloadDir.name))
|
||||
}
|
||||
}
|
||||
|
||||
// make dirs we need
|
||||
listOf(
|
||||
applicationDirs.dataRoot,
|
||||
@@ -214,17 +218,11 @@ fun applicationSetup() {
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
// cover both java.lang.Exception and java.lang.Error
|
||||
e.printStackTrace()
|
||||
logger.error(e) { "Failed to create/remove SystemTray due to" }
|
||||
}
|
||||
}, ignoreInitialValue = false)
|
||||
|
||||
val prefRootNode = "suwayomi/tachidesk"
|
||||
val isMigrationRequired = Preferences.userRoot().nodeExists(prefRootNode)
|
||||
if (isMigrationRequired) {
|
||||
val preferences = Preferences.userRoot().node(prefRootNode)
|
||||
migratePreferences(null, preferences)
|
||||
preferences.removeNode()
|
||||
}
|
||||
runMigrations(applicationDirs)
|
||||
|
||||
// Disable jetty's logging
|
||||
System.setProperty("org.eclipse.jetty.util.log.announce", "false")
|
||||
@@ -299,36 +297,3 @@ fun applicationSetup() {
|
||||
// start DownloadManager and restore + resume downloads
|
||||
DownloadManager.restoreAndResumeDownloads()
|
||||
}
|
||||
|
||||
fun migratePreferences(
|
||||
parent: String?,
|
||||
rootNode: Preferences,
|
||||
) {
|
||||
val subNodes = rootNode.childrenNames()
|
||||
|
||||
for (subNodeName in subNodes) {
|
||||
val subNode = rootNode.node(subNodeName)
|
||||
val key =
|
||||
if (parent != null) {
|
||||
"$parent/$subNodeName"
|
||||
} else {
|
||||
subNodeName
|
||||
}
|
||||
val preferences = Injekt.get<Application>().getSharedPreferences(key, Context.MODE_PRIVATE)
|
||||
|
||||
val items: Map<String, String?> =
|
||||
subNode.keys().associateWith {
|
||||
subNode[it, null]?.ifBlank { null }
|
||||
}
|
||||
|
||||
preferences.edit().apply {
|
||||
items.forEach { (key, value) ->
|
||||
if (value != null) {
|
||||
putString(key, value)
|
||||
}
|
||||
}
|
||||
}.apply()
|
||||
|
||||
migratePreferences(key, subNode) // Recursively migrate sub-level nodes
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package suwayomi.tachidesk.server.database.migration
|
||||
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* 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 de.neonew.exposed.migrations.helpers.SQLMigration
|
||||
import suwayomi.tachidesk.manga.impl.track.tracker.TrackerManager
|
||||
|
||||
@Suppress("ClassName", "unused")
|
||||
class M0037_RemoveTrackRecordsOfUnsupportedTrackers : SQLMigration() {
|
||||
override val sql: String =
|
||||
"""
|
||||
DELETE FROM TRACKRECORD WHERE SYNC_ID NOT IN (${TrackerManager.MYANIMELIST}, ${TrackerManager.ANILIST}, ${TrackerManager.MANGA_UPDATES})
|
||||
""".trimIndent()
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package suwayomi.tachidesk.server.database.migration
|
||||
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* 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 de.neonew.exposed.migrations.helpers.SQLMigration
|
||||
import suwayomi.tachidesk.manga.impl.track.tracker.TrackerManager
|
||||
|
||||
@Suppress("ClassName", "unused")
|
||||
class M0038_RemoveTrackRecordsOfUnsupportedTrackersII : SQLMigration() {
|
||||
override val sql: String =
|
||||
"""
|
||||
DELETE FROM TRACKRECORD WHERE SYNC_ID NOT IN (${TrackerManager.MYANIMELIST}, ${TrackerManager.ANILIST}, ${TrackerManager.MANGA_UPDATES})
|
||||
""".trimIndent()
|
||||
}
|
||||
@@ -8,9 +8,11 @@ package suwayomi.tachidesk.server.util
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import dorkbox.desktop.Desktop
|
||||
import mu.KotlinLogging
|
||||
import suwayomi.tachidesk.server.serverConfig
|
||||
|
||||
object Browser {
|
||||
private val logger = KotlinLogging.logger { }
|
||||
private val electronInstances = mutableListOf<Any>()
|
||||
|
||||
private fun getAppBaseUrl(): String {
|
||||
@@ -28,14 +30,14 @@ object Browser {
|
||||
electronInstances.add(ProcessBuilder(electronPath, appBaseUrl).start())
|
||||
} catch (e: Throwable) {
|
||||
// cover both java.lang.Exception and java.lang.Error
|
||||
e.printStackTrace()
|
||||
logger.error(e) { "openInBrowser: failed to launch electron due to" }
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
Desktop.browseURL(appBaseUrl)
|
||||
} catch (e: Throwable) {
|
||||
// cover both java.lang.Exception and java.lang.Error
|
||||
e.printStackTrace()
|
||||
logger.error(e) { "openInBrowser: failed to launch browser due to" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ package suwayomi.tachidesk.server.util
|
||||
import dorkbox.systemTray.MenuItem
|
||||
import dorkbox.systemTray.SystemTray
|
||||
import dorkbox.util.CacheUtil
|
||||
import mu.KotlinLogging
|
||||
import suwayomi.tachidesk.server.ServerConfig
|
||||
import suwayomi.tachidesk.server.generated.BuildConfig
|
||||
import suwayomi.tachidesk.server.serverConfig
|
||||
@@ -17,6 +18,7 @@ import suwayomi.tachidesk.server.util.Browser.openInBrowser
|
||||
import suwayomi.tachidesk.server.util.ExitCode.Success
|
||||
|
||||
object SystemTray {
|
||||
private val logger = KotlinLogging.logger { }
|
||||
private var instance: SystemTray? = null
|
||||
|
||||
fun create() {
|
||||
@@ -60,7 +62,7 @@ object SystemTray {
|
||||
|
||||
systemTray
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
logger.error(e) { "create: failed to create SystemTray due to" }
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ server.downloadsPath = ""
|
||||
server.autoDownloadNewChapters = false # if new chapters that have been retrieved should get automatically downloaded
|
||||
server.excludeEntryWithUnreadChapters = true # ignore automatic chapter downloads of entries with unread chapters
|
||||
server.autoDownloadNewChaptersLimit = 0 # 0 to disable it - how many unread downloaded chapters should be available - if the limit is reached, new chapters won't be downloaded automatically. this limit will also be applied to the auto download of new chapters on an update
|
||||
server.autoDownloadIgnoreReUploads = false # decides if re-uploads should be ignored during auto download of new chapters
|
||||
|
||||
# extension repos
|
||||
server.extensionRepos = [
|
||||
|
||||
@@ -10,40 +10,60 @@ server.socksProxyPort = ""
|
||||
server.socksProxyUsername = ""
|
||||
server.socksProxyPassword = ""
|
||||
|
||||
# webUI
|
||||
server.webUIEnabled = true
|
||||
server.webUIFlavor = "WebUI" # "WebUI", "VUI" or "Custom"
|
||||
server.initialOpenInBrowserEnabled = true
|
||||
server.webUIInterface = "browser" # "browser" or "electron"
|
||||
server.electronPath = ""
|
||||
server.webUIChannel = "stable" # "bundled" (the version bundled with the server release), "stable" or "preview" - the webUI version that should be used
|
||||
server.webUIUpdateCheckInterval = 23 # time in hours - 0 to disable auto update - range: 1 <= n < 24 - default 23 hours - how often the server should check for webUI updates
|
||||
|
||||
# downloader
|
||||
server.downloadAsCbz = false
|
||||
server.autoDownloadNewChapters = false
|
||||
server.excludeEntryWithUnreadChapters = true
|
||||
server.autoDownloadNewChaptersLimit = 0
|
||||
server.downloadsPath = ""
|
||||
server.autoDownloadNewChapters = false # if new chapters that have been retrieved should get automatically downloaded
|
||||
server.excludeEntryWithUnreadChapters = true # ignore automatic chapter downloads of entries with unread chapters
|
||||
server.autoDownloadNewChaptersLimit = 0 # 0 to disable it - how many unread downloaded chapters should be available - if the limit is reached, new chapters won't be downloaded automatically. this limit will also be applied to the auto download of new chapters on an update
|
||||
server.autoDownloadIgnoreReUploads = false # decides if re-uploads should be ignored during auto download of new chapters
|
||||
|
||||
# extension repos
|
||||
server.extensionRepos = [
|
||||
# an example: https://github.com/MY_ACCOUNT/MY_REPO/tree/repo
|
||||
]
|
||||
|
||||
# requests
|
||||
server.maxSourcesInParallel = 10
|
||||
server.maxSourcesInParallel = 6 # range: 1 <= n <= 20 - default: 6 - sets how many sources can do requests (updates, downloads) in parallel. updates/downloads are grouped by source and all mangas of a source are updated/downloaded synchronously
|
||||
|
||||
# updater
|
||||
server.excludeUnreadChapters = true
|
||||
server.excludeNotStarted = true
|
||||
server.excludeCompleted = true
|
||||
server.globalUpdateInterval = 12
|
||||
server.updateMangas = false
|
||||
server.globalUpdateInterval = 12 # time in hours - 0 to disable it - (doesn't have to be full hours e.g. 12.5) - range: 6 <= n < ∞ - default: 12 hours - interval in which the global update will be automatically triggered
|
||||
server.updateMangas = false # if the mangas should be updated along with the chapter list during a library/category update
|
||||
|
||||
# Authentication
|
||||
server.basicAuthEnabled = false
|
||||
server.basicAuthUsername = ""
|
||||
server.basicAuthPassword = ""
|
||||
|
||||
# misc
|
||||
server.debugLogsEnabled = true
|
||||
server.gqlDebugLogsEnabled = false
|
||||
server.systemTrayEnabled = false
|
||||
|
||||
# webUI
|
||||
server.webUIEnabled = true
|
||||
server.initialOpenInBrowserEnabled = true
|
||||
server.webUIInterface = "browser" # "browser" or "electron"
|
||||
server.electronPath = ""
|
||||
server.webUIChannel = "stable"
|
||||
server.webUIUpdateCheckInterval = 24
|
||||
server.debugLogsEnabled = false
|
||||
server.gqlDebugLogsEnabled = false # this includes logs with non privacy safe information
|
||||
server.systemTrayEnabled = true
|
||||
|
||||
# backup
|
||||
server.backupPath = ""
|
||||
server.backupTime = "00:00"
|
||||
server.backupInterval = 1
|
||||
server.backupTTL = 14
|
||||
server.backupTime = "00:00" # range: hour: 0-23, minute: 0-59 - default: "00:00" - time of day at which the automated backup should be triggered
|
||||
server.backupInterval = 1 # time in days - 0 to disable it - range: 1 <= n < ∞ - default: 1 day - interval in which the server will automatically create a backup
|
||||
server.backupTTL = 14 # time in days - 0 to disable it - range: 1 <= n < ∞ - default: 14 days - how long backup files will be kept before they will get deleted
|
||||
|
||||
# local source
|
||||
server.localSourcePath = ""
|
||||
|
||||
# Cloudflare bypass
|
||||
server.flareSolverrEnabled = false
|
||||
server.flareSolverrUrl = "http://localhost:8191"
|
||||
server.flareSolverrTimeout = 60 # time in seconds
|
||||
server.flareSolverrSessionName = "suwayomi"
|
||||
server.flareSolverrSessionTtl = 15 # time in minutes
|
||||
|
||||
Reference in New Issue
Block a user