Compare commits

..

33 Commits

Author SHA1 Message Date
Mitchell Syer
f748bc7f6d Merge branch 'master' into extensions_1.6 2026-06-18 15:02:54 -04:00
Syer10
11ff4bb280 Remove replaceWith and add specific description for GQL APIs 2026-06-18 14:58:19 -04:00
Mitchell Syer
388586f23b Update server/server-config/src/main/kotlin/suwayomi/tachidesk/server/ServerConfig.kt
Co-authored-by: Constantin Piber <59023762+cpiber@users.noreply.github.com>
2026-06-18 14:55:29 -04:00
Syer10
b8ffbebc76 Optimize Imports 2026-06-17 22:39:09 -04:00
Syer10
3048df307d Use syncDbToPrefs in ExtensionStoreMutation 2026-06-17 22:38:46 -04:00
Syer10
3b3770cb3d Exclude ServerConfig.extensionStores from GraphQL 2026-06-17 21:38:30 -04:00
Syer10
9667aeba18 Use a single version of ContentRating 2026-06-17 21:29:14 -04:00
Syer10
6c8a024b0f Add ExtenionStores to the fetchExtensions result since its possible for the stores to change. 2026-06-17 21:17:55 -04:00
Syer10
5b2613dad3 Lint 2026-06-17 21:15:44 -04:00
Constantin Piber
396cfa734a fix: re-sync (#2121) 2026-06-17 17:22:54 -04:00
Syer10
a1fdf6d77a Improve extension store sync 2026-06-17 16:12:51 -04:00
Syer10
07ae17105b Add ContentRatingFilter 2026-06-17 16:02:14 -04:00
Syer10
b8772f60bf Optimize import fixes 2026-06-17 14:49:57 -04:00
Syer10
733b9c9919 Proper extension store queries 2026-06-17 14:47:28 -04:00
Syer10
0b0c056bcb Optimize imports and fix unchecked cast warning 2026-06-17 14:13:57 -04:00
Syer10
aff95bfc37 Lint 2026-06-17 14:06:06 -04:00
Syer10
00bc3e39b6 Lint 2026-06-17 14:04:02 -04:00
Syer10
e9c2cc49a6 Fix SearchTest 2026-06-17 13:57:58 -04:00
Syer10
ea310ba54b No magic numbers for ContentRating, improves safety for future versions of extension api 2026-06-17 13:53:54 -04:00
Syer10
72347f45cc Simplify isNsfw in SourceType 2026-06-17 13:44:02 -04:00
Syer10
1e73e526c6 Simplify ContentRating in Source.kt 2026-06-17 13:43:02 -04:00
Syer10
8fd0fdba08 Simplify deprecated isNsfw in SourceQuery 2026-06-17 13:42:15 -04:00
Syer10
4b61d375ed Fixes 2026-06-17 13:39:53 -04:00
Syer10
3cf4cf6cf8 Improve Fetch Extension Store 2026-06-17 13:31:31 -04:00
Mitchell Syer
74ade8a3a3 Update docs/Configuring-Suwayomi‐Server.md
Co-authored-by: Constantin Piber <59023762+cpiber@users.noreply.github.com>
2026-06-17 13:26:09 -04:00
Syer10
3bb2e4329e Use EMPTY JsonObject 2026-06-17 13:25:31 -04:00
Syer10
33ec15c136 Simplify fetching manga and chapters 2026-06-17 13:24:38 -04:00
Syer10
3a78453a02 Docs 2026-06-16 23:00:06 -04:00
Syer10
b7c259a4cb Test build fix 2026-06-16 22:55:15 -04:00
Syer10
41ef220a0b Implement extension store 2026-06-16 22:38:03 -04:00
Syer10
85fe9802e2 Minor fixes 2026-06-15 20:45:43 -04:00
Syer10
676aed14c0 Changelog 2026-06-15 20:13:18 -04:00
Syer10
ceac5f74c4 Non-Extension Index changes for 1.6 2026-06-15 20:11:55 -04:00
62 changed files with 322 additions and 474 deletions

View File

@@ -42,7 +42,7 @@ body:
label: Suwayomi-Server version
description: You can find your Suwayomi-Server version in **More → About**.
placeholder: |
Example: "v2.3.2223"
Example: "v2.2.2100"
validations:
required: true
@@ -143,13 +143,11 @@ body:
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.
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).
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)
required: true
- 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
- label: I have updated to the **[latest version](https://github.com/suwayomi/suwayomi-server/releases/latest)**.
required: true
- label: I have filled out all of the requested information in this form, including specific version numbers.
required: true

View File

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

View File

@@ -6,33 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [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
- (**Sync**) Added [SyncYomi](https://github.com/syncyomi/syncyomi) support
- (**OPDS**) Add option to skip chapter metadata feed providing direct stream/download links
- (**Extension/API**) Support Extensions API v1.6
- (**Tracker/API**) Add mutation to bind existing track record
### Changed
- (**Database/H2**) Use the latest H2 database engine
@@ -449,7 +426,6 @@ Huge thanks to @martinek who pulled the most of the weight this release!
<!-- 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: 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
@@ -476,8 +452,7 @@ Huge thanks to @martinek who pulled the most of the weight this release!
<!-- SERVER LINKS -->
[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
[unreleased]: https://github.com/suwayomi/suwayomi-server/compare/v2.2.2100...HEAD
[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.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"
// should be bumped with each stable release
val getTachideskVersion = { "v2.3.${getCommitCount()}" }
val getTachideskVersion = { "v2.2.${getCommitCount()}" }
val webUIRevisionTag = "r3147"
val webUIRevisionTag = "r3136"
val webviewJbrRelease = "jbr-release-25.0.3b508.4"

View File

@@ -47,7 +47,6 @@ okhttp-core = { module = "com.squareup.okhttp3:okhttp", 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-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"
# Javalin api
@@ -229,7 +228,6 @@ okhttp = [
"okhttp-logging",
"okhttp-dnsoverhttps",
"okhttp-brotli",
"okhttp-zstd",
]
javalin = [
"javalin-core",

View File

@@ -8,7 +8,7 @@
<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_sources_title">Alle Quellen</string>
<string name="opds_feeds_genres_title">Genren</string>
<string name="opds_feeds_genres_title">Genres</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_languages_title">Sprachen</string>
@@ -122,7 +122,4 @@
<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_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>

View File

@@ -122,6 +122,4 @@
<string name="login_label_login">Σύνδεση</string>
<string name="login_placeholder_username">Πληκτρολόγησε όνομα χρήστη...</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>

View File

@@ -122,6 +122,4 @@
<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_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>

View File

@@ -122,7 +122,4 @@
<string name="login_label_login">Se connecter</string>
<string name="login_placeholder_username">Tapez le nom d\'utilisateur…</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>

View File

@@ -122,6 +122,4 @@
<string name="login_label_login">Accedi</string>
<string name="login_placeholder_username">Digita il nome utente...</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>

View File

@@ -60,7 +60,4 @@
<string name="opds_feeds_library_sources_title">ソース</string>
<string name="opds_feeds_library_sources_entry_content">ソース別にライブラリ内のマンガを閲覧</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>

View File

@@ -76,6 +76,4 @@
<string name="opds_facet_filter_all">Wszystkie</string>
<string name="opds_facet_filter_downloaded">Pobrane</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>

View File

@@ -122,6 +122,4 @@
<string name="login_label_login">Entrar</string>
<string name="login_placeholder_username">Digite o nome de usuário...</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>

View File

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

View File

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

View File

@@ -122,6 +122,4 @@
<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_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>

View File

@@ -122,7 +122,4 @@
<string name="login_placeholder_username">输入用户名…</string>
<string name="login_placeholder_password">密匙…</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>

View File

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

View File

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

View File

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

View File

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

View File

@@ -95,7 +95,6 @@ class ExtensionStoreMutation {
contactDiscord = it.contactDiscord,
indexUrl = it.indexUrl,
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.manga.impl.MangaList.insertOrUpdate
import suwayomi.tachidesk.manga.impl.Source
import suwayomi.tachidesk.manga.impl.util.source.GetSource
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource
import suwayomi.tachidesk.manga.model.table.MangaTable
import suwayomi.tachidesk.manga.model.table.SourceMetaTable
import suwayomi.tachidesk.manga.model.table.SourceTable
@@ -256,7 +256,7 @@ class SourceMutation {
val (clientMutationId, sourceId, type, page, query, filters) = input
return future {
val source = GetSource.getSourceOrNull(sourceId)!!
val source = GetCatalogueSource.getCatalogueSourceOrNull(sourceId)!!
val mangasPage =
when (type) {
FetchSourceMangaType.SEARCH -> {

View File

@@ -148,36 +148,6 @@ 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(
val clientMutationId: String? = null,
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 suwayomi.tachidesk.graphql.directives.RequireAuth
import suwayomi.tachidesk.graphql.queries.filter.BooleanFilter
import suwayomi.tachidesk.graphql.queries.filter.ContentWarningFilter
import suwayomi.tachidesk.graphql.queries.filter.ContentRatingFilter
import suwayomi.tachidesk.graphql.queries.filter.Filter
import suwayomi.tachidesk.graphql.queries.filter.HasGetOp
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.types.ExtensionNodeList
import suwayomi.tachidesk.graphql.types.ExtensionType
import suwayomi.tachidesk.manga.model.dataclass.ContentWarning
import suwayomi.tachidesk.manga.model.dataclass.ContentRating
import suwayomi.tachidesk.manga.model.table.ExtensionTable
import java.util.concurrent.CompletableFuture
@@ -108,9 +108,9 @@ class ExtensionQuery {
val versionCode: Int? = null,
val versionCodeLong: Long? = null,
val lang: String? = null,
@GraphQLDeprecated("", ReplaceWith("contentWarning"))
@GraphQLDeprecated("", ReplaceWith("contentRating"))
val isNsfw: Boolean? = null,
val contentWarning: ContentWarning? = null,
val contentRating: ContentRating? = null,
val isInstalled: Boolean? = null,
val hasUpdate: Boolean? = null,
val isObsolete: Boolean? = null,
@@ -129,10 +129,10 @@ class ExtensionQuery {
opAnd.eq(versionCodeLong, ExtensionTable.versionCode)
opAnd.eq(lang, ExtensionTable.lang)
opAnd.eq(
isNsfw?.let { if (it) ContentWarning.MIXED.ordinal else ContentWarning.SAFE.ordinal },
ExtensionTable.contentWarning,
isNsfw?.let { if (it) ContentRating.PORNOGRAPHIC.ordinal else ContentRating.SAFE.ordinal },
ExtensionTable.contentRating,
)
opAnd.eq(contentWarning?.ordinal, ExtensionTable.contentWarning)
opAnd.eq(contentRating?.ordinal, ExtensionTable.contentRating)
opAnd.eq(isInstalled, ExtensionTable.isInstalled)
opAnd.eq(hasUpdate, ExtensionTable.hasUpdate)
opAnd.eq(isObsolete, ExtensionTable.isObsolete)
@@ -156,9 +156,9 @@ class ExtensionQuery {
val versionCode: IntFilter? = null,
val versionCodeLong: LongFilter? = null,
val lang: StringFilter? = null,
@GraphQLDeprecated("", ReplaceWith("contentWarning"))
@GraphQLDeprecated("", ReplaceWith("storeIndexUrl"))
val isNsfw: BooleanFilter? = null,
val contentWarning: ContentWarningFilter? = null,
val contentRating: ContentRatingFilter? = null,
val isInstalled: BooleanFilter? = null,
val hasUpdate: BooleanFilter? = null,
val isObsolete: BooleanFilter? = null,
@@ -179,7 +179,7 @@ class ExtensionQuery {
andFilterWithCompareString(ExtensionTable.versionName, versionName),
andFilterWithCompare(ExtensionTable.versionCode, versionCodeLong),
andFilterWithCompareString(ExtensionTable.lang, lang),
andFilterWithCompareEnum(ExtensionTable.contentWarning, contentWarning),
andFilterWithCompareEnum(ExtensionTable.contentRating, contentRating),
andFilterWithCompare(ExtensionTable.isInstalled, isInstalled),
andFilterWithCompare(ExtensionTable.hasUpdate, hasUpdate),
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.eq
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.neq
import org.jetbrains.exposed.v1.jdbc.selectAll
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
import suwayomi.tachidesk.graphql.directives.RequireAuth
import suwayomi.tachidesk.graphql.queries.filter.BooleanFilter
import suwayomi.tachidesk.graphql.queries.filter.ContentWarningFilter
import suwayomi.tachidesk.graphql.queries.filter.ContentRatingFilter
import suwayomi.tachidesk.graphql.queries.filter.Filter
import suwayomi.tachidesk.graphql.queries.filter.HasGetOp
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.types.SourceNodeList
import suwayomi.tachidesk.graphql.types.SourceType
import suwayomi.tachidesk.manga.model.dataclass.ContentWarning
import suwayomi.tachidesk.manga.model.dataclass.ContentRating
import suwayomi.tachidesk.manga.model.table.SourceTable
import java.util.concurrent.CompletableFuture
@@ -95,9 +95,9 @@ class SourceQuery {
val id: Long? = null,
val name: String? = null,
val lang: String? = null,
@GraphQLDeprecated("replace with contentWarning == ContentRating.MIXED", ReplaceWith("contentWarning"))
@GraphQLDeprecated("replace with contentRating == ContentRating.PORNOGRAPHIC", ReplaceWith("contentRating"))
val isNsfw: Boolean? = null,
val contentWarning: ContentWarning? = null,
val contentRating: ContentRating? = null,
) : HasGetOp {
override fun getOp(): Op<Boolean>? {
val opAnd = OpAnd()
@@ -106,12 +106,13 @@ class SourceQuery {
opAnd.eq(lang, SourceTable.lang)
opAnd.andWhere(isNsfw) {
if (it) {
SourceTable.contentWarning greaterEq ContentWarning.MIXED.ordinal
SourceTable.contentRating eq ContentRating.PORNOGRAPHIC.ordinal
} else {
SourceTable.contentWarning less ContentWarning.MIXED.ordinal
SourceTable.contentRating neq
ContentRating.PORNOGRAPHIC.ordinal
}
}
opAnd.andWhere(contentWarning) { SourceTable.contentWarning eq it.ordinal }
opAnd.andWhere(contentRating) { SourceTable.contentRating eq it.ordinal }
return opAnd.op
}
@@ -121,9 +122,9 @@ class SourceQuery {
val id: LongFilter? = null,
val name: StringFilter? = null,
val lang: StringFilter? = null,
@GraphQLDeprecated("replace with contentWarning", ReplaceWith("contentWarning"))
@GraphQLDeprecated("replace with contentRating == ContentRating.PORNOGRAPHIC", ReplaceWith("contentRating"))
val isNsfw: BooleanFilter? = null,
val contentWarning: ContentWarningFilter? = null,
val contentRating: ContentRatingFilter? = null,
override val and: List<SourceFilter>? = null,
override val or: List<SourceFilter>? = null,
override val not: SourceFilter? = null,
@@ -133,7 +134,7 @@ class SourceQuery {
andFilterWithCompareEntity(SourceTable.id, id),
andFilterWithCompareString(SourceTable.name, name),
andFilterWithCompareString(SourceTable.lang, lang),
andFilterWithCompareEnum(SourceTable.contentWarning, contentWarning),
andFilterWithCompareEnum(SourceTable.contentRating, contentRating),
)
}

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

View File

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

View File

@@ -26,7 +26,6 @@ class ExtensionStoreType(
val contactDiscord: String?,
val indexUrl: String,
val isLegacy: Boolean,
val extensionListUrl: String?,
) : Node {
constructor(row: ResultRow) : this(
name = row[ExtensionStoreTable.name],
@@ -36,11 +35,10 @@ class ExtensionStoreType(
contactDiscord = row[ExtensionStoreTable.contactDiscord],
indexUrl = row[ExtensionStoreTable.indexUrl],
isLegacy = row[ExtensionStoreTable.isLegacy],
extensionListUrl = row[ExtensionStoreTable.extensionListUrl],
)
fun extensions(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture<ExtensionNodeList> =
dataFetchingEnvironment.getValueFromDataLoader<String, ExtensionNodeList>("ExtensionsForExtensionStore", indexUrl)
dataFetchingEnvironment.getValueFromDataLoader<String, ExtensionNodeList>("ExtensionForExtensionStore", indexUrl)
}
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.PageInfo
import suwayomi.tachidesk.manga.impl.extension.Extension
import suwayomi.tachidesk.manga.model.dataclass.ContentWarning
import suwayomi.tachidesk.manga.model.dataclass.ContentRating
import suwayomi.tachidesk.manga.model.table.ExtensionTable
import java.util.concurrent.CompletableFuture
@@ -41,9 +41,9 @@ class ExtensionType(
val versionCode: Int,
val versionCodeLong: Long,
val lang: String,
@GraphQLDeprecated("Removed in extension api v1.6", ReplaceWith("contentWarning"))
@GraphQLDeprecated("Removed in extension api v1.6", ReplaceWith("contentRating"))
val isNsfw: Boolean,
val contentWarning: ContentWarning,
val contentRating: ContentRating,
val isInstalled: Boolean,
val hasUpdate: Boolean,
val isObsolete: Boolean,
@@ -61,8 +61,8 @@ class ExtensionType(
versionCode = row[ExtensionTable.versionCode].toInt(),
versionCodeLong = row[ExtensionTable.versionCode],
lang = row[ExtensionTable.lang],
isNsfw = row[ExtensionTable.contentWarning] >= ContentWarning.MIXED.ordinal,
contentWarning = ContentWarning.valueOf(row[ExtensionTable.contentWarning]),
isNsfw = row[ExtensionTable.contentRating] == ContentRating.PORNOGRAPHIC.ordinal,
contentRating = ContentRating.valueOf(row[ExtensionTable.contentRating]),
isInstalled = row[ExtensionTable.isInstalled],
hasUpdate = row[ExtensionTable.hasUpdate],
isObsolete = row[ExtensionTable.isObsolete],
@@ -72,7 +72,7 @@ class ExtensionType(
dataFetchingEnvironment.getValueFromDataLoader<String, SourceNodeList>("SourcesForExtensionDataLoader", pkgName)
fun extensionStore(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture<ExtensionStoreType> =
dataFetchingEnvironment.getValueFromDataLoader<String, ExtensionStoreType>("ExtensionStoreDataLoader", storeIndexUrl.orEmpty())
dataFetchingEnvironment.getValueFromDataLoader<String, ExtensionStoreType>("ExtensionStoreForExtension", storeIndexUrl.orEmpty())
}
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.server.extensions.getValueFromDataLoader
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.online.HttpSource
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.manga.impl.Source.getSourcePreferencesRaw
import suwayomi.tachidesk.manga.impl.extension.Extension
import suwayomi.tachidesk.manga.impl.util.source.GetSource
import suwayomi.tachidesk.manga.impl.util.source.GetSource.getSourceOrStub
import suwayomi.tachidesk.manga.model.dataclass.ContentWarning
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub
import suwayomi.tachidesk.manga.model.dataclass.ContentRating
import suwayomi.tachidesk.manga.model.table.ExtensionTable
import suwayomi.tachidesk.manga.model.table.SourceTable
import java.util.concurrent.CompletableFuture
@@ -42,29 +42,31 @@ class SourceType(
val id: Long,
val name: String,
val lang: String,
val contentWarning: ContentWarning,
val message: String?,
val contentRating: ContentRating,
val iconUrl: String,
val supportsLatest: Boolean,
val isConfigurable: Boolean,
@GraphQLDeprecated("", ReplaceWith("contentWarning"))
@GraphQLDeprecated("", ReplaceWith("contentRating"))
val isNsfw: Boolean,
val displayName: String,
val homeUrl: String?,
@GraphQLDeprecated("", ReplaceWith("homeUrl"))
val baseUrl: String?,
) : Node {
constructor(row: ResultRow, sourceExtension: ResultRow, source: Source) : this(
constructor(row: ResultRow, sourceExtension: ResultRow, catalogueSource: CatalogueSource) : this(
id = row[SourceTable.id].value,
name = row[SourceTable.name],
lang = row[SourceTable.lang],
contentWarning = ContentWarning.valueOf(row[SourceTable.contentWarning]),
message = row[SourceTable.message],
contentRating = ContentRating.valueOf(row[SourceTable.contentRating]),
iconUrl = Extension.proxyExtensionIconUrl(sourceExtension[ExtensionTable.pkgName]),
supportsLatest = source.supportsLatest,
isConfigurable = source is ConfigurableSource,
isNsfw = row[SourceTable.contentWarning] >= ContentWarning.MIXED.ordinal,
displayName = source.toString(),
homeUrl = runCatching { (source as? HttpSource)?.getHomeUrl() }.getOrNull(),
baseUrl = runCatching { (source as? HttpSource)?.baseUrl }.getOrNull(),
supportsLatest = catalogueSource.supportsLatest,
isConfigurable = catalogueSource is ConfigurableSource,
isNsfw = row[SourceTable.contentRating] == ContentRating.PORNOGRAPHIC.ordinal,
displayName = catalogueSource.toString(),
homeUrl = runCatching { (catalogueSource as? HttpSource)?.getHomeUrl() }.getOrNull(),
baseUrl = runCatching { (catalogueSource as? HttpSource)?.baseUrl }.getOrNull(),
)
fun manga(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture<MangaNodeList> =
@@ -75,7 +77,7 @@ class SourceType(
fun preferences(): List<Preference> = getSourcePreferencesRaw(id).map { preferenceOf(it) }
fun filters(): List<Filter> = getSourceOrStub(id).getFilterList().map { filterOf(it) }
fun filters(): List<Filter> = getCatalogueSourceOrStub(id).getFilterList().map { filterOf(it) }
fun meta(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture<List<SourceMetaType>> =
dataFetchingEnvironment.getValueFromDataLoader<Long, List<SourceMetaType>>("SourceMetaDataLoader", id)
@@ -84,8 +86,8 @@ class SourceType(
@Suppress("ktlint:standard:function-naming")
fun SourceType(row: ResultRow): SourceType? {
val catalogueSource =
GetSource
.getSourceOrNull(row[SourceTable.id].value)
GetCatalogueSource
.getCatalogueSourceOrNull(row[SourceTable.id].value)
?: return null
val sourceExtension =
if (row.hasValue(ExtensionTable.id)) {
@@ -296,7 +298,7 @@ data class FilterChange(
)
fun updateFilterList(
source: Source,
source: CatalogueSource,
changes: List<FilterChange>?,
): FilterList {
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
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
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.EnqueueInput
import suwayomi.tachidesk.manga.impl.track.Track
import suwayomi.tachidesk.manga.impl.util.source.GetSource.getSourceOrStub
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub
import suwayomi.tachidesk.manga.model.dataclass.ChapterDataClass
import suwayomi.tachidesk.manga.model.dataclass.MangaChapterDataClass
import suwayomi.tachidesk.manga.model.dataclass.PaginatedList
@@ -119,7 +119,7 @@ object Chapter {
transaction {
MangaTable.selectAll().where { MangaTable.id eq mangaId }.first()
}
val source = getSourceOrStub(mangaEntry[MangaTable.sourceReference])
val source = getCatalogueSourceOrStub(mangaEntry[MangaTable.sourceReference])
val chapters =
Manga
@@ -139,7 +139,7 @@ object Chapter {
fun updateChapterListDatabase(
mangaEntry: ResultRow,
chapters: List<SChapter>,
source: Source,
source: CatalogueSource,
): List<SChapter> {
val currentLatestChapterNumber = Manga.getLatestChapter(mangaEntry[MangaTable.id].value)?.chapterNumber ?: 0f
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.NetworkHelper
import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.local.LocalSource
import eu.kanade.tachiyomi.source.model.SChapter
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 suwayomi.tachidesk.manga.impl.download.fileProvider.impl.MissingThumbnailException
import suwayomi.tachidesk.manga.impl.util.network.await
import suwayomi.tachidesk.manga.impl.util.source.GetSource.getSourceOrNull
import suwayomi.tachidesk.manga.impl.util.source.GetSource.getSourceOrStub
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrNull
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub
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.getImageResponse
@@ -91,7 +91,7 @@ object Manga {
suspend fun fetchMangaAndChapters(
mangaEntry: ResultRow,
source: Source,
source: CatalogueSource,
fetchDetails: Boolean,
fetchChapters: Boolean,
): SMangaUpdate {
@@ -139,7 +139,7 @@ object Manga {
return mangaInfoMutex.get(mangaId) { Mutex() }.withLock {
val mangaEntry =
transaction { MangaTable.selectAll().where { MangaTable.id eq mangaId }.first() }
val source = getSourceOrNull(mangaEntry[MangaTable.sourceReference]) ?: return null
val source = getCatalogueSourceOrNull(mangaEntry[MangaTable.sourceReference]) ?: return null
val sManga =
fetchMangaAndChapters(
mangaEntry,
@@ -161,7 +161,7 @@ object Manga {
var mangaEntry =
transaction { MangaTable.selectAll().where { MangaTable.id eq mangaId }.first() }
val source =
getSourceOrNull(mangaEntry[MangaTable.sourceReference])
getCatalogueSourceOrNull(mangaEntry[MangaTable.sourceReference])
?: throw NullPointerException("Missing source ${mangaEntry[MangaTable.sourceReference]}")
val mangaUpdate =
fetchMangaAndChapters(
@@ -186,7 +186,7 @@ object Manga {
fun updateMangaDatabase(
mangaEntry: ResultRow,
source: Source,
source: CatalogueSource,
sManga: SManga,
): SManga {
transaction {
@@ -238,7 +238,6 @@ object Manga {
it[MangaTable.lastFetchedAt] = Instant.now().epochSecond
it[MangaTable.updateStrategy] = sManga.update_strategy.name
it[MangaTable.memo] = Json.encodeToString(sManga.memo)
}
}
@@ -413,7 +412,7 @@ object Manga {
val mangaEntry = transaction { MangaTable.selectAll().where { MangaTable.id eq mangaId }.first() }
val sourceId = mangaEntry[MangaTable.sourceReference]
return when (val source = getSourceOrStub(sourceId)) {
return when (val source = getCatalogueSourceOrStub(sourceId)) {
is HttpSource -> {
getImageResponse(cacheSaveDir, fileName) {
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.statements.toExecutable
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
import suwayomi.tachidesk.manga.impl.util.source.GetSource.getSourceOrStub
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub
import suwayomi.tachidesk.manga.model.dataclass.PagedMangaListDataClass
import suwayomi.tachidesk.manga.model.table.MangaTable
import suwayomi.tachidesk.manga.model.table.toDataClass
@@ -36,7 +36,7 @@ object MangaList {
require(pageNum > 0) {
"pageNum = $pageNum is not in valid range"
}
val source = getSourceOrStub(sourceId)
val source = getCatalogueSourceOrStub(sourceId)
val mangasPage =
if (popular) {
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 suwayomi.tachidesk.graphql.types.DownloadConversion
import suwayomi.tachidesk.manga.impl.util.getChapterCachePath
import suwayomi.tachidesk.manga.impl.util.source.GetSource.getSourceOrStub
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub
import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.getImageResponse
import suwayomi.tachidesk.manga.impl.util.storage.ImageUtil
import suwayomi.tachidesk.manga.model.table.ChapterTable
@@ -118,7 +118,7 @@ object Page {
return imageFile.inputStream() to (ImageUtil.findImageType { imageFile.inputStream() }?.mime ?: "image/jpeg")
}
val source = getSourceOrStub(mangaEntry[MangaTable.sourceReference])
val source = getCatalogueSourceOrStub(mangaEntry[MangaTable.sourceReference])
source as HttpSource
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
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import io.javalin.json.JsonMapper
import io.javalin.json.fromJsonString
import kotlinx.serialization.Serializable
import suwayomi.tachidesk.manga.impl.MangaList.processEntries
import suwayomi.tachidesk.manga.impl.util.source.GetSource.getSourceOrStub
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub
import suwayomi.tachidesk.manga.model.dataclass.PagedMangaListDataClass
import uy.kohesive.injekt.injectLazy
@@ -24,7 +24,7 @@ object Search {
searchTerm: String,
pageNum: Int,
): PagedMangaListDataClass {
val source = getSourceOrStub(sourceId)
val source = getCatalogueSourceOrStub(sourceId)
val searchManga = source.getSearchManga(pageNum, searchTerm, getFilterListOf(source))
return searchManga.processEntries(sourceId)
}
@@ -34,7 +34,7 @@ object Search {
pageNum: Int,
filter: FilterData,
): PagedMangaListDataClass {
val source = getSourceOrStub(sourceId)
val source = getCatalogueSourceOrStub(sourceId)
val filterList = if (filter.filter != null) buildFilterList(sourceId, filter.filter) else source.getFilterList()
val searchManga = source.getSearchManga(pageNum, filter.searchTerm ?: "", filterList)
return searchManga.processEntries(sourceId)
@@ -43,7 +43,7 @@ object Search {
private val filterListCache = mutableMapOf<Long, FilterList>()
private fun getFilterListOf(
source: Source,
source: CatalogueSource,
reset: Boolean = false,
): FilterList {
if (reset || !filterListCache.containsKey(source.id)) {
@@ -56,7 +56,7 @@ object Search {
sourceId: Long,
reset: Boolean,
): List<FilterObject> {
val source = getSourceOrStub(sourceId)
val source = getCatalogueSourceOrStub(sourceId)
return getFilterListOf(source, reset).list.map {
FilterObject(
@@ -111,7 +111,7 @@ object Search {
sourceId: Long,
changes: List<FilterChange>,
) {
val source = getSourceOrStub(sourceId)
val source = getCatalogueSourceOrStub(sourceId)
val filterList = getFilterListOf(source, false)
updateFilterList(filterList, changes)
}
@@ -169,7 +169,7 @@ object Search {
sourceId: Long,
changes: List<FilterChange>,
): FilterList {
val source = getSourceOrStub(sourceId)
val source = getCatalogueSourceOrStub(sourceId)
val filterList = source.getFilterList()
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 suwayomi.tachidesk.manga.impl.Source.preferenceScreenMap
import suwayomi.tachidesk.manga.impl.extension.Extension.proxyExtensionIconUrl
import suwayomi.tachidesk.manga.impl.util.source.GetSource.getSourceOrNull
import suwayomi.tachidesk.manga.impl.util.source.GetSource.getSourceOrStub
import suwayomi.tachidesk.manga.impl.util.source.GetSource.unregisterSource
import suwayomi.tachidesk.manga.model.dataclass.ContentWarning
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrNull
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.unregisterCatalogueSource
import suwayomi.tachidesk.manga.model.dataclass.ContentRating
import suwayomi.tachidesk.manga.model.dataclass.SourceDataClass
import suwayomi.tachidesk.manga.model.table.ExtensionTable
import suwayomi.tachidesk.manga.model.table.SourceMetaTable
@@ -43,7 +43,7 @@ object Source {
fun getSourceList(): List<SourceDataClass> {
return transaction {
SourceTable.selectAll().mapNotNull {
val catalogueSource = getSourceOrNull(it[SourceTable.id].value) ?: return@mapNotNull null
val catalogueSource = getCatalogueSourceOrNull(it[SourceTable.id].value) ?: return@mapNotNull null
val sourceExtension = ExtensionTable.selectAll().where { ExtensionTable.id eq it[SourceTable.extension] }.first()
SourceDataClass(
@@ -53,7 +53,7 @@ object Source {
iconUrl = proxyExtensionIconUrl(sourceExtension[ExtensionTable.pkgName]),
supportsLatest = catalogueSource.supportsLatest,
isConfigurable = catalogueSource is ConfigurableSource,
isNsfw = it[SourceTable.contentWarning] >= ContentWarning.MIXED.ordinal,
isNsfw = it[SourceTable.contentRating] == ContentRating.PORNOGRAPHIC.ordinal,
displayName = catalogueSource.toString(),
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
return transaction {
val source = SourceTable.selectAll().where { SourceTable.id eq sourceId }.firstOrNull() ?: return@transaction null
val catalogueSource = getSourceOrNull(sourceId) ?: return@transaction null
val catalogueSource = getCatalogueSourceOrNull(sourceId) ?: return@transaction null
val extension = ExtensionTable.selectAll().where { ExtensionTable.id eq source[SourceTable.extension] }.first()
SourceDataClass(
@@ -74,7 +74,7 @@ object Source {
iconUrl = proxyExtensionIconUrl(extension[ExtensionTable.pkgName]),
supportsLatest = catalogueSource.supportsLatest,
isConfigurable = catalogueSource is ConfigurableSource,
isNsfw = source[SourceTable.contentWarning] >= ContentWarning.MIXED.ordinal,
isNsfw = source[SourceTable.contentRating] == ContentRating.PORNOGRAPHIC.ordinal,
displayName = catalogueSource.toString(),
baseUrl = runCatching { (catalogueSource as? HttpSource)?.baseUrl }.getOrNull(),
)
@@ -107,7 +107,7 @@ object Source {
}
fun getSourcePreferencesRaw(sourceId: Long): List<Preference> {
val source = getSourceOrStub(sourceId)
val source = getCatalogueSourceOrStub(sourceId)
if (source is ConfigurableSource) {
val sourceShardPreferences = source.sourcePreferences()
@@ -157,7 +157,7 @@ object Source {
pref.callChangeListener(newValue)
// must reload the source because a preference was changed
unregisterSource(sourceId)
unregisterCatalogueSource(sourceId)
}
fun getSourcesMetaMaps(ids: List<Long>): Map<Long, Map<String, String>> =

View File

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

View File

@@ -39,7 +39,7 @@ data class BackupManga(
@ProtoNumber(106) var lastModifiedAt: Long = 0,
@ProtoNumber(109) var version: Long = 0,
@ProtoNumber(111) var initialized: Boolean = false,
@ProtoNumber(112) var memo: ByteArray = JsonObjectEmptyBytes,
@ProtoNumber(13) var memo: ByteArray = JsonObjectEmptyBytes,
// suwayomi
@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.update
import suwayomi.tachidesk.manga.impl.ChapterDownloadHelper
import suwayomi.tachidesk.manga.impl.util.source.GetSource.getSourceOrStub
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub
import suwayomi.tachidesk.manga.model.dataclass.ChapterDataClass
import suwayomi.tachidesk.manga.model.table.ChapterTable
import suwayomi.tachidesk.manga.model.table.MangaTable
@@ -77,7 +77,7 @@ suspend fun refreshChapterPageList(
return mutex.withLock {
val chapterEntry = existingChapterEntry ?: transaction { ChapterTable.selectAll().where { ChapterTable.id eq chapterId }.first() }
val mangaEntry = transaction { MangaTable.selectAll().where { MangaTable.id eq mangaId }.first() }
val source = getSourceOrStub(mangaEntry[MangaTable.sourceReference])
val source = getCatalogueSourceOrStub(mangaEntry[MangaTable.sourceReference])
val pageList =
source

View File

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

View File

@@ -15,9 +15,6 @@ import kotlinx.serialization.decodeFromByteArray
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.okio.decodeFromBufferedSource
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.inList
import org.jetbrains.exposed.v1.jdbc.deleteWhere
@@ -44,45 +41,45 @@ object ExtensionStoreService {
val protoBuf: ProtoBuf by injectLazy()
val json: Json by injectLazy()
suspend fun fetch(indexUrl: String): ExtensionStore {
var updatedIndexUrl: String = indexUrl
suspend fun fetch(indexUrl: String): ExtensionStore = fetch(indexUrl, forceV2 = false)
private suspend fun fetch(
indexUrl: String,
forceV2: Boolean,
): ExtensionStore {
var updatedIndexUrl = indexUrl
return try {
val response = network.client.newCall(GET(updatedIndexUrl)).awaitSuccess()
response.body.source().decompressIfGzipped().use { source ->
val networkStore =
when (source.peek().readByte()) {
// "[..."
0x5B.toByte() -> {
run {
network.client.newCall(GET(indexUrl)).awaitSuccess().body.source().use { source ->
try {
protoBuf.decodeFromByteArray<NetworkExtensionStore>(source.peek().readByteArray())
} catch (e: IllegalArgumentException) {
logger.debug { "Failed to decode as protobuf, trying JSON" }
try {
json.decodeFromBufferedSource<NetworkExtensionStore>(source.peek())
} 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")) {
throw IllegalArgumentException("Provided legacy store url is not valid")
throw e
}
logger.debug { "Checking for new repo.json format" }
updatedIndexUrl = indexUrl.replace("/index.min.json", "/repo.json")
network.client.newCall(GET(updatedIndexUrl)).awaitSuccess().body.source().use {
json.decodeFromBufferedSource<NetworkLegacyExtensionRepo>(it)
}
}
}
// "{..."
0x7B.toByte() -> {
try {
json.decodeFromBufferedSource<NetworkLegacyExtensionRepo>(source.peek())
} catch (_: IllegalArgumentException) {
json.decodeFromBufferedSource<NetworkExtensionStore>(source)
}
}
else -> {
protoBuf.decodeFromByteArray<NetworkExtensionStore>(source.readByteArray())
if (legacyIndex.indexV2 != null) {
return fetch(legacyIndex.indexV2, forceV2 = true)
} else {
legacyIndex
}
}
if (networkStore is NetworkLegacyExtensionRepo && networkStore.indexV2 != null) {
return fetch(networkStore.indexV2)
}
networkStore.toExtensionStore(updatedIndexUrl)
}.toExtensionStore(updatedIndexUrl)
}
} catch (e: Exception) {
if (e is CancellationException) throw e
@@ -108,7 +105,6 @@ object ExtensionStoreService {
it[contactDiscord] = store.contact.discord
it[indexUrl] = store.indexUrl
it[isLegacy] = store.isLegacy
it[extensionListUrl] = store.extensionListUrl
}
} else {
ExtensionStoreTable.update({ ExtensionStoreTable.indexUrl eq store.indexUrl }) {
@@ -118,7 +114,6 @@ object ExtensionStoreService {
it[contactWebsite] = store.contact.website
it[contactDiscord] = store.contact.discord
it[isLegacy] = store.isLegacy
it[extensionListUrl] = store.extensionListUrl
}
}
}
@@ -215,33 +210,17 @@ object ExtensionStoreService {
suspend fun getExtensions(store: ExtensionStore): List<ExtensionInfo> {
val extensions =
if (store.extensionListUrl != null) {
val response = network.client.newCall(GET(store.extensionListUrl)).awaitSuccess()
response.body.source().decompressIfGzipped().use { source ->
when (source.peek().readByte()) {
// "{..."
0x7B.toByte() -> {
json.decodeFromBufferedSource<NetworkExtensionStore.ExtensionList>(source)
}
else -> {
protoBuf.decodeFromByteArray<NetworkExtensionStore.ExtensionList>(
source.readByteArray(),
)
if (!store.isLegacy) {
val response = network.client.newCall(GET(store.indexUrl)).awaitSuccess()
response.body
.source()
.use { source ->
try {
protoBuf.decodeFromByteArray<NetworkExtensionStore>(source.peek().readByteArray())
} catch (_: IllegalArgumentException) {
json.decodeFromBufferedSource<NetworkExtensionStore>(source.peek())
}
}.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 {
val storeBaseUrl = store.indexUrl.removeSuffix("/repo.json")
val response = network.client.newCall(GET("$storeBaseUrl/index.min.json")).awaitSuccess()
@@ -253,17 +232,4 @@ object ExtensionStoreService {
}
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.update
import suwayomi.tachidesk.manga.impl.extension.Extension.proxyExtensionIconUrl
import suwayomi.tachidesk.manga.model.dataclass.ContentWarning
import suwayomi.tachidesk.manga.model.dataclass.ContentRating
import suwayomi.tachidesk.manga.model.dataclass.ExtensionDataClass
import suwayomi.tachidesk.manga.model.dataclass.ExtensionInfo
import suwayomi.tachidesk.manga.model.table.ExtensionTable
@@ -81,7 +81,7 @@ object ExtensionsList {
versionName = it[ExtensionTable.versionName],
versionCode = it[ExtensionTable.versionCode].toInt(),
lang = it[ExtensionTable.lang],
isNsfw = it[ExtensionTable.contentWarning] >= ContentWarning.MIXED.ordinal,
isNsfw = it[ExtensionTable.contentRating] == ContentRating.PORNOGRAPHIC.ordinal,
installed = it[ExtensionTable.isInstalled],
hasUpdate = it[ExtensionTable.hasUpdate],
obsolete = it[ExtensionTable.isObsolete],
@@ -173,7 +173,7 @@ object ExtensionsList {
this[ExtensionTable.versionName] = foundExtension.versionName
this[ExtensionTable.versionCode] = foundExtension.versionCode
this[ExtensionTable.lang] = foundExtension.lang
this[ExtensionTable.contentWarning] = foundExtension.contentWarning.ordinal
this[ExtensionTable.contentRating] = foundExtension.contentRating.ordinal
this[ExtensionTable.apkUrl] = foundExtension.apkUrl
this[ExtensionTable.iconUrl] = foundExtension.iconUrl
}
@@ -190,7 +190,7 @@ object ExtensionsList {
this[ExtensionTable.versionName] = foundExtension.versionName
this[ExtensionTable.versionCode] = foundExtension.versionCode
this[ExtensionTable.lang] = foundExtension.lang
this[ExtensionTable.contentWarning] = foundExtension.contentWarning.ordinal
this[ExtensionTable.contentRating] = foundExtension.contentRating.ordinal
this[ExtensionTable.apkUrl] = foundExtension.apkUrl
this[ExtensionTable.iconUrl] = foundExtension.iconUrl
}

View File

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

View File

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

View File

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

View File

@@ -222,48 +222,6 @@ 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) {
val recordDb =
transaction {
@@ -465,9 +423,9 @@ object Track {
}
}
fun updateTrackRecord(track: Track): Int = updateTrackRecords(listOf(track)).first()
fun updateTrackRecord(track: Track) = updateTrackRecords(listOf(track))
fun updateTrackRecords(tracks: List<Track>): List<Int> =
fun updateTrackRecords(tracks: List<Track>) =
transaction {
if (tracks.isNotEmpty()) {
BatchUpdateStatement(TrackRecordTable)
@@ -489,8 +447,6 @@ object Track {
}.toExecutable()
.execute(this@transaction)
}
tracks.map { it.id!! }
}
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.jdbc.selectAll
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
import suwayomi.tachidesk.manga.impl.util.source.GetSource
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource
import suwayomi.tachidesk.manga.model.table.ChapterTable
import suwayomi.tachidesk.manga.model.table.MangaTable
import suwayomi.tachidesk.server.ApplicationDirs
@@ -37,7 +37,7 @@ private fun getMangaDir(
private fun getMangaDir(mangaId: Int): String =
transaction {
val mangaEntry = MangaTable.selectAll().where { MangaTable.id eq mangaId }.first()
val source = GetSource.getSourceOrStub(mangaEntry[MangaTable.sourceReference])
val source = GetCatalogueSource.getCatalogueSourceOrStub(mangaEntry[MangaTable.sourceReference])
getMangaDir(mangaEntry[MangaTable.title], source.toString())
}

View File

@@ -40,11 +40,6 @@ object PackageTools {
const val METADATA_SOURCE_CLASS = "tachiyomi.extension.class"
const val METADATA_SOURCE_FACTORY = "tachiyomi.extension.factory"
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_MAX = 1.6

View File

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

View File

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

View File

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

View File

@@ -10,12 +10,11 @@ package suwayomi.tachidesk.manga.model.table
import org.jetbrains.exposed.v1.core.dao.id.IntIdTable
object ExtensionStoreTable : IntIdTable() {
val indexUrl = varchar("index_url", 2048).uniqueIndex()
val indexUrl = varchar("index_url", 2048)
val name = varchar("name", 256)
val badgeLabel = varchar("badge_label", 32)
val signingKey = varchar("signing_key", 512)
val contactWebsite = varchar("contact_website", 2048)
val contactDiscord = varchar("contact_discord", 2048).nullable()
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() {
val apkName = varchar("apk_name", 1024).nullable()
val storeIndexUrl = varchar("store_index_url", 2048).nullable().index()
val storeIndexUrl = varchar("store_index_url", 2048).nullable()
// default is the local source icon from tachiyomi
@Suppress("ktlint:standard:max-line-length")
@@ -23,12 +23,12 @@ object ExtensionTable : IntIdTable() {
val name = varchar("name", 128)
val pkgName = varchar("pkg_name", 128)
val apkUrl = varchar("apk_url", 2048).nullable()
val apkUrl = varchar("apk_url", 2048)
val extensionLib = varchar("extension_lib", 16).nullable()
val versionName = varchar("version_name", 16)
val versionCode = long("version_code")
val lang = varchar("lang", 32)
val contentWarning = integer("content_warning")
val contentRating = integer("content_rating")
val isInstalled = bool("is_installed").default(false)
val hasUpdate = bool("has_update").default(false)

View File

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

View File

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

View File

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

View File

@@ -14,14 +14,13 @@ import org.jetbrains.exposed.v1.core.dao.id.IntIdTable
@Suppress("ClassName", "unused")
class M0058_AddExtensionStore : AddTableMigration() {
private class ExtensionStoreTable : IntIdTable() {
val indexUrl = varchar("index_url", 2048).uniqueIndex()
val indexUrl = varchar("index_url", 2048)
val name = varchar("name", 256)
val badgeLabel = varchar("badge_label", 32)
val signingKey = varchar("signing_key", 512)
val contactWebsite = varchar("contact_website", 2048)
val contactDiscord = varchar("contact_discord", 2048).nullable()
val isLegacy = bool("is_legacy").default(false)
val extensionListUrl = varchar("extension_list_url", 2048).nullable()
}
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.extension.Extension
import suwayomi.tachidesk.manga.impl.extension.ExtensionsList
import suwayomi.tachidesk.manga.impl.util.source.GetSource
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource
import suwayomi.tachidesk.server.applicationSetup
import suwayomi.tachidesk.server.settings.SettingsRegistry
import suwayomi.tachidesk.test.BASE_PATH
@@ -51,7 +51,7 @@ class CloudFlareTest {
Source
.getSourceList()
.firstNotNullOf { it.id.toLong().takeIf { it == 3122156392225024195L } }
.let(GetSource::getSourceOrNull) as HttpSource
.let(GetCatalogueSource::getCatalogueSourceOrNull) as HttpSource
}
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.updateExtension
import suwayomi.tachidesk.manga.impl.extension.ExtensionsList.getExtensionList
import suwayomi.tachidesk.manga.impl.util.source.GetSource.getSourceOrNull
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrNull
import suwayomi.tachidesk.manga.model.dataclass.ExtensionDataClass
import suwayomi.tachidesk.server.applicationSetup
import suwayomi.tachidesk.server.settings.SettingsRegistry
@@ -82,7 +82,7 @@ class TestExtensionCompatibility {
.filter {
// filter local source
it.id.toLong() != 0L
}.map { getSourceOrNull(it.id.toLong())!! as HttpSource }
}.map { getCatalogueSourceOrNull(it.id.toLong())!! as HttpSource }
}
setLoggingEnabled(true)
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.setFilter
import suwayomi.tachidesk.manga.impl.Search.sourceSearch
import suwayomi.tachidesk.manga.impl.util.source.GetSource.registerSource
import suwayomi.tachidesk.manga.impl.util.source.GetSource.unregisterSource
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.registerCatalogueSource
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.unregisterCatalogueSource
import suwayomi.tachidesk.manga.impl.util.source.StubSource
import suwayomi.tachidesk.test.ApplicationTest
import suwayomi.tachidesk.test.createSMangas
@@ -53,7 +53,7 @@ class SearchTest : ApplicationTest() {
@BeforeAll
fun setup() {
registerSource(sourceId to source)
registerCatalogueSource(sourceId to source)
this.source.mangas = createSMangas(mangasCount)
}
@@ -70,7 +70,7 @@ class SearchTest : ApplicationTest() {
@AfterAll
fun teardown() {
unregisterSource(this.sourceId)
unregisterCatalogueSource(this.sourceId)
}
}
@@ -347,7 +347,7 @@ class FilterListTest : ApplicationTest() {
private fun registerSource(sourceClass: KClass<*>): EmptyFilterListSource =
synchronized(sourceClass) {
val source = sourceClass.primaryConstructor!!.call(sourceCount) as EmptyFilterListSource
registerSource(sourceCount to source)
registerCatalogueSource(sourceCount to source)
sourceCount++
source
}
@@ -355,7 +355,7 @@ class FilterListTest : ApplicationTest() {
@AfterAll
@JvmStatic
fun teardown() {
(0 until sourceCount).forEach { unregisterSource(it) }
(0 until sourceCount).forEach { unregisterCatalogueSource(it) }
}
}
}