Compare commits

..

13 Commits

Author SHA1 Message Date
renovate[bot]
fcea37ba29 Update polyglot to v25.1.3 2026-06-30 19:19:00 +00:00
Syer10
612768faeb Release v2.3.2223 2026-06-30 15:17:18 -04:00
Syer10
9a1745b626 [skip ci] Add changelog for bind existing track 2026-06-30 14:53:58 -04:00
schroda
323d58717e Add mutation to bind existing track record (#2045)
Makes it possible to copy a bound track record to another manga.
This is necessary during a migration to prevent spamming the actual tracker and causing 429 errors

closes #2033
2026-06-29 14:59:53 -04:00
Mitchell Syer
4d7b7617a9 Fix backup corruption with new extension lib (#2146)
* Fix backup corruption with new extension lib

* Fix missing chapter memo backup
2026-06-29 14:52:37 -04:00
Mitchell Syer
35b48114c6 Use Plain Source over CatalogueSource (#2141) 2026-06-28 14:25:37 -04:00
Mitchell Syer
3031aa7ccd Make contentWarning check more robust (#2140)
* Make contentWarning check more robust

* Lint
2026-06-28 12:01:48 -04:00
Mitchell Syer
c79486b8be Manual Extension Fixes (#2139)
* Fix manual extension icons

* Delete extension where APK Url is null
2026-06-28 02:08:53 -04:00
Mitchell Syer
e2fd15158c Fix Backups (#2138) 2026-06-27 14:09:48 -04:00
Bartu Özen
b6de3c3e39 Use stable manga and chapter composite keys for sync matching (#2124) 2026-06-27 13:41:14 -04:00
Weblate (bot)
656d86c6f6 Weblate translations (#2130)
Translate-URL: https://hosted.weblate.org/projects/suwayomi/suwayomi-server/de/
Translate-URL: https://hosted.weblate.org/projects/suwayomi/suwayomi-server/el/
Translate-URL: https://hosted.weblate.org/projects/suwayomi/suwayomi-server/es/
Translate-URL: https://hosted.weblate.org/projects/suwayomi/suwayomi-server/fr/
Translate-URL: https://hosted.weblate.org/projects/suwayomi/suwayomi-server/it/
Translate-URL: https://hosted.weblate.org/projects/suwayomi/suwayomi-server/ja/
Translate-URL: https://hosted.weblate.org/projects/suwayomi/suwayomi-server/pl/
Translate-URL: https://hosted.weblate.org/projects/suwayomi/suwayomi-server/pt/
Translate-URL: https://hosted.weblate.org/projects/suwayomi/suwayomi-server/ru/
Translate-URL: https://hosted.weblate.org/projects/suwayomi/suwayomi-server/ta/
Translate-URL: https://hosted.weblate.org/projects/suwayomi/suwayomi-server/vi/
Translate-URL: https://hosted.weblate.org/projects/suwayomi/suwayomi-server/zh_Hans/
Translation: Suwayomi/Suwayomi-Server

Co-authored-by: Constantin Piber <cp.piber@gmail.com>
Co-authored-by: Damien O'Neil <maxiburning@gmail.com>
Co-authored-by: zeedif <carlos_antonio-rl@hotmail.com>
2026-06-27 13:40:44 -04:00
schroda
a0fbff5756 Update issue templates (#2137) 2026-06-27 13:39:43 -04:00
Mitchell Syer
2d535b44d8 Extension API 1.6 (#2120)
* Non-Extension Index changes for 1.6

* Changelog

* Minor fixes

* Implement extension store

* Test build fix

* Docs

* Simplify fetching manga and chapters

* Use EMPTY JsonObject

* Update docs/Configuring-Suwayomi‐Server.md

Co-authored-by: Constantin Piber <59023762+cpiber@users.noreply.github.com>

* Improve Fetch Extension Store

* Fixes

* Simplify deprecated isNsfw in SourceQuery

* Simplify ContentRating in Source.kt

* Simplify isNsfw in SourceType

* No magic numbers for ContentRating, improves safety for future versions of extension api

* Fix SearchTest

* Lint

* Lint

* Optimize imports and fix unchecked cast warning

* Proper extension store queries

* Optimize import fixes

* Add ContentRatingFilter

* Improve extension store sync

* fix: re-sync (#2121)

* Lint

* Add ExtenionStores to the fetchExtensions result since its possible for the stores to change.

* Use a single version of ContentRating

* Exclude ServerConfig.extensionStores from GraphQL

* Use syncDbToPrefs in ExtensionStoreMutation

* Optimize Imports

* Update server/server-config/src/main/kotlin/suwayomi/tachidesk/server/ServerConfig.kt

Co-authored-by: Constantin Piber <59023762+cpiber@users.noreply.github.com>

* Remove replaceWith and add specific description for GQL APIs

* Include OkHttp ZSTD

* Update to latest Mihon extension lib

* Fix latest Mihon Extension Lib

* Lint

* Optimize imports

* Lint

* Review fixes

* Add a index to extesnion table store url

* Lint

---------

Co-authored-by: Constantin Piber <59023762+cpiber@users.noreply.github.com>
2026-06-27 13:39:28 -04:00
62 changed files with 474 additions and 322 deletions

View File

@@ -42,7 +42,7 @@ body:
label: Suwayomi-Server version label: Suwayomi-Server version
description: You can find your Suwayomi-Server version in **More → About**. description: You can find your Suwayomi-Server version in **More → About**.
placeholder: | placeholder: |
Example: "v2.2.2100" Example: "v2.3.2223"
validations: validations:
required: true required: true
@@ -143,11 +143,13 @@ body:
options: options:
- label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open or closed issue. - label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open or closed issue.
required: true required: true
- label: I have checked the ongoing preview changelog of **[Suwayomi-WebUI](https://github.com/Suwayomi/Suwayomi-WebUI/blob/master/CHANGELOG.md)** and **[Suwayomi-Server](https://github.com/Suwayomi/Suwayomi-Server/blob/master/CHANGELOG.md)** and this bug has **NOT** been listed as fixed
required: true
- label: I have written a short but informative title (ideally less than ~100 characters). - label: I have written a short but informative title (ideally less than ~100 characters).
required: true required: true
- label: I have tried the troubleshooting guide described in [README.md](https://github.com/Suwayomi/Suwayomi-Server?tab=readme-ov-file#troubleshooting-and-support) - label: I have tried the troubleshooting guide described in [README.md](https://github.com/Suwayomi/Suwayomi-Server?tab=readme-ov-file#troubleshooting-and-support)
required: true required: true
- label: I have updated to the **[latest version](https://github.com/suwayomi/suwayomi-server/releases/latest)**. - label: I have updated the (**[Suwayomi-WebUI](https://github.com/suwayomi/suwayomi-webui/releases/latest)** and **[Suwayomi-Server](https://github.com/suwayomi/suwayomi-server/releases/latest)**) to the latest versions
required: true required: true
- label: I have filled out all of the requested information in this form, including specific version numbers. - label: I have filled out all of the requested information in this form, including specific version numbers.
required: true required: true

View File

@@ -31,7 +31,7 @@ body:
required: true required: true
- label: I have written a short but informative title (ideally less than ~100 characters). - label: I have written a short but informative title (ideally less than ~100 characters).
required: true required: true
- label: I have updated to the **[latest version](https://github.com/suwayomi/suwayomi-server/releases/latest)**. - label: I have updated the (**[Suwayomi-WebUI](https://github.com/suwayomi/suwayomi-webui/releases/latest)** and **[Suwayomi-Server](https://github.com/suwayomi/suwayomi-server/releases/latest)**) to the latest versions
required: true required: true
- label: I have filled out all of the requested information in this form, including specific version numbers. - label: I have filled out all of the requested information in this form, including specific version numbers.
required: true required: true

View File

@@ -6,10 +6,33 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [Unreleased] (Preview) ## [Unreleased] (Preview)
### Added
- .
### Changed
- .
### Fixed
- .
## [v2.3.2223] + [WebUI: v20260509.01] - 2026-06-30
### Major Changes
#### Added [SyncYomi](https://github.com/syncyomi/syncyomi) support
This allows you to sync your server manga with other Mihon-based forks! As long as the fork supports SyncYomi it can be sync with!
#### Support Extension API v1.6
This update allows Suwayomi to load and use v1.6 extensions, it is a minor improvement over the existing 1.4 extension API that cleans up much of what we had! It is the basis of future extension APIs that will allow for further development.
> [!WARNING]
> Please back up your extension repos, because of the new extension stores system you may lose them in the update process and may need to re-add them.
### Added ### Added
- (**Sync**) Added [SyncYomi](https://github.com/syncyomi/syncyomi) support - (**Sync**) Added [SyncYomi](https://github.com/syncyomi/syncyomi) support
- (**OPDS**) Add option to skip chapter metadata feed providing direct stream/download links - (**OPDS**) Add option to skip chapter metadata feed providing direct stream/download links
- (**Extension/API**) Support Extensions API v1.6 - (**Extension/API**) Support Extensions API v1.6
- (**Tracker/API**) Add mutation to bind existing track record
### Changed ### Changed
- (**Database/H2**) Use the latest H2 database engine - (**Database/H2**) Use the latest H2 database engine
@@ -426,6 +449,7 @@ Huge thanks to @martinek who pulled the most of the weight this release!
<!-- WEBUI LINKS --> <!-- WEBUI LINKS -->
[WebUI: v20260509.01]: https://github.com/Suwayomi/Suwayomi-WebUI/blob/master/CHANGELOG.md#2026050901-r3147---2026-05-09
[WebUI: v20260508.01]: https://github.com/Suwayomi/Suwayomi-WebUI/blob/master/CHANGELOG.md#2026050801-r3136---2026-05-08 [WebUI: v20260508.01]: https://github.com/Suwayomi/Suwayomi-WebUI/blob/master/CHANGELOG.md#2026050801-r3136---2026-05-08
[WebUI: v20251230.01]: https://github.com/Suwayomi/Suwayomi-WebUI/blob/master/CHANGELOG.md#2025123001-r2937---2025-12-30 [WebUI: v20251230.01]: https://github.com/Suwayomi/Suwayomi-WebUI/blob/master/CHANGELOG.md#2025123001-r2937---2025-12-30
[WebUI: v20250801.01]: https://github.com/Suwayomi/Suwayomi-WebUI/blob/master/CHANGELOG.md#2025080101-r2717---2025-08-01 [WebUI: v20250801.01]: https://github.com/Suwayomi/Suwayomi-WebUI/blob/master/CHANGELOG.md#2025080101-r2717---2025-08-01
@@ -452,7 +476,8 @@ Huge thanks to @martinek who pulled the most of the weight this release!
<!-- SERVER LINKS --> <!-- SERVER LINKS -->
[unreleased]: https://github.com/suwayomi/suwayomi-server/compare/v2.2.2100...HEAD [unreleased]: https://github.com/suwayomi/suwayomi-server/compare/v2.3.2223...HEAD
[v2.3.2223]: https://github.com/suwayomi/suwayomi-server/compare/v2.1.2100...v2.3.2223
[v2.2.2100]: https://github.com/suwayomi/suwayomi-server/compare/v2.1.1867...v2.2.2100 [v2.2.2100]: https://github.com/suwayomi/suwayomi-server/compare/v2.1.1867...v2.2.2100
[v2.1.1867]: https://github.com/suwayomi/suwayomi-server/compare/v2.0.1727...v2.1.1867 [v2.1.1867]: https://github.com/suwayomi/suwayomi-server/compare/v2.0.1727...v2.1.1867
[v2.0.1727]: https://github.com/suwayomi/suwayomi-server/compare/v1.1.1...v2.0.1727 [v2.0.1727]: https://github.com/suwayomi/suwayomi-server/compare/v1.1.1...v2.0.1727

View File

@@ -10,9 +10,9 @@ import java.io.BufferedReader
const val MainClass = "suwayomi.tachidesk.MainKt" const val MainClass = "suwayomi.tachidesk.MainKt"
// should be bumped with each stable release // should be bumped with each stable release
val getTachideskVersion = { "v2.2.${getCommitCount()}" } val getTachideskVersion = { "v2.3.${getCommitCount()}" }
val webUIRevisionTag = "r3136" val webUIRevisionTag = "r3147"
val webviewJbrRelease = "jbr-release-25.0.3b508.4" val webviewJbrRelease = "jbr-release-25.0.3b508.4"

View File

@@ -9,7 +9,7 @@ jte = "3.2.4"
jackson = "3.2.0" # jackson version locked by javalin, ref: `io.javalin.core.util.OptionalDependency` jackson = "3.2.0" # jackson version locked by javalin, ref: `io.javalin.core.util.OptionalDependency`
exposed = "1.2.0" exposed = "1.2.0"
dex2jar = "2.4.37" dex2jar = "2.4.37"
polyglot = "25.0.3" polyglot = "25.1.3"
settings = "1.3.0" settings = "1.3.0"
twelvemonkeys = "3.13.1" twelvemonkeys = "3.13.1"
graphqlkotlin = "10.0.0" graphqlkotlin = "10.0.0"
@@ -47,6 +47,7 @@ okhttp-core = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" } okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" }
okhttp-dnsoverhttps = { module = "com.squareup.okhttp3:okhttp-dnsoverhttps", version.ref = "okhttp" } okhttp-dnsoverhttps = { module = "com.squareup.okhttp3:okhttp-dnsoverhttps", version.ref = "okhttp" }
okhttp-brotli = { module = "com.squareup.okhttp3:okhttp-brotli", version.ref = "okhttp" } okhttp-brotli = { module = "com.squareup.okhttp3:okhttp-brotli", version.ref = "okhttp" }
okhttp-zstd = { module = "com.squareup.okhttp3:okhttp-zstd", version.ref = "okhttp" }
okio = "com.squareup.okio:okio:3.17.0" okio = "com.squareup.okio:okio:3.17.0"
# Javalin api # Javalin api
@@ -228,6 +229,7 @@ okhttp = [
"okhttp-logging", "okhttp-logging",
"okhttp-dnsoverhttps", "okhttp-dnsoverhttps",
"okhttp-brotli", "okhttp-brotli",
"okhttp-zstd",
] ]
javalin = [ javalin = [
"javalin-core", "javalin-core",

View File

@@ -8,7 +8,7 @@
<string name="opds_feeds_root">Suwayomi OPDS Katalog</string> <string name="opds_feeds_root">Suwayomi OPDS Katalog</string>
<string name="opds_feeds_chapter_details">%1$s | %2$s | Details</string> <string name="opds_feeds_chapter_details">%1$s | %2$s | Details</string>
<string name="opds_feeds_sources_title">Alle Quellen</string> <string name="opds_feeds_sources_title">Alle Quellen</string>
<string name="opds_feeds_genres_title">Genres</string> <string name="opds_feeds_genres_title">Genren</string>
<string name="opds_feeds_genres_entry_content">Durchsuche Serien nach Genre</string> <string name="opds_feeds_genres_entry_content">Durchsuche Serien nach Genre</string>
<string name="opds_feeds_status_entry_content">Durchsuche Serien nach Publikationsstatus</string> <string name="opds_feeds_status_entry_content">Durchsuche Serien nach Publikationsstatus</string>
<string name="opds_feeds_languages_title">Sprachen</string> <string name="opds_feeds_languages_title">Sprachen</string>
@@ -122,4 +122,7 @@
<string name="webview_label_login_required">Deine Konfiguration erfordert die Anmeldung. Bitte gib Benutzername und Passwort ein.</string> <string name="webview_label_login_required">Deine Konfiguration erfordert die Anmeldung. Bitte gib Benutzername und Passwort ein.</string>
<string name="opds_linktitle_first_page">Erste Seite</string> <string name="opds_linktitle_first_page">Erste Seite</string>
<string name="opds_linktitle_last_page">Letzte Seite</string> <string name="opds_linktitle_last_page">Letzte Seite</string>
<string name="opds_error_chapters_not_found">Keine Kapitel gefunden oder die Quelle ist nicht erreichbar auf Seite %1$d.</string>
<string name="opds_chapter_title_fallback">Kapitel %1$s</string>
<string name="opds_chapter_title_oneshot">Oneshot</string>
</resources> </resources>

View File

@@ -122,4 +122,6 @@
<string name="login_label_login">Σύνδεση</string> <string name="login_label_login">Σύνδεση</string>
<string name="login_placeholder_username">Πληκτρολόγησε όνομα χρήστη...</string> <string name="login_placeholder_username">Πληκτρολόγησε όνομα χρήστη...</string>
<string name="login_placeholder_password">Μυστικό...</string> <string name="login_placeholder_password">Μυστικό...</string>
<string name="opds_error_chapters_not_found">Δεν βρέθηκαν κεφάλαια ή η πηγή είναι μη διαθέσιμη στη σελίδα %1$d.</string>
<string name="opds_chapter_title_fallback">Κεφάλαιο %1$s</string>
</resources> </resources>

View File

@@ -122,4 +122,6 @@
<string name="webview_label_login_required">Su configuración requiere que inicie sesión. Introduzca su nombre de usuario y contraseña.</string> <string name="webview_label_login_required">Su configuración requiere que inicie sesión. Introduzca su nombre de usuario y contraseña.</string>
<string name="opds_linktitle_first_page">Primera página</string> <string name="opds_linktitle_first_page">Primera página</string>
<string name="opds_linktitle_last_page">Última página</string> <string name="opds_linktitle_last_page">Última página</string>
<string name="opds_error_chapters_not_found">No se encontraron capítulos o la fuente no está disponible en la página %1$d.</string>
<string name="opds_chapter_title_fallback">Capítulo %1$s</string>
</resources> </resources>

View File

@@ -122,4 +122,7 @@
<string name="login_label_login">Se connecter</string> <string name="login_label_login">Se connecter</string>
<string name="login_placeholder_username">Tapez le nom d\'utilisateur…</string> <string name="login_placeholder_username">Tapez le nom d\'utilisateur…</string>
<string name="login_placeholder_password">Secret…</string> <string name="login_placeholder_password">Secret…</string>
<string name="opds_error_chapters_not_found">Aucun chapitre trouvé ou la source est inaccessible à la page %1$d.</string>
<string name="opds_chapter_title_fallback">Chapitre %1$s</string>
<string name="opds_chapter_title_oneshot">One shot</string>
</resources> </resources>

View File

@@ -122,4 +122,6 @@
<string name="login_label_login">Accedi</string> <string name="login_label_login">Accedi</string>
<string name="login_placeholder_username">Digita il nome utente...</string> <string name="login_placeholder_username">Digita il nome utente...</string>
<string name="login_placeholder_password">Segreto...</string> <string name="login_placeholder_password">Segreto...</string>
<string name="opds_error_chapters_not_found">Nessun capitolo trovato o la fonte non è raggiungibile alla pagina %1$d.</string>
<string name="opds_chapter_title_fallback">Capitolo %1$s</string>
</resources> </resources>

View File

@@ -60,4 +60,7 @@
<string name="opds_feeds_library_sources_title">ソース</string> <string name="opds_feeds_library_sources_title">ソース</string>
<string name="opds_feeds_library_sources_entry_content">ソース別にライブラリ内のマンガを閲覧</string> <string name="opds_feeds_library_sources_entry_content">ソース別にライブラリ内のマンガを閲覧</string>
<string name="opds_feeds_search_results_title">検索結果</string> <string name="opds_feeds_search_results_title">検索結果</string>
<string name="opds_error_chapters_not_found">ページ %1$d で章が見つからないか、ソースに接続できません。</string>
<string name="opds_chapter_title_oneshot">読み切り</string>
<string name="opds_chapter_title_fallback">第 %1$s 話</string>
</resources> </resources>

View File

@@ -76,4 +76,6 @@
<string name="opds_facet_filter_all">Wszystkie</string> <string name="opds_facet_filter_all">Wszystkie</string>
<string name="opds_facet_filter_downloaded">Pobrane</string> <string name="opds_facet_filter_downloaded">Pobrane</string>
<string name="opds_facet_filter_ongoing">Trwające</string> <string name="opds_facet_filter_ongoing">Trwające</string>
<string name="opds_error_chapters_not_found">Nie znaleziono rozdziałów lub źródło jest nieosiągalne na stronie %1$d.</string>
<string name="opds_chapter_title_fallback">Rozdział %1$s</string>
</resources> </resources>

View File

@@ -122,4 +122,6 @@
<string name="login_label_login">Entrar</string> <string name="login_label_login">Entrar</string>
<string name="login_placeholder_username">Digite o nome de usuário...</string> <string name="login_placeholder_username">Digite o nome de usuário...</string>
<string name="login_placeholder_password">Segredo...</string> <string name="login_placeholder_password">Segredo...</string>
<string name="opds_error_chapters_not_found">Nenhum capítulo encontrado ou a fonte está inacessível na página %1$d.</string>
<string name="opds_chapter_title_fallback">Capítulo %1$s</string>
</resources> </resources>

View File

@@ -122,4 +122,7 @@
<string name="opds_search_description">Ищите тайтлы в каталоге.</string> <string name="opds_search_description">Ищите тайтлы в каталоге.</string>
<string name="opds_error_manga_not_found">Тайтл с ID %1$d не найден.</string> <string name="opds_error_manga_not_found">Тайтл с ID %1$d не найден.</string>
<string name="opds_chapter_details_base">Тайтл: %1$s | %2$s</string> <string name="opds_chapter_details_base">Тайтл: %1$s | %2$s</string>
<string name="opds_error_chapters_not_found">Главы не найдены или источник недоступен на странице %1$d.</string>
<string name="opds_chapter_title_fallback">Глава %1$s</string>
<string name="opds_chapter_title_oneshot">Ваншот</string>
</resources> </resources>

View File

@@ -53,4 +53,7 @@
<string name="opds_chapter_status_unread"></string> <string name="opds_chapter_status_unread"></string>
<string name="opds_chapter_details_base">%1$s | %2$s</string> <string name="opds_chapter_details_base">%1$s | %2$s</string>
<string name="opds_feeds_genre_specific_title">இசைவகை: %1$s</string> <string name="opds_feeds_genre_specific_title">இசைவகை: %1$s</string>
<string name="opds_error_chapters_not_found">பக்கம் %1$d இல் அத்தியாயங்கள் எதுவும் காணப்படவில்லை அல்லது மூலத்தை அணுக முடியவில்லை.</string>
<string name="opds_chapter_title_oneshot">ஒன்-ஷாட்</string>
<string name="opds_chapter_title_fallback">அத்தியாயம் %1$s</string>
</resources> </resources>

View File

@@ -122,4 +122,6 @@
<string name="webview_label_login_required">Cấu hình của bạn yêu cầu bạn phải đăng nhập. Vui lòng nhập tên người dùng và mật khẩu.</string> <string name="webview_label_login_required">Cấu hình của bạn yêu cầu bạn phải đăng nhập. Vui lòng nhập tên người dùng và mật khẩu.</string>
<string name="opds_linktitle_first_page">Trang đầu</string> <string name="opds_linktitle_first_page">Trang đầu</string>
<string name="opds_linktitle_last_page">Trang cuối</string> <string name="opds_linktitle_last_page">Trang cuối</string>
<string name="opds_error_chapters_not_found">Không tìm thấy chương nào hoặc nguồn không thể truy cập tại trang %1$d.</string>
<string name="opds_chapter_title_fallback">Chương %1$s</string>
</resources> </resources>

View File

@@ -122,4 +122,7 @@
<string name="login_placeholder_username">输入用户名…</string> <string name="login_placeholder_username">输入用户名…</string>
<string name="login_placeholder_password">密匙…</string> <string name="login_placeholder_password">密匙…</string>
<string name="label_error">错误</string> <string name="label_error">错误</string>
<string name="opds_error_chapters_not_found">第 %1$d 页未找到任何章节,或图源无法访问。</string>
<string name="opds_chapter_title_fallback">第 %1$s 章</string>
<string name="opds_chapter_title_oneshot">单篇</string>
</resources> </resources>

View File

@@ -9,7 +9,6 @@ package eu.kanade.tachiyomi.network
import android.content.Context import android.content.Context
import eu.kanade.tachiyomi.network.interceptor.CloudflareInterceptor import eu.kanade.tachiyomi.network.interceptor.CloudflareInterceptor
import eu.kanade.tachiyomi.network.interceptor.IgnoreGzipInterceptor
import eu.kanade.tachiyomi.network.interceptor.UncaughtExceptionInterceptor import eu.kanade.tachiyomi.network.interceptor.UncaughtExceptionInterceptor
import eu.kanade.tachiyomi.network.interceptor.UserAgentInterceptor import eu.kanade.tachiyomi.network.interceptor.UserAgentInterceptor
import io.github.oshai.kotlinlogging.KotlinLogging import io.github.oshai.kotlinlogging.KotlinLogging
@@ -22,9 +21,8 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import okhttp3.Cache import okhttp3.Cache
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.brotli.BrotliInterceptor
import okhttp3.logging.HttpLoggingInterceptor import okhttp3.logging.HttpLoggingInterceptor
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource import suwayomi.tachidesk.manga.impl.util.source.GetSource
import java.net.CookieHandler import java.net.CookieHandler
import java.net.CookieManager import java.net.CookieManager
import java.net.CookiePolicy import java.net.CookiePolicy
@@ -64,7 +62,7 @@ class NetworkHelper(
userAgent userAgent
.drop(1) .drop(1)
.onEach { .onEach {
GetCatalogueSource.unregisterAllCatalogueSources() // need to reset the headers GetSource.unregisterAllSources() // need to reset the headers
}.launchIn(GlobalScope) }.launchIn(GlobalScope)
} }
@@ -84,8 +82,6 @@ class NetworkHelper(
), ),
).addInterceptor(UncaughtExceptionInterceptor()) ).addInterceptor(UncaughtExceptionInterceptor())
.addInterceptor(UserAgentInterceptor(::defaultUserAgentProvider)) .addInterceptor(UserAgentInterceptor(::defaultUserAgentProvider))
.addNetworkInterceptor(IgnoreGzipInterceptor())
.addNetworkInterceptor(BrotliInterceptor)
// if (preferences.verboseLogging().get()) { // if (preferences.verboseLogging().get()) {
val httpLoggingInterceptor = val httpLoggingInterceptor =

View File

@@ -1,21 +0,0 @@
package eu.kanade.tachiyomi.network.interceptor
import okhttp3.Interceptor
import okhttp3.Response
/**
* To use [okhttp3.brotli.BrotliInterceptor] as a network interceptor,
* add [IgnoreGzipInterceptor] right before it.
*
* This nullifies the transparent gzip of [okhttp3.internal.http.BridgeInterceptor]
* so gzip and Brotli are explicitly handled by the [okhttp3.brotli.BrotliInterceptor].
*/
class IgnoreGzipInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
var request = chain.request()
if (request.header("Accept-Encoding") == "gzip") {
request = request.newBuilder().removeHeader("Accept-Encoding").build()
}
return chain.proceed(request)
}
}

View File

@@ -44,7 +44,7 @@ import org.jetbrains.exposed.v1.jdbc.insert
import org.jetbrains.exposed.v1.jdbc.insertAndGetId import org.jetbrains.exposed.v1.jdbc.insertAndGetId
import org.jetbrains.exposed.v1.jdbc.selectAll import org.jetbrains.exposed.v1.jdbc.selectAll
import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.jetbrains.exposed.v1.jdbc.transactions.transaction
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.registerCatalogueSource import suwayomi.tachidesk.manga.impl.util.source.GetSource.registerSource
import suwayomi.tachidesk.manga.impl.util.storage.ImageUtil import suwayomi.tachidesk.manga.impl.util.storage.ImageUtil
import suwayomi.tachidesk.manga.model.table.ExtensionTable import suwayomi.tachidesk.manga.model.table.ExtensionTable
import suwayomi.tachidesk.manga.model.table.SourceTable import suwayomi.tachidesk.manga.model.table.SourceTable
@@ -483,7 +483,7 @@ class LocalSource(
it[versionCode] = 0 it[versionCode] = 0
it[lang] = LANG it[lang] = LANG
it[extensionLib] = "1.2" it[extensionLib] = "1.2"
it[contentRating] = 0 it[contentWarning] = 0
it[isInstalled] = true it[isInstalled] = true
} }
@@ -497,7 +497,7 @@ class LocalSource(
} }
val fs = LocalSourceFileSystem(applicationDirs) val fs = LocalSourceFileSystem(applicationDirs)
registerCatalogueSource(ID to LocalSource(fs, LocalCoverManager(fs))) registerSource(ID to LocalSource(fs, LocalCoverManager(fs)))
} }
} }
} }

View File

@@ -305,8 +305,7 @@ object SyncYomiSyncService {
logger.debug { "Starting merge. Local list size: ${localMangaListSafe.size}, Remote list size: ${remoteMangaListSafe.size}" } logger.debug { "Starting merge. Local list size: ${localMangaListSafe.size}, Remote list size: ${remoteMangaListSafe.size}" }
fun mangaCompositeKey(manga: BackupManga): String = fun mangaCompositeKey(manga: BackupManga): String = "${manga.source}|${manga.url}"
"${manga.source}|${manga.url}|${manga.title.lowercase().trim()}|${manga.author?.lowercase()?.trim()}"
// Create maps using composite keys // Create maps using composite keys
val localMangaMap = localMangaListSafe.associateBy { mangaCompositeKey(it) } val localMangaMap = localMangaListSafe.associateBy { mangaCompositeKey(it) }
@@ -415,7 +414,7 @@ object SyncYomiSyncService {
return remoteChapters // If not syncing chapters, keep remote untouched return remoteChapters // If not syncing chapters, keep remote untouched
} }
fun chapterCompositeKey(chapter: BackupChapter): String = "${chapter.url}|${chapter.name}|${chapter.chapterNumber}" fun chapterCompositeKey(chapter: BackupChapter): String = chapter.url
val localChapterMap = localChapters.associateBy { chapterCompositeKey(it) } val localChapterMap = localChapters.associateBy { chapterCompositeKey(it) }
val remoteChapterMap = remoteChapters.associateBy { chapterCompositeKey(it) } val remoteChapterMap = remoteChapters.associateBy { chapterCompositeKey(it) }

View File

@@ -21,26 +21,6 @@ class ExtensionStoreDataLoader : KotlinDataLoader<String, ExtensionStoreType> {
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<String, ExtensionStoreType> = override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<String, ExtensionStoreType> =
DataLoaderFactory.newDataLoader { ids -> DataLoaderFactory.newDataLoader { ids ->
future {
transaction {
addLogger(Slf4jSqlDebugLogger)
val manga =
ExtensionStoreTable
.selectAll()
.where { ExtensionStoreTable.indexUrl inList ids }
.map { ExtensionStoreType(it) }
.associateBy { it.indexUrl }
ids.map { manga[it] }
}
}
}
}
class ExtensionStoreForExtension : KotlinDataLoader<String, ExtensionStoreType> {
override val dataLoaderName = "ExtensionStoreForExtension"
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<String, ExtensionStoreType> =
DataLoaderFactory.newDataLoader<String, ExtensionStoreType> { ids ->
future { future {
transaction { transaction {
addLogger(Slf4jSqlDebugLogger) addLogger(Slf4jSqlDebugLogger)
@@ -50,14 +30,14 @@ class ExtensionStoreForExtension : KotlinDataLoader<String, ExtensionStoreType>
.where { ExtensionStoreTable.indexUrl inList ids } .where { ExtensionStoreTable.indexUrl inList ids }
.map { ExtensionStoreType(it) } .map { ExtensionStoreType(it) }
.associateBy { it.indexUrl } .associateBy { it.indexUrl }
ids.map { (extensionStoreByIndexUrl[it]) } ids.map { extensionStoreByIndexUrl[it] }
} }
} }
} }
} }
class ExtensionForExtensionStore : KotlinDataLoader<String, ExtensionNodeList> { class ExtensionsForExtensionStore : KotlinDataLoader<String, ExtensionNodeList> {
override val dataLoaderName = "ExtensionForExtensionStore" override val dataLoaderName = "ExtensionsForExtensionStore"
override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<String, ExtensionNodeList> = override fun getDataLoader(graphQLContext: GraphQLContext): DataLoader<String, ExtensionNodeList> =
DataLoaderFactory.newDataLoader<String, ExtensionNodeList> { ids -> DataLoaderFactory.newDataLoader<String, ExtensionNodeList> { ids ->

View File

@@ -95,6 +95,7 @@ class ExtensionStoreMutation {
contactDiscord = it.contactDiscord, contactDiscord = it.contactDiscord,
indexUrl = it.indexUrl, indexUrl = it.indexUrl,
isLegacy = it.isLegacy, isLegacy = it.isLegacy,
extensionListUrl = it.extensionListUrl,
) )
}, },
) )

View File

@@ -28,7 +28,7 @@ import suwayomi.tachidesk.graphql.types.preferenceOf
import suwayomi.tachidesk.graphql.types.updateFilterList import suwayomi.tachidesk.graphql.types.updateFilterList
import suwayomi.tachidesk.manga.impl.MangaList.insertOrUpdate import suwayomi.tachidesk.manga.impl.MangaList.insertOrUpdate
import suwayomi.tachidesk.manga.impl.Source import suwayomi.tachidesk.manga.impl.Source
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource import suwayomi.tachidesk.manga.impl.util.source.GetSource
import suwayomi.tachidesk.manga.model.table.MangaTable import suwayomi.tachidesk.manga.model.table.MangaTable
import suwayomi.tachidesk.manga.model.table.SourceMetaTable import suwayomi.tachidesk.manga.model.table.SourceMetaTable
import suwayomi.tachidesk.manga.model.table.SourceTable import suwayomi.tachidesk.manga.model.table.SourceTable
@@ -256,7 +256,7 @@ class SourceMutation {
val (clientMutationId, sourceId, type, page, query, filters) = input val (clientMutationId, sourceId, type, page, query, filters) = input
return future { return future {
val source = GetCatalogueSource.getCatalogueSourceOrNull(sourceId)!! val source = GetSource.getSourceOrNull(sourceId)!!
val mangasPage = val mangasPage =
when (type) { when (type) {
FetchSourceMangaType.SEARCH -> { FetchSourceMangaType.SEARCH -> {

View File

@@ -148,6 +148,36 @@ class TrackMutation {
} }
} }
data class BindTrackRecordInput(
val clientMutationId: String? = null,
val mangaId: Int,
val trackRecordId: Int,
)
data class BindTrackRecordPayload(
val clientMutationId: String?,
val trackRecord: TrackRecordType,
)
@RequireAuth
fun bindTrackRecord(input: BindTrackRecordInput): CompletableFuture<BindTrackRecordPayload?> {
val (clientMutationId, mangaId, trackRecordId) = input
return future {
val boundTrackRecordId = Track.bindTrackRecord(mangaId, trackRecordId)
val trackRecord =
transaction {
TrackRecordTable.selectAll().where { TrackRecordTable.id eq boundTrackRecordId }.first()
}
BindTrackRecordPayload(
clientMutationId,
TrackRecordType(trackRecord),
)
}
}
data class FetchTrackInput( data class FetchTrackInput(
val clientMutationId: String? = null, val clientMutationId: String? = null,
val recordId: Int, val recordId: Int,

View File

@@ -21,7 +21,7 @@ import org.jetbrains.exposed.v1.jdbc.selectAll
import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.jetbrains.exposed.v1.jdbc.transactions.transaction
import suwayomi.tachidesk.graphql.directives.RequireAuth import suwayomi.tachidesk.graphql.directives.RequireAuth
import suwayomi.tachidesk.graphql.queries.filter.BooleanFilter import suwayomi.tachidesk.graphql.queries.filter.BooleanFilter
import suwayomi.tachidesk.graphql.queries.filter.ContentRatingFilter import suwayomi.tachidesk.graphql.queries.filter.ContentWarningFilter
import suwayomi.tachidesk.graphql.queries.filter.Filter import suwayomi.tachidesk.graphql.queries.filter.Filter
import suwayomi.tachidesk.graphql.queries.filter.HasGetOp import suwayomi.tachidesk.graphql.queries.filter.HasGetOp
import suwayomi.tachidesk.graphql.queries.filter.IntFilter import suwayomi.tachidesk.graphql.queries.filter.IntFilter
@@ -43,7 +43,7 @@ import suwayomi.tachidesk.graphql.server.primitives.lessNotUnique
import suwayomi.tachidesk.graphql.server.primitives.maybeSwap import suwayomi.tachidesk.graphql.server.primitives.maybeSwap
import suwayomi.tachidesk.graphql.types.ExtensionNodeList import suwayomi.tachidesk.graphql.types.ExtensionNodeList
import suwayomi.tachidesk.graphql.types.ExtensionType import suwayomi.tachidesk.graphql.types.ExtensionType
import suwayomi.tachidesk.manga.model.dataclass.ContentRating import suwayomi.tachidesk.manga.model.dataclass.ContentWarning
import suwayomi.tachidesk.manga.model.table.ExtensionTable import suwayomi.tachidesk.manga.model.table.ExtensionTable
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture
@@ -108,9 +108,9 @@ class ExtensionQuery {
val versionCode: Int? = null, val versionCode: Int? = null,
val versionCodeLong: Long? = null, val versionCodeLong: Long? = null,
val lang: String? = null, val lang: String? = null,
@GraphQLDeprecated("", ReplaceWith("contentRating")) @GraphQLDeprecated("", ReplaceWith("contentWarning"))
val isNsfw: Boolean? = null, val isNsfw: Boolean? = null,
val contentRating: ContentRating? = null, val contentWarning: ContentWarning? = null,
val isInstalled: Boolean? = null, val isInstalled: Boolean? = null,
val hasUpdate: Boolean? = null, val hasUpdate: Boolean? = null,
val isObsolete: Boolean? = null, val isObsolete: Boolean? = null,
@@ -129,10 +129,10 @@ class ExtensionQuery {
opAnd.eq(versionCodeLong, ExtensionTable.versionCode) opAnd.eq(versionCodeLong, ExtensionTable.versionCode)
opAnd.eq(lang, ExtensionTable.lang) opAnd.eq(lang, ExtensionTable.lang)
opAnd.eq( opAnd.eq(
isNsfw?.let { if (it) ContentRating.PORNOGRAPHIC.ordinal else ContentRating.SAFE.ordinal }, isNsfw?.let { if (it) ContentWarning.MIXED.ordinal else ContentWarning.SAFE.ordinal },
ExtensionTable.contentRating, ExtensionTable.contentWarning,
) )
opAnd.eq(contentRating?.ordinal, ExtensionTable.contentRating) opAnd.eq(contentWarning?.ordinal, ExtensionTable.contentWarning)
opAnd.eq(isInstalled, ExtensionTable.isInstalled) opAnd.eq(isInstalled, ExtensionTable.isInstalled)
opAnd.eq(hasUpdate, ExtensionTable.hasUpdate) opAnd.eq(hasUpdate, ExtensionTable.hasUpdate)
opAnd.eq(isObsolete, ExtensionTable.isObsolete) opAnd.eq(isObsolete, ExtensionTable.isObsolete)
@@ -156,9 +156,9 @@ class ExtensionQuery {
val versionCode: IntFilter? = null, val versionCode: IntFilter? = null,
val versionCodeLong: LongFilter? = null, val versionCodeLong: LongFilter? = null,
val lang: StringFilter? = null, val lang: StringFilter? = null,
@GraphQLDeprecated("", ReplaceWith("storeIndexUrl")) @GraphQLDeprecated("", ReplaceWith("contentWarning"))
val isNsfw: BooleanFilter? = null, val isNsfw: BooleanFilter? = null,
val contentRating: ContentRatingFilter? = null, val contentWarning: ContentWarningFilter? = null,
val isInstalled: BooleanFilter? = null, val isInstalled: BooleanFilter? = null,
val hasUpdate: BooleanFilter? = null, val hasUpdate: BooleanFilter? = null,
val isObsolete: BooleanFilter? = null, val isObsolete: BooleanFilter? = null,
@@ -179,7 +179,7 @@ class ExtensionQuery {
andFilterWithCompareString(ExtensionTable.versionName, versionName), andFilterWithCompareString(ExtensionTable.versionName, versionName),
andFilterWithCompare(ExtensionTable.versionCode, versionCodeLong), andFilterWithCompare(ExtensionTable.versionCode, versionCodeLong),
andFilterWithCompareString(ExtensionTable.lang, lang), andFilterWithCompareString(ExtensionTable.lang, lang),
andFilterWithCompareEnum(ExtensionTable.contentRating, contentRating), andFilterWithCompareEnum(ExtensionTable.contentWarning, contentWarning),
andFilterWithCompare(ExtensionTable.isInstalled, isInstalled), andFilterWithCompare(ExtensionTable.isInstalled, isInstalled),
andFilterWithCompare(ExtensionTable.hasUpdate, hasUpdate), andFilterWithCompare(ExtensionTable.hasUpdate, hasUpdate),
andFilterWithCompare(ExtensionTable.isObsolete, isObsolete), andFilterWithCompare(ExtensionTable.isObsolete, isObsolete),

View File

@@ -15,13 +15,13 @@ import org.jetbrains.exposed.v1.core.Op
import org.jetbrains.exposed.v1.core.SortOrder import org.jetbrains.exposed.v1.core.SortOrder
import org.jetbrains.exposed.v1.core.eq import org.jetbrains.exposed.v1.core.eq
import org.jetbrains.exposed.v1.core.greater import org.jetbrains.exposed.v1.core.greater
import org.jetbrains.exposed.v1.core.greaterEq
import org.jetbrains.exposed.v1.core.less import org.jetbrains.exposed.v1.core.less
import org.jetbrains.exposed.v1.core.neq
import org.jetbrains.exposed.v1.jdbc.selectAll import org.jetbrains.exposed.v1.jdbc.selectAll
import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.jetbrains.exposed.v1.jdbc.transactions.transaction
import suwayomi.tachidesk.graphql.directives.RequireAuth import suwayomi.tachidesk.graphql.directives.RequireAuth
import suwayomi.tachidesk.graphql.queries.filter.BooleanFilter import suwayomi.tachidesk.graphql.queries.filter.BooleanFilter
import suwayomi.tachidesk.graphql.queries.filter.ContentRatingFilter import suwayomi.tachidesk.graphql.queries.filter.ContentWarningFilter
import suwayomi.tachidesk.graphql.queries.filter.Filter import suwayomi.tachidesk.graphql.queries.filter.Filter
import suwayomi.tachidesk.graphql.queries.filter.HasGetOp import suwayomi.tachidesk.graphql.queries.filter.HasGetOp
import suwayomi.tachidesk.graphql.queries.filter.LongFilter import suwayomi.tachidesk.graphql.queries.filter.LongFilter
@@ -42,7 +42,7 @@ import suwayomi.tachidesk.graphql.server.primitives.lessNotUnique
import suwayomi.tachidesk.graphql.server.primitives.maybeSwap import suwayomi.tachidesk.graphql.server.primitives.maybeSwap
import suwayomi.tachidesk.graphql.types.SourceNodeList import suwayomi.tachidesk.graphql.types.SourceNodeList
import suwayomi.tachidesk.graphql.types.SourceType import suwayomi.tachidesk.graphql.types.SourceType
import suwayomi.tachidesk.manga.model.dataclass.ContentRating import suwayomi.tachidesk.manga.model.dataclass.ContentWarning
import suwayomi.tachidesk.manga.model.table.SourceTable import suwayomi.tachidesk.manga.model.table.SourceTable
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture
@@ -95,9 +95,9 @@ class SourceQuery {
val id: Long? = null, val id: Long? = null,
val name: String? = null, val name: String? = null,
val lang: String? = null, val lang: String? = null,
@GraphQLDeprecated("replace with contentRating == ContentRating.PORNOGRAPHIC", ReplaceWith("contentRating")) @GraphQLDeprecated("replace with contentWarning == ContentRating.MIXED", ReplaceWith("contentWarning"))
val isNsfw: Boolean? = null, val isNsfw: Boolean? = null,
val contentRating: ContentRating? = null, val contentWarning: ContentWarning? = null,
) : HasGetOp { ) : HasGetOp {
override fun getOp(): Op<Boolean>? { override fun getOp(): Op<Boolean>? {
val opAnd = OpAnd() val opAnd = OpAnd()
@@ -106,13 +106,12 @@ class SourceQuery {
opAnd.eq(lang, SourceTable.lang) opAnd.eq(lang, SourceTable.lang)
opAnd.andWhere(isNsfw) { opAnd.andWhere(isNsfw) {
if (it) { if (it) {
SourceTable.contentRating eq ContentRating.PORNOGRAPHIC.ordinal SourceTable.contentWarning greaterEq ContentWarning.MIXED.ordinal
} else { } else {
SourceTable.contentRating neq SourceTable.contentWarning less ContentWarning.MIXED.ordinal
ContentRating.PORNOGRAPHIC.ordinal
} }
} }
opAnd.andWhere(contentRating) { SourceTable.contentRating eq it.ordinal } opAnd.andWhere(contentWarning) { SourceTable.contentWarning eq it.ordinal }
return opAnd.op return opAnd.op
} }
@@ -122,9 +121,9 @@ class SourceQuery {
val id: LongFilter? = null, val id: LongFilter? = null,
val name: StringFilter? = null, val name: StringFilter? = null,
val lang: StringFilter? = null, val lang: StringFilter? = null,
@GraphQLDeprecated("replace with contentRating == ContentRating.PORNOGRAPHIC", ReplaceWith("contentRating")) @GraphQLDeprecated("replace with contentWarning", ReplaceWith("contentWarning"))
val isNsfw: BooleanFilter? = null, val isNsfw: BooleanFilter? = null,
val contentRating: ContentRatingFilter? = null, val contentWarning: ContentWarningFilter? = null,
override val and: List<SourceFilter>? = null, override val and: List<SourceFilter>? = null,
override val or: List<SourceFilter>? = null, override val or: List<SourceFilter>? = null,
override val not: SourceFilter? = null, override val not: SourceFilter? = null,
@@ -134,7 +133,7 @@ class SourceQuery {
andFilterWithCompareEntity(SourceTable.id, id), andFilterWithCompareEntity(SourceTable.id, id),
andFilterWithCompareString(SourceTable.name, name), andFilterWithCompareString(SourceTable.name, name),
andFilterWithCompareString(SourceTable.lang, lang), andFilterWithCompareString(SourceTable.lang, lang),
andFilterWithCompareEnum(SourceTable.contentRating, contentRating), andFilterWithCompareEnum(SourceTable.contentWarning, contentWarning),
) )
} }

View File

@@ -28,7 +28,7 @@ import org.jetbrains.exposed.v1.core.upperCase
import org.jetbrains.exposed.v1.core.wrap import org.jetbrains.exposed.v1.core.wrap
import org.jetbrains.exposed.v1.jdbc.Query import org.jetbrains.exposed.v1.jdbc.Query
import org.jetbrains.exposed.v1.jdbc.andWhere import org.jetbrains.exposed.v1.jdbc.andWhere
import suwayomi.tachidesk.manga.model.dataclass.ContentRating import suwayomi.tachidesk.manga.model.dataclass.ContentWarning
class ILikeEscapeOp( class ILikeEscapeOp(
expr1: Expression<*>, expr1: Expression<*>,
@@ -330,23 +330,23 @@ data class DoubleFilter(
) )
} }
data class ContentRatingFilter( data class ContentWarningFilter(
override val isNull: Boolean? = null, override val isNull: Boolean? = null,
override val equalTo: ContentRating? = null, override val equalTo: ContentWarning? = null,
override val notEqualTo: ContentRating? = null, override val notEqualTo: ContentWarning? = null,
override val notEqualToAll: List<ContentRating>? = null, override val notEqualToAll: List<ContentWarning>? = null,
override val notEqualToAny: List<ContentRating>? = null, override val notEqualToAny: List<ContentWarning>? = null,
override val distinctFrom: ContentRating? = null, override val distinctFrom: ContentWarning? = null,
override val distinctFromAll: List<ContentRating>? = null, override val distinctFromAll: List<ContentWarning>? = null,
override val distinctFromAny: List<ContentRating>? = null, override val distinctFromAny: List<ContentWarning>? = null,
override val notDistinctFrom: ContentRating? = null, override val notDistinctFrom: ContentWarning? = null,
override val `in`: List<ContentRating>? = null, override val `in`: List<ContentWarning>? = null,
override val notIn: List<ContentRating>? = null, override val notIn: List<ContentWarning>? = null,
override val lessThan: ContentRating? = null, override val lessThan: ContentWarning? = null,
override val lessThanOrEqualTo: ContentRating? = null, override val lessThanOrEqualTo: ContentWarning? = null,
override val greaterThan: ContentRating? = null, override val greaterThan: ContentWarning? = null,
override val greaterThanOrEqualTo: ContentRating? = null, override val greaterThanOrEqualTo: ContentWarning? = null,
) : ComparableScalarFilter<ContentRating> ) : ComparableScalarFilter<ContentWarning>
data class StringFilter( data class StringFilter(
override val isNull: Boolean? = null, override val isNull: Boolean? = null,

View File

@@ -20,10 +20,9 @@ import suwayomi.tachidesk.graphql.dataLoaders.DisplayScoreForTrackRecordDataLoad
import suwayomi.tachidesk.graphql.dataLoaders.DisplayScoreForTrackSearchDataLoader import suwayomi.tachidesk.graphql.dataLoaders.DisplayScoreForTrackSearchDataLoader
import suwayomi.tachidesk.graphql.dataLoaders.DownloadedChapterCountForMangaDataLoader import suwayomi.tachidesk.graphql.dataLoaders.DownloadedChapterCountForMangaDataLoader
import suwayomi.tachidesk.graphql.dataLoaders.ExtensionDataLoader import suwayomi.tachidesk.graphql.dataLoaders.ExtensionDataLoader
import suwayomi.tachidesk.graphql.dataLoaders.ExtensionForExtensionStore
import suwayomi.tachidesk.graphql.dataLoaders.ExtensionForSourceDataLoader import suwayomi.tachidesk.graphql.dataLoaders.ExtensionForSourceDataLoader
import suwayomi.tachidesk.graphql.dataLoaders.ExtensionStoreDataLoader import suwayomi.tachidesk.graphql.dataLoaders.ExtensionStoreDataLoader
import suwayomi.tachidesk.graphql.dataLoaders.ExtensionStoreForExtension import suwayomi.tachidesk.graphql.dataLoaders.ExtensionsForExtensionStore
import suwayomi.tachidesk.graphql.dataLoaders.FirstUnreadChapterForMangaDataLoader import suwayomi.tachidesk.graphql.dataLoaders.FirstUnreadChapterForMangaDataLoader
import suwayomi.tachidesk.graphql.dataLoaders.GlobalMetaDataLoader import suwayomi.tachidesk.graphql.dataLoaders.GlobalMetaDataLoader
import suwayomi.tachidesk.graphql.dataLoaders.HasDuplicateChaptersForMangaDataLoader import suwayomi.tachidesk.graphql.dataLoaders.HasDuplicateChaptersForMangaDataLoader
@@ -81,9 +80,8 @@ class TachideskDataLoaderRegistryFactory {
SourceMetaDataLoader(), SourceMetaDataLoader(),
ExtensionDataLoader(), ExtensionDataLoader(),
ExtensionForSourceDataLoader(), ExtensionForSourceDataLoader(),
ExtensionForExtensionStore(), ExtensionsForExtensionStore(),
ExtensionStoreDataLoader(), ExtensionStoreDataLoader(),
ExtensionStoreForExtension(),
TrackerDataLoader(), TrackerDataLoader(),
TrackerStatusesDataLoader(), TrackerStatusesDataLoader(),
TrackerScoresDataLoader(), TrackerScoresDataLoader(),

View File

@@ -26,6 +26,7 @@ class ExtensionStoreType(
val contactDiscord: String?, val contactDiscord: String?,
val indexUrl: String, val indexUrl: String,
val isLegacy: Boolean, val isLegacy: Boolean,
val extensionListUrl: String?,
) : Node { ) : Node {
constructor(row: ResultRow) : this( constructor(row: ResultRow) : this(
name = row[ExtensionStoreTable.name], name = row[ExtensionStoreTable.name],
@@ -35,10 +36,11 @@ class ExtensionStoreType(
contactDiscord = row[ExtensionStoreTable.contactDiscord], contactDiscord = row[ExtensionStoreTable.contactDiscord],
indexUrl = row[ExtensionStoreTable.indexUrl], indexUrl = row[ExtensionStoreTable.indexUrl],
isLegacy = row[ExtensionStoreTable.isLegacy], isLegacy = row[ExtensionStoreTable.isLegacy],
extensionListUrl = row[ExtensionStoreTable.extensionListUrl],
) )
fun extensions(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture<ExtensionNodeList> = fun extensions(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture<ExtensionNodeList> =
dataFetchingEnvironment.getValueFromDataLoader<String, ExtensionNodeList>("ExtensionForExtensionStore", indexUrl) dataFetchingEnvironment.getValueFromDataLoader<String, ExtensionNodeList>("ExtensionsForExtensionStore", indexUrl)
} }
data class ExtensionStoreNodeList( data class ExtensionStoreNodeList(

View File

@@ -18,7 +18,7 @@ import suwayomi.tachidesk.graphql.server.primitives.Node
import suwayomi.tachidesk.graphql.server.primitives.NodeList import suwayomi.tachidesk.graphql.server.primitives.NodeList
import suwayomi.tachidesk.graphql.server.primitives.PageInfo import suwayomi.tachidesk.graphql.server.primitives.PageInfo
import suwayomi.tachidesk.manga.impl.extension.Extension import suwayomi.tachidesk.manga.impl.extension.Extension
import suwayomi.tachidesk.manga.model.dataclass.ContentRating import suwayomi.tachidesk.manga.model.dataclass.ContentWarning
import suwayomi.tachidesk.manga.model.table.ExtensionTable import suwayomi.tachidesk.manga.model.table.ExtensionTable
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture
@@ -41,9 +41,9 @@ class ExtensionType(
val versionCode: Int, val versionCode: Int,
val versionCodeLong: Long, val versionCodeLong: Long,
val lang: String, val lang: String,
@GraphQLDeprecated("Removed in extension api v1.6", ReplaceWith("contentRating")) @GraphQLDeprecated("Removed in extension api v1.6", ReplaceWith("contentWarning"))
val isNsfw: Boolean, val isNsfw: Boolean,
val contentRating: ContentRating, val contentWarning: ContentWarning,
val isInstalled: Boolean, val isInstalled: Boolean,
val hasUpdate: Boolean, val hasUpdate: Boolean,
val isObsolete: Boolean, val isObsolete: Boolean,
@@ -61,8 +61,8 @@ class ExtensionType(
versionCode = row[ExtensionTable.versionCode].toInt(), versionCode = row[ExtensionTable.versionCode].toInt(),
versionCodeLong = row[ExtensionTable.versionCode], versionCodeLong = row[ExtensionTable.versionCode],
lang = row[ExtensionTable.lang], lang = row[ExtensionTable.lang],
isNsfw = row[ExtensionTable.contentRating] == ContentRating.PORNOGRAPHIC.ordinal, isNsfw = row[ExtensionTable.contentWarning] >= ContentWarning.MIXED.ordinal,
contentRating = ContentRating.valueOf(row[ExtensionTable.contentRating]), contentWarning = ContentWarning.valueOf(row[ExtensionTable.contentWarning]),
isInstalled = row[ExtensionTable.isInstalled], isInstalled = row[ExtensionTable.isInstalled],
hasUpdate = row[ExtensionTable.hasUpdate], hasUpdate = row[ExtensionTable.hasUpdate],
isObsolete = row[ExtensionTable.isObsolete], isObsolete = row[ExtensionTable.isObsolete],
@@ -72,7 +72,7 @@ class ExtensionType(
dataFetchingEnvironment.getValueFromDataLoader<String, SourceNodeList>("SourcesForExtensionDataLoader", pkgName) dataFetchingEnvironment.getValueFromDataLoader<String, SourceNodeList>("SourcesForExtensionDataLoader", pkgName)
fun extensionStore(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture<ExtensionStoreType> = fun extensionStore(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture<ExtensionStoreType> =
dataFetchingEnvironment.getValueFromDataLoader<String, ExtensionStoreType>("ExtensionStoreForExtension", storeIndexUrl.orEmpty()) dataFetchingEnvironment.getValueFromDataLoader<String, ExtensionStoreType>("ExtensionStoreDataLoader", storeIndexUrl.orEmpty())
} }
data class ExtensionNodeList( data class ExtensionNodeList(

View File

@@ -9,8 +9,8 @@ package suwayomi.tachidesk.graphql.types
import com.expediagroup.graphql.generator.annotations.GraphQLDeprecated import com.expediagroup.graphql.generator.annotations.GraphQLDeprecated
import com.expediagroup.graphql.server.extensions.getValueFromDataLoader import com.expediagroup.graphql.server.extensions.getValueFromDataLoader
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.ConfigurableSource import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import graphql.schema.DataFetchingEnvironment import graphql.schema.DataFetchingEnvironment
@@ -24,9 +24,9 @@ import suwayomi.tachidesk.graphql.server.primitives.NodeList
import suwayomi.tachidesk.graphql.server.primitives.PageInfo import suwayomi.tachidesk.graphql.server.primitives.PageInfo
import suwayomi.tachidesk.manga.impl.Source.getSourcePreferencesRaw import suwayomi.tachidesk.manga.impl.Source.getSourcePreferencesRaw
import suwayomi.tachidesk.manga.impl.extension.Extension import suwayomi.tachidesk.manga.impl.extension.Extension
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource import suwayomi.tachidesk.manga.impl.util.source.GetSource
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub import suwayomi.tachidesk.manga.impl.util.source.GetSource.getSourceOrStub
import suwayomi.tachidesk.manga.model.dataclass.ContentRating import suwayomi.tachidesk.manga.model.dataclass.ContentWarning
import suwayomi.tachidesk.manga.model.table.ExtensionTable import suwayomi.tachidesk.manga.model.table.ExtensionTable
import suwayomi.tachidesk.manga.model.table.SourceTable import suwayomi.tachidesk.manga.model.table.SourceTable
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture
@@ -42,31 +42,29 @@ class SourceType(
val id: Long, val id: Long,
val name: String, val name: String,
val lang: String, val lang: String,
val message: String?, val contentWarning: ContentWarning,
val contentRating: ContentRating,
val iconUrl: String, val iconUrl: String,
val supportsLatest: Boolean, val supportsLatest: Boolean,
val isConfigurable: Boolean, val isConfigurable: Boolean,
@GraphQLDeprecated("", ReplaceWith("contentRating")) @GraphQLDeprecated("", ReplaceWith("contentWarning"))
val isNsfw: Boolean, val isNsfw: Boolean,
val displayName: String, val displayName: String,
val homeUrl: String?, val homeUrl: String?,
@GraphQLDeprecated("", ReplaceWith("homeUrl")) @GraphQLDeprecated("", ReplaceWith("homeUrl"))
val baseUrl: String?, val baseUrl: String?,
) : Node { ) : Node {
constructor(row: ResultRow, sourceExtension: ResultRow, catalogueSource: CatalogueSource) : this( constructor(row: ResultRow, sourceExtension: ResultRow, source: Source) : this(
id = row[SourceTable.id].value, id = row[SourceTable.id].value,
name = row[SourceTable.name], name = row[SourceTable.name],
lang = row[SourceTable.lang], lang = row[SourceTable.lang],
message = row[SourceTable.message], contentWarning = ContentWarning.valueOf(row[SourceTable.contentWarning]),
contentRating = ContentRating.valueOf(row[SourceTable.contentRating]),
iconUrl = Extension.proxyExtensionIconUrl(sourceExtension[ExtensionTable.pkgName]), iconUrl = Extension.proxyExtensionIconUrl(sourceExtension[ExtensionTable.pkgName]),
supportsLatest = catalogueSource.supportsLatest, supportsLatest = source.supportsLatest,
isConfigurable = catalogueSource is ConfigurableSource, isConfigurable = source is ConfigurableSource,
isNsfw = row[SourceTable.contentRating] == ContentRating.PORNOGRAPHIC.ordinal, isNsfw = row[SourceTable.contentWarning] >= ContentWarning.MIXED.ordinal,
displayName = catalogueSource.toString(), displayName = source.toString(),
homeUrl = runCatching { (catalogueSource as? HttpSource)?.getHomeUrl() }.getOrNull(), homeUrl = runCatching { (source as? HttpSource)?.getHomeUrl() }.getOrNull(),
baseUrl = runCatching { (catalogueSource as? HttpSource)?.baseUrl }.getOrNull(), baseUrl = runCatching { (source as? HttpSource)?.baseUrl }.getOrNull(),
) )
fun manga(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture<MangaNodeList> = fun manga(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture<MangaNodeList> =
@@ -77,7 +75,7 @@ class SourceType(
fun preferences(): List<Preference> = getSourcePreferencesRaw(id).map { preferenceOf(it) } fun preferences(): List<Preference> = getSourcePreferencesRaw(id).map { preferenceOf(it) }
fun filters(): List<Filter> = getCatalogueSourceOrStub(id).getFilterList().map { filterOf(it) } fun filters(): List<Filter> = getSourceOrStub(id).getFilterList().map { filterOf(it) }
fun meta(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture<List<SourceMetaType>> = fun meta(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture<List<SourceMetaType>> =
dataFetchingEnvironment.getValueFromDataLoader<Long, List<SourceMetaType>>("SourceMetaDataLoader", id) dataFetchingEnvironment.getValueFromDataLoader<Long, List<SourceMetaType>>("SourceMetaDataLoader", id)
@@ -86,8 +84,8 @@ class SourceType(
@Suppress("ktlint:standard:function-naming") @Suppress("ktlint:standard:function-naming")
fun SourceType(row: ResultRow): SourceType? { fun SourceType(row: ResultRow): SourceType? {
val catalogueSource = val catalogueSource =
GetCatalogueSource GetSource
.getCatalogueSourceOrNull(row[SourceTable.id].value) .getSourceOrNull(row[SourceTable.id].value)
?: return null ?: return null
val sourceExtension = val sourceExtension =
if (row.hasValue(ExtensionTable.id)) { if (row.hasValue(ExtensionTable.id)) {
@@ -298,7 +296,7 @@ data class FilterChange(
) )
fun updateFilterList( fun updateFilterList(
source: CatalogueSource, source: Source,
changes: List<FilterChange>?, changes: List<FilterChange>?,
): FilterList { ): FilterList {
val filterList = source.getFilterList() val filterList = source.getFilterList()

View File

@@ -7,7 +7,7 @@ package suwayomi.tachidesk.manga.impl
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.model.UpdateStrategy import eu.kanade.tachiyomi.source.model.UpdateStrategy
@@ -38,7 +38,7 @@ import org.jetbrains.exposed.v1.jdbc.update
import suwayomi.tachidesk.manga.impl.download.DownloadManager import suwayomi.tachidesk.manga.impl.download.DownloadManager
import suwayomi.tachidesk.manga.impl.download.DownloadManager.EnqueueInput import suwayomi.tachidesk.manga.impl.download.DownloadManager.EnqueueInput
import suwayomi.tachidesk.manga.impl.track.Track import suwayomi.tachidesk.manga.impl.track.Track
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub import suwayomi.tachidesk.manga.impl.util.source.GetSource.getSourceOrStub
import suwayomi.tachidesk.manga.model.dataclass.ChapterDataClass import suwayomi.tachidesk.manga.model.dataclass.ChapterDataClass
import suwayomi.tachidesk.manga.model.dataclass.MangaChapterDataClass import suwayomi.tachidesk.manga.model.dataclass.MangaChapterDataClass
import suwayomi.tachidesk.manga.model.dataclass.PaginatedList import suwayomi.tachidesk.manga.model.dataclass.PaginatedList
@@ -119,7 +119,7 @@ object Chapter {
transaction { transaction {
MangaTable.selectAll().where { MangaTable.id eq mangaId }.first() MangaTable.selectAll().where { MangaTable.id eq mangaId }.first()
} }
val source = getCatalogueSourceOrStub(mangaEntry[MangaTable.sourceReference]) val source = getSourceOrStub(mangaEntry[MangaTable.sourceReference])
val chapters = val chapters =
Manga Manga
@@ -139,7 +139,7 @@ object Chapter {
fun updateChapterListDatabase( fun updateChapterListDatabase(
mangaEntry: ResultRow, mangaEntry: ResultRow,
chapters: List<SChapter>, chapters: List<SChapter>,
source: CatalogueSource, source: Source,
): List<SChapter> { ): List<SChapter> {
val currentLatestChapterNumber = Manga.getLatestChapter(mangaEntry[MangaTable.id].value)?.chapterNumber ?: 0f val currentLatestChapterNumber = Manga.getLatestChapter(mangaEntry[MangaTable.id].value)?.chapterNumber ?: 0f
val numberOfCurrentChapters = getCountOfMangaChapters(mangaEntry[MangaTable.id].value) val numberOfCurrentChapters = getCountOfMangaChapters(mangaEntry[MangaTable.id].value)

View File

@@ -11,7 +11,7 @@ import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.HttpException import eu.kanade.tachiyomi.network.HttpException
import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.network.awaitSuccess import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.local.LocalSource import eu.kanade.tachiyomi.source.local.LocalSource
import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
@@ -41,8 +41,8 @@ import org.jetbrains.exposed.v1.jdbc.transactions.transaction
import org.jetbrains.exposed.v1.jdbc.update import org.jetbrains.exposed.v1.jdbc.update
import suwayomi.tachidesk.manga.impl.download.fileProvider.impl.MissingThumbnailException import suwayomi.tachidesk.manga.impl.download.fileProvider.impl.MissingThumbnailException
import suwayomi.tachidesk.manga.impl.util.network.await import suwayomi.tachidesk.manga.impl.util.network.await
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrNull import suwayomi.tachidesk.manga.impl.util.source.GetSource.getSourceOrNull
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub import suwayomi.tachidesk.manga.impl.util.source.GetSource.getSourceOrStub
import suwayomi.tachidesk.manga.impl.util.source.StubSource import suwayomi.tachidesk.manga.impl.util.source.StubSource
import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.clearCachedImage import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.clearCachedImage
import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.getImageResponse import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.getImageResponse
@@ -91,7 +91,7 @@ object Manga {
suspend fun fetchMangaAndChapters( suspend fun fetchMangaAndChapters(
mangaEntry: ResultRow, mangaEntry: ResultRow,
source: CatalogueSource, source: Source,
fetchDetails: Boolean, fetchDetails: Boolean,
fetchChapters: Boolean, fetchChapters: Boolean,
): SMangaUpdate { ): SMangaUpdate {
@@ -139,7 +139,7 @@ object Manga {
return mangaInfoMutex.get(mangaId) { Mutex() }.withLock { return mangaInfoMutex.get(mangaId) { Mutex() }.withLock {
val mangaEntry = val mangaEntry =
transaction { MangaTable.selectAll().where { MangaTable.id eq mangaId }.first() } transaction { MangaTable.selectAll().where { MangaTable.id eq mangaId }.first() }
val source = getCatalogueSourceOrNull(mangaEntry[MangaTable.sourceReference]) ?: return null val source = getSourceOrNull(mangaEntry[MangaTable.sourceReference]) ?: return null
val sManga = val sManga =
fetchMangaAndChapters( fetchMangaAndChapters(
mangaEntry, mangaEntry,
@@ -161,7 +161,7 @@ object Manga {
var mangaEntry = var mangaEntry =
transaction { MangaTable.selectAll().where { MangaTable.id eq mangaId }.first() } transaction { MangaTable.selectAll().where { MangaTable.id eq mangaId }.first() }
val source = val source =
getCatalogueSourceOrNull(mangaEntry[MangaTable.sourceReference]) getSourceOrNull(mangaEntry[MangaTable.sourceReference])
?: throw NullPointerException("Missing source ${mangaEntry[MangaTable.sourceReference]}") ?: throw NullPointerException("Missing source ${mangaEntry[MangaTable.sourceReference]}")
val mangaUpdate = val mangaUpdate =
fetchMangaAndChapters( fetchMangaAndChapters(
@@ -186,7 +186,7 @@ object Manga {
fun updateMangaDatabase( fun updateMangaDatabase(
mangaEntry: ResultRow, mangaEntry: ResultRow,
source: CatalogueSource, source: Source,
sManga: SManga, sManga: SManga,
): SManga { ): SManga {
transaction { transaction {
@@ -238,6 +238,7 @@ object Manga {
it[MangaTable.lastFetchedAt] = Instant.now().epochSecond it[MangaTable.lastFetchedAt] = Instant.now().epochSecond
it[MangaTable.updateStrategy] = sManga.update_strategy.name it[MangaTable.updateStrategy] = sManga.update_strategy.name
it[MangaTable.memo] = Json.encodeToString(sManga.memo)
} }
} }
@@ -412,7 +413,7 @@ object Manga {
val mangaEntry = transaction { MangaTable.selectAll().where { MangaTable.id eq mangaId }.first() } val mangaEntry = transaction { MangaTable.selectAll().where { MangaTable.id eq mangaId }.first() }
val sourceId = mangaEntry[MangaTable.sourceReference] val sourceId = mangaEntry[MangaTable.sourceReference]
return when (val source = getCatalogueSourceOrStub(sourceId)) { return when (val source = getSourceOrStub(sourceId)) {
is HttpSource -> { is HttpSource -> {
getImageResponse(cacheSaveDir, fileName) { getImageResponse(cacheSaveDir, fileName) {
fetchHttpSourceMangaThumbnail(source, mangaEntry) fetchHttpSourceMangaThumbnail(source, mangaEntry)

View File

@@ -19,7 +19,7 @@ import org.jetbrains.exposed.v1.jdbc.batchInsert
import org.jetbrains.exposed.v1.jdbc.selectAll import org.jetbrains.exposed.v1.jdbc.selectAll
import org.jetbrains.exposed.v1.jdbc.statements.toExecutable import org.jetbrains.exposed.v1.jdbc.statements.toExecutable
import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.jetbrains.exposed.v1.jdbc.transactions.transaction
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub import suwayomi.tachidesk.manga.impl.util.source.GetSource.getSourceOrStub
import suwayomi.tachidesk.manga.model.dataclass.PagedMangaListDataClass import suwayomi.tachidesk.manga.model.dataclass.PagedMangaListDataClass
import suwayomi.tachidesk.manga.model.table.MangaTable import suwayomi.tachidesk.manga.model.table.MangaTable
import suwayomi.tachidesk.manga.model.table.toDataClass import suwayomi.tachidesk.manga.model.table.toDataClass
@@ -36,7 +36,7 @@ object MangaList {
require(pageNum > 0) { require(pageNum > 0) {
"pageNum = $pageNum is not in valid range" "pageNum = $pageNum is not in valid range"
} }
val source = getCatalogueSourceOrStub(sourceId) val source = getSourceOrStub(sourceId)
val mangasPage = val mangasPage =
if (popular) { if (popular) {
source.getPopularManga(pageNum) source.getPopularManga(pageNum)

View File

@@ -21,7 +21,7 @@ import org.jetbrains.exposed.v1.jdbc.transactions.transaction
import org.jetbrains.exposed.v1.jdbc.update import org.jetbrains.exposed.v1.jdbc.update
import suwayomi.tachidesk.graphql.types.DownloadConversion import suwayomi.tachidesk.graphql.types.DownloadConversion
import suwayomi.tachidesk.manga.impl.util.getChapterCachePath import suwayomi.tachidesk.manga.impl.util.getChapterCachePath
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub import suwayomi.tachidesk.manga.impl.util.source.GetSource.getSourceOrStub
import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.getImageResponse import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.getImageResponse
import suwayomi.tachidesk.manga.impl.util.storage.ImageUtil import suwayomi.tachidesk.manga.impl.util.storage.ImageUtil
import suwayomi.tachidesk.manga.model.table.ChapterTable import suwayomi.tachidesk.manga.model.table.ChapterTable
@@ -118,7 +118,7 @@ object Page {
return imageFile.inputStream() to (ImageUtil.findImageType { imageFile.inputStream() }?.mime ?: "image/jpeg") return imageFile.inputStream() to (ImageUtil.findImageType { imageFile.inputStream() }?.mime ?: "image/jpeg")
} }
val source = getCatalogueSourceOrStub(mangaEntry[MangaTable.sourceReference]) val source = getSourceOrStub(mangaEntry[MangaTable.sourceReference])
source as HttpSource source as HttpSource
if (pageEntry[PageTable.imageUrl] == null) { if (pageEntry[PageTable.imageUrl] == null) {

View File

@@ -7,14 +7,14 @@ package suwayomi.tachidesk.manga.impl
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
import io.javalin.json.JsonMapper import io.javalin.json.JsonMapper
import io.javalin.json.fromJsonString import io.javalin.json.fromJsonString
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import suwayomi.tachidesk.manga.impl.MangaList.processEntries import suwayomi.tachidesk.manga.impl.MangaList.processEntries
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub import suwayomi.tachidesk.manga.impl.util.source.GetSource.getSourceOrStub
import suwayomi.tachidesk.manga.model.dataclass.PagedMangaListDataClass import suwayomi.tachidesk.manga.model.dataclass.PagedMangaListDataClass
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
@@ -24,7 +24,7 @@ object Search {
searchTerm: String, searchTerm: String,
pageNum: Int, pageNum: Int,
): PagedMangaListDataClass { ): PagedMangaListDataClass {
val source = getCatalogueSourceOrStub(sourceId) val source = getSourceOrStub(sourceId)
val searchManga = source.getSearchManga(pageNum, searchTerm, getFilterListOf(source)) val searchManga = source.getSearchManga(pageNum, searchTerm, getFilterListOf(source))
return searchManga.processEntries(sourceId) return searchManga.processEntries(sourceId)
} }
@@ -34,7 +34,7 @@ object Search {
pageNum: Int, pageNum: Int,
filter: FilterData, filter: FilterData,
): PagedMangaListDataClass { ): PagedMangaListDataClass {
val source = getCatalogueSourceOrStub(sourceId) val source = getSourceOrStub(sourceId)
val filterList = if (filter.filter != null) buildFilterList(sourceId, filter.filter) else source.getFilterList() val filterList = if (filter.filter != null) buildFilterList(sourceId, filter.filter) else source.getFilterList()
val searchManga = source.getSearchManga(pageNum, filter.searchTerm ?: "", filterList) val searchManga = source.getSearchManga(pageNum, filter.searchTerm ?: "", filterList)
return searchManga.processEntries(sourceId) return searchManga.processEntries(sourceId)
@@ -43,7 +43,7 @@ object Search {
private val filterListCache = mutableMapOf<Long, FilterList>() private val filterListCache = mutableMapOf<Long, FilterList>()
private fun getFilterListOf( private fun getFilterListOf(
source: CatalogueSource, source: Source,
reset: Boolean = false, reset: Boolean = false,
): FilterList { ): FilterList {
if (reset || !filterListCache.containsKey(source.id)) { if (reset || !filterListCache.containsKey(source.id)) {
@@ -56,7 +56,7 @@ object Search {
sourceId: Long, sourceId: Long,
reset: Boolean, reset: Boolean,
): List<FilterObject> { ): List<FilterObject> {
val source = getCatalogueSourceOrStub(sourceId) val source = getSourceOrStub(sourceId)
return getFilterListOf(source, reset).list.map { return getFilterListOf(source, reset).list.map {
FilterObject( FilterObject(
@@ -111,7 +111,7 @@ object Search {
sourceId: Long, sourceId: Long,
changes: List<FilterChange>, changes: List<FilterChange>,
) { ) {
val source = getCatalogueSourceOrStub(sourceId) val source = getSourceOrStub(sourceId)
val filterList = getFilterListOf(source, false) val filterList = getFilterListOf(source, false)
updateFilterList(filterList, changes) updateFilterList(filterList, changes)
} }
@@ -169,7 +169,7 @@ object Search {
sourceId: Long, sourceId: Long,
changes: List<FilterChange>, changes: List<FilterChange>,
): FilterList { ): FilterList {
val source = getCatalogueSourceOrStub(sourceId) val source = getSourceOrStub(sourceId)
val filterList = source.getFilterList() val filterList = source.getFilterList()
return updateFilterList(filterList, changes) return updateFilterList(filterList, changes)
} }

View File

@@ -26,10 +26,10 @@ import org.jetbrains.exposed.v1.jdbc.statements.toExecutable
import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.jetbrains.exposed.v1.jdbc.transactions.transaction
import suwayomi.tachidesk.manga.impl.Source.preferenceScreenMap import suwayomi.tachidesk.manga.impl.Source.preferenceScreenMap
import suwayomi.tachidesk.manga.impl.extension.Extension.proxyExtensionIconUrl import suwayomi.tachidesk.manga.impl.extension.Extension.proxyExtensionIconUrl
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrNull import suwayomi.tachidesk.manga.impl.util.source.GetSource.getSourceOrNull
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub import suwayomi.tachidesk.manga.impl.util.source.GetSource.getSourceOrStub
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.unregisterCatalogueSource import suwayomi.tachidesk.manga.impl.util.source.GetSource.unregisterSource
import suwayomi.tachidesk.manga.model.dataclass.ContentRating import suwayomi.tachidesk.manga.model.dataclass.ContentWarning
import suwayomi.tachidesk.manga.model.dataclass.SourceDataClass import suwayomi.tachidesk.manga.model.dataclass.SourceDataClass
import suwayomi.tachidesk.manga.model.table.ExtensionTable import suwayomi.tachidesk.manga.model.table.ExtensionTable
import suwayomi.tachidesk.manga.model.table.SourceMetaTable import suwayomi.tachidesk.manga.model.table.SourceMetaTable
@@ -43,7 +43,7 @@ object Source {
fun getSourceList(): List<SourceDataClass> { fun getSourceList(): List<SourceDataClass> {
return transaction { return transaction {
SourceTable.selectAll().mapNotNull { SourceTable.selectAll().mapNotNull {
val catalogueSource = getCatalogueSourceOrNull(it[SourceTable.id].value) ?: return@mapNotNull null val catalogueSource = getSourceOrNull(it[SourceTable.id].value) ?: return@mapNotNull null
val sourceExtension = ExtensionTable.selectAll().where { ExtensionTable.id eq it[SourceTable.extension] }.first() val sourceExtension = ExtensionTable.selectAll().where { ExtensionTable.id eq it[SourceTable.extension] }.first()
SourceDataClass( SourceDataClass(
@@ -53,7 +53,7 @@ object Source {
iconUrl = proxyExtensionIconUrl(sourceExtension[ExtensionTable.pkgName]), iconUrl = proxyExtensionIconUrl(sourceExtension[ExtensionTable.pkgName]),
supportsLatest = catalogueSource.supportsLatest, supportsLatest = catalogueSource.supportsLatest,
isConfigurable = catalogueSource is ConfigurableSource, isConfigurable = catalogueSource is ConfigurableSource,
isNsfw = it[SourceTable.contentRating] == ContentRating.PORNOGRAPHIC.ordinal, isNsfw = it[SourceTable.contentWarning] >= ContentWarning.MIXED.ordinal,
displayName = catalogueSource.toString(), displayName = catalogueSource.toString(),
baseUrl = runCatching { (catalogueSource as? HttpSource)?.baseUrl }.getOrNull(), baseUrl = runCatching { (catalogueSource as? HttpSource)?.baseUrl }.getOrNull(),
) )
@@ -64,7 +64,7 @@ object Source {
fun getSource(sourceId: Long): SourceDataClass? { // all the data extracted fresh form the source instance fun getSource(sourceId: Long): SourceDataClass? { // all the data extracted fresh form the source instance
return transaction { return transaction {
val source = SourceTable.selectAll().where { SourceTable.id eq sourceId }.firstOrNull() ?: return@transaction null val source = SourceTable.selectAll().where { SourceTable.id eq sourceId }.firstOrNull() ?: return@transaction null
val catalogueSource = getCatalogueSourceOrNull(sourceId) ?: return@transaction null val catalogueSource = getSourceOrNull(sourceId) ?: return@transaction null
val extension = ExtensionTable.selectAll().where { ExtensionTable.id eq source[SourceTable.extension] }.first() val extension = ExtensionTable.selectAll().where { ExtensionTable.id eq source[SourceTable.extension] }.first()
SourceDataClass( SourceDataClass(
@@ -74,7 +74,7 @@ object Source {
iconUrl = proxyExtensionIconUrl(extension[ExtensionTable.pkgName]), iconUrl = proxyExtensionIconUrl(extension[ExtensionTable.pkgName]),
supportsLatest = catalogueSource.supportsLatest, supportsLatest = catalogueSource.supportsLatest,
isConfigurable = catalogueSource is ConfigurableSource, isConfigurable = catalogueSource is ConfigurableSource,
isNsfw = source[SourceTable.contentRating] == ContentRating.PORNOGRAPHIC.ordinal, isNsfw = source[SourceTable.contentWarning] >= ContentWarning.MIXED.ordinal,
displayName = catalogueSource.toString(), displayName = catalogueSource.toString(),
baseUrl = runCatching { (catalogueSource as? HttpSource)?.baseUrl }.getOrNull(), baseUrl = runCatching { (catalogueSource as? HttpSource)?.baseUrl }.getOrNull(),
) )
@@ -107,7 +107,7 @@ object Source {
} }
fun getSourcePreferencesRaw(sourceId: Long): List<Preference> { fun getSourcePreferencesRaw(sourceId: Long): List<Preference> {
val source = getCatalogueSourceOrStub(sourceId) val source = getSourceOrStub(sourceId)
if (source is ConfigurableSource) { if (source is ConfigurableSource) {
val sourceShardPreferences = source.sourcePreferences() val sourceShardPreferences = source.sourcePreferences()
@@ -157,7 +157,7 @@ object Source {
pref.callChangeListener(newValue) pref.callChangeListener(newValue)
// must reload the source because a preference was changed // must reload the source because a preference was changed
unregisterCatalogueSource(sourceId) unregisterSource(sourceId)
} }
fun getSourcesMetaMaps(ids: List<Long>): Map<Long, Map<String, String>> = fun getSourcesMetaMaps(ids: List<Long>): Map<Long, Map<String, String>> =

View File

@@ -8,7 +8,6 @@ package suwayomi.tachidesk.manga.impl.backup.proto.handlers
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import eu.kanade.tachiyomi.source.model.UpdateStrategy import eu.kanade.tachiyomi.source.model.UpdateStrategy
import kotlinx.serialization.json.Json
import org.jetbrains.exposed.v1.core.ResultRow import org.jetbrains.exposed.v1.core.ResultRow
import org.jetbrains.exposed.v1.core.SortOrder import org.jetbrains.exposed.v1.core.SortOrder
import org.jetbrains.exposed.v1.core.and import org.jetbrains.exposed.v1.core.and
@@ -78,7 +77,7 @@ object BackupMangaHandler {
lastModifiedAt = mangaRow[MangaTable.lastModifiedAt], lastModifiedAt = mangaRow[MangaTable.lastModifiedAt],
version = mangaRow[MangaTable.version], version = mangaRow[MangaTable.version],
initialized = mangaRow[MangaTable.initialized], initialized = mangaRow[MangaTable.initialized],
memo = Json.encodeToString(mangaRow[MangaTable.memo]).encodeToByteArray(), memo = mangaRow[MangaTable.memo].encodeToByteArray(),
) )
val mangaId = mangaRow[MangaTable.id].value val mangaId = mangaRow[MangaTable.id].value
@@ -116,6 +115,7 @@ object BackupMangaHandler {
sourceOrder = chapters.size - it[ChapterTable.sourceOrder], sourceOrder = chapters.size - it[ChapterTable.sourceOrder],
lastModifiedAt = it[ChapterTable.lastModifiedAt], lastModifiedAt = it[ChapterTable.lastModifiedAt],
version = it[ChapterTable.version], version = it[ChapterTable.version],
memo = it[ChapterTable.memo].encodeToByteArray(),
).apply { ).apply {
if (flags.includeClientData) { if (flags.includeClientData) {
this.meta = chapterToMeta[it[ChapterTable.id].value] ?: emptyMap() this.meta = chapterToMeta[it[ChapterTable.id].value] ?: emptyMap()

View File

@@ -39,7 +39,7 @@ data class BackupManga(
@ProtoNumber(106) var lastModifiedAt: Long = 0, @ProtoNumber(106) var lastModifiedAt: Long = 0,
@ProtoNumber(109) var version: Long = 0, @ProtoNumber(109) var version: Long = 0,
@ProtoNumber(111) var initialized: Boolean = false, @ProtoNumber(111) var initialized: Boolean = false,
@ProtoNumber(13) var memo: ByteArray = JsonObjectEmptyBytes, @ProtoNumber(112) var memo: ByteArray = JsonObjectEmptyBytes,
// suwayomi // suwayomi
@ProtoNumber(9000) var meta: Map<String, String> = emptyMap(), @ProtoNumber(9000) var meta: Map<String, String> = emptyMap(),
) )

View File

@@ -23,7 +23,7 @@ import org.jetbrains.exposed.v1.jdbc.selectAll
import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.jetbrains.exposed.v1.jdbc.transactions.transaction
import org.jetbrains.exposed.v1.jdbc.update import org.jetbrains.exposed.v1.jdbc.update
import suwayomi.tachidesk.manga.impl.ChapterDownloadHelper import suwayomi.tachidesk.manga.impl.ChapterDownloadHelper
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub import suwayomi.tachidesk.manga.impl.util.source.GetSource.getSourceOrStub
import suwayomi.tachidesk.manga.model.dataclass.ChapterDataClass import suwayomi.tachidesk.manga.model.dataclass.ChapterDataClass
import suwayomi.tachidesk.manga.model.table.ChapterTable import suwayomi.tachidesk.manga.model.table.ChapterTable
import suwayomi.tachidesk.manga.model.table.MangaTable import suwayomi.tachidesk.manga.model.table.MangaTable
@@ -77,7 +77,7 @@ suspend fun refreshChapterPageList(
return mutex.withLock { return mutex.withLock {
val chapterEntry = existingChapterEntry ?: transaction { ChapterTable.selectAll().where { ChapterTable.id eq chapterId }.first() } val chapterEntry = existingChapterEntry ?: transaction { ChapterTable.selectAll().where { ChapterTable.id eq chapterId }.first() }
val mangaEntry = transaction { MangaTable.selectAll().where { MangaTable.id eq mangaId }.first() } val mangaEntry = transaction { MangaTable.selectAll().where { MangaTable.id eq mangaId }.first() }
val source = getCatalogueSourceOrStub(mangaEntry[MangaTable.sourceReference]) val source = getSourceOrStub(mangaEntry[MangaTable.sourceReference])
val pageList = val pageList =
source source

View File

@@ -10,7 +10,6 @@ package suwayomi.tachidesk.manga.impl.extension
import android.net.Uri import android.net.Uri
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceFactory import eu.kanade.tachiyomi.source.SourceFactory
import eu.kanade.tachiyomi.source.local.LocalSource import eu.kanade.tachiyomi.source.local.LocalSource
@@ -32,17 +31,19 @@ import suwayomi.tachidesk.manga.impl.util.PackageTools
import suwayomi.tachidesk.manga.impl.util.PackageTools.EXTENSION_FEATURE import suwayomi.tachidesk.manga.impl.util.PackageTools.EXTENSION_FEATURE
import suwayomi.tachidesk.manga.impl.util.PackageTools.LIB_VERSION_MAX import suwayomi.tachidesk.manga.impl.util.PackageTools.LIB_VERSION_MAX
import suwayomi.tachidesk.manga.impl.util.PackageTools.LIB_VERSION_MIN import suwayomi.tachidesk.manga.impl.util.PackageTools.LIB_VERSION_MIN
import suwayomi.tachidesk.manga.impl.util.PackageTools.METADATA_CONTENT_WARNING
import suwayomi.tachidesk.manga.impl.util.PackageTools.METADATA_EXTENSION_LIB
import suwayomi.tachidesk.manga.impl.util.PackageTools.METADATA_NAME
import suwayomi.tachidesk.manga.impl.util.PackageTools.METADATA_NSFW import suwayomi.tachidesk.manga.impl.util.PackageTools.METADATA_NSFW
import suwayomi.tachidesk.manga.impl.util.PackageTools.METADATA_SOURCE_CLASS import suwayomi.tachidesk.manga.impl.util.PackageTools.METADATA_SOURCE_CLASS
import suwayomi.tachidesk.manga.impl.util.PackageTools.dex2jar import suwayomi.tachidesk.manga.impl.util.PackageTools.dex2jar
import suwayomi.tachidesk.manga.impl.util.PackageTools.getPackageInfo import suwayomi.tachidesk.manga.impl.util.PackageTools.getPackageInfo
import suwayomi.tachidesk.manga.impl.util.PackageTools.loadExtensionSources import suwayomi.tachidesk.manga.impl.util.PackageTools.loadExtensionSources
import suwayomi.tachidesk.manga.impl.util.network.await import suwayomi.tachidesk.manga.impl.util.network.await
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource import suwayomi.tachidesk.manga.impl.util.source.GetSource
import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.clearCachedImage import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.clearCachedImage
import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.getImageResponse import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.getImageResponse
import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.saveImage import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.saveImage
import suwayomi.tachidesk.manga.model.dataclass.ContentRating
import suwayomi.tachidesk.manga.model.table.ExtensionTable import suwayomi.tachidesk.manga.model.table.ExtensionTable
import suwayomi.tachidesk.manga.model.table.SourceTable import suwayomi.tachidesk.manga.model.table.SourceTable
import suwayomi.tachidesk.server.ApplicationDirs import suwayomi.tachidesk.server.ApplicationDirs
@@ -151,7 +152,19 @@ object Extension {
// throw Exception("This apk is not a signed with the official tachiyomi signature") // throw Exception("This apk is not a signed with the official tachiyomi signature")
// } // }
val isNsfw = packageInfo.applicationInfo.metaData.getString(METADATA_NSFW) == "1" var contentWarning = packageInfo.applicationInfo.metaData.getInt(METADATA_CONTENT_WARNING)
if (contentWarning == 0) {
contentWarning = packageInfo.applicationInfo.metaData
.getString(METADATA_CONTENT_WARNING)
?.toIntOrNull()
?: 0
if (contentWarning == 0) {
contentWarning = packageInfo.applicationInfo.metaData
.getString(METADATA_NSFW)
?.toIntOrNull()
?: 0
}
}
val className = val className =
packageInfo.packageName + packageInfo.applicationInfo.metaData.getString(METADATA_SOURCE_CLASS) packageInfo.packageName + packageInfo.applicationInfo.metaData.getString(METADATA_SOURCE_CLASS)
@@ -160,7 +173,7 @@ object Extension {
dex2jar(apkFilePath, jarFilePath, fileNameWithoutType) dex2jar(apkFilePath, jarFilePath, fileNameWithoutType)
extractAssetsFromApk(apkFilePath, jarFilePath) extractAssetsFromApk(apkFilePath, jarFilePath)
extractAndCacheApkIcon(apkFilePath, apkName) extractAndCacheApkIcon(apkFilePath, packageInfo.packageName)
// clean up // clean up
File(apkFilePath).delete() File(apkFilePath).delete()
@@ -168,12 +181,12 @@ object Extension {
try { try {
// collect sources from the extension // collect sources from the extension
val extensionMainClassInstance = loadExtensionSources(jarFilePath, className) val extensionMainClassInstance = loadExtensionSources(jarFilePath, className)
val sources: List<CatalogueSource> = val sources: List<Source> =
when (extensionMainClassInstance) { when (extensionMainClassInstance) {
is Source -> listOf(extensionMainClassInstance) is Source -> listOf(extensionMainClassInstance)
is SourceFactory -> extensionMainClassInstance.createSources() is SourceFactory -> extensionMainClassInstance.createSources()
else -> throw RuntimeException("Unknown source class type! ${extensionMainClassInstance.javaClass}") else -> throw RuntimeException("Unknown source class type! ${extensionMainClassInstance.javaClass}")
}.map { it as CatalogueSource } }
val langs = sources.map { it.lang }.toSet() val langs = sources.map { it.lang }.toSet()
val extensionLang = val extensionLang =
@@ -184,10 +197,17 @@ object Extension {
} }
val extensionName = val extensionName =
packageInfo.applicationInfo.nonLocalizedLabel packageInfo.applicationInfo.metaData.getString(METADATA_NAME)
?: packageInfo.applicationInfo.nonLocalizedLabel
.toString() .toString()
.substringAfter("Tachiyomi: ") .substringAfter("Tachiyomi: ")
val extensionLibVersion =
packageInfo.applicationInfo.metaData
.getString(METADATA_EXTENSION_LIB)
.takeUnless { it == "0" }
?: packageInfo.versionName.substringBeforeLast('.')
// update extension info // update extension info
transaction { transaction {
if (ExtensionTable.selectAll().where { ExtensionTable.pkgName eq pkgName }.firstOrNull() == null) { if (ExtensionTable.selectAll().where { ExtensionTable.pkgName eq pkgName }.firstOrNull() == null) {
@@ -197,14 +217,9 @@ object Extension {
it[this.pkgName] = packageInfo.packageName it[this.pkgName] = packageInfo.packageName
it[versionName] = packageInfo.versionName it[versionName] = packageInfo.versionName
it[versionCode] = packageInfo.versionCode.toLong() it[versionCode] = packageInfo.versionCode.toLong()
it[extensionLib] = extensionLibVersion
it[lang] = extensionLang it[lang] = extensionLang
// todo will change it[this.contentWarning] = contentWarning
it[contentRating] =
if (isNsfw) {
ContentRating.PORNOGRAPHIC.ordinal
} else {
ContentRating.SAFE.ordinal
}
} }
} }
@@ -229,7 +244,7 @@ object Extension {
it[name] = httpSource.name it[name] = httpSource.name
it[lang] = httpSource.lang it[lang] = httpSource.lang
it[extension] = extensionId it[extension] = extensionId
it[contentRating] = if (isNsfw) ContentRating.PORNOGRAPHIC.ordinal else ContentRating.SAFE.ordinal it[this.contentWarning] = contentWarning
} }
logger.debug { "Installed source ${httpSource.name} (${httpSource.lang}) with id:${httpSource.id}" } logger.debug { "Installed source ${httpSource.name} (${httpSource.lang}) with id:${httpSource.id}" }
} }
@@ -250,7 +265,7 @@ object Extension {
private fun extractAndCacheApkIcon( private fun extractAndCacheApkIcon(
apkFilePath: String, apkFilePath: String,
apkName: String, pkgName: String,
) { ) {
val iconCacheDir = "${applicationDirs.extensionsRoot}/icon" val iconCacheDir = "${applicationDirs.extensionsRoot}/icon"
try { try {
@@ -263,15 +278,15 @@ object Extension {
?.first ?.first
} }
if (iconData == null) { if (iconData == null) {
logger.warn { "No icon found in APK $apkName" } logger.warn { "No icon found in APK $pkgName" }
return return
} }
File(iconCacheDir).mkdirs() File(iconCacheDir).mkdirs()
clearCachedImage(iconCacheDir, apkName) clearCachedImage(iconCacheDir, pkgName)
saveImage("$iconCacheDir/$apkName", iconData.inputStream(), null) saveImage("$iconCacheDir/$pkgName", iconData.inputStream(), null)
} catch (e: Exception) { } catch (e: Exception) {
logger.warn(e) { "Failed to extract icon from APK $apkName" } logger.warn(e) { "Failed to extract icon from APK $pkgName" }
} }
} }
@@ -364,7 +379,7 @@ object Extension {
SourceTable.deleteWhere { SourceTable.extension eq extensionId } SourceTable.deleteWhere { SourceTable.extension eq extensionId }
if (extensionRecord[ExtensionTable.isObsolete]) { if (extensionRecord[ExtensionTable.isObsolete] || extensionRecord[ExtensionTable.apkUrl] == null) {
ExtensionTable.deleteWhere { ExtensionTable.pkgName eq pkgName } ExtensionTable.deleteWhere { ExtensionTable.pkgName eq pkgName }
} else { } else {
ExtensionTable.update({ ExtensionTable.pkgName eq pkgName }) { ExtensionTable.update({ ExtensionTable.pkgName eq pkgName }) {
@@ -382,7 +397,7 @@ object Extension {
PackageTools.jarLoaderMap.remove(jarPath)?.close() PackageTools.jarLoaderMap.remove(jarPath)?.close()
// clear all loaded sources // clear all loaded sources
sources.forEach { GetCatalogueSource.unregisterCatalogueSource(it) } sources.forEach { GetSource.unregisterSource(it) }
File(jarPath).delete() File(jarPath).delete()
} }
@@ -397,7 +412,7 @@ object Extension {
it[versionName] = targetExtension.versionName it[versionName] = targetExtension.versionName
it[versionCode] = targetExtension.versionCode it[versionCode] = targetExtension.versionCode
it[lang] = targetExtension.lang it[lang] = targetExtension.lang
it[contentRating] = targetExtension.contentRating.ordinal it[contentWarning] = targetExtension.contentWarning.ordinal
it[iconUrl] = targetExtension.iconUrl it[iconUrl] = targetExtension.iconUrl
it[hasUpdate] = false it[hasUpdate] = false
} }

View File

@@ -15,6 +15,9 @@ import kotlinx.serialization.decodeFromByteArray
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.okio.decodeFromBufferedSource import kotlinx.serialization.json.okio.decodeFromBufferedSource
import kotlinx.serialization.protobuf.ProtoBuf import kotlinx.serialization.protobuf.ProtoBuf
import okio.BufferedSource
import okio.buffer
import okio.gzip
import org.jetbrains.exposed.v1.core.eq import org.jetbrains.exposed.v1.core.eq
import org.jetbrains.exposed.v1.core.inList import org.jetbrains.exposed.v1.core.inList
import org.jetbrains.exposed.v1.jdbc.deleteWhere import org.jetbrains.exposed.v1.jdbc.deleteWhere
@@ -41,45 +44,45 @@ object ExtensionStoreService {
val protoBuf: ProtoBuf by injectLazy() val protoBuf: ProtoBuf by injectLazy()
val json: Json by injectLazy() val json: Json by injectLazy()
suspend fun fetch(indexUrl: String): ExtensionStore = fetch(indexUrl, forceV2 = false) suspend fun fetch(indexUrl: String): ExtensionStore {
var updatedIndexUrl: String = indexUrl
private suspend fun fetch(
indexUrl: String,
forceV2: Boolean,
): ExtensionStore {
var updatedIndexUrl = indexUrl
return try { return try {
network.client.newCall(GET(indexUrl)).awaitSuccess().body.source().use { source -> val response = network.client.newCall(GET(updatedIndexUrl)).awaitSuccess()
try { response.body.source().decompressIfGzipped().use { source ->
protoBuf.decodeFromByteArray<NetworkExtensionStore>(source.peek().readByteArray()) val networkStore =
} catch (e: IllegalArgumentException) { when (source.peek().readByte()) {
logger.debug { "Failed to decode as protobuf, trying JSON" } // "[..."
try { 0x5B.toByte() -> {
json.decodeFromBufferedSource<NetworkExtensionStore>(source.peek()) run {
} catch (e: IllegalArgumentException) {
if (forceV2) throw e
logger.debug { "Failed to decode as NetworkExtensionStore, trying LegacyExtensionRepo" }
val legacyIndex =
try {
json.decodeFromBufferedSource<NetworkLegacyExtensionRepo>(source.peek())
} catch (e: IllegalArgumentException) {
if (!indexUrl.endsWith("/index.min.json")) { if (!indexUrl.endsWith("/index.min.json")) {
throw e throw IllegalArgumentException("Provided legacy store url is not valid")
} }
logger.debug { "Checking for new repo.json format" }
updatedIndexUrl = indexUrl.replace("/index.min.json", "/repo.json") updatedIndexUrl = indexUrl.replace("/index.min.json", "/repo.json")
network.client.newCall(GET(updatedIndexUrl)).awaitSuccess().body.source().use { network.client.newCall(GET(updatedIndexUrl)).awaitSuccess().body.source().use {
json.decodeFromBufferedSource<NetworkLegacyExtensionRepo>(it) json.decodeFromBufferedSource<NetworkLegacyExtensionRepo>(it)
} }
} }
}
if (legacyIndex.indexV2 != null) { // "{..."
return fetch(legacyIndex.indexV2, forceV2 = true) 0x7B.toByte() -> {
} else { try {
legacyIndex json.decodeFromBufferedSource<NetworkLegacyExtensionRepo>(source.peek())
} catch (_: IllegalArgumentException) {
json.decodeFromBufferedSource<NetworkExtensionStore>(source)
} }
} }
}.toExtensionStore(updatedIndexUrl)
else -> {
protoBuf.decodeFromByteArray<NetworkExtensionStore>(source.readByteArray())
}
}
if (networkStore is NetworkLegacyExtensionRepo && networkStore.indexV2 != null) {
return fetch(networkStore.indexV2)
}
networkStore.toExtensionStore(updatedIndexUrl)
} }
} catch (e: Exception) { } catch (e: Exception) {
if (e is CancellationException) throw e if (e is CancellationException) throw e
@@ -105,6 +108,7 @@ object ExtensionStoreService {
it[contactDiscord] = store.contact.discord it[contactDiscord] = store.contact.discord
it[indexUrl] = store.indexUrl it[indexUrl] = store.indexUrl
it[isLegacy] = store.isLegacy it[isLegacy] = store.isLegacy
it[extensionListUrl] = store.extensionListUrl
} }
} else { } else {
ExtensionStoreTable.update({ ExtensionStoreTable.indexUrl eq store.indexUrl }) { ExtensionStoreTable.update({ ExtensionStoreTable.indexUrl eq store.indexUrl }) {
@@ -114,6 +118,7 @@ object ExtensionStoreService {
it[contactWebsite] = store.contact.website it[contactWebsite] = store.contact.website
it[contactDiscord] = store.contact.discord it[contactDiscord] = store.contact.discord
it[isLegacy] = store.isLegacy it[isLegacy] = store.isLegacy
it[extensionListUrl] = store.extensionListUrl
} }
} }
} }
@@ -210,17 +215,33 @@ object ExtensionStoreService {
suspend fun getExtensions(store: ExtensionStore): List<ExtensionInfo> { suspend fun getExtensions(store: ExtensionStore): List<ExtensionInfo> {
val extensions = val extensions =
if (!store.isLegacy) { if (store.extensionListUrl != null) {
val response = network.client.newCall(GET(store.indexUrl)).awaitSuccess() val response = network.client.newCall(GET(store.extensionListUrl)).awaitSuccess()
response.body response.body.source().decompressIfGzipped().use { source ->
.source() when (source.peek().readByte()) {
.use { source -> // "{..."
try { 0x7B.toByte() -> {
protoBuf.decodeFromByteArray<NetworkExtensionStore>(source.peek().readByteArray()) json.decodeFromBufferedSource<NetworkExtensionStore.ExtensionList>(source)
} catch (_: IllegalArgumentException) { }
json.decodeFromBufferedSource<NetworkExtensionStore>(source.peek())
else -> {
protoBuf.decodeFromByteArray<NetworkExtensionStore.ExtensionList>(
source.readByteArray(),
)
} }
}.toExtensionInfos(store) }.toExtensionInfos(store)
}
} else if (!store.isLegacy) {
val response = network.client.newCall(GET(store.indexUrl)).awaitSuccess()
response.body.source().decompressIfGzipped().use { source ->
when (source.peek().readByte()) {
// "{..."
0x7B.toByte() -> json.decodeFromBufferedSource<NetworkExtensionStore>(source)
else -> protoBuf.decodeFromByteArray<NetworkExtensionStore>(source.readByteArray())
}.extensionList!!
.toExtensionInfos(store)
}
} else { } else {
val storeBaseUrl = store.indexUrl.removeSuffix("/repo.json") val storeBaseUrl = store.indexUrl.removeSuffix("/repo.json")
val response = network.client.newCall(GET("$storeBaseUrl/index.min.json")).awaitSuccess() val response = network.client.newCall(GET("$storeBaseUrl/index.min.json")).awaitSuccess()
@@ -232,4 +253,17 @@ object ExtensionStoreService {
} }
return extensions return extensions
} }
private fun BufferedSource.decompressIfGzipped(): BufferedSource {
val isGzip =
peek().use { peeked ->
try {
peeked.readShort().toInt() == 0x1f8b
} catch (_: Exception) {
false
}
}
return if (isGzip) gzip().buffer() else this
}
} }

View File

@@ -22,7 +22,7 @@ import org.jetbrains.exposed.v1.jdbc.statements.toExecutable
import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.jetbrains.exposed.v1.jdbc.transactions.transaction
import org.jetbrains.exposed.v1.jdbc.update import org.jetbrains.exposed.v1.jdbc.update
import suwayomi.tachidesk.manga.impl.extension.Extension.proxyExtensionIconUrl import suwayomi.tachidesk.manga.impl.extension.Extension.proxyExtensionIconUrl
import suwayomi.tachidesk.manga.model.dataclass.ContentRating import suwayomi.tachidesk.manga.model.dataclass.ContentWarning
import suwayomi.tachidesk.manga.model.dataclass.ExtensionDataClass import suwayomi.tachidesk.manga.model.dataclass.ExtensionDataClass
import suwayomi.tachidesk.manga.model.dataclass.ExtensionInfo import suwayomi.tachidesk.manga.model.dataclass.ExtensionInfo
import suwayomi.tachidesk.manga.model.table.ExtensionTable import suwayomi.tachidesk.manga.model.table.ExtensionTable
@@ -81,7 +81,7 @@ object ExtensionsList {
versionName = it[ExtensionTable.versionName], versionName = it[ExtensionTable.versionName],
versionCode = it[ExtensionTable.versionCode].toInt(), versionCode = it[ExtensionTable.versionCode].toInt(),
lang = it[ExtensionTable.lang], lang = it[ExtensionTable.lang],
isNsfw = it[ExtensionTable.contentRating] == ContentRating.PORNOGRAPHIC.ordinal, isNsfw = it[ExtensionTable.contentWarning] >= ContentWarning.MIXED.ordinal,
installed = it[ExtensionTable.isInstalled], installed = it[ExtensionTable.isInstalled],
hasUpdate = it[ExtensionTable.hasUpdate], hasUpdate = it[ExtensionTable.hasUpdate],
obsolete = it[ExtensionTable.isObsolete], obsolete = it[ExtensionTable.isObsolete],
@@ -173,7 +173,7 @@ object ExtensionsList {
this[ExtensionTable.versionName] = foundExtension.versionName this[ExtensionTable.versionName] = foundExtension.versionName
this[ExtensionTable.versionCode] = foundExtension.versionCode this[ExtensionTable.versionCode] = foundExtension.versionCode
this[ExtensionTable.lang] = foundExtension.lang this[ExtensionTable.lang] = foundExtension.lang
this[ExtensionTable.contentRating] = foundExtension.contentRating.ordinal this[ExtensionTable.contentWarning] = foundExtension.contentWarning.ordinal
this[ExtensionTable.apkUrl] = foundExtension.apkUrl this[ExtensionTable.apkUrl] = foundExtension.apkUrl
this[ExtensionTable.iconUrl] = foundExtension.iconUrl this[ExtensionTable.iconUrl] = foundExtension.iconUrl
} }
@@ -190,7 +190,7 @@ object ExtensionsList {
this[ExtensionTable.versionName] = foundExtension.versionName this[ExtensionTable.versionName] = foundExtension.versionName
this[ExtensionTable.versionCode] = foundExtension.versionCode this[ExtensionTable.versionCode] = foundExtension.versionCode
this[ExtensionTable.lang] = foundExtension.lang this[ExtensionTable.lang] = foundExtension.lang
this[ExtensionTable.contentRating] = foundExtension.contentRating.ordinal this[ExtensionTable.contentWarning] = foundExtension.contentWarning.ordinal
this[ExtensionTable.apkUrl] = foundExtension.apkUrl this[ExtensionTable.apkUrl] = foundExtension.apkUrl
this[ExtensionTable.iconUrl] = foundExtension.iconUrl this[ExtensionTable.iconUrl] = foundExtension.iconUrl
} }

View File

@@ -11,7 +11,7 @@ import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonNames import kotlinx.serialization.json.JsonNames
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
import suwayomi.tachidesk.manga.model.dataclass.ContentRating import suwayomi.tachidesk.manga.model.dataclass.ContentWarning
import suwayomi.tachidesk.manga.model.dataclass.ExtensionInfo import suwayomi.tachidesk.manga.model.dataclass.ExtensionInfo
import suwayomi.tachidesk.manga.model.dataclass.ExtensionSource import suwayomi.tachidesk.manga.model.dataclass.ExtensionSource
import suwayomi.tachidesk.manga.model.dataclass.ExtensionStore import suwayomi.tachidesk.manga.model.dataclass.ExtensionStore
@@ -23,7 +23,8 @@ data class NetworkExtensionStore(
@ProtoNumber(2) val badgeLabel: String, @ProtoNumber(2) val badgeLabel: String,
@ProtoNumber(3) val signingKey: String, @ProtoNumber(3) val signingKey: String,
@ProtoNumber(4) val contact: Contact, @ProtoNumber(4) val contact: Contact,
@ProtoNumber(5) val extensions: List<Extension>, @ProtoNumber(101) val extensionList: ExtensionList?,
@ProtoNumber(102) val extensionListUrl: String?,
) : BaseNetworkExtensionStore { ) : BaseNetworkExtensionStore {
@Serializable @Serializable
data class Contact( data class Contact(
@@ -31,6 +32,11 @@ data class NetworkExtensionStore(
@ProtoNumber(2) val discord: String?, @ProtoNumber(2) val discord: String?,
) )
@Serializable
data class ExtensionList(
@ProtoNumber(1) val extensions: List<Extension>,
)
@Serializable @Serializable
data class Extension( data class Extension(
@ProtoNumber(1) val name: String, @ProtoNumber(1) val name: String,
@@ -39,7 +45,8 @@ data class NetworkExtensionStore(
@ProtoNumber(4) val extensionLib: String, @ProtoNumber(4) val extensionLib: String,
@ProtoNumber(5) val versionCode: Long, @ProtoNumber(5) val versionCode: Long,
@ProtoNumber(6) val versionName: String, @ProtoNumber(6) val versionName: String,
@ProtoNumber(7) val sources: List<Source>, @ProtoNumber(7) val contentWarning: ContentWarning,
@ProtoNumber(8) val sources: List<Source>,
) )
@Serializable @Serializable
@@ -55,27 +62,27 @@ data class NetworkExtensionStore(
@ProtoNumber(3) val language: String, @ProtoNumber(3) val language: String,
@ProtoNumber(4) val homeUrl: String = "", @ProtoNumber(4) val homeUrl: String = "",
@ProtoNumber(5) val mirrorUrls: List<String> = emptyList(), @ProtoNumber(5) val mirrorUrls: List<String> = emptyList(),
@ProtoNumber(6) val contentRating: ContentRating = ContentRating.SAFE, // @ProtoNumber(6) val contentWarning: ContentWarning = ContentWarning.SAFE,
@ProtoNumber(7) val message: String? = null, @ProtoNumber(7) val message: String? = null,
) )
@Serializable @Serializable
enum class ContentRating { enum class ContentWarning {
@ProtoNumber(0) @ProtoNumber(0)
@JsonNames("CONTENT_RATING_SAFE") @JsonNames("CONTENT_WARNING_UNSPECIFIED")
SAFE, UNSPECIFIED,
@ProtoNumber(1) @ProtoNumber(1)
@JsonNames("CONTENT_RATING_SUGGESTIVE") @JsonNames("CONTENT_WARNING_SAFE")
SUGGESTIVE, SAFE,
@ProtoNumber(2) @ProtoNumber(2)
@JsonNames("CONTENT_RATING_EROTICA") @JsonNames("CONTENT_WARNING_MIXED")
EROTICA, MIXED,
@ProtoNumber(3) @ProtoNumber(3)
@JsonNames("CONTENT_RATING_PORNOGRAPHIC") @JsonNames("CONTENT_WARNING_NSFW")
PORNOGRAPHIC, NSFW,
} }
override fun toExtensionStore(indexUrl: String): ExtensionStore = override fun toExtensionStore(indexUrl: String): ExtensionStore =
@@ -90,10 +97,11 @@ data class NetworkExtensionStore(
discord = contact.discord, discord = contact.discord,
), ),
isLegacy = false, isLegacy = false,
extensionListUrl = extensionListUrl,
) )
} }
fun NetworkExtensionStore.toExtensionInfos(store: ExtensionStore): List<ExtensionInfo> = fun NetworkExtensionStore.ExtensionList.toExtensionInfos(store: ExtensionStore): List<ExtensionInfo> =
extensions.map { extension -> extensions.map { extension ->
val lang = extension.sources.map { it.language }.toSet() val lang = extension.sources.map { it.language }.toSet()
ExtensionInfo( ExtensionInfo(
@@ -106,13 +114,15 @@ fun NetworkExtensionStore.toExtensionInfos(store: ExtensionStore): List<Extensio
versionCode = extension.versionCode, versionCode = extension.versionCode,
versionName = extension.versionName, versionName = extension.versionName,
lang = if (lang.size == 1) lang.first() else "all", lang = if (lang.size == 1) lang.first() else "all",
contentRating = contentWarning =
when (extension.sources.maxOfOrNull { it.contentRating }) { when (extension.contentWarning) {
NetworkExtensionStore.ContentRating.SAFE -> ContentRating.SAFE NetworkExtensionStore.ContentWarning.SAFE,
NetworkExtensionStore.ContentRating.SUGGESTIVE -> ContentRating.SUGGESTIVE NetworkExtensionStore.ContentWarning.UNSPECIFIED,
NetworkExtensionStore.ContentRating.EROTICA -> ContentRating.EROTICA -> ContentWarning.SAFE
NetworkExtensionStore.ContentRating.PORNOGRAPHIC -> ContentRating.PORNOGRAPHIC
null -> ContentRating.SAFE NetworkExtensionStore.ContentWarning.MIXED -> ContentWarning.MIXED
NetworkExtensionStore.ContentWarning.NSFW -> ContentWarning.NSFW
}, },
sources = sources =
extension.sources.map { source -> extension.sources.map { source ->
@@ -122,12 +132,15 @@ fun NetworkExtensionStore.toExtensionInfos(store: ExtensionStore): List<Extensio
lang = source.language, lang = source.language,
homeUrl = source.homeUrl, homeUrl = source.homeUrl,
message = source.message, message = source.message,
contentRating = contentWarning =
when (source.contentRating) { when (extension.contentWarning) { // todo source.contentWarning
NetworkExtensionStore.ContentRating.SAFE -> ContentRating.SAFE NetworkExtensionStore.ContentWarning.SAFE,
NetworkExtensionStore.ContentRating.SUGGESTIVE -> ContentRating.SUGGESTIVE NetworkExtensionStore.ContentWarning.UNSPECIFIED,
NetworkExtensionStore.ContentRating.EROTICA -> ContentRating.EROTICA -> ContentWarning.SAFE
NetworkExtensionStore.ContentRating.PORNOGRAPHIC -> ContentRating.PORNOGRAPHIC
NetworkExtensionStore.ContentWarning.MIXED -> ContentWarning.MIXED
NetworkExtensionStore.ContentWarning.NSFW -> ContentWarning.NSFW
}, },
) )
}, },

View File

@@ -9,7 +9,7 @@ package suwayomi.tachidesk.manga.impl.extension.github
import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import suwayomi.tachidesk.manga.model.dataclass.ContentRating import suwayomi.tachidesk.manga.model.dataclass.ContentWarning
import suwayomi.tachidesk.manga.model.dataclass.ExtensionInfo import suwayomi.tachidesk.manga.model.dataclass.ExtensionInfo
import suwayomi.tachidesk.manga.model.dataclass.ExtensionSource import suwayomi.tachidesk.manga.model.dataclass.ExtensionSource
import suwayomi.tachidesk.manga.model.dataclass.ExtensionStore import suwayomi.tachidesk.manga.model.dataclass.ExtensionStore
@@ -49,7 +49,7 @@ fun NetworkLegacyExtension.toExtensionInfo(
versionCode = code, versionCode = code,
versionName = version, versionName = version,
lang = lang, lang = lang,
contentRating = if (nsfw == 1) ContentRating.PORNOGRAPHIC else ContentRating.SAFE, contentWarning = if (nsfw == 1) ContentWarning.MIXED else ContentWarning.SAFE,
sources = sources =
if (sources.isNullOrEmpty()) { if (sources.isNullOrEmpty()) {
listOf( listOf(
@@ -59,7 +59,7 @@ fun NetworkLegacyExtension.toExtensionInfo(
lang = lang, lang = lang,
homeUrl = "", homeUrl = "",
message = null, message = null,
contentRating = if (nsfw == 1) ContentRating.PORNOGRAPHIC else ContentRating.SAFE, contentWarning = if (nsfw == 1) ContentWarning.MIXED else ContentWarning.SAFE,
), ),
) )
} else { } else {
@@ -70,7 +70,7 @@ fun NetworkLegacyExtension.toExtensionInfo(
lang = source.lang, lang = source.lang,
homeUrl = source.baseUrl, homeUrl = source.baseUrl,
message = null, message = null,
contentRating = if (nsfw == 1) ContentRating.PORNOGRAPHIC else ContentRating.SAFE, contentWarning = if (nsfw == 1) ContentWarning.MIXED else ContentWarning.SAFE,
) )
} }
}, },

View File

@@ -36,5 +36,6 @@ data class NetworkLegacyExtensionRepo(
discord = null, discord = null,
), ),
isLegacy = true, isLegacy = true,
extensionListUrl = null,
) )
} }

View File

@@ -222,6 +222,48 @@ object Track {
} }
} }
fun bindTrackRecord(
mangaId: Int,
trackRecordId: Int,
): Int {
val (trackRecord, existingTrackRecord) =
transaction {
val trackRecord =
TrackRecordTable
.selectAll()
.where {
(TrackRecordTable.id eq trackRecordId)
}.first()
.toTrackRecordDataClass()
val existingTrackRecord =
TrackRecordTable
.selectAll()
.where {
(TrackRecordTable.mangaId eq mangaId) and (TrackRecordTable.trackerId eq trackRecord.trackerId)
}.firstOrNull()
?.toTrackRecordDataClass()
trackRecord to existingTrackRecord
}
val isAlreadyBoundToManga = trackRecord.mangaId == mangaId
if (isAlreadyBoundToManga) {
return trackRecordId
}
val hasRecordForTracker = existingTrackRecord != null
if (hasRecordForTracker) {
val updatedTrack = trackRecord.copy(id = existingTrackRecord.id, mangaId = mangaId).toTrack()
return updateTrackRecord(updatedTrack)
}
val newTrack = trackRecord.copy(mangaId = mangaId).toTrack()
return insertTrackRecord(newTrack)
}
suspend fun refresh(recordId: Int) { suspend fun refresh(recordId: Int) {
val recordDb = val recordDb =
transaction { transaction {
@@ -423,9 +465,9 @@ object Track {
} }
} }
fun updateTrackRecord(track: Track) = updateTrackRecords(listOf(track)) fun updateTrackRecord(track: Track): Int = updateTrackRecords(listOf(track)).first()
fun updateTrackRecords(tracks: List<Track>) = fun updateTrackRecords(tracks: List<Track>): List<Int> =
transaction { transaction {
if (tracks.isNotEmpty()) { if (tracks.isNotEmpty()) {
BatchUpdateStatement(TrackRecordTable) BatchUpdateStatement(TrackRecordTable)
@@ -447,6 +489,8 @@ object Track {
}.toExecutable() }.toExecutable()
.execute(this@transaction) .execute(this@transaction)
} }
tracks.map { it.id!! }
} }
fun insertTrackRecord(track: Track): Int = insertTrackRecords(listOf(track)).first() fun insertTrackRecord(track: Track): Int = insertTrackRecords(listOf(track)).first()

View File

@@ -11,7 +11,7 @@ import io.github.oshai.kotlinlogging.KotlinLogging
import org.jetbrains.exposed.v1.core.eq import org.jetbrains.exposed.v1.core.eq
import org.jetbrains.exposed.v1.jdbc.selectAll import org.jetbrains.exposed.v1.jdbc.selectAll
import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.jetbrains.exposed.v1.jdbc.transactions.transaction
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource import suwayomi.tachidesk.manga.impl.util.source.GetSource
import suwayomi.tachidesk.manga.model.table.ChapterTable import suwayomi.tachidesk.manga.model.table.ChapterTable
import suwayomi.tachidesk.manga.model.table.MangaTable import suwayomi.tachidesk.manga.model.table.MangaTable
import suwayomi.tachidesk.server.ApplicationDirs import suwayomi.tachidesk.server.ApplicationDirs
@@ -37,7 +37,7 @@ private fun getMangaDir(
private fun getMangaDir(mangaId: Int): String = private fun getMangaDir(mangaId: Int): String =
transaction { transaction {
val mangaEntry = MangaTable.selectAll().where { MangaTable.id eq mangaId }.first() val mangaEntry = MangaTable.selectAll().where { MangaTable.id eq mangaId }.first()
val source = GetCatalogueSource.getCatalogueSourceOrStub(mangaEntry[MangaTable.sourceReference]) val source = GetSource.getSourceOrStub(mangaEntry[MangaTable.sourceReference])
getMangaDir(mangaEntry[MangaTable.title], source.toString()) getMangaDir(mangaEntry[MangaTable.title], source.toString())
} }

View File

@@ -40,6 +40,11 @@ object PackageTools {
const val METADATA_SOURCE_CLASS = "tachiyomi.extension.class" const val METADATA_SOURCE_CLASS = "tachiyomi.extension.class"
const val METADATA_SOURCE_FACTORY = "tachiyomi.extension.factory" const val METADATA_SOURCE_FACTORY = "tachiyomi.extension.factory"
const val METADATA_NSFW = "tachiyomi.extension.nsfw" const val METADATA_NSFW = "tachiyomi.extension.nsfw"
const val METADATA_NAME = "tachiyomix.name"
const val METADATA_EXTENSION_LIB = "tachiyomix.extensionLib"
const val METADATA_CONTENT_WARNING = "tachiyomix.contentWarning"
const val LIB_VERSION_MIN = 1.3 const val LIB_VERSION_MIN = 1.3
const val LIB_VERSION_MAX = 1.6 const val LIB_VERSION_MAX = 1.6

View File

@@ -7,7 +7,6 @@ package suwayomi.tachidesk.manga.impl.util.source
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceFactory import eu.kanade.tachiyomi.source.SourceFactory
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
@@ -22,14 +21,14 @@ import suwayomi.tachidesk.server.ApplicationDirs
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
object GetCatalogueSource { object GetSource {
private val logger = KotlinLogging.logger { } private val logger = KotlinLogging.logger { }
private val sourceCache = ConcurrentHashMap<Long, CatalogueSource>() private val sourceCache = ConcurrentHashMap<Long, Source>()
private val applicationDirs: ApplicationDirs by injectLazy() private val applicationDirs: ApplicationDirs by injectLazy()
private fun getCatalogueSource(sourceId: Long): CatalogueSource? { private fun getSource(sourceId: Long): Source? {
val cachedResult: CatalogueSource? = sourceCache[sourceId] val cachedResult: Source? = sourceCache[sourceId]
if (cachedResult != null) { if (cachedResult != null) {
return cachedResult return cachedResult
} }
@@ -62,25 +61,25 @@ object GetCatalogueSource {
return sourceCache[sourceId]!! return sourceCache[sourceId]!!
} }
fun getCatalogueSourceOrNull(sourceId: Long): CatalogueSource? = fun getSourceOrNull(sourceId: Long): Source? =
try { try {
getCatalogueSource(sourceId) getSource(sourceId)
} catch (e: Exception) { } catch (e: Exception) {
logger.warn(e) { "getCatalogueSource($sourceId) failed" } logger.warn(e) { "getCatalogueSource($sourceId) failed" }
null null
} }
fun getCatalogueSourceOrStub(sourceId: Long): CatalogueSource = getCatalogueSourceOrNull(sourceId) ?: StubSource(sourceId) fun getSourceOrStub(sourceId: Long): Source = getSourceOrNull(sourceId) ?: StubSource(sourceId)
fun registerCatalogueSource(sourcePair: Pair<Long, CatalogueSource>) { fun registerSource(sourcePair: Pair<Long, Source>) {
sourceCache += sourcePair sourceCache += sourcePair
} }
fun unregisterCatalogueSource(sourceId: Long) { fun unregisterSource(sourceId: Long) {
sourceCache.remove(sourceId) sourceCache.remove(sourceId)
} }
fun unregisterAllCatalogueSources() { fun unregisterAllSources() {
(sourceCache - 0L).forEach { (id, _) -> (sourceCache - 0L).forEach { (id, _) ->
sourceCache.remove(id) sourceCache.remove(id)
} }

View File

@@ -17,7 +17,7 @@ data class ExtensionInfo(
val versionCode: Long, val versionCode: Long,
val versionName: String, val versionName: String,
val lang: String, val lang: String,
val contentRating: ContentRating, val contentWarning: ContentWarning,
val sources: List<ExtensionSource>, val sources: List<ExtensionSource>,
) )
@@ -27,17 +27,16 @@ data class ExtensionSource(
val lang: String, val lang: String,
val homeUrl: String, val homeUrl: String,
val message: String?, val message: String?,
val contentRating: ContentRating, val contentWarning: ContentWarning,
) )
enum class ContentRating { enum class ContentWarning {
SAFE, SAFE,
SUGGESTIVE, MIXED,
EROTICA, NSFW,
PORNOGRAPHIC,
; ;
companion object { companion object {
fun valueOf(contentRating: Int) = entries.find { it.ordinal == contentRating } ?: SAFE fun valueOf(contentWarning: Int) = entries.find { it.ordinal == contentWarning } ?: SAFE
} }
} }

View File

@@ -14,6 +14,7 @@ data class ExtensionStore(
val signingKey: String, val signingKey: String,
val contact: Contact, val contact: Contact,
val isLegacy: Boolean, val isLegacy: Boolean,
val extensionListUrl: String?,
) { ) {
data class Contact( data class Contact(
val website: String, val website: String,

View File

@@ -10,11 +10,12 @@ package suwayomi.tachidesk.manga.model.table
import org.jetbrains.exposed.v1.core.dao.id.IntIdTable import org.jetbrains.exposed.v1.core.dao.id.IntIdTable
object ExtensionStoreTable : IntIdTable() { object ExtensionStoreTable : IntIdTable() {
val indexUrl = varchar("index_url", 2048) val indexUrl = varchar("index_url", 2048).uniqueIndex()
val name = varchar("name", 256) val name = varchar("name", 256)
val badgeLabel = varchar("badge_label", 32) val badgeLabel = varchar("badge_label", 32)
val signingKey = varchar("signing_key", 512) val signingKey = varchar("signing_key", 512)
val contactWebsite = varchar("contact_website", 2048) val contactWebsite = varchar("contact_website", 2048)
val contactDiscord = varchar("contact_discord", 2048).nullable() val contactDiscord = varchar("contact_discord", 2048).nullable()
val isLegacy = bool("is_legacy").default(false) val isLegacy = bool("is_legacy").default(false)
val extensionListUrl = varchar("extension_list_url", 2048).nullable()
} }

View File

@@ -11,7 +11,7 @@ import org.jetbrains.exposed.v1.core.dao.id.IntIdTable
object ExtensionTable : IntIdTable() { object ExtensionTable : IntIdTable() {
val apkName = varchar("apk_name", 1024).nullable() val apkName = varchar("apk_name", 1024).nullable()
val storeIndexUrl = varchar("store_index_url", 2048).nullable() val storeIndexUrl = varchar("store_index_url", 2048).nullable().index()
// default is the local source icon from tachiyomi // default is the local source icon from tachiyomi
@Suppress("ktlint:standard:max-line-length") @Suppress("ktlint:standard:max-line-length")
@@ -23,12 +23,12 @@ object ExtensionTable : IntIdTable() {
val name = varchar("name", 128) val name = varchar("name", 128)
val pkgName = varchar("pkg_name", 128) val pkgName = varchar("pkg_name", 128)
val apkUrl = varchar("apk_url", 2048) val apkUrl = varchar("apk_url", 2048).nullable()
val extensionLib = varchar("extension_lib", 16).nullable() val extensionLib = varchar("extension_lib", 16).nullable()
val versionName = varchar("version_name", 16) val versionName = varchar("version_name", 16)
val versionCode = long("version_code") val versionCode = long("version_code")
val lang = varchar("lang", 32) val lang = varchar("lang", 32)
val contentRating = integer("content_rating") val contentWarning = integer("content_warning")
val isInstalled = bool("is_installed").default(false) val isInstalled = bool("is_installed").default(false)
val hasUpdate = bool("has_update").default(false) val hasUpdate = bool("has_update").default(false)

View File

@@ -8,13 +8,11 @@ package suwayomi.tachidesk.manga.model.table
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import org.jetbrains.exposed.v1.core.dao.id.IdTable import org.jetbrains.exposed.v1.core.dao.id.IdTable
import suwayomi.tachidesk.manga.model.table.columns.unlimitedVarchar
object SourceTable : IdTable<Long>() { object SourceTable : IdTable<Long>() {
override val id = long("id").entityId() override val id = long("id").entityId()
val name = varchar("name", 128) val name = varchar("name", 128)
val lang = varchar("lang", 32) val lang = varchar("lang", 32)
val extension = reference("extension", ExtensionTable) val extension = reference("extension", ExtensionTable)
val message = unlimitedVarchar("message").nullable() val contentWarning = integer("content_warning").default(0)
val contentRating = integer("content_rating").default(0)
} }

View File

@@ -9,7 +9,6 @@ import org.jetbrains.exposed.v1.core.SortOrder
import org.jetbrains.exposed.v1.core.alias import org.jetbrains.exposed.v1.core.alias
import org.jetbrains.exposed.v1.core.and import org.jetbrains.exposed.v1.core.and
import org.jetbrains.exposed.v1.core.eq import org.jetbrains.exposed.v1.core.eq
import org.jetbrains.exposed.v1.core.greater
import org.jetbrains.exposed.v1.core.inList import org.jetbrains.exposed.v1.core.inList
import org.jetbrains.exposed.v1.core.inSubQuery import org.jetbrains.exposed.v1.core.inSubQuery
import org.jetbrains.exposed.v1.core.intLiteral import org.jetbrains.exposed.v1.core.intLiteral
@@ -23,7 +22,7 @@ import org.jetbrains.exposed.v1.jdbc.andWhere
import org.jetbrains.exposed.v1.jdbc.select import org.jetbrains.exposed.v1.jdbc.select
import org.jetbrains.exposed.v1.jdbc.transactions.transaction import org.jetbrains.exposed.v1.jdbc.transactions.transaction
import suwayomi.tachidesk.manga.impl.MangaList.insertOrUpdate import suwayomi.tachidesk.manga.impl.MangaList.insertOrUpdate
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource import suwayomi.tachidesk.manga.impl.util.source.GetSource
import suwayomi.tachidesk.manga.model.dataclass.toGenreList import suwayomi.tachidesk.manga.model.dataclass.toGenreList
import suwayomi.tachidesk.manga.model.table.CategoryMangaTable import suwayomi.tachidesk.manga.model.table.CategoryMangaTable
import suwayomi.tachidesk.manga.model.table.CategoryTable import suwayomi.tachidesk.manga.model.table.CategoryTable
@@ -232,7 +231,7 @@ object MangaRepository {
pageNum: Int, pageNum: Int,
sort: String, sort: String,
): Pair<List<OpdsMangaAcqEntry>, Boolean> { ): Pair<List<OpdsMangaAcqEntry>, Boolean> {
val source = GetCatalogueSource.getCatalogueSourceOrStub(sourceId) val source = GetSource.getSourceOrStub(sourceId)
val mangasPage: MangasPage = val mangasPage: MangasPage =
if (sort == "latest" && source.supportsLatest) { if (sort == "latest" && source.supportsLatest) {
source.getLatestUpdates(pageNum) source.getLatestUpdates(pageNum)

View File

@@ -9,7 +9,7 @@ package suwayomi.tachidesk.server.database.migration
import de.neonew.exposed.migrations.helpers.SQLMigration import de.neonew.exposed.migrations.helpers.SQLMigration
import suwayomi.tachidesk.graphql.types.DatabaseType import suwayomi.tachidesk.graphql.types.DatabaseType
import suwayomi.tachidesk.manga.model.dataclass.ContentRating import suwayomi.tachidesk.manga.model.dataclass.ContentWarning
import suwayomi.tachidesk.server.database.migration.helpers.MAYBE_TYPE_PREFIX import suwayomi.tachidesk.server.database.migration.helpers.MAYBE_TYPE_PREFIX
import suwayomi.tachidesk.server.database.migration.helpers.UNLIMITED_TEXT import suwayomi.tachidesk.server.database.migration.helpers.UNLIMITED_TEXT
import suwayomi.tachidesk.server.database.migration.helpers.toSqlName import suwayomi.tachidesk.server.database.migration.helpers.toSqlName
@@ -34,6 +34,7 @@ class M0057_AddNewExtensionApiFields : SQLMigration() {
} }
} }
ALTER TABLE EXTENSION ALTER COLUMN store_index_url ${MAYBE_TYPE_PREFIX}VARCHAR(2048); ALTER TABLE EXTENSION ALTER COLUMN store_index_url ${MAYBE_TYPE_PREFIX}VARCHAR(2048);
CREATE INDEX extension_store_index_url ON EXTENSION (store_index_url);
ALTER TABLE EXTENSION ALTER COLUMN version_code ${MAYBE_TYPE_PREFIX}BIGINT; ALTER TABLE EXTENSION ALTER COLUMN version_code ${MAYBE_TYPE_PREFIX}BIGINT;
ALTER TABLE EXTENSION ALTER COLUMN apk_name DROP NOT NULL; ALTER TABLE EXTENSION ALTER COLUMN apk_name DROP NOT NULL;
${ ${
@@ -43,12 +44,11 @@ class M0057_AddNewExtensionApiFields : SQLMigration() {
} }
} }
ALTER TABLE EXTENSION ADD COLUMN apk_url VARCHAR(2048); ALTER TABLE EXTENSION ADD COLUMN apk_url VARCHAR(2048);
ALTER TABLE EXTENSION ADD COLUMN content_rating INTEGER DEFAULT 0; ALTER TABLE EXTENSION ADD COLUMN content_warning INTEGER DEFAULT 0;
UPDATE EXTENSION SET content_rating = ${ContentRating.PORNOGRAPHIC.ordinal} WHERE is_nsfw = TRUE; UPDATE EXTENSION SET content_warning = ${ContentWarning.MIXED.ordinal} WHERE is_nsfw = TRUE;
ALTER TABLE EXTENSION DROP COLUMN is_nsfw; ALTER TABLE EXTENSION DROP COLUMN is_nsfw;
ALTER TABLE SOURCE ADD COLUMN message $UNLIMITED_TEXT; ALTER TABLE SOURCE ADD COLUMN content_warning INTEGER DEFAULT 0;
ALTER TABLE SOURCE ADD COLUMN content_rating INTEGER DEFAULT 0; UPDATE SOURCE SET content_warning = ${ContentWarning.MIXED.ordinal} WHERE is_nsfw = TRUE;
UPDATE SOURCE SET content_rating = ${ContentRating.PORNOGRAPHIC.ordinal} WHERE is_nsfw = TRUE;
ALTER TABLE SOURCE DROP COLUMN is_nsfw; ALTER TABLE SOURCE DROP COLUMN is_nsfw;

View File

@@ -14,13 +14,14 @@ import org.jetbrains.exposed.v1.core.dao.id.IntIdTable
@Suppress("ClassName", "unused") @Suppress("ClassName", "unused")
class M0058_AddExtensionStore : AddTableMigration() { class M0058_AddExtensionStore : AddTableMigration() {
private class ExtensionStoreTable : IntIdTable() { private class ExtensionStoreTable : IntIdTable() {
val indexUrl = varchar("index_url", 2048) val indexUrl = varchar("index_url", 2048).uniqueIndex()
val name = varchar("name", 256) val name = varchar("name", 256)
val badgeLabel = varchar("badge_label", 32) val badgeLabel = varchar("badge_label", 32)
val signingKey = varchar("signing_key", 512) val signingKey = varchar("signing_key", 512)
val contactWebsite = varchar("contact_website", 2048) val contactWebsite = varchar("contact_website", 2048)
val contactDiscord = varchar("contact_discord", 2048).nullable() val contactDiscord = varchar("contact_discord", 2048).nullable()
val isLegacy = bool("is_legacy").default(false) val isLegacy = bool("is_legacy").default(false)
val extensionListUrl = varchar("extension_list_url", 2048).nullable()
} }
override val tables: Array<Table> override val tables: Array<Table>

View File

@@ -14,7 +14,7 @@ import org.koin.core.context.stopKoin
import suwayomi.tachidesk.manga.impl.Source import suwayomi.tachidesk.manga.impl.Source
import suwayomi.tachidesk.manga.impl.extension.Extension import suwayomi.tachidesk.manga.impl.extension.Extension
import suwayomi.tachidesk.manga.impl.extension.ExtensionsList import suwayomi.tachidesk.manga.impl.extension.ExtensionsList
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource import suwayomi.tachidesk.manga.impl.util.source.GetSource
import suwayomi.tachidesk.server.applicationSetup import suwayomi.tachidesk.server.applicationSetup
import suwayomi.tachidesk.server.settings.SettingsRegistry import suwayomi.tachidesk.server.settings.SettingsRegistry
import suwayomi.tachidesk.test.BASE_PATH import suwayomi.tachidesk.test.BASE_PATH
@@ -51,7 +51,7 @@ class CloudFlareTest {
Source Source
.getSourceList() .getSourceList()
.firstNotNullOf { it.id.toLong().takeIf { it == 3122156392225024195L } } .firstNotNullOf { it.id.toLong().takeIf { it == 3122156392225024195L } }
.let(GetCatalogueSource::getCatalogueSourceOrNull) as HttpSource .let(GetSource::getSourceOrNull) as HttpSource
} }
setLoggingEnabled(true) setLoggingEnabled(true)
} }

View File

@@ -28,7 +28,7 @@ import suwayomi.tachidesk.manga.impl.extension.Extension.installExtension
import suwayomi.tachidesk.manga.impl.extension.Extension.uninstallExtension import suwayomi.tachidesk.manga.impl.extension.Extension.uninstallExtension
import suwayomi.tachidesk.manga.impl.extension.Extension.updateExtension import suwayomi.tachidesk.manga.impl.extension.Extension.updateExtension
import suwayomi.tachidesk.manga.impl.extension.ExtensionsList.getExtensionList import suwayomi.tachidesk.manga.impl.extension.ExtensionsList.getExtensionList
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrNull import suwayomi.tachidesk.manga.impl.util.source.GetSource.getSourceOrNull
import suwayomi.tachidesk.manga.model.dataclass.ExtensionDataClass import suwayomi.tachidesk.manga.model.dataclass.ExtensionDataClass
import suwayomi.tachidesk.server.applicationSetup import suwayomi.tachidesk.server.applicationSetup
import suwayomi.tachidesk.server.settings.SettingsRegistry import suwayomi.tachidesk.server.settings.SettingsRegistry
@@ -82,7 +82,7 @@ class TestExtensionCompatibility {
.filter { .filter {
// filter local source // filter local source
it.id.toLong() != 0L it.id.toLong() != 0L
}.map { getCatalogueSourceOrNull(it.id.toLong())!! as HttpSource } }.map { getSourceOrNull(it.id.toLong())!! as HttpSource }
} }
setLoggingEnabled(true) setLoggingEnabled(true)
File("$BASE_PATH/sources.txt").writeText(sources.joinToString("\n") { "${it.name} - ${it.lang.uppercase()} - ${it.id}" }) File("$BASE_PATH/sources.txt").writeText(sources.joinToString("\n") { "${it.name} - ${it.lang.uppercase()} - ${it.id}" })

View File

@@ -24,8 +24,8 @@ import suwayomi.tachidesk.manga.impl.Search.SerializableGroup
import suwayomi.tachidesk.manga.impl.Search.getFilterList import suwayomi.tachidesk.manga.impl.Search.getFilterList
import suwayomi.tachidesk.manga.impl.Search.setFilter import suwayomi.tachidesk.manga.impl.Search.setFilter
import suwayomi.tachidesk.manga.impl.Search.sourceSearch import suwayomi.tachidesk.manga.impl.Search.sourceSearch
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.registerCatalogueSource import suwayomi.tachidesk.manga.impl.util.source.GetSource.registerSource
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.unregisterCatalogueSource import suwayomi.tachidesk.manga.impl.util.source.GetSource.unregisterSource
import suwayomi.tachidesk.manga.impl.util.source.StubSource import suwayomi.tachidesk.manga.impl.util.source.StubSource
import suwayomi.tachidesk.test.ApplicationTest import suwayomi.tachidesk.test.ApplicationTest
import suwayomi.tachidesk.test.createSMangas import suwayomi.tachidesk.test.createSMangas
@@ -53,7 +53,7 @@ class SearchTest : ApplicationTest() {
@BeforeAll @BeforeAll
fun setup() { fun setup() {
registerCatalogueSource(sourceId to source) registerSource(sourceId to source)
this.source.mangas = createSMangas(mangasCount) this.source.mangas = createSMangas(mangasCount)
} }
@@ -70,7 +70,7 @@ class SearchTest : ApplicationTest() {
@AfterAll @AfterAll
fun teardown() { fun teardown() {
unregisterCatalogueSource(this.sourceId) unregisterSource(this.sourceId)
} }
} }
@@ -347,7 +347,7 @@ class FilterListTest : ApplicationTest() {
private fun registerSource(sourceClass: KClass<*>): EmptyFilterListSource = private fun registerSource(sourceClass: KClass<*>): EmptyFilterListSource =
synchronized(sourceClass) { synchronized(sourceClass) {
val source = sourceClass.primaryConstructor!!.call(sourceCount) as EmptyFilterListSource val source = sourceClass.primaryConstructor!!.call(sourceCount) as EmptyFilterListSource
registerCatalogueSource(sourceCount to source) registerSource(sourceCount to source)
sourceCount++ sourceCount++
source source
} }
@@ -355,7 +355,7 @@ class FilterListTest : ApplicationTest() {
@AfterAll @AfterAll
@JvmStatic @JvmStatic
fun teardown() { fun teardown() {
(0 until sourceCount).forEach { unregisterCatalogueSource(it) } (0 until sourceCount).forEach { unregisterSource(it) }
} }
} }
} }