From 849acfca3d44c3d035e232dd4ccc55818c789198 Mon Sep 17 00:00:00 2001 From: Mitchell Syer Date: Fri, 6 Oct 2023 23:38:39 -0400 Subject: [PATCH] Switch to a new Ktlint Formatter (#705) * Switch to new Ktlint plugin * Add ktlintCheck to PR builds * Run formatter * Put ktlint version in libs toml * Fix lint * Use Zip4Java from libs.toml --- .editorconfig | 11 + .github/workflows/build_pull_request.yml | 2 +- AndroidCompat/Config/build.gradle.kts | 5 +- .../nulldev/ts/config/ApplicationRootDir.kt | 2 +- .../nulldev/ts/config/ConfigKodeinModule.kt | 9 +- .../xyz/nulldev/ts/config/ConfigManager.kt | 38 +- .../xyz/nulldev/ts/config/ConfigModule.kt | 14 +- .../java/xyz/nulldev/ts/config/Logging.kt | 68 +- .../ts/config/util/ConfigExtensions.kt | 5 +- AndroidCompat/build.gradle.kts | 3 +- .../android/preference/PreferenceManager.kt | 2 +- .../nulldev/androidcompat/AndroidCompat.kt | 1 - .../androidcompat/AndroidCompatInitializer.kt | 7 +- .../androidcompat/AndroidCompatModule.kt | 26 +- .../config/ApplicationInfoConfigModule.kt | 3 +- .../androidcompat/config/FilesConfigModule.kt | 3 +- .../config/SystemConfigModule.kt | 7 +- .../androidcompat/db/ScrollableResultSet.kt | 503 ++++++++++--- .../io/sharedprefs/JavaSharedPreferences.kt | 84 ++- .../androidcompat/pm/InstalledPackage.kt | 78 +- .../androidcompat/pm/PackageController.kt | 5 +- .../nulldev/androidcompat/pm/PackageUtil.kt | 22 +- .../androidcompat/service/ServiceSupport.kt | 10 +- .../androidcompat/util/KodeinGlobalHelper.kt | 5 +- .../androidcompat/util/UriExtensions.kt | 2 + .../androidcompat/webkit/CookieManagerImpl.kt | 38 +- build.gradle.kts | 30 +- buildSrc/build.gradle.kts | 4 +- buildSrc/settings.gradle.kts | 9 + gradle/libs.versions.toml | 3 +- server/build.gradle.kts | 35 +- .../main/kotlin/eu/kanade/tachiyomi/App.kt | 4 - .../kotlin/eu/kanade/tachiyomi/AppInfo.kt | 5 +- .../kotlin/eu/kanade/tachiyomi/AppModule.kt | 1 - .../tachiyomi/network/JavaScriptEngine.kt | 10 +- .../tachiyomi/network/MemoryCookieJar.kt | 5 +- .../kanade/tachiyomi/network/NetworkHelper.kt | 36 +- .../tachiyomi/network/OkHttpExtensions.kt | 77 +- .../tachiyomi/network/PersistentCookieJar.kt | 6 +- .../network/PersistentCookieStore.kt | 110 +-- .../tachiyomi/network/ProgressListener.kt | 6 +- .../tachiyomi/network/ProgressResponseBody.kt | 6 +- .../eu/kanade/tachiyomi/network/Requests.kt | 8 +- .../interceptor/CloudflareInterceptor.kt | 89 +-- .../interceptor/RateLimitInterceptor.kt | 14 +- .../SpecificHostRateLimitInterceptor.kt | 6 +- .../interceptor/UserAgentInterceptor.kt | 11 +- .../tachiyomi/source/CatalogueSource.kt | 26 +- .../tachiyomi/source/ConfigurableSource.kt | 4 +- .../eu/kanade/tachiyomi/source/Source.kt | 7 +- .../tachiyomi/source/local/LocalSource.kt | 280 ++++---- .../tachiyomi/source/local/filter/OrderBy.kt | 3 +- .../source/local/image/LocalCoverManager.kt | 5 +- .../tachiyomi/source/local/io/Archive.kt | 8 +- .../tachiyomi/source/local/io/Format.kt | 21 +- .../source/local/io/LocalSourceFileSystem.kt | 3 +- .../source/local/loader/EpubPageLoader.kt | 1 - .../source/local/loader/RarPageLoader.kt | 6 +- .../source/local/loader/ReaderPage.kt | 2 +- .../source/local/loader/ZipPageLoader.kt | 1 - .../source/local/metadata/ComicInfo.kt | 74 +- .../source/local/metadata/MangaDetails.kt | 2 +- .../kanade/tachiyomi/source/model/Filter.kt | 7 + .../tachiyomi/source/model/FilterList.kt | 1 - .../eu/kanade/tachiyomi/source/model/Page.kt | 21 +- .../kanade/tachiyomi/source/model/SChapter.kt | 3 +- .../tachiyomi/source/model/SChapterImpl.kt | 3 +- .../kanade/tachiyomi/source/model/SManga.kt | 3 +- .../tachiyomi/source/model/SMangaImpl.kt | 3 +- .../tachiyomi/source/model/UpdateStrategy.kt | 2 +- .../tachiyomi/source/online/HttpSource.kt | 32 +- .../source/online/ParsedHttpSource.kt | 43 +- .../source/online/ResolvableSource.kt | 1 - .../kanade/tachiyomi/util/JsoupExtensions.kt | 10 +- .../util/chapter/ChapterRecognition.kt | 12 +- .../eu/kanade/tachiyomi/util/lang/Hash.kt | 10 +- .../tachiyomi/util/lang/StringExtensions.kt | 10 +- .../kanade/tachiyomi/util/storage/EpubFile.kt | 18 +- .../global/controller/GlobalMetaController.kt | 68 +- .../global/controller/SettingsController.kt | 62 +- .../suwayomi/tachidesk/global/impl/About.kt | 6 +- .../tachidesk/global/impl/AppUpdate.kt | 32 +- .../tachidesk/global/impl/GlobalMeta.kt | 12 +- .../graphql/controller/GraphQLController.kt | 2 +- .../graphql/dataLoaders/CategoryDataLoader.kt | 66 +- .../graphql/dataLoaders/ChapterDataLoader.kt | 117 +-- .../dataLoaders/ExtensionDataLoader.kt | 65 +- .../graphql/dataLoaders/MangaDataLoader.kt | 113 +-- .../graphql/dataLoaders/MetaDataLoader.kt | 84 ++- .../graphql/dataLoaders/SourceDataLoader.kt | 50 +- .../graphql/mutations/BackupMutation.kt | 49 +- .../graphql/mutations/CategoryMutation.kt | 259 ++++--- .../graphql/mutations/ChapterMutation.kt | 133 ++-- .../graphql/mutations/DownloadMutation.kt | 157 ++-- .../graphql/mutations/ExtensionMutation.kt | 72 +- .../graphql/mutations/InfoMutation.kt | 21 +- .../graphql/mutations/MangaMutation.kt | 102 +-- .../graphql/mutations/MetaMutation.kt | 41 +- .../graphql/mutations/SettingsMutation.kt | 130 +++- .../graphql/mutations/SourceMutation.kt | 73 +- .../graphql/mutations/UpdateMutation.kt | 28 +- .../tachidesk/graphql/queries/BackupQuery.kt | 15 +- .../graphql/queries/CategoryQuery.kt | 127 ++-- .../tachidesk/graphql/queries/ChapterQuery.kt | 141 ++-- .../graphql/queries/DownloadQuery.kt | 1 - .../graphql/queries/ExtensionQuery.kt | 123 ++-- .../tachidesk/graphql/queries/InfoQuery.kt | 12 +- .../tachidesk/graphql/queries/MangaQuery.kt | 151 ++-- .../tachidesk/graphql/queries/MetaQuery.kt | 125 ++-- .../tachidesk/graphql/queries/SourceQuery.kt | 129 ++-- .../graphql/queries/filter/Filter.kt | 167 +++-- .../server/JavalinGraphQLRequestParser.kt | 62 +- .../TachideskDataLoaderRegistryFactory.kt | 2 +- .../server/TachideskGraphQLContextFactory.kt | 3 +- .../graphql/server/TachideskGraphQLSchema.kt | 92 +-- .../graphql/server/TachideskGraphQLServer.kt | 9 +- .../graphql/server/TemporaryFileStorage.kt | 7 +- .../graphql/server/primitives/Cursor.kt | 45 +- .../graphql/server/primitives/LongAsString.kt | 35 +- .../graphql/server/primitives/NodeList.kt | 2 +- .../graphql/server/primitives/OrderBy.kt | 16 +- .../graphql/server/primitives/Upload.kt | 28 +- .../ApolloSubscriptionProtocolHandler.kt | 33 +- .../ApolloSubscriptionSessionState.kt | 23 +- .../GraphQLSubscriptionHandler.kt | 4 +- .../SubscriptionOperationMessage.kt | 8 +- .../subscriptions/DownloadSubscription.kt | 1 - .../tachidesk/graphql/types/BackupTypes.kt | 37 +- .../tachidesk/graphql/types/CategoryType.kt | 29 +- .../tachidesk/graphql/types/ChapterType.kt | 31 +- .../tachidesk/graphql/types/DownloadType.kt | 37 +- .../tachidesk/graphql/types/ExtensionType.kt | 31 +- .../tachidesk/graphql/types/MangaType.kt | 31 +- .../tachidesk/graphql/types/MetaType.kt | 41 +- .../tachidesk/graphql/types/SettingsType.kt | 33 +- .../tachidesk/graphql/types/SourceType.kt | 230 +++--- .../tachidesk/graphql/types/UpdateType.kt | 8 +- .../graphql/types/WebUIUpdateType.kt | 6 +- .../manga/controller/BackupController.kt | 273 +++---- .../manga/controller/CategoryController.kt | 247 +++---- .../manga/controller/DownloadController.kt | 284 ++++---- .../manga/controller/ExtensionController.kt | 256 +++---- .../manga/controller/MangaController.kt | 673 +++++++++--------- .../manga/controller/SourceController.kt | 388 +++++----- .../manga/controller/UpdateController.kt | 174 ++--- .../suwayomi/tachidesk/manga/impl/Category.kt | 63 +- .../tachidesk/manga/impl/CategoryManga.kt | 65 +- .../suwayomi/tachidesk/manga/impl/Chapter.kt | 169 +++-- .../manga/impl/ChapterDownloadHelper.kt | 18 +- .../suwayomi/tachidesk/manga/impl/Library.kt | 12 +- .../suwayomi/tachidesk/manga/impl/Manga.kt | 222 +++--- .../tachidesk/manga/impl/MangaList.kt | 184 ++--- .../suwayomi/tachidesk/manga/impl/Page.kt | 34 +- .../suwayomi/tachidesk/manga/impl/Search.kt | 48 +- .../suwayomi/tachidesk/manga/impl/Source.kt | 12 +- .../manga/impl/backup/BackupFlags.kt | 2 +- .../manga/impl/backup/models/Category.kt | 23 - .../manga/impl/backup/models/CategoryImpl.kt | 24 - .../manga/impl/backup/models/Chapter.kt | 11 +- .../manga/impl/backup/models/ChapterImpl.kt | 3 +- .../manga/impl/backup/models/History.kt | 42 -- .../manga/impl/backup/models/HistoryImpl.kt | 27 - .../manga/impl/backup/models/Manga.kt | 38 +- .../manga/impl/backup/models/MangaCategory.kt | 20 - .../manga/impl/backup/models/MangaChapter.kt | 3 - .../impl/backup/models/MangaChapterHistory.kt | 10 - .../manga/impl/backup/models/MangaImpl.kt | 3 +- .../manga/impl/backup/models/Track.kt | 10 +- .../manga/impl/backup/models/TrackImpl.kt | 3 +- .../impl/backup/proto/ProtoBackupExport.kt | 115 +-- .../impl/backup/proto/ProtoBackupImport.kt | 71 +- .../impl/backup/proto/ProtoBackupValidator.kt | 11 +- .../manga/impl/backup/proto/models/Backup.kt | 2 +- .../backup/proto/models/BackupCategory.kt | 24 +- .../impl/backup/proto/models/BackupChapter.kt | 4 +- .../impl/backup/proto/models/BackupHistory.kt | 4 +- .../impl/backup/proto/models/BackupManga.kt | 4 +- .../impl/backup/proto/models/BackupSource.kt | 6 +- .../backup/proto/models/BackupTracking.kt | 4 +- .../manga/impl/chapter/ChapterForDownload.kt | 27 +- .../manga/impl/download/DownloadManager.kt | 176 +++-- .../manga/impl/download/Downloader.kt | 41 +- .../fileProvider/ChaptersFilesProvider.kt | 21 +- .../download/fileProvider/FileDownloader.kt | 6 +- .../fileProvider/impl/ArchiveProvider.kt | 7 +- .../fileProvider/impl/FolderProvider.kt | 14 +- .../impl/download/model/DownloadChapter.kt | 2 +- .../impl/download/model/DownloadState.kt | 2 +- .../impl/download/model/DownloadStatus.kt | 5 +- .../manga/impl/extension/Extension.kt | 89 ++- .../manga/impl/extension/ExtensionsList.kt | 33 +- .../extension/github/ExtensionGithubApi.kt | 34 +- .../impl/extension/github/OnlineExtension.kt | 4 +- .../tachidesk/manga/impl/update/IUpdater.kt | 9 +- .../tachidesk/manga/impl/update/UpdateJob.kt | 4 +- .../manga/impl/update/UpdateStatus.kt | 24 +- .../tachidesk/manga/impl/update/Updater.kt | 111 ++- .../manga/impl/update/UpdaterSocket.kt | 14 +- .../tachidesk/manga/impl/update/Websocket.kt | 10 +- .../manga/impl/util/BytecodeEditor.kt | 77 +- .../tachidesk/manga/impl/util/DirName.kt | 38 +- .../tachidesk/manga/impl/util/PackageTools.kt | 76 +- .../impl/util/lang/{io.kt => FileExt.kt} | 0 .../manga/impl/util/lang/RxCoroutineBridge.kt | 66 +- .../manga/impl/util/network/OkHttp.kt | 12 +- .../impl/util/source/GetCatalogueSource.kt | 14 +- .../manga/impl/util/source/StubSource.kt | 6 +- .../manga/impl/util/storage/ImageResponse.kt | 29 +- .../manga/impl/util/storage/ImageUtil.kt | 31 +- .../tachidesk/manga/impl/util/storage/Io.kt | 5 +- .../manga/impl/util/storage/OkioExtensions.kt | 1 + .../model/dataclass/CategoryDataClass.kt | 11 +- .../manga/model/dataclass/ChapterDataClass.kt | 13 +- .../model/dataclass/ExtensionDataClass.kt | 4 +- .../model/dataclass/MangaChapterDataClass.kt | 2 +- .../manga/model/dataclass/MangaDataClass.kt | 12 +- .../manga/model/dataclass/PageDataClass.kt | 2 +- .../manga/model/dataclass/PaginatedList.kt | 10 +- .../manga/model/dataclass/SourceDataClass.kt | 6 +- .../manga/model/table/CategoryTable.kt | 19 +- .../manga/model/table/ChapterTable.kt | 2 +- .../manga/model/table/ExtensionTable.kt | 8 +- .../tachidesk/manga/model/table/MangaTable.kt | 8 +- .../suwayomi/tachidesk/server/JavalinSetup.kt | 81 ++- .../suwayomi/tachidesk/server/ServerConfig.kt | 38 +- .../suwayomi/tachidesk/server/ServerSetup.kt | 16 +- .../tachidesk/server/database/DBManager.kt | 8 +- .../database/migration/M0001_Initial.kt | 8 +- .../M0002_ChapterTableIndexRename.kt | 2 +- .../migration/M0003_DefaultCategory.kt | 2 +- .../migration/M0004_AnimeTablesBatch1.kt | 17 +- .../migration/M0005_AnimeTablesBatch2.kt | 9 +- .../migration/M0006_AnimeTablesBatch3.kt | 9 +- .../migration/M0007_ChapterIsDownloaded.kt | 2 +- .../migration/M0008_ChapterPageCount.kt | 2 +- .../migration/M0009_ChapterLastReadAt.kt | 2 +- .../migration/M0010_MangaAndChapterMeta.kt | 9 +- .../M0011_SourceDropPartOfFactorySource.kt | 2 +- .../database/migration/M0012_SourceIsNsfw.kt | 2 +- .../database/migration/M0013_MangaRealUrl.kt | 2 +- .../migration/M0014_MangaRemoveLengthLimit.kt | 5 +- ...15_SourceAndExtensionLangAddLengthLimit.kt | 5 +- .../M0016_ChapterIndexRenameToSourceOrder.kt | 5 +- .../migration/M0017_ChapterFetchedAt.kt | 2 +- .../migration/M0018_MangaInLibraryAt.kt | 2 +- .../database/migration/M0019_RemoveAnime.kt | 7 +- .../M0020_AddMangaLastFetchedAtColumns.kt | 5 +- .../migration/M0021_GlobalAndCategoryMeta.kt | 9 +- .../M0022_MangaThumbnailLastFetched.kt | 2 +- .../migration/M0023_CategoryMetaRefFix.kt | 4 +- .../migration/M0024_MangaUpdateStrategy.kt | 2 +- .../migration/M0025_ChapterRealUrl.kt | 2 +- .../M0026_CategoryIncludeInUpdate.kt | 2 +- .../migration/M0027_AddDefaultCategory.kt | 5 +- .../database/migration/M0028_AddCascade.kt | 5 +- .../M0029_DropMangaDefaultCategory.kt | 2 +- .../suwayomi/tachidesk/server/util/AppExit.kt | 2 +- .../tachidesk/server/util/AppMutex.kt | 27 +- .../suwayomi/tachidesk/server/util/Browser.kt | 6 +- .../tachidesk/server/util/Chromium.kt | 24 +- .../tachidesk/server/util/DocumentationDsl.kt | 317 ++++++--- .../tachidesk/server/util/SystemTray.kt | 71 +- .../server/util/WebInterfaceManager.kt | 150 ++-- .../suwayomi/tachidesk/util/HAScheduler.kt | 94 ++- .../test/kotlin/masstest/CloudFlareTest.kt | 16 +- .../masstest/TestExtensionCompatibility.kt | 40 +- .../tachidesk/graphql/RequestParserTest.kt | 105 ++- .../controller/CategoryControllerTest.kt | 2 +- .../manga/controller/UpdateControllerTest.kt | 6 +- .../tachidesk/manga/impl/CategoryMangaTest.kt | 14 +- .../tachidesk/manga/impl/MangaTest.kt | 10 +- .../suwayomi/tachidesk/manga/impl/PageTest.kt | 8 +- .../tachidesk/manga/impl/SearchTest.kt | 115 +-- .../manga/model/PaginatedListTest.kt | 49 +- .../tachidesk/test/ApplicationTest.kt | 9 +- .../suwayomi/tachidesk/test/TestUtils.kt | 21 +- settings.gradle.kts | 2 +- 277 files changed, 6660 insertions(+), 5041 deletions(-) create mode 100644 .editorconfig create mode 100644 buildSrc/settings.gradle.kts delete mode 100644 server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/Category.kt delete mode 100644 server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/CategoryImpl.kt delete mode 100644 server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/History.kt delete mode 100644 server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/HistoryImpl.kt delete mode 100644 server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/MangaCategory.kt delete mode 100644 server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/MangaChapter.kt delete mode 100644 server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/MangaChapterHistory.kt rename server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/lang/{io.kt => FileExt.kt} (100%) diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..6e41a3107 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +[*.{kt,kts}] +indent_size=4 +insert_final_newline=true +ij_kotlin_allow_trailing_comma=true +ij_kotlin_allow_trailing_comma_on_call_site=true +ij_kotlin_name_count_to_use_star_import=2147483647 +ij_kotlin_name_count_to_use_star_import_for_members=2147483647 + +ktlint_standard_discouraged-comment-location=disabled +ktlint_standard_if-else-wrapping=disabled +ktlint_standard_no-consecutive-comments=disabled \ No newline at end of file diff --git a/.github/workflows/build_pull_request.yml b/.github/workflows/build_pull_request.yml index 920ef3990..eb9ecaa91 100644 --- a/.github/workflows/build_pull_request.yml +++ b/.github/workflows/build_pull_request.yml @@ -49,5 +49,5 @@ jobs: uses: gradle/gradle-build-action@v2 with: build-root-directory: master - arguments: :server:shadowJar --stacktrace + arguments: ktlintCheck :server:shadowJar --stacktrace diff --git a/AndroidCompat/Config/build.gradle.kts b/AndroidCompat/Config/build.gradle.kts index dc1017af3..18bea3c7b 100644 --- a/AndroidCompat/Config/build.gradle.kts +++ b/AndroidCompat/Config/build.gradle.kts @@ -1,12 +1,11 @@ -@Suppress("DSL_SCOPE_VIOLATION") plugins { id(libs.plugins.kotlin.jvm.get().pluginId) id(libs.plugins.kotlin.serialization.get().pluginId) - id(libs.plugins.kotlinter.get().pluginId) + id(libs.plugins.ktlint.get().pluginId) } dependencies { // Shared implementation(libs.bundles.shared) testImplementation(libs.bundles.sharedTest) -} \ No newline at end of file +} diff --git a/AndroidCompat/Config/src/main/java/xyz/nulldev/ts/config/ApplicationRootDir.kt b/AndroidCompat/Config/src/main/java/xyz/nulldev/ts/config/ApplicationRootDir.kt index babdd51ad..12bee921d 100644 --- a/AndroidCompat/Config/src/main/java/xyz/nulldev/ts/config/ApplicationRootDir.kt +++ b/AndroidCompat/Config/src/main/java/xyz/nulldev/ts/config/ApplicationRootDir.kt @@ -15,6 +15,6 @@ val ApplicationRootDir: String get(): String { return System.getProperty( "$CONFIG_PREFIX.server.rootDir", - AppDirsFactory.getInstance().getUserDataDir("Tachidesk", null, null) + AppDirsFactory.getInstance().getUserDataDir("Tachidesk", null, null), ) } diff --git a/AndroidCompat/Config/src/main/java/xyz/nulldev/ts/config/ConfigKodeinModule.kt b/AndroidCompat/Config/src/main/java/xyz/nulldev/ts/config/ConfigKodeinModule.kt index c509e5f0b..47681922a 100644 --- a/AndroidCompat/Config/src/main/java/xyz/nulldev/ts/config/ConfigKodeinModule.kt +++ b/AndroidCompat/Config/src/main/java/xyz/nulldev/ts/config/ConfigKodeinModule.kt @@ -5,8 +5,9 @@ import org.kodein.di.bind import org.kodein.di.singleton class ConfigKodeinModule { - fun create() = DI.Module("ConfigManager") { - // Config module - bind() with singleton { GlobalConfigManager } - } + fun create() = + DI.Module("ConfigManager") { + // Config module + bind() with singleton { GlobalConfigManager } + } } diff --git a/AndroidCompat/Config/src/main/java/xyz/nulldev/ts/config/ConfigManager.kt b/AndroidCompat/Config/src/main/java/xyz/nulldev/ts/config/ConfigManager.kt index c4ad099b6..518778548 100644 --- a/AndroidCompat/Config/src/main/java/xyz/nulldev/ts/config/ConfigManager.kt +++ b/AndroidCompat/Config/src/main/java/xyz/nulldev/ts/config/ConfigManager.kt @@ -63,19 +63,21 @@ open class ConfigManager { val baseConfig = ConfigFactory.parseMap( mapOf( - "androidcompat.rootDir" to "$ApplicationRootDir/android-compat" // override AndroidCompat's rootDir - ) + // override AndroidCompat's rootDir + "androidcompat.rootDir" to "$ApplicationRootDir/android-compat", + ), ) // Load user config val userConfig = getUserConfig() - val config = ConfigFactory.empty() - .withFallback(baseConfig) - .withFallback(userConfig) - .withFallback(compatConfig) - .withFallback(serverConfig) - .resolve() + val config = + ConfigFactory.empty() + .withFallback(baseConfig) + .withFallback(userConfig) + .withFallback(compatConfig) + .withFallback(serverConfig) + .resolve() // set log level early if (debugLogsEnabled(config)) { @@ -95,14 +97,20 @@ open class ConfigManager { } } - private fun updateUserConfigFile(path: String, value: ConfigValue) { + private fun updateUserConfigFile( + path: String, + value: ConfigValue, + ) { val userConfigDoc = ConfigDocumentFactory.parseFile(userConfigFile) val updatedConfigDoc = userConfigDoc.withValue(path, value) val newFileContent = updatedConfigDoc.render() userConfigFile.writeText(newFileContent) } - suspend fun updateValue(path: String, value: Any) { + suspend fun updateValue( + path: String, + value: Any, + ) { mutex.withLock { val configValue = ConfigValueFactory.fromAnyRef(value) @@ -140,10 +148,16 @@ open class ConfigManager { return } - logger.debug { "user config is out of date, updating... (missingSettings= $hasMissingSettings, outdatedSettings= $hasOutdatedSettings" } + logger.debug { + "user config is out of date, updating... (missingSettings= $hasMissingSettings, outdatedSettings= $hasOutdatedSettings" + } var newUserConfigDoc: ConfigDocument = resetUserConfig(false) - userConfig.entrySet().filter { serverConfig.hasPath(it.key) }.forEach { newUserConfigDoc = newUserConfigDoc.withValue(it.key, it.value) } + userConfig.entrySet().filter { + serverConfig.hasPath( + it.key, + ) + }.forEach { newUserConfigDoc = newUserConfigDoc.withValue(it.key, it.value) } userConfigFile.writeText(newUserConfigDoc.render()) } diff --git a/AndroidCompat/Config/src/main/java/xyz/nulldev/ts/config/ConfigModule.kt b/AndroidCompat/Config/src/main/java/xyz/nulldev/ts/config/ConfigModule.kt index 643adbfa2..5b5507131 100644 --- a/AndroidCompat/Config/src/main/java/xyz/nulldev/ts/config/ConfigModule.kt +++ b/AndroidCompat/Config/src/main/java/xyz/nulldev/ts/config/ConfigModule.kt @@ -26,13 +26,17 @@ abstract class SystemPropertyOverridableConfigModule(getConfig: () -> Config, mo /** Defines a config property that is overridable with jvm `-D` commandline arguments prefixed with [CONFIG_PREFIX] */ class SystemPropertyOverrideDelegate(val getConfig: () -> Config, val moduleName: String) { - inline operator fun getValue(thisRef: R, property: KProperty<*>): T { + inline operator fun getValue( + thisRef: R, + property: KProperty<*>, + ): T { val configValue: T = getConfig().getValue(thisRef, property) - val combined = System.getProperty( - "$CONFIG_PREFIX.$moduleName.${property.name}", - configValue.toString() - ) + val combined = + System.getProperty( + "$CONFIG_PREFIX.$moduleName.${property.name}", + configValue.toString(), + ) return when (T::class.simpleName) { "Int" -> combined.toInt() diff --git a/AndroidCompat/Config/src/main/java/xyz/nulldev/ts/config/Logging.kt b/AndroidCompat/Config/src/main/java/xyz/nulldev/ts/config/Logging.kt index 883bce0cf..83812ca6b 100644 --- a/AndroidCompat/Config/src/main/java/xyz/nulldev/ts/config/Logging.kt +++ b/AndroidCompat/Config/src/main/java/xyz/nulldev/ts/config/Logging.kt @@ -19,31 +19,37 @@ import mu.KotlinLogging import org.slf4j.Logger import org.slf4j.LoggerFactory -private fun createRollingFileAppender(logContext: LoggerContext, logDirPath: String): RollingFileAppender { +private fun createRollingFileAppender( + logContext: LoggerContext, + logDirPath: String, +): RollingFileAppender { val logFilename = "application" - val logEncoder = PatternLayoutEncoder().apply { - pattern = "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n" - context = logContext - start() - } - - val appender = RollingFileAppender().apply { - name = "FILE" - context = logContext - encoder = logEncoder - file = "$logDirPath/$logFilename.log" - } - - val rollingPolicy = SizeAndTimeBasedRollingPolicy().apply { - context = logContext - setParent(appender) - fileNamePattern = "$logDirPath/${logFilename}_%d{yyyy-MM-dd}_%i.log.gz" - setMaxFileSize(FileSize.valueOf("10mb")) - maxHistory = 14 - setTotalSizeCap(FileSize.valueOf("1gb")) - start() - } + val logEncoder = + PatternLayoutEncoder().apply { + pattern = "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n" + context = logContext + start() + } + + val appender = + RollingFileAppender().apply { + name = "FILE" + context = logContext + encoder = logEncoder + file = "$logDirPath/$logFilename.log" + } + + val rollingPolicy = + SizeAndTimeBasedRollingPolicy().apply { + context = logContext + setParent(appender) + fileNamePattern = "$logDirPath/${logFilename}_%d{yyyy-MM-dd}_%i.log.gz" + setMaxFileSize(FileSize.valueOf("10mb")) + maxHistory = 14 + setTotalSizeCap(FileSize.valueOf("1gb")) + start() + } appender.rollingPolicy = rollingPolicy appender.start() @@ -72,12 +78,16 @@ fun initLoggerConfig(appRootPath: String) { const val BASE_LOGGER_NAME = "_BaseLogger" -fun setLogLevelFor(name: String, level: Level) { - val logger = if (name == BASE_LOGGER_NAME) { - getBaseLogger() - } else { - getLogger(name) - } +fun setLogLevelFor( + name: String, + level: Level, +) { + val logger = + if (name == BASE_LOGGER_NAME) { + getBaseLogger() + } else { + getLogger(name) + } logger.level = level } diff --git a/AndroidCompat/Config/src/main/java/xyz/nulldev/ts/config/util/ConfigExtensions.kt b/AndroidCompat/Config/src/main/java/xyz/nulldev/ts/config/util/ConfigExtensions.kt index 292eefc54..a0a9bc066 100644 --- a/AndroidCompat/Config/src/main/java/xyz/nulldev/ts/config/util/ConfigExtensions.kt +++ b/AndroidCompat/Config/src/main/java/xyz/nulldev/ts/config/util/ConfigExtensions.kt @@ -2,5 +2,6 @@ package xyz.nulldev.ts.config.util import com.typesafe.config.Config -operator fun Config.get(key: String) = getString(key) - ?: throw IllegalStateException("Could not find value for config entry: $key!") +operator fun Config.get(key: String) = + getString(key) + ?: throw IllegalStateException("Could not find value for config entry: $key!") diff --git a/AndroidCompat/build.gradle.kts b/AndroidCompat/build.gradle.kts index ef5f6c418..d6d392e4d 100644 --- a/AndroidCompat/build.gradle.kts +++ b/AndroidCompat/build.gradle.kts @@ -1,8 +1,7 @@ -@Suppress("DSL_SCOPE_VIOLATION") plugins { id(libs.plugins.kotlin.jvm.get().pluginId) id(libs.plugins.kotlin.serialization.get().pluginId) - id(libs.plugins.kotlinter.get().pluginId) + id(libs.plugins.ktlint.get().pluginId) } dependencies { diff --git a/AndroidCompat/src/main/java/android/preference/PreferenceManager.kt b/AndroidCompat/src/main/java/android/preference/PreferenceManager.kt index 0e4bd51dc..a2ffc4487 100644 --- a/AndroidCompat/src/main/java/android/preference/PreferenceManager.kt +++ b/AndroidCompat/src/main/java/android/preference/PreferenceManager.kt @@ -12,7 +12,7 @@ class PreferenceManager { fun getDefaultSharedPreferences(context: Context) = context.getSharedPreferences( context.applicationInfo.packageName, - Context.MODE_PRIVATE + Context.MODE_PRIVATE, )!! } } diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/AndroidCompat.kt b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/AndroidCompat.kt index 0e47b7546..5836bef53 100644 --- a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/AndroidCompat.kt +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/AndroidCompat.kt @@ -7,7 +7,6 @@ import org.kodein.di.instance import xyz.nulldev.androidcompat.androidimpl.CustomContext class AndroidCompat { - val context: CustomContext by DI.global.instance() fun startApp(application: Application) { diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/AndroidCompatInitializer.kt b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/AndroidCompatInitializer.kt index 7c832ed19..16094d889 100644 --- a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/AndroidCompatInitializer.kt +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/AndroidCompatInitializer.kt @@ -18,10 +18,13 @@ class AndroidCompatInitializer { GlobalConfigManager.registerModules( FilesConfigModule.register(GlobalConfigManager.config), ApplicationInfoConfigModule.register(GlobalConfigManager.config), - SystemConfigModule.register(GlobalConfigManager.config) + SystemConfigModule.register(GlobalConfigManager.config), ) // Set some properties extensions use - System.setProperty("http.agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36") + System.setProperty( + "http.agent", + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36", + ) } } diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/AndroidCompatModule.kt b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/AndroidCompatModule.kt index 5d00237ba..faecce896 100644 --- a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/AndroidCompatModule.kt +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/AndroidCompatModule.kt @@ -18,22 +18,24 @@ import xyz.nulldev.androidcompat.service.ServiceSupport */ class AndroidCompatModule { - fun create() = DI.Module("AndroidCompat") { - bind() with singleton { AndroidFiles() } + fun create() = + DI.Module("AndroidCompat") { + bind() with singleton { AndroidFiles() } - bind() with singleton { ApplicationInfoImpl() } + bind() with singleton { ApplicationInfoImpl() } - bind() with singleton { ServiceSupport() } + bind() with singleton { ServiceSupport() } - bind() with singleton { FakePackageManager() } + bind() with singleton { FakePackageManager() } - bind() with singleton { PackageController() } + bind() with singleton { PackageController() } - // Context - bind() with singleton { CustomContext() } - bind() with singleton { - val context: Context by DI.global.instance() - context + // Context + bind() with singleton { CustomContext() } + bind() with + singleton { + val context: Context by DI.global.instance() + context + } } - } } diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/config/ApplicationInfoConfigModule.kt b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/config/ApplicationInfoConfigModule.kt index 1c3a2eb03..1ec0d09d3 100644 --- a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/config/ApplicationInfoConfigModule.kt +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/config/ApplicationInfoConfigModule.kt @@ -13,7 +13,6 @@ class ApplicationInfoConfigModule(getConfig: () -> Config) : ConfigModule(getCon val debug: Boolean by getConfig() companion object { - fun register(config: Config) = - ApplicationInfoConfigModule { config.getConfig("android.app") } + fun register(config: Config) = ApplicationInfoConfigModule { config.getConfig("android.app") } } } diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/config/FilesConfigModule.kt b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/config/FilesConfigModule.kt index 8bb717db3..dca29751d 100644 --- a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/config/FilesConfigModule.kt +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/config/FilesConfigModule.kt @@ -28,7 +28,6 @@ class FilesConfigModule(getConfig: () -> Config) : ConfigModule(getConfig) { val packageDir: String by getConfig() companion object { - fun register(config: Config) = - FilesConfigModule { config.getConfig("android.files") } + fun register(config: Config) = FilesConfigModule { config.getConfig("android.files") } } } diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/config/SystemConfigModule.kt b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/config/SystemConfigModule.kt index 5447ba6e7..e7d04172d 100644 --- a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/config/SystemConfigModule.kt +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/config/SystemConfigModule.kt @@ -10,13 +10,16 @@ class SystemConfigModule(val getConfig: () -> Config) : ConfigModule(getConfig) val propertyPrefix = "properties." fun getStringProperty(property: String) = getConfig().getString("$propertyPrefix$property")!! + fun getIntProperty(property: String) = getConfig().getInt("$propertyPrefix$property") + fun getLongProperty(property: String) = getConfig().getLong("$propertyPrefix$property") + fun getBooleanProperty(property: String) = getConfig().getBoolean("$propertyPrefix$property") + fun hasProperty(property: String) = getConfig().hasPath("$propertyPrefix$property") companion object { - fun register(config: Config) = - SystemConfigModule { config.getConfig("android.system") } + fun register(config: Config) = SystemConfigModule { config.getConfig("android.system") } } } diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/db/ScrollableResultSet.kt b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/db/ScrollableResultSet.kt index 767355256..62ae95abb 100644 --- a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/db/ScrollableResultSet.kt +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/db/ScrollableResultSet.kt @@ -20,7 +20,6 @@ import java.util.Calendar @Suppress("UNCHECKED_CAST") class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent { - private val cachedContent = mutableListOf() private val columnCache = mutableMapOf() private var lastReturnWasNull = false @@ -29,9 +28,10 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent { val parentMetadata = parent.metaData val columnCount = parentMetadata.columnCount - val columnLabels = (1..columnCount).map { - parentMetadata.getColumnLabel(it) - }.toTypedArray() + val columnLabels = + (1..columnCount).map { + parentMetadata.getColumnLabel(it) + }.toTypedArray() init { val columnCount = columnCount @@ -43,10 +43,11 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent { // Fill cache while (parent.next()) { - cachedContent += ResultSetEntry().apply { - for (i in 1..columnCount) - data += parent.getObject(i) - } + cachedContent += + ResultSetEntry().apply { + for (i in 1..columnCount) + data += parent.getObject(i) + } resultSetLength++ } } @@ -92,67 +93,121 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent { return obj(columnLabel) as NClob } - override fun updateNString(columnIndex: Int, nString: String?) { + override fun updateNString( + columnIndex: Int, + nString: String?, + ) { notImplemented() } - override fun updateNString(columnLabel: String?, nString: String?) { + override fun updateNString( + columnLabel: String?, + nString: String?, + ) { notImplemented() } - override fun updateBinaryStream(columnIndex: Int, x: InputStream?, length: Int) { + override fun updateBinaryStream( + columnIndex: Int, + x: InputStream?, + length: Int, + ) { notImplemented() } - override fun updateBinaryStream(columnLabel: String?, x: InputStream?, length: Int) { + override fun updateBinaryStream( + columnLabel: String?, + x: InputStream?, + length: Int, + ) { notImplemented() } - override fun updateBinaryStream(columnIndex: Int, x: InputStream?, length: Long) { + override fun updateBinaryStream( + columnIndex: Int, + x: InputStream?, + length: Long, + ) { notImplemented() } - override fun updateBinaryStream(columnLabel: String?, x: InputStream?, length: Long) { + override fun updateBinaryStream( + columnLabel: String?, + x: InputStream?, + length: Long, + ) { notImplemented() } - override fun updateBinaryStream(columnIndex: Int, x: InputStream?) { + override fun updateBinaryStream( + columnIndex: Int, + x: InputStream?, + ) { notImplemented() } - override fun updateBinaryStream(columnLabel: String?, x: InputStream?) { + override fun updateBinaryStream( + columnLabel: String?, + x: InputStream?, + ) { notImplemented() } - override fun updateTimestamp(columnIndex: Int, x: Timestamp?) { + override fun updateTimestamp( + columnIndex: Int, + x: Timestamp?, + ) { notImplemented() } - override fun updateTimestamp(columnLabel: String?, x: Timestamp?) { + override fun updateTimestamp( + columnLabel: String?, + x: Timestamp?, + ) { notImplemented() } - override fun updateNCharacterStream(columnIndex: Int, x: Reader?, length: Long) { + override fun updateNCharacterStream( + columnIndex: Int, + x: Reader?, + length: Long, + ) { notImplemented() } - override fun updateNCharacterStream(columnLabel: String?, reader: Reader?, length: Long) { + override fun updateNCharacterStream( + columnLabel: String?, + reader: Reader?, + length: Long, + ) { notImplemented() } - override fun updateNCharacterStream(columnIndex: Int, x: Reader?) { + override fun updateNCharacterStream( + columnIndex: Int, + x: Reader?, + ) { notImplemented() } - override fun updateNCharacterStream(columnLabel: String?, reader: Reader?) { + override fun updateNCharacterStream( + columnLabel: String?, + reader: Reader?, + ) { notImplemented() } - override fun updateInt(columnIndex: Int, x: Int) { + override fun updateInt( + columnIndex: Int, + x: Int, + ) { notImplemented() } - override fun updateInt(columnLabel: String?, x: Int) { + override fun updateInt( + columnLabel: String?, + x: Int, + ) { notImplemented() } @@ -170,12 +225,18 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent { notImplemented() } - override fun getDate(columnIndex: Int, cal: Calendar?): Date { + override fun getDate( + columnIndex: Int, + cal: Calendar?, + ): Date { // TODO Maybe? notImplemented() } - override fun getDate(columnLabel: String?, cal: Calendar?): Date { + override fun getDate( + columnLabel: String?, + cal: Calendar?, + ): Date { // TODO Maybe? notImplemented() } @@ -185,11 +246,17 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent { notImplemented() } - override fun updateFloat(columnIndex: Int, x: Float) { + override fun updateFloat( + columnIndex: Int, + x: Float, + ) { notImplemented() } - override fun updateFloat(columnLabel: String?, x: Float) { + override fun updateFloat( + columnLabel: String?, + x: Float, + ) { notImplemented() } @@ -205,12 +272,18 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent { return cursor - 1 < resultSetLength } - override fun getBigDecimal(columnIndex: Int, scale: Int): BigDecimal { + override fun getBigDecimal( + columnIndex: Int, + scale: Int, + ): BigDecimal { // TODO Maybe? notImplemented() } - override fun getBigDecimal(columnLabel: String?, scale: Int): BigDecimal { + override fun getBigDecimal( + columnLabel: String?, + scale: Int, + ): BigDecimal { // TODO Maybe? notImplemented() } @@ -223,11 +296,17 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent { return obj(columnLabel) as BigDecimal } - override fun updateBytes(columnIndex: Int, x: ByteArray?) { + override fun updateBytes( + columnIndex: Int, + x: ByteArray?, + ) { notImplemented() } - override fun updateBytes(columnLabel: String?, x: ByteArray?) { + override fun updateBytes( + columnLabel: String?, + x: ByteArray?, + ) { notImplemented() } @@ -249,12 +328,18 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent { notImplemented() } - override fun getTime(columnIndex: Int, cal: Calendar?): Time { + override fun getTime( + columnIndex: Int, + cal: Calendar?, + ): Time { // TODO Maybe? notImplemented() } - override fun getTime(columnLabel: String?, cal: Calendar?): Time { + override fun getTime( + columnLabel: String?, + cal: Calendar?, + ): Time { // TODO Maybe? notImplemented() } @@ -328,27 +413,49 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent { return cursorValid() } - override fun updateAsciiStream(columnIndex: Int, x: InputStream?, length: Int) { + override fun updateAsciiStream( + columnIndex: Int, + x: InputStream?, + length: Int, + ) { notImplemented() } - override fun updateAsciiStream(columnLabel: String?, x: InputStream?, length: Int) { + override fun updateAsciiStream( + columnLabel: String?, + x: InputStream?, + length: Int, + ) { notImplemented() } - override fun updateAsciiStream(columnIndex: Int, x: InputStream?, length: Long) { + override fun updateAsciiStream( + columnIndex: Int, + x: InputStream?, + length: Long, + ) { notImplemented() } - override fun updateAsciiStream(columnLabel: String?, x: InputStream?, length: Long) { + override fun updateAsciiStream( + columnLabel: String?, + x: InputStream?, + length: Long, + ) { notImplemented() } - override fun updateAsciiStream(columnIndex: Int, x: InputStream?) { + override fun updateAsciiStream( + columnIndex: Int, + x: InputStream?, + ) { notImplemented() } - override fun updateAsciiStream(columnLabel: String?, x: InputStream?) { + override fun updateAsciiStream( + columnLabel: String?, + x: InputStream?, + ) { notImplemented() } @@ -360,61 +467,107 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent { return obj(columnLabel) as URL } - override fun updateShort(columnIndex: Int, x: Short) { + override fun updateShort( + columnIndex: Int, + x: Short, + ) { notImplemented() } - override fun updateShort(columnLabel: String?, x: Short) { + override fun updateShort( + columnLabel: String?, + x: Short, + ) { notImplemented() } override fun getType() = ResultSet.TYPE_SCROLL_INSENSITIVE - override fun updateNClob(columnIndex: Int, nClob: NClob?) { + override fun updateNClob( + columnIndex: Int, + nClob: NClob?, + ) { notImplemented() } - override fun updateNClob(columnLabel: String?, nClob: NClob?) { + override fun updateNClob( + columnLabel: String?, + nClob: NClob?, + ) { notImplemented() } - override fun updateNClob(columnIndex: Int, reader: Reader?, length: Long) { + override fun updateNClob( + columnIndex: Int, + reader: Reader?, + length: Long, + ) { notImplemented() } - override fun updateNClob(columnLabel: String?, reader: Reader?, length: Long) { + override fun updateNClob( + columnLabel: String?, + reader: Reader?, + length: Long, + ) { notImplemented() } - override fun updateNClob(columnIndex: Int, reader: Reader?) { + override fun updateNClob( + columnIndex: Int, + reader: Reader?, + ) { notImplemented() } - override fun updateNClob(columnLabel: String?, reader: Reader?) { + override fun updateNClob( + columnLabel: String?, + reader: Reader?, + ) { notImplemented() } - override fun updateRef(columnIndex: Int, x: Ref?) { + override fun updateRef( + columnIndex: Int, + x: Ref?, + ) { notImplemented() } - override fun updateRef(columnLabel: String?, x: Ref?) { + override fun updateRef( + columnLabel: String?, + x: Ref?, + ) { notImplemented() } - override fun updateObject(columnIndex: Int, x: Any?, scaleOrLength: Int) { + override fun updateObject( + columnIndex: Int, + x: Any?, + scaleOrLength: Int, + ) { notImplemented() } - override fun updateObject(columnIndex: Int, x: Any?) { + override fun updateObject( + columnIndex: Int, + x: Any?, + ) { notImplemented() } - override fun updateObject(columnLabel: String?, x: Any?, scaleOrLength: Int) { + override fun updateObject( + columnLabel: String?, + x: Any?, + scaleOrLength: Int, + ) { notImplemented() } - override fun updateObject(columnLabel: String?, x: Any?) { + override fun updateObject( + columnLabel: String?, + x: Any?, + ) { notImplemented() } @@ -422,11 +575,17 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent { internalMove(resultSetLength + 1) } - override fun updateLong(columnIndex: Int, x: Long) { + override fun updateLong( + columnIndex: Int, + x: Long, + ) { notImplemented() } - override fun updateLong(columnLabel: String?, x: Long) { + override fun updateLong( + columnLabel: String?, + x: Long, + ) { notImplemented() } @@ -440,27 +599,47 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent { notImplemented() } - override fun updateClob(columnIndex: Int, x: Clob?) { + override fun updateClob( + columnIndex: Int, + x: Clob?, + ) { notImplemented() } - override fun updateClob(columnLabel: String?, x: Clob?) { + override fun updateClob( + columnLabel: String?, + x: Clob?, + ) { notImplemented() } - override fun updateClob(columnIndex: Int, reader: Reader?, length: Long) { + override fun updateClob( + columnIndex: Int, + reader: Reader?, + length: Long, + ) { notImplemented() } - override fun updateClob(columnLabel: String?, reader: Reader?, length: Long) { + override fun updateClob( + columnLabel: String?, + reader: Reader?, + length: Long, + ) { notImplemented() } - override fun updateClob(columnIndex: Int, reader: Reader?) { + override fun updateClob( + columnIndex: Int, + reader: Reader?, + ) { notImplemented() } - override fun updateClob(columnLabel: String?, reader: Reader?) { + override fun updateClob( + columnLabel: String?, + reader: Reader?, + ) { notImplemented() } @@ -480,19 +659,31 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent { return obj(columnLabel) as String? } - override fun updateSQLXML(columnIndex: Int, xmlObject: SQLXML?) { + override fun updateSQLXML( + columnIndex: Int, + xmlObject: SQLXML?, + ) { notImplemented() } - override fun updateSQLXML(columnLabel: String?, xmlObject: SQLXML?) { + override fun updateSQLXML( + columnLabel: String?, + xmlObject: SQLXML?, + ) { notImplemented() } - override fun updateDate(columnIndex: Int, x: Date?) { + override fun updateDate( + columnIndex: Int, + x: Date?, + ) { notImplemented() } - override fun updateDate(columnLabel: String?, x: Date?) { + override fun updateDate( + columnLabel: String?, + x: Date?, + ) { notImplemented() } @@ -504,21 +695,33 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent { return obj(columnLabel) } - override fun getObject(columnIndex: Int, map: MutableMap>?): Any { + override fun getObject( + columnIndex: Int, + map: MutableMap>?, + ): Any { // TODO Maybe? notImplemented() } - override fun getObject(columnLabel: String?, map: MutableMap>?): Any { + override fun getObject( + columnLabel: String?, + map: MutableMap>?, + ): Any { // TODO Maybe? notImplemented() } - override fun getObject(columnIndex: Int, type: Class?): T { + override fun getObject( + columnIndex: Int, + type: Class?, + ): T { return obj(columnIndex) as T } - override fun getObject(columnLabel: String?, type: Class?): T { + override fun getObject( + columnLabel: String?, + type: Class?, + ): T { return obj(columnLabel) as T } @@ -527,11 +730,17 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent { return cursorValid() } - override fun updateDouble(columnIndex: Int, x: Double) { + override fun updateDouble( + columnIndex: Int, + x: Double, + ) { notImplemented() } - override fun updateDouble(columnLabel: String?, x: Double) { + override fun updateDouble( + columnLabel: String?, + x: Double, + ) { notImplemented() } @@ -565,35 +774,61 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent { notImplemented() } - override fun updateBlob(columnIndex: Int, x: Blob?) { + override fun updateBlob( + columnIndex: Int, + x: Blob?, + ) { notImplemented() } - override fun updateBlob(columnLabel: String?, x: Blob?) { + override fun updateBlob( + columnLabel: String?, + x: Blob?, + ) { notImplemented() } - override fun updateBlob(columnIndex: Int, inputStream: InputStream?, length: Long) { + override fun updateBlob( + columnIndex: Int, + inputStream: InputStream?, + length: Long, + ) { notImplemented() } - override fun updateBlob(columnLabel: String?, inputStream: InputStream?, length: Long) { + override fun updateBlob( + columnLabel: String?, + inputStream: InputStream?, + length: Long, + ) { notImplemented() } - override fun updateBlob(columnIndex: Int, inputStream: InputStream?) { + override fun updateBlob( + columnIndex: Int, + inputStream: InputStream?, + ) { notImplemented() } - override fun updateBlob(columnLabel: String?, inputStream: InputStream?) { + override fun updateBlob( + columnLabel: String?, + inputStream: InputStream?, + ) { notImplemented() } - override fun updateByte(columnIndex: Int, x: Byte) { + override fun updateByte( + columnIndex: Int, + x: Byte, + ) { notImplemented() } - override fun updateByte(columnLabel: String?, x: Byte) { + override fun updateByte( + columnLabel: String?, + x: Byte, + ) { notImplemented() } @@ -627,11 +862,17 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent { notImplemented() } - override fun updateString(columnIndex: Int, x: String?) { + override fun updateString( + columnIndex: Int, + x: String?, + ) { notImplemented() } - override fun updateString(columnLabel: String?, x: String?) { + override fun updateString( + columnLabel: String?, + x: String?, + ) { notImplemented() } @@ -651,11 +892,17 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent { return cursor - 1 < resultSetLength } - override fun updateBoolean(columnIndex: Int, x: Boolean) { + override fun updateBoolean( + columnIndex: Int, + x: Boolean, + ) { notImplemented() } - override fun updateBoolean(columnLabel: String?, x: Boolean) { + override fun updateBoolean( + columnLabel: String?, + x: Boolean, + ) { notImplemented() } @@ -665,11 +912,17 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent { override fun rowUpdated() = false - override fun updateBigDecimal(columnIndex: Int, x: BigDecimal?) { + override fun updateBigDecimal( + columnIndex: Int, + x: BigDecimal?, + ) { notImplemented() } - override fun updateBigDecimal(columnLabel: String?, x: BigDecimal?) { + override fun updateBigDecimal( + columnLabel: String?, + x: BigDecimal?, + ) { notImplemented() } @@ -689,11 +942,17 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent { return getBinaryStream(columnLabel) } - override fun updateTime(columnIndex: Int, x: Time?) { + override fun updateTime( + columnIndex: Int, + x: Time?, + ) { notImplemented() } - override fun updateTime(columnLabel: String?, x: Time?) { + override fun updateTime( + columnLabel: String?, + x: Time?, + ) { notImplemented() } @@ -707,12 +966,18 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent { notImplemented() } - override fun getTimestamp(columnIndex: Int, cal: Calendar?): Timestamp { + override fun getTimestamp( + columnIndex: Int, + cal: Calendar?, + ): Timestamp { // TODO Maybe? notImplemented() } - override fun getTimestamp(columnLabel: String?, cal: Calendar?): Timestamp { + override fun getTimestamp( + columnLabel: String?, + cal: Calendar?, + ): Timestamp { // TODO Maybe? notImplemented() } @@ -729,11 +994,17 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent { override fun getConcurrency() = ResultSet.CONCUR_READ_ONLY - override fun updateRowId(columnIndex: Int, x: RowId?) { + override fun updateRowId( + columnIndex: Int, + x: RowId?, + ) { notImplemented() } - override fun updateRowId(columnLabel: String?, x: RowId?) { + override fun updateRowId( + columnLabel: String?, + x: RowId?, + ) { notImplemented() } @@ -745,11 +1016,17 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent { return getBinaryStream(columnLabel).reader() } - override fun updateArray(columnIndex: Int, x: Array?) { + override fun updateArray( + columnIndex: Int, + x: Array?, + ) { notImplemented() } - override fun updateArray(columnLabel: String?, x: Array?) { + override fun updateArray( + columnLabel: String?, + x: Array?, + ) { notImplemented() } @@ -814,9 +1091,13 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent { override fun getMetaData(): ResultSetMetaData { return object : ResultSetMetaData by parentMetadata { override fun isReadOnly(column: Int) = true + override fun isWritable(column: Int) = false + override fun isDefinitelyWritable(column: Int) = false + override fun getColumnCount() = this@ScrollableResultSet.columnCount + override fun getColumnLabel(column: Int): String { return columnLabels[column - 1] } @@ -831,27 +1112,49 @@ class ScrollableResultSet(val parent: ResultSet) : ResultSet by parent { return (obj(columnLabel) as ByteArray).inputStream() } - override fun updateCharacterStream(columnIndex: Int, x: Reader?, length: Int) { + override fun updateCharacterStream( + columnIndex: Int, + x: Reader?, + length: Int, + ) { notImplemented() } - override fun updateCharacterStream(columnLabel: String?, reader: Reader?, length: Int) { + override fun updateCharacterStream( + columnLabel: String?, + reader: Reader?, + length: Int, + ) { notImplemented() } - override fun updateCharacterStream(columnIndex: Int, x: Reader?, length: Long) { + override fun updateCharacterStream( + columnIndex: Int, + x: Reader?, + length: Long, + ) { notImplemented() } - override fun updateCharacterStream(columnLabel: String?, reader: Reader?, length: Long) { + override fun updateCharacterStream( + columnLabel: String?, + reader: Reader?, + length: Long, + ) { notImplemented() } - override fun updateCharacterStream(columnIndex: Int, x: Reader?) { + override fun updateCharacterStream( + columnIndex: Int, + x: Reader?, + ) { notImplemented() } - override fun updateCharacterStream(columnLabel: String?, reader: Reader?) { + override fun updateCharacterStream( + columnLabel: String?, + reader: Reader?, + ) { notImplemented() } diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/io/sharedprefs/JavaSharedPreferences.kt b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/io/sharedprefs/JavaSharedPreferences.kt index 7e79eca09..b0cac7a2c 100644 --- a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/io/sharedprefs/JavaSharedPreferences.kt +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/io/sharedprefs/JavaSharedPreferences.kt @@ -31,7 +31,10 @@ class JavaSharedPreferences(key: String) : SharedPreferences { return preferences.keys.associateWith { preferences.getStringOrNull(it) }.toMutableMap() } - override fun getString(key: String, defValue: String?): String? { + override fun getString( + key: String, + defValue: String?, + ): String? { return if (defValue != null) { preferences.getString(key, defValue) } else { @@ -39,7 +42,10 @@ class JavaSharedPreferences(key: String) : SharedPreferences { } } - override fun getStringSet(key: String, defValues: Set?): Set? { + override fun getStringSet( + key: String, + defValues: Set?, + ): Set? { try { return if (defValues != null) { preferences.decodeValue(SetSerializer(String.serializer()), key, defValues) @@ -51,19 +57,31 @@ class JavaSharedPreferences(key: String) : SharedPreferences { } } - override fun getInt(key: String, defValue: Int): Int { + override fun getInt( + key: String, + defValue: Int, + ): Int { return preferences.getInt(key, defValue) } - override fun getLong(key: String, defValue: Long): Long { + override fun getLong( + key: String, + defValue: Long, + ): Long { return preferences.getLong(key, defValue) } - override fun getFloat(key: String, defValue: Float): Float { + override fun getFloat( + key: String, + defValue: Float, + ): Float { return preferences.getFloat(key, defValue) } - override fun getBoolean(key: String, defValue: Boolean): Boolean { + override fun getBoolean( + key: String, + defValue: Boolean, + ): Boolean { return preferences.getBoolean(key, defValue) } @@ -80,11 +98,15 @@ class JavaSharedPreferences(key: String) : SharedPreferences { private sealed class Action { data class Add(val key: String, val value: Any) : Action() + data class Remove(val key: String) : Action() object Clear : Action() } - override fun putString(key: String, value: String?): SharedPreferences.Editor { + override fun putString( + key: String, + value: String?, + ): SharedPreferences.Editor { if (value != null) { actions += Action.Add(key, value) } else { @@ -95,7 +117,7 @@ class JavaSharedPreferences(key: String) : SharedPreferences { override fun putStringSet( key: String, - values: MutableSet? + values: MutableSet?, ): SharedPreferences.Editor { if (values != null) { actions += Action.Add(key, values) @@ -105,22 +127,34 @@ class JavaSharedPreferences(key: String) : SharedPreferences { return this } - override fun putInt(key: String, value: Int): SharedPreferences.Editor { + override fun putInt( + key: String, + value: Int, + ): SharedPreferences.Editor { actions += Action.Add(key, value) return this } - override fun putLong(key: String, value: Long): SharedPreferences.Editor { + override fun putLong( + key: String, + value: Long, + ): SharedPreferences.Editor { actions += Action.Add(key, value) return this } - override fun putFloat(key: String, value: Float): SharedPreferences.Editor { + override fun putFloat( + key: String, + value: Float, + ): SharedPreferences.Editor { actions += Action.Add(key, value) return this } - override fun putBoolean(key: String, value: Boolean): SharedPreferences.Editor { + override fun putBoolean( + key: String, + value: Boolean, + ): SharedPreferences.Editor { actions += Action.Add(key, value) return this } @@ -148,15 +182,16 @@ class JavaSharedPreferences(key: String) : SharedPreferences { actions.forEach { @Suppress("UNCHECKED_CAST") when (it) { - is Action.Add -> when (val value = it.value) { - is Set<*> -> preferences.encodeValue(SetSerializer(String.serializer()), it.key, value as Set) - is String -> preferences.putString(it.key, value) - is Int -> preferences.putInt(it.key, value) - is Long -> preferences.putLong(it.key, value) - is Float -> preferences.putFloat(it.key, value) - is Double -> preferences.putDouble(it.key, value) - is Boolean -> preferences.putBoolean(it.key, value) - } + is Action.Add -> + when (val value = it.value) { + is Set<*> -> preferences.encodeValue(SetSerializer(String.serializer()), it.key, value as Set) + is String -> preferences.putString(it.key, value) + is Int -> preferences.putInt(it.key, value) + is Long -> preferences.putLong(it.key, value) + is Float -> preferences.putFloat(it.key, value) + is Double -> preferences.putDouble(it.key, value) + is Boolean -> preferences.putBoolean(it.key, value) + } is Action.Remove -> { preferences.remove(it.key) /** @@ -178,9 +213,10 @@ class JavaSharedPreferences(key: String) : SharedPreferences { } override fun registerOnSharedPreferenceChangeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener) { - val javaListener = PreferenceChangeListener { - listener.onSharedPreferenceChanged(this, it.key) - } + val javaListener = + PreferenceChangeListener { + listener.onSharedPreferenceChanged(this, it.key) + } listeners[listener] = javaListener javaPreferences.addPreferenceChangeListener(javaListener) } diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/pm/InstalledPackage.kt b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/pm/InstalledPackage.kt index ed50a9e7c..ebbf7102d 100644 --- a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/pm/InstalledPackage.kt +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/pm/InstalledPackage.kt @@ -20,42 +20,47 @@ data class InstalledPackage(val root: File) { val icon = File(root, "icon.png") val info: PackageInfo - get() = ApkParsers.getMetaInfo(apk).toPackageInfo(apk).also { - val parsed = ApkFile(apk) - val dbFactory = DocumentBuilderFactory.newInstance() - val dBuilder = dbFactory.newDocumentBuilder() - val doc = parsed.manifestXml.byteInputStream().use { - dBuilder.parse(it) - } + get() = + ApkParsers.getMetaInfo(apk).toPackageInfo(apk).also { + val parsed = ApkFile(apk) + val dbFactory = DocumentBuilderFactory.newInstance() + val dBuilder = dbFactory.newDocumentBuilder() + val doc = + parsed.manifestXml.byteInputStream().use { + dBuilder.parse(it) + } - it.applicationInfo.metaData = Bundle().apply { - val appTag = doc.getElementsByTagName("application").item(0) + it.applicationInfo.metaData = + Bundle().apply { + val appTag = doc.getElementsByTagName("application").item(0) - appTag?.childNodes?.toList()?.filter { - it.nodeType == Node.ELEMENT_NODE - }?.map { - it as Element - }?.filter { - it.tagName == "meta-data" - }?.map { - putString( - it.attributes.getNamedItem("android:name").nodeValue, - it.attributes.getNamedItem("android:value").nodeValue - ) - } - } + appTag?.childNodes?.toList()?.filter { + it.nodeType == Node.ELEMENT_NODE + }?.map { + it as Element + }?.filter { + it.tagName == "meta-data" + }?.map { + putString( + it.attributes.getNamedItem("android:name").nodeValue, + it.attributes.getNamedItem("android:value").nodeValue, + ) + } + } - it.signatures = ( - parsed.apkSingers.flatMap { it.certificateMetas } - /*+ parsed.apkV2Singers.flatMap { it.certificateMetas }*/ - ) // Blocked by: https://github.com/hsiafan/apk-parser/issues/72 - .map { Signature(it.data) }.toTypedArray() - } + it.signatures = + ( + parsed.apkSingers.flatMap { it.certificateMetas } + // + parsed.apkV2Singers.flatMap { it.certificateMetas } + ) // Blocked by: https://github.com/hsiafan/apk-parser/issues/72 + .map { Signature(it.data) }.toTypedArray() + } fun verify(): Boolean { - val res = ApkVerifier.Builder(apk) - .build() - .verify() + val res = + ApkVerifier.Builder(apk) + .build() + .verify() return res.isVerified } @@ -64,11 +69,12 @@ data class InstalledPackage(val root: File) { try { val icons = ApkFile(apk).allIcons - val read = icons.filter { it.isFile }.map { - it.data.inputStream().use { - ImageIO.read(it) - } - }.sortedByDescending { it.width * it.height }.firstOrNull() ?: return + val read = + icons.filter { it.isFile }.map { + it.data.inputStream().use { + ImageIO.read(it) + } + }.sortedByDescending { it.width * it.height }.firstOrNull() ?: return ImageIO.write(read, "png", icon) } catch (e: Exception) { diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/pm/PackageController.kt b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/pm/PackageController.kt index f94f3222b..1b6ecc798 100644 --- a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/pm/PackageController.kt +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/pm/PackageController.kt @@ -25,7 +25,10 @@ class PackageController { return File(androidFiles.packagesDir, pn) } - fun installPackage(apk: File, allowReinstall: Boolean) { + fun installPackage( + apk: File, + allowReinstall: Boolean, + ) { val root = findRoot(apk) if (root.exists()) { diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/pm/PackageUtil.kt b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/pm/PackageUtil.kt index ce3afc17f..bf4b207a5 100644 --- a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/pm/PackageUtil.kt +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/pm/PackageUtil.kt @@ -12,16 +12,18 @@ fun ApkMeta.toPackageInfo(apk: File): PackageInfo { it.versionCode = versionCode.toInt() it.versionName = versionName - it.reqFeatures = usesFeatures.map { - FeatureInfo().apply { - name = it.name - } - }.toTypedArray() + it.reqFeatures = + usesFeatures.map { + FeatureInfo().apply { + name = it.name + } + }.toTypedArray() - it.applicationInfo = ApplicationInfo().apply { - packageName = it.packageName - nonLocalizedLabel = label - sourceDir = apk.absolutePath - } + it.applicationInfo = + ApplicationInfo().apply { + packageName = it.packageName + nonLocalizedLabel = label + sourceDir = apk.absolutePath + } } } diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/service/ServiceSupport.kt b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/service/ServiceSupport.kt index 0d3c4b16a..b28ba148e 100644 --- a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/service/ServiceSupport.kt +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/service/ServiceSupport.kt @@ -18,7 +18,10 @@ class ServiceSupport { private val logger = KotlinLogging.logger {} - fun startService(@Suppress("UNUSED_PARAMETER") context: Context, intent: Intent) { + fun startService( + @Suppress("UNUSED_PARAMETER") context: Context, + intent: Intent, + ) { val name = intentToClassName(intent) logger.debug { "Starting service: $name" } @@ -35,7 +38,10 @@ class ServiceSupport { } } - fun stopService(@Suppress("UNUSED_PARAMETER") context: Context, intent: Intent) { + fun stopService( + @Suppress("UNUSED_PARAMETER") context: Context, + intent: Intent, + ) { val name = intentToClassName(intent) stopService(name) } diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/util/KodeinGlobalHelper.kt b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/util/KodeinGlobalHelper.kt index 488fe7f62..ceaabfd09 100644 --- a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/util/KodeinGlobalHelper.kt +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/util/KodeinGlobalHelper.kt @@ -26,7 +26,10 @@ object KodeinGlobalHelper { */ @JvmStatic @Suppress("UNCHECKED_CAST") - fun instance(type: Class, kodein: DI? = null): T { + fun instance( + type: Class, + kodein: DI? = null, + ): T { return when (type) { AndroidFiles::class.java -> { val instance: AndroidFiles by (kodein ?: kodein()).instance() diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/util/UriExtensions.kt b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/util/UriExtensions.kt index d8f92a82b..2b1044108 100644 --- a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/util/UriExtensions.kt +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/util/UriExtensions.kt @@ -24,5 +24,7 @@ import java.net.URI * Utilites to convert between Java and Android Uris. */ fun Uri.java() = URI(this.toString()) + fun Uri.file() = File(this.path) + fun URI.android() = Uri.parse(this.toString())!! diff --git a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/webkit/CookieManagerImpl.kt b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/webkit/CookieManagerImpl.kt index af4340ea7..d400327fb 100644 --- a/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/webkit/CookieManagerImpl.kt +++ b/AndroidCompat/src/main/java/xyz/nulldev/androidcompat/webkit/CookieManagerImpl.kt @@ -22,7 +22,10 @@ class CookieManagerImpl : CookieManager() { return acceptCookie } - override fun setAcceptThirdPartyCookies(webview: WebView?, accept: Boolean) { + override fun setAcceptThirdPartyCookies( + webview: WebView?, + accept: Boolean, + ) { acceptThirdPartyCookies = accept } @@ -30,29 +33,38 @@ class CookieManagerImpl : CookieManager() { return acceptThirdPartyCookies } - override fun setCookie(url: String, value: String?) { - val uri = if (url.startsWith("http")) { - URI(url) - } else { - URI("http://$url") - } + override fun setCookie( + url: String, + value: String?, + ) { + val uri = + if (url.startsWith("http")) { + URI(url) + } else { + URI("http://$url") + } HttpCookie.parse(value).forEach { cookieHandler.cookieStore.add(uri, it) } } - override fun setCookie(url: String, value: String?, callback: ValueCallback?) { + override fun setCookie( + url: String, + value: String?, + callback: ValueCallback?, + ) { setCookie(url, value) callback?.onReceiveValue(true) } override fun getCookie(url: String): String { - val uri = if (url.startsWith("http")) { - URI(url) - } else { - URI("http://$url") - } + val uri = + if (url.startsWith("http")) { + URI(url) + } else { + URI("http://$url") + } return cookieHandler.cookieStore.get(uri) .joinToString("; ") { "${it.name}=${it.value}" } } diff --git a/build.gradle.kts b/build.gradle.kts index d7e6efa42..49aa8723b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,12 +1,11 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile -import org.jmailen.gradle.kotlinter.tasks.FormatTask -import org.jmailen.gradle.kotlinter.tasks.LintTask +import org.jlleitschuh.gradle.ktlint.KtlintExtension +import org.jlleitschuh.gradle.ktlint.KtlintPlugin -@Suppress("DSL_SCOPE_VIOLATION") plugins { alias(libs.plugins.kotlin.jvm) alias(libs.plugins.kotlin.serialization) - alias(libs.plugins.kotlinter) + alias(libs.plugins.ktlint) alias(libs.plugins.buildconfig) apply false alias(libs.plugins.download) } @@ -32,24 +31,25 @@ subprojects { } } + plugins.withType { + extensions.configure("ktlint") { + version.set(libs.versions.ktlint.get()) + filter { + exclude("**/generated/**") + } + } + } + tasks { withType { - dependsOn("formatKotlin") + dependsOn("ktlintFormat") kotlinOptions { jvmTarget = JavaVersion.VERSION_1_8.toString() freeCompilerArgs += listOf( - "-Xcontext-receivers" + "-Xcontext-receivers", ) } } - - withType { - source(files("src/kotlin")) - } - - withType { - source(files("src/kotlin")) - } } -} \ No newline at end of file +} diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 44116137a..3793de627 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -7,5 +7,5 @@ repositories { } dependencies { - implementation("net.lingala.zip4j:zip4j:2.9.0") -} \ No newline at end of file + implementation(libs.zip4j) +} diff --git a/buildSrc/settings.gradle.kts b/buildSrc/settings.gradle.kts new file mode 100644 index 000000000..62991c1ef --- /dev/null +++ b/buildSrc/settings.gradle.kts @@ -0,0 +1,9 @@ +rootProject.name = "buildSrc" + +dependencyResolutionManagement { + versionCatalogs { + create("libs") { + from(files("../gradle/libs.versions.toml")) + } + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 5f2b9ccb7..8085822d0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,6 +13,7 @@ twelvemonkeys = "3.9.4" playwright = "1.28.0" graphqlkotlin = "6.5.6" xmlserialization = "0.86.2" +ktlint = "1.0.0" [libraries] # Kotlin @@ -150,7 +151,7 @@ kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin"} kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin"} # Linter -kotlinter = { id = "org.jmailen.kotlinter", version = "3.12.0"} +ktlint = { id = "org.jlleitschuh.gradle.ktlint", version = "11.6.0"} # Build config buildconfig = { id = "com.github.gmazzo.buildconfig", version = "3.1.0"} diff --git a/server/build.gradle.kts b/server/build.gradle.kts index 5bd9ecb04..868c141d6 100644 --- a/server/build.gradle.kts +++ b/server/build.gradle.kts @@ -1,11 +1,10 @@ import de.undercouch.gradle.tasks.download.Download import java.time.Instant -@Suppress("DSL_SCOPE_VIOLATION") plugins { id(libs.plugins.kotlin.jvm.get().pluginId) id(libs.plugins.kotlin.serialization.get().pluginId) - id(libs.plugins.kotlinter.get().pluginId) + id(libs.plugins.ktlint.get().pluginId) application alias(libs.plugins.shadowjar) id(libs.plugins.buildconfig.get().pluginId) @@ -82,9 +81,10 @@ dependencies { } application { - applicationDefaultJvmArgs = listOf( - "-Djunrar.extractor.thread-keep-alive-seconds=30" - ) + applicationDefaultJvmArgs = + listOf( + "-Djunrar.extractor.thread-keep-alive-seconds=30", + ) mainClass.set(MainClass) } @@ -98,7 +98,7 @@ sourceSets { buildConfig { className("BuildConfig") - packageName("suwayomi.tachidesk.server") + packageName("suwayomi.tachidesk.server.generated") useKotlinOutput() @@ -125,7 +125,7 @@ tasks { "Implementation-Title" to rootProject.name, "Implementation-Vendor" to "The Suwayomi Project", "Specification-Version" to tachideskVersion, - "Implementation-Version" to tachideskRevision + "Implementation-Version" to tachideskRevision, ) } archiveBaseName.set(rootProject.name) @@ -151,16 +151,16 @@ tasks { src("https://github.com/Suwayomi/Tachidesk-WebUI-preview/releases/download/$webUIRevisionTag/Tachidesk-WebUI-$webUIRevisionTag.zip") dest("src/main/resources/WebUI.zip") - fun shouldOverwrite(): Boolean { val zipPath = project.projectDir.absolutePath + "/src/main/resources/WebUI.zip" - val zipFile = net.lingala.zip4j.ZipFile(zipPath) + val zipFile = net.lingala.zip4j.ZipFile(zipPath) var shouldOverwrite = true if (zipFile.isValidZipFile) { - val zipRevision = zipFile.getInputStream(zipFile.getFileHeader("revision")).bufferedReader().use { - it.readText().trim() - } + val zipRevision = + zipFile.getInputStream(zipFile.getFileHeader("revision")).bufferedReader().use { + it.readText().trim() + } if (zipRevision == webUIRevisionTag) { shouldOverwrite = false @@ -177,11 +177,12 @@ tasks { group = "application" finalizedBy(run) doFirst { - application.applicationDefaultJvmArgs = listOf( - "-Dsuwayomi.tachidesk.config.server.webUIInterface=electron", - // Change this to the installed electron application - "-Dsuwayomi.tachidesk.config.server.electronPath=/usr/bin/electron" - ) + application.applicationDefaultJvmArgs = + listOf( + "-Dsuwayomi.tachidesk.config.server.webUIInterface=electron", + // Change this to the installed electron application + "-Dsuwayomi.tachidesk.config.server.electronPath=/usr/bin/electron", + ) } } } diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/App.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/App.kt index 86ad19557..2962604ae 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/App.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/App.kt @@ -9,15 +9,11 @@ package eu.kanade.tachiyomi import android.app.Application import android.content.Context -// import android.content.res.Configuration -// import android.support.multidex.MultiDex -// import timber.log.Timber import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.InjektScope import uy.kohesive.injekt.registry.default.DefaultRegistrar open class App : Application() { - override fun onCreate() { super.onCreate() Injekt = InjektScope(DefaultRegistrar()) diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/AppInfo.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/AppInfo.kt index b205b7257..503687877 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/AppInfo.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/AppInfo.kt @@ -1,6 +1,7 @@ package eu.kanade.tachiyomi import suwayomi.tachidesk.manga.impl.util.storage.ImageUtil +import suwayomi.tachidesk.server.generated.BuildConfig /** * Used by extensions. @@ -14,14 +15,14 @@ object AppInfo { * * @since extension-lib 1.3 */ - fun getVersionCode() = suwayomi.tachidesk.server.BuildConfig.REVISION.substring(1).toInt() + fun getVersionCode() = BuildConfig.REVISION.substring(1).toInt() /** * should be something like "0.13.1" * * @since extension-lib 1.3 */ - fun getVersionName() = suwayomi.tachidesk.server.BuildConfig.VERSION.substring(1) + fun getVersionName() = BuildConfig.VERSION.substring(1) /** * A list of supported image MIME types by the reader. diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/AppModule.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/AppModule.kt index b8620cde5..4c9670c73 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/AppModule.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/AppModule.kt @@ -28,7 +28,6 @@ import uy.kohesive.injekt.api.addSingletonFactory import uy.kohesive.injekt.api.get class AppModule(val app: Application) : InjektModule { - override fun InjektRegistrar.registerInjectables() { addSingleton(app) diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/network/JavaScriptEngine.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/network/JavaScriptEngine.kt index d63d24968..5e479f045 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/network/JavaScriptEngine.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/network/JavaScriptEngine.kt @@ -9,7 +9,6 @@ import kotlinx.coroutines.withContext * Util for evaluating JavaScript in sources. */ class JavaScriptEngine(context: Context) { - /** * Evaluate arbitrary JavaScript code and get the result as a primitive type * (e.g., String, Int). @@ -19,9 +18,10 @@ class JavaScriptEngine(context: Context) { * @return Result of JavaScript code as a primitive type. */ @Suppress("UNUSED", "UNCHECKED_CAST") - suspend fun evaluate(script: String): T = withContext(Dispatchers.IO) { - QuickJs.create().use { - it.evaluate(script) as T + suspend fun evaluate(script: String): T = + withContext(Dispatchers.IO) { + QuickJs.create().use { + it.evaluate(script) as T + } } - } } diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/network/MemoryCookieJar.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/network/MemoryCookieJar.kt index 89c402ffe..852591198 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/network/MemoryCookieJar.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/network/MemoryCookieJar.kt @@ -33,7 +33,10 @@ class MemoryCookieJar : CookieJar { } @Synchronized - override fun saveFromResponse(url: HttpUrl, cookies: List) { + override fun saveFromResponse( + url: HttpUrl, + cookies: List, + ) { val cookiesToAdd = cookies.map { WrappedCookie.wrap(it) } cache.removeAll(cookiesToAdd) diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/network/NetworkHelper.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/network/NetworkHelper.kt index 387521f00..ff6e7d14b 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/network/NetworkHelper.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/network/NetworkHelper.kt @@ -27,8 +27,7 @@ import java.util.concurrent.TimeUnit @Suppress("UNUSED_PARAMETER") class NetworkHelper(context: Context) { - -// private val preferences: PreferencesHelper by injectLazy() + // private val preferences: PreferencesHelper by injectLazy() // private val cacheDir = File(context.cacheDir, "network_cache") @@ -36,31 +35,36 @@ class NetworkHelper(context: Context) { // Tachidesk --> val cookieStore = PersistentCookieStore(context) + init { CookieHandler.setDefault( - CookieManager(cookieStore, CookiePolicy.ACCEPT_ALL) + CookieManager(cookieStore, CookiePolicy.ACCEPT_ALL), ) } // Tachidesk <-- private val baseClientBuilder: OkHttpClient.Builder get() { - val builder = OkHttpClient.Builder() - .cookieJar(PersistentCookieJar(cookieStore)) - .connectTimeout(30, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) - .callTimeout(2, TimeUnit.MINUTES) - .addInterceptor(UserAgentInterceptor()) + val builder = + OkHttpClient.Builder() + .cookieJar(PersistentCookieJar(cookieStore)) + .connectTimeout(30, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) + .callTimeout(2, TimeUnit.MINUTES) + .addInterceptor(UserAgentInterceptor()) - val httpLoggingInterceptor = HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger { - val logger = KotlinLogging.logger { } + val httpLoggingInterceptor = + HttpLoggingInterceptor( + object : HttpLoggingInterceptor.Logger { + val logger = KotlinLogging.logger { } - override fun log(message: String) { - logger.debug { message } + override fun log(message: String) { + logger.debug { message } + } + }, + ).apply { + level = HttpLoggingInterceptor.Level.BASIC } - }).apply { - level = HttpLoggingInterceptor.Level.BASIC - } builder.addInterceptor(httpLoggingInterceptor) // when (preferences.dohProvider()) { diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/network/OkHttpExtensions.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/network/OkHttpExtensions.kt index 8d6fe136d..ef8915661 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/network/OkHttpExtensions.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/network/OkHttpExtensions.kt @@ -25,31 +25,32 @@ fun Call.asObservable(): Observable { val call = clone() // Wrap the call in a helper which handles both unsubscription and backpressure. - val requestArbiter = object : AtomicBoolean(), Producer, Subscription { - override fun request(n: Long) { - if (n == 0L || !compareAndSet(false, true)) return - - try { - val response = call.execute() - if (!subscriber.isUnsubscribed) { - subscriber.onNext(response) - subscriber.onCompleted() - } - } catch (error: Exception) { - if (!subscriber.isUnsubscribed) { - subscriber.onError(error) + val requestArbiter = + object : AtomicBoolean(), Producer, Subscription { + override fun request(n: Long) { + if (n == 0L || !compareAndSet(false, true)) return + + try { + val response = call.execute() + if (!subscriber.isUnsubscribed) { + subscriber.onNext(response) + subscriber.onCompleted() + } + } catch (error: Exception) { + if (!subscriber.isUnsubscribed) { + subscriber.onError(error) + } } } - } - override fun unsubscribe() { - // call.cancel() - } + override fun unsubscribe() { + // call.cancel() + } - override fun isUnsubscribed(): Boolean { - return call.isCanceled() + override fun isUnsubscribed(): Boolean { + return call.isCanceled() + } } - } subscriber.add(requestArbiter) subscriber.setProducer(requestArbiter) @@ -72,13 +73,19 @@ private suspend fun Call.await(callStack: Array): Response { return suspendCancellableCoroutine { continuation -> val callback = object : Callback { - override fun onResponse(call: Call, response: Response) { + override fun onResponse( + call: Call, + response: Response, + ) { continuation.resume(response) { response.body.close() } } - override fun onFailure(call: Call, e: IOException) { + override fun onFailure( + call: Call, + e: IOException, + ) { // Don't bother with resuming the continuation if it is already cancelled. if (continuation.isCancelled) return val exception = IOException(e.message, e).apply { stackTrace = callStack } @@ -116,16 +123,20 @@ suspend fun Call.awaitSuccess(): Response { return response } -fun OkHttpClient.newCachelessCallWithProgress(request: Request, listener: ProgressListener): Call { - val progressClient = newBuilder() - .cache(null) - .addNetworkInterceptor { chain -> - val originalResponse = chain.proceed(chain.request()) - originalResponse.newBuilder() - .body(ProgressResponseBody(originalResponse.body, listener)) - .build() - } - .build() +fun OkHttpClient.newCachelessCallWithProgress( + request: Request, + listener: ProgressListener, +): Call { + val progressClient = + newBuilder() + .cache(null) + .addNetworkInterceptor { chain -> + val originalResponse = chain.proceed(chain.request()) + originalResponse.newBuilder() + .body(ProgressResponseBody(originalResponse.body, listener)) + .build() + } + .build() return progressClient.newCall(request) } @@ -139,7 +150,7 @@ context(Json) @OptIn(ExperimentalSerializationApi::class) fun decodeFromJsonResponse( deserializer: DeserializationStrategy, - response: Response + response: Response, ): T { return response.body.source().use { decodeFromBufferedSource(deserializer, it) diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/network/PersistentCookieJar.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/network/PersistentCookieJar.kt index b9cd0245a..24013382c 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/network/PersistentCookieJar.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/network/PersistentCookieJar.kt @@ -6,8 +6,10 @@ import okhttp3.HttpUrl // from TachiWeb-Server class PersistentCookieJar(private val store: PersistentCookieStore) : CookieJar { - - override fun saveFromResponse(url: HttpUrl, cookies: List) { + override fun saveFromResponse( + url: HttpUrl, + cookies: List, + ) { store.addAll(url, cookies) } diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/network/PersistentCookieStore.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/network/PersistentCookieStore.kt index bf63d47e7..9ed4d93cf 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/network/PersistentCookieStore.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/network/PersistentCookieStore.kt @@ -15,7 +15,6 @@ import kotlin.time.Duration.Companion.seconds // from TachiWeb-Server class PersistentCookieStore(context: Context) : CookieStore { - private val cookieMap = ConcurrentHashMap>() private val prefs = context.getSharedPreferences("cookie_store", Context.MODE_PRIVATE) @@ -28,8 +27,9 @@ class PersistentCookieStore(context: Context) : CookieStore { if (cookies != null) { try { val url = "http://$key".toHttpUrlOrNull() ?: continue - val nonExpiredCookies = cookies.mapNotNull { Cookie.parse(url, it) } - .filter { !it.hasExpired() } + val nonExpiredCookies = + cookies.mapNotNull { Cookie.parse(url, it) } + .filter { !it.hasExpired() } cookieMap.put(key, nonExpiredCookies) } catch (e: Exception) { // Ignore @@ -38,7 +38,10 @@ class PersistentCookieStore(context: Context) : CookieStore { } } - fun addAll(url: HttpUrl, cookies: List) { + fun addAll( + url: HttpUrl, + cookies: List, + ) { lock.withLock { val uri = url.toUri() @@ -75,13 +78,17 @@ class PersistentCookieStore(context: Context) : CookieStore { } } - override fun get(uri: URI): List = get(uri.host).map { - it.toHttpCookie() - } + override fun get(uri: URI): List = + get(uri.host).map { + it.toHttpCookie() + } fun get(url: HttpUrl) = get(url.toUri().host) - override fun add(uri: URI?, cookie: HttpCookie) { + override fun add( + uri: URI?, + cookie: HttpCookie, + ) { @Suppress("NAME_SHADOWING") val uri = uri ?: URI("http://" + cookie.domain.removePrefix(".")) lock.withLock { @@ -105,15 +112,19 @@ class PersistentCookieStore(context: Context) : CookieStore { } } - override fun remove(uri: URI?, cookie: HttpCookie): Boolean { + override fun remove( + uri: URI?, + cookie: HttpCookie, + ): Boolean { @Suppress("NAME_SHADOWING") val uri = uri ?: URI("http://" + cookie.domain.removePrefix(".")) return lock.withLock { val cookies = cookieMap[uri.host].orEmpty() - val index = cookies.indexOfFirst { - it.name == cookie.name && - it.path == cookie.path - } + val index = + cookies.indexOfFirst { + it.name == cookie.name && + it.path == cookie.path + } if (index >= 0) { val newList = cookies.toMutableList() newList.removeAt(index) @@ -132,45 +143,47 @@ class PersistentCookieStore(context: Context) : CookieStore { private fun saveToDisk(uri: URI) { // Get cookies to be stored in disk - val newValues = cookieMap[uri.host] - .orEmpty() - .asSequence() - .filter { it.persistent && !it.hasExpired() } - .map(Cookie::toString) - .toSet() + val newValues = + cookieMap[uri.host] + .orEmpty() + .asSequence() + .filter { it.persistent && !it.hasExpired() } + .map(Cookie::toString) + .toSet() prefs.edit().putStringSet(uri.host, newValues).apply() } private fun Cookie.hasExpired() = System.currentTimeMillis() >= expiresAt - private fun HttpCookie.toCookie(uri: URI) = Cookie.Builder() - .name(name) - .value(value) - .domain(uri.host) - .path(path ?: "/") - .let { - if (maxAge != -1L) { - it.expiresAt(System.currentTimeMillis() + maxAge.seconds.inWholeMilliseconds) - } else { - it.expiresAt(Long.MAX_VALUE) + private fun HttpCookie.toCookie(uri: URI) = + Cookie.Builder() + .name(name) + .value(value) + .domain(uri.host) + .path(path ?: "/") + .let { + if (maxAge != -1L) { + it.expiresAt(System.currentTimeMillis() + maxAge.seconds.inWholeMilliseconds) + } else { + it.expiresAt(Long.MAX_VALUE) + } } - } - .let { - if (secure) { - it.secure() - } else { - it + .let { + if (secure) { + it.secure() + } else { + it + } } - } - .let { - if (isHttpOnly) { - it.httpOnly() - } else { - it + .let { + if (isHttpOnly) { + it.httpOnly() + } else { + it + } } - } - .build() + .build() private fun Cookie.toHttpCookie(): HttpCookie { val it = this @@ -178,11 +191,12 @@ class PersistentCookieStore(context: Context) : CookieStore { domain = it.domain path = it.path secure = it.secure - maxAge = if (it.persistent) { - -1 - } else { - (it.expiresAt.milliseconds - System.currentTimeMillis().milliseconds).inWholeSeconds - } + maxAge = + if (it.persistent) { + -1 + } else { + (it.expiresAt.milliseconds - System.currentTimeMillis().milliseconds).inWholeSeconds + } isHttpOnly = it.httpOnly } diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/network/ProgressListener.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/network/ProgressListener.kt index 2e219895f..6d8da0890 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/network/ProgressListener.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/network/ProgressListener.kt @@ -1,5 +1,9 @@ package eu.kanade.tachiyomi.network interface ProgressListener { - fun update(bytesRead: Long, contentLength: Long, done: Boolean) + fun update( + bytesRead: Long, + contentLength: Long, + done: Boolean, + ) } diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/network/ProgressResponseBody.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/network/ProgressResponseBody.kt index 72248f17b..c52dd6923 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/network/ProgressResponseBody.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/network/ProgressResponseBody.kt @@ -10,7 +10,6 @@ import okio.buffer import java.io.IOException class ProgressResponseBody(private val responseBody: ResponseBody, private val progressListener: ProgressListener) : ResponseBody() { - private val bufferedSource: BufferedSource by lazy { source(responseBody.source()).buffer() } @@ -32,7 +31,10 @@ class ProgressResponseBody(private val responseBody: ResponseBody, private val p var totalBytesRead = 0L @Throws(IOException::class) - override fun read(sink: Buffer, byteCount: Long): Long { + override fun read( + sink: Buffer, + byteCount: Long, + ): Long { val bytesRead = super.read(sink, byteCount) // read() returns the number of bytes read, or -1 if this source is exhausted. totalBytesRead += if (bytesRead != -1L) bytesRead else 0 diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/network/Requests.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/network/Requests.kt index b8bfa73e5..39f84967b 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/network/Requests.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/network/Requests.kt @@ -1,3 +1,5 @@ +@file:Suppress("ktlint:standard:function-naming") + package eu.kanade.tachiyomi.network import okhttp3.CacheControl @@ -15,7 +17,7 @@ private val DEFAULT_BODY: RequestBody = FormBody.Builder().build() fun GET( url: String, headers: Headers = DEFAULT_HEADERS, - cache: CacheControl = DEFAULT_CACHE_CONTROL + cache: CacheControl = DEFAULT_CACHE_CONTROL, ): Request { return Request.Builder() .url(url) @@ -30,7 +32,7 @@ fun GET( fun GET( url: HttpUrl, headers: Headers = DEFAULT_HEADERS, - cache: CacheControl = DEFAULT_CACHE_CONTROL + cache: CacheControl = DEFAULT_CACHE_CONTROL, ): Request { return Request.Builder() .url(url) @@ -43,7 +45,7 @@ fun POST( url: String, headers: Headers = DEFAULT_HEADERS, body: RequestBody = DEFAULT_BODY, - cache: CacheControl = DEFAULT_CACHE_CONTROL + cache: CacheControl = DEFAULT_CACHE_CONTROL, ): Request { return Request.Builder() .url(url) diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt index 17e1b3f56..14addee7e 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt @@ -81,51 +81,56 @@ object CFClearance { logger.debug { "resolveWithWebView($url)" } - val cookies = Playwright.create().use { playwright -> - playwright.chromium().launch( - LaunchOptions() - .setHeadless(false) - .apply { - if (serverConfig.socksProxyEnabled.value) { - setProxy("socks5://${serverConfig.socksProxyHost.value}:${serverConfig.socksProxyPort.value}") + val cookies = + Playwright.create().use { playwright -> + playwright.chromium().launch( + LaunchOptions() + .setHeadless(false) + .apply { + if (serverConfig.socksProxyEnabled.value) { + setProxy("socks5://${serverConfig.socksProxyHost.value}:${serverConfig.socksProxyPort.value}") + } + }, + ).use { browser -> + val userAgent = originalRequest.header("User-Agent") + if (userAgent != null) { + browser.newContext(Browser.NewContextOptions().setUserAgent(userAgent)).use { browserContext -> + browserContext.newPage().use { getCookies(it, url) } } + } else { + browser.newPage().use { getCookies(it, url) } } - ).use { browser -> - val userAgent = originalRequest.header("User-Agent") - if (userAgent != null) { - browser.newContext(Browser.NewContextOptions().setUserAgent(userAgent)).use { browserContext -> - browserContext.newPage().use { getCookies(it, url) } - } - } else { - browser.newPage().use { getCookies(it, url) } } } - } // Copy cookies to cookie store cookies.groupBy { it.domain }.forEach { (domain, cookies) -> network.cookieStore.addAll( - url = HttpUrl.Builder() - .scheme("http") - .host(domain) - .build(), - cookies = cookies + url = + HttpUrl.Builder() + .scheme("http") + .host(domain) + .build(), + cookies = cookies, ) } // Merge new and existing cookies for this request // Find the cookies that we need to merge into this request - val convertedForThisRequest = cookies.filter { - it.matches(originalRequest.url) - } + val convertedForThisRequest = + cookies.filter { + it.matches(originalRequest.url) + } // Extract cookies from current request - val existingCookies = Cookie.parseAll( - originalRequest.url, - originalRequest.headers - ) + val existingCookies = + Cookie.parseAll( + originalRequest.url, + originalRequest.headers, + ) // Filter out existing values of cookies that we are about to merge in - val filteredExisting = existingCookies.filter { existing -> - convertedForThisRequest.none { converted -> converted.name == existing.name } - } + val filteredExisting = + existingCookies.filter { existing -> + convertedForThisRequest.none { converted -> converted.name == existing.name } + } logger.trace { "Existing cookies" } logger.trace { existingCookies.joinToString("; ") } val newCookies = filteredExisting + convertedForThisRequest @@ -143,7 +148,7 @@ object CFClearance { Playwright.create().use { playwright -> playwright.chromium().launch( LaunchOptions() - .setHeadless(true) + .setHeadless(true), ).use { browser -> browser.newPage().use { page -> val userAgent = page.evaluate("() => {return navigator.userAgent}") as String @@ -158,7 +163,10 @@ object CFClearance { } } - private fun getCookies(page: Page, url: String): List { + private fun getCookies( + page: Page, + url: String, + ): List { applyStealthInitScripts(page) page.navigate(url) val challengeResolved = waitForChallengeResolve(page) @@ -198,7 +206,7 @@ object CFClearance { ServerConfig::class.java.getResource("/cloudflare-js/navigator.permissions.js")!!.readText(), ServerConfig::class.java.getResource("/cloudflare-js/navigator.webdriver.js")!!.readText(), ServerConfig::class.java.getResource("/cloudflare-js/chrome.runtime.js")!!.readText(), - ServerConfig::class.java.getResource("/cloudflare-js/chrome.plugin.js")!!.readText() + ServerConfig::class.java.getResource("/cloudflare-js/chrome.plugin.js")!!.readText(), ) } @@ -215,12 +223,13 @@ object CFClearance { val timeoutSeconds = 120 repeat(timeoutSeconds) { page.waitForTimeout(1.seconds.toDouble(DurationUnit.MILLISECONDS)) - val success = try { - page.querySelector("#challenge-form") == null - } catch (e: Exception) { - logger.debug(e) { "query Error" } - false - } + val success = + try { + page.querySelector("#challenge-form") == null + } catch (e: Exception) { + logger.debug(e) { "query Error" } + false + } if (success) return true } return false diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/RateLimitInterceptor.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/RateLimitInterceptor.kt index 52d8cff10..a6a3346e0 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/RateLimitInterceptor.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/RateLimitInterceptor.kt @@ -34,7 +34,7 @@ import kotlin.time.toDurationUnit fun OkHttpClient.Builder.rateLimit( permits: Int, period: Long = 1, - unit: TimeUnit = TimeUnit.SECONDS + unit: TimeUnit = TimeUnit.SECONDS, ) = addInterceptor(RateLimitInterceptor(null, permits, period.toDuration(unit.toDurationUnit()))) /** @@ -50,17 +50,18 @@ fun OkHttpClient.Builder.rateLimit( * @param permits [Int] Number of requests allowed within a period of units. * @param period [Duration] The limiting duration. Defaults to 1.seconds. */ -fun OkHttpClient.Builder.rateLimit(permits: Int, period: Duration = 1.seconds) = - addInterceptor(RateLimitInterceptor(null, permits, period)) +fun OkHttpClient.Builder.rateLimit( + permits: Int, + period: Duration = 1.seconds, +) = addInterceptor(RateLimitInterceptor(null, permits, period)) /** We can probably accept domains or wildcards by comparing with [endsWith], etc. */ @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN") internal class RateLimitInterceptor( private val host: String?, private val permits: Int, - period: Duration + period: Duration, ) : Interceptor { - private val requestQueue = ArrayDeque(permits) private val rateLimitMillis = period.inWholeMilliseconds private val fairLock = Semaphore(1, true) @@ -98,7 +99,8 @@ internal class RateLimitInterceptor( } else if (hasRemovedExpired) { break } else { - try { // wait for the first entry to expire, or notified by cached response + try { + // wait for the first entry to expire, or notified by cached response (requestQueue as Object).wait(requestQueue.first - periodStart) } catch (_: InterruptedException) { continue diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/SpecificHostRateLimitInterceptor.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/SpecificHostRateLimitInterceptor.kt index 0f0790086..3e03d2a8c 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/SpecificHostRateLimitInterceptor.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/SpecificHostRateLimitInterceptor.kt @@ -29,7 +29,7 @@ fun OkHttpClient.Builder.rateLimitHost( httpUrl: HttpUrl, permits: Int, period: Long = 1, - unit: TimeUnit = TimeUnit.SECONDS + unit: TimeUnit = TimeUnit.SECONDS, ) = addInterceptor(RateLimitInterceptor(httpUrl.host, permits, period.toDuration(unit.toDurationUnit()))) /** @@ -49,7 +49,7 @@ fun OkHttpClient.Builder.rateLimitHost( fun OkHttpClient.Builder.rateLimitHost( httpUrl: HttpUrl, permits: Int, - period: Duration = 1.seconds + period: Duration = 1.seconds, ): OkHttpClient.Builder = addInterceptor(RateLimitInterceptor(httpUrl.host, permits, period)) /** @@ -69,5 +69,5 @@ fun OkHttpClient.Builder.rateLimitHost( fun OkHttpClient.Builder.rateLimitHost( url: String, permits: Int, - period: Duration = 1.seconds + period: Duration = 1.seconds, ): OkHttpClient.Builder = addInterceptor(RateLimitInterceptor(url.toHttpUrlOrNull()?.host, permits, period)) diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/UserAgentInterceptor.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/UserAgentInterceptor.kt index 5a3789eec..5488c8775 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/UserAgentInterceptor.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/UserAgentInterceptor.kt @@ -9,11 +9,12 @@ class UserAgentInterceptor : Interceptor { val originalRequest = chain.request() return if (originalRequest.header("User-Agent").isNullOrEmpty()) { - val newRequest = originalRequest - .newBuilder() - .removeHeader("User-Agent") - .addHeader("User-Agent", HttpSource.DEFAULT_USER_AGENT) - .build() + val newRequest = + originalRequest + .newBuilder() + .removeHeader("User-Agent") + .addHeader("User-Agent", HttpSource.DEFAULT_USER_AGENT) + .build() chain.proceed(newRequest) } else { chain.proceed(originalRequest) diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/source/CatalogueSource.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/source/CatalogueSource.kt index 71484c6f4..c141aebd0 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/source/CatalogueSource.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/source/CatalogueSource.kt @@ -6,7 +6,6 @@ import rx.Observable import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle interface CatalogueSource : Source { - /** * An ISO 639-1 compliant language code (two letters in lower case). */ @@ -37,7 +36,11 @@ interface CatalogueSource : Source { * @param filters the list of filters to apply. */ @Suppress("DEPRECATION") - suspend fun getSearchManga(page: Int, query: String, filters: FilterList): MangasPage { + suspend fun getSearchManga( + page: Int, + query: String, + filters: FilterList, + ): MangasPage { return fetchSearchManga(page, query, filters).awaitSingle() } @@ -59,22 +62,23 @@ interface CatalogueSource : Source { @Deprecated( "Use the non-RxJava API instead", - ReplaceWith("getPopularManga") + ReplaceWith("getPopularManga"), ) - fun fetchPopularManga(page: Int): Observable = - throw IllegalStateException("Not used") + fun fetchPopularManga(page: Int): Observable = throw IllegalStateException("Not used") @Deprecated( "Use the non-RxJava API instead", - ReplaceWith("getSearchManga") + ReplaceWith("getSearchManga"), ) - fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable = - throw IllegalStateException("Not used") + fun fetchSearchManga( + page: Int, + query: String, + filters: FilterList, + ): Observable = throw IllegalStateException("Not used") @Deprecated( "Use the non-RxJava API instead", - ReplaceWith("getLatestUpdates") + ReplaceWith("getLatestUpdates"), ) - fun fetchLatestUpdates(page: Int): Observable = - throw IllegalStateException("Not used") + fun fetchLatestUpdates(page: Int): Observable = throw IllegalStateException("Not used") } diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/source/ConfigurableSource.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/source/ConfigurableSource.kt index 9f1abe2ec..cda8f54aa 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/source/ConfigurableSource.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/source/ConfigurableSource.kt @@ -8,14 +8,12 @@ import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get interface ConfigurableSource : Source { - /** * Gets instance of [SharedPreferences] scoped to the specific source. * * @since extensions-lib 1.5 */ - fun getSourcePreferences(): SharedPreferences = - Injekt.get().getSharedPreferences(preferenceKey(), Context.MODE_PRIVATE) + fun getSourcePreferences(): SharedPreferences = Injekt.get().getSharedPreferences(preferenceKey(), Context.MODE_PRIVATE) fun setupPreferenceScreen(screen: PreferenceScreen) } diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/source/Source.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/source/Source.kt index 5d126bbab..ee7e07fca 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/source/Source.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/source/Source.kt @@ -10,7 +10,6 @@ import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle * A basic interface for creating a source. It could be an online source, a local source, etc... */ interface Source { - /** * Id for the source. Must be unique. */ @@ -60,19 +59,19 @@ interface Source { @Deprecated( "Use the non-RxJava API instead", - ReplaceWith("getMangaDetails") + ReplaceWith("getMangaDetails"), ) fun fetchMangaDetails(manga: SManga): Observable = throw IllegalStateException("Not used") @Deprecated( "Use the non-RxJava API instead", - ReplaceWith("getChapterList") + ReplaceWith("getChapterList"), ) fun fetchChapterList(manga: SManga): Observable> = throw IllegalStateException("Not used") @Deprecated( "Use the non-RxJava API instead", - ReplaceWith("getPageList") + ReplaceWith("getPageList"), ) fun fetchPageList(chapter: SChapter): Observable> = Observable.empty() } diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/LocalSource.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/LocalSource.kt index 6e2572396..8dfa5fed8 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/LocalSource.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/LocalSource.kt @@ -1,3 +1,5 @@ +@file:Suppress("ktlint:standard:property-naming") + package eu.kanade.tachiyomi.source.local import eu.kanade.tachiyomi.source.CatalogueSource @@ -55,9 +57,8 @@ import com.github.junrar.Archive as JunrarArchive class LocalSource( private val fileSystem: LocalSourceFileSystem, - private val coverManager: LocalCoverManager + private val coverManager: LocalCoverManager, ) : CatalogueSource, UnmeteredSource { - private val json: Json by injectLazy() private val xml: XML by injectLazy() @@ -79,56 +80,64 @@ class LocalSource( override suspend fun getLatestUpdates(page: Int) = getSearchManga(page, "", LATEST_FILTERS) - override suspend fun getSearchManga(page: Int, query: String, filters: FilterList): MangasPage { + override suspend fun getSearchManga( + page: Int, + query: String, + filters: FilterList, + ): MangasPage { val baseDirsFiles = fileSystem.getFilesInBaseDirectories() val lastModifiedLimit by lazy { if (filters === LATEST_FILTERS) System.currentTimeMillis() - LATEST_THRESHOLD else 0L } - var mangaDirs = baseDirsFiles - // Filter out files that are hidden and is not a folder - .filter { it.isDirectory && !it.name.startsWith('.') } - .distinctBy { it.name } - .filter { // Filter by query or last modified - if (lastModifiedLimit == 0L) { - it.name.contains(query, ignoreCase = true) - } else { - it.lastModified() >= lastModifiedLimit + var mangaDirs = + baseDirsFiles + // Filter out files that are hidden and is not a folder + .filter { it.isDirectory && !it.name.startsWith('.') } + .distinctBy { it.name } + .filter { // Filter by query or last modified + if (lastModifiedLimit == 0L) { + it.name.contains(query, ignoreCase = true) + } else { + it.lastModified() >= lastModifiedLimit + } } - } filters.forEach { filter -> when (filter) { is OrderBy.Popular -> { - mangaDirs = if (filter.state!!.ascending) { - mangaDirs.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.name }) - } else { - mangaDirs.sortedWith(compareByDescending(String.CASE_INSENSITIVE_ORDER) { it.name }) - } + mangaDirs = + if (filter.state!!.ascending) { + mangaDirs.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER) { it.name }) + } else { + mangaDirs.sortedWith(compareByDescending(String.CASE_INSENSITIVE_ORDER) { it.name }) + } } is OrderBy.Latest -> { - mangaDirs = if (filter.state!!.ascending) { - mangaDirs.sortedBy(File::lastModified) - } else { - mangaDirs.sortedByDescending(File::lastModified) - } + mangaDirs = + if (filter.state!!.ascending) { + mangaDirs.sortedBy(File::lastModified) + } else { + mangaDirs.sortedByDescending(File::lastModified) + } } else -> { - /* Do nothing */ + // Do nothing } } } // Transform mangaDirs to list of SManga - val mangas = mangaDirs.map { mangaDir -> - SManga.create().apply { - title = mangaDir.name - url = mangaDir.name - - // Try to find the cover - coverManager.find(mangaDir.name) - ?.takeIf(File::exists) - ?.let { thumbnail_url = it.absolutePath } + val mangas = + mangaDirs.map { mangaDir -> + SManga.create().apply { + title = mangaDir.name + url = mangaDir.name + + // Try to find the cover + coverManager.find(mangaDir.name) + ?.takeIf(File::exists) + ?.let { thumbnail_url = it.absolutePath } + } } - } // Fetch chapters of all the manga mangas.forEach { manga -> @@ -156,67 +165,75 @@ class LocalSource( } // Manga details related - override suspend fun getMangaDetails(manga: SManga): SManga = withContext(Dispatchers.IO) { - coverManager.find(manga.url)?.let { - manga.thumbnail_url = it.absolutePath - } - - // Augment manga details based on metadata files - try { - val mangaDirFiles = fileSystem.getFilesInMangaDirectory(manga.url).toList() - - val comicInfoFile = mangaDirFiles - .firstOrNull { it.name == COMIC_INFO_FILE } - val noXmlFile = mangaDirFiles - .firstOrNull { it.name == ".noxml" } - val legacyJsonDetailsFile = mangaDirFiles - .firstOrNull { it.extension == "json" } - - when { - // Top level ComicInfo.xml - comicInfoFile != null -> { - noXmlFile?.delete() - setMangaDetailsFromComicInfoFile(comicInfoFile.inputStream(), manga) - } + override suspend fun getMangaDetails(manga: SManga): SManga = + withContext(Dispatchers.IO) { + coverManager.find(manga.url)?.let { + manga.thumbnail_url = it.absolutePath + } - // TODO: automatically convert these to ComicInfo.xml - legacyJsonDetailsFile != null -> { - json.decodeFromStream(legacyJsonDetailsFile.inputStream()).run { - title?.let { manga.title = it } - author?.let { manga.author = it } - artist?.let { manga.artist = it } - description?.let { manga.description = it } - genre?.let { manga.genre = it.joinToString() } - status?.let { manga.status = it } + // Augment manga details based on metadata files + try { + val mangaDirFiles = fileSystem.getFilesInMangaDirectory(manga.url).toList() + + val comicInfoFile = + mangaDirFiles + .firstOrNull { it.name == COMIC_INFO_FILE } + val noXmlFile = + mangaDirFiles + .firstOrNull { it.name == ".noxml" } + val legacyJsonDetailsFile = + mangaDirFiles + .firstOrNull { it.extension == "json" } + + when { + // Top level ComicInfo.xml + comicInfoFile != null -> { + noXmlFile?.delete() + setMangaDetailsFromComicInfoFile(comicInfoFile.inputStream(), manga) } - } - // Copy ComicInfo.xml from chapter archive to top level if found - noXmlFile == null -> { - val chapterArchives = mangaDirFiles - .filter(Archive::isSupported) - .toList() - - val mangaDir = fileSystem.getMangaDirectory(manga.url) - val folderPath = mangaDir?.absolutePath + // TODO: automatically convert these to ComicInfo.xml + legacyJsonDetailsFile != null -> { + json.decodeFromStream(legacyJsonDetailsFile.inputStream()).run { + title?.let { manga.title = it } + author?.let { manga.author = it } + artist?.let { manga.artist = it } + description?.let { manga.description = it } + genre?.let { manga.genre = it.joinToString() } + status?.let { manga.status = it } + } + } - val copiedFile = copyComicInfoFileFromArchive(chapterArchives, folderPath) - if (copiedFile != null) { - setMangaDetailsFromComicInfoFile(copiedFile.inputStream(), manga) - } else { - // Avoid re-scanning - File("$folderPath/.noxml").createNewFile() + // Copy ComicInfo.xml from chapter archive to top level if found + noXmlFile == null -> { + val chapterArchives = + mangaDirFiles + .filter(Archive::isSupported) + .toList() + + val mangaDir = fileSystem.getMangaDirectory(manga.url) + val folderPath = mangaDir?.absolutePath + + val copiedFile = copyComicInfoFileFromArchive(chapterArchives, folderPath) + if (copiedFile != null) { + setMangaDetailsFromComicInfoFile(copiedFile.inputStream(), manga) + } else { + // Avoid re-scanning + File("$folderPath/.noxml").createNewFile() + } } } + } catch (e: Throwable) { + logger.error(e) { "Error setting manga details from local metadata for ${manga.title}" } } - } catch (e: Throwable) { - logger.error(e) { "Error setting manga details from local metadata for ${manga.title}" } - } - return@withContext manga - } + return@withContext manga + } - private fun copyComicInfoFileFromArchive(chapterArchives: List, folderPath: String?): File? { + private fun copyComicInfoFileFromArchive( + chapterArchives: List, + folderPath: String?, + ): File? { for (chapter in chapterArchives) { when (Format.valueOf(chapter)) { is Format.Zip -> { @@ -243,7 +260,10 @@ class LocalSource( return null } - private fun copyComicInfoFile(comicInfoFileStream: InputStream, folderPath: String?): File { + private fun copyComicInfoFile( + comicInfoFileStream: InputStream, + folderPath: String?, + ): File { return File("$folderPath/$COMIC_INFO_FILE").apply { outputStream().use { outputStream -> comicInfoFileStream.use { it.copyTo(outputStream) } @@ -251,10 +271,14 @@ class LocalSource( } } - private fun setMangaDetailsFromComicInfoFile(stream: InputStream, manga: SManga) { - val comicInfo = KtXmlReader(stream, StandardCharsets.UTF_8.name()).use { - xml.decodeFromReader(it) - } + private fun setMangaDetailsFromComicInfoFile( + stream: InputStream, + manga: SManga, + ) { + val comicInfo = + KtXmlReader(stream, StandardCharsets.UTF_8.name()).use { + xml.decodeFromReader(it) + } manga.copyFromComicInfo(comicInfo) } @@ -267,15 +291,17 @@ class LocalSource( .map { chapterFile -> SChapter.create().apply { url = "${manga.url}/${chapterFile.name}" - name = if (chapterFile.isDirectory) { - chapterFile.name - } else { - chapterFile.nameWithoutExtension - } + name = + if (chapterFile.isDirectory) { + chapterFile.name + } else { + chapterFile.nameWithoutExtension + } date_upload = chapterFile.lastModified() - chapter_number = ChapterRecognition - .parseChapterNumber(manga.title, this.name, this.chapter_number.toDouble()) - .toFloat() + chapter_number = + ChapterRecognition + .parseChapterNumber(manga.title, this.name, this.chapter_number.toDouble()) + .toFloat() val format = Format.valueOf(chapterFile) if (format is Format.Epub) { @@ -305,7 +331,7 @@ class LocalSource( .mapIndexed { index, page -> Page( index, - imageUrl = applicationDirs.localMangaRoot + "/" + chapter.url + "/" + page.name + imageUrl = applicationDirs.localMangaRoot + "/" + chapter.url + "/" + page.name, ) } } @@ -347,39 +373,46 @@ class LocalSource( } } - private fun updateCover(chapter: SChapter, manga: SManga): File? { + private fun updateCover( + chapter: SChapter, + manga: SManga, + ): File? { return try { when (val format = getFormat(chapter)) { is Format.Directory -> { - val entry = format.file.listFiles() - ?.sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) } - ?.find { !it.isDirectory && ImageUtil.isImage(it.name) { FileInputStream(it) } } + val entry = + format.file.listFiles() + ?.sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) } + ?.find { !it.isDirectory && ImageUtil.isImage(it.name) { FileInputStream(it) } } entry?.let { coverManager.update(manga, it.inputStream()) } } is Format.Zip -> { ZipFile(format.file).use { zip -> - val entry = zip.entries.toList() - .sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) } - .find { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } } + val entry = + zip.entries.toList() + .sortedWith { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) } + .find { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } } entry?.let { coverManager.update(manga, zip.getInputStream(it)) } } } is Format.Rar -> { JunrarArchive(format.file).use { archive -> - val entry = archive.fileHeaders - .sortedWith { f1, f2 -> f1.fileName.compareToCaseInsensitiveNaturalOrder(f2.fileName) } - .find { !it.isDirectory && ImageUtil.isImage(it.fileName) { archive.getInputStream(it) } } + val entry = + archive.fileHeaders + .sortedWith { f1, f2 -> f1.fileName.compareToCaseInsensitiveNaturalOrder(f2.fileName) } + .find { !it.isDirectory && ImageUtil.isImage(it.fileName) { archive.getInputStream(it) } } entry?.let { coverManager.update(manga, archive.getInputStream(it)) } } } is Format.Epub -> { EpubFile(format.file).use { epub -> - val entry = epub.getImagesFromPages() - .firstOrNull() - ?.let { epub.getEntry(it) } + val entry = + epub.getImagesFromPages() + .firstOrNull() + ?.let { epub.getEntry(it) } entry?.let { coverManager.update(manga, epub.getInputStream(it)) } } @@ -412,16 +445,17 @@ class LocalSource( if (sourceRecord == null) { // must do this to avoid database integrity errors - val extensionId = ExtensionTable.insertAndGetId { - it[apkName] = "localSource" - it[name] = EXTENSION_NAME - it[pkgName] = LocalSource::class.java.`package`.name - it[versionName] = "1.2" - it[versionCode] = 0 - it[lang] = LANG - it[isNsfw] = false - it[isInstalled] = true - } + val extensionId = + ExtensionTable.insertAndGetId { + it[apkName] = "localSource" + it[name] = EXTENSION_NAME + it[pkgName] = LocalSource::class.java.`package`.name + it[versionName] = "1.2" + it[versionCode] = 0 + it[lang] = LANG + it[isNsfw] = false + it[isInstalled] = true + } SourceTable.insert { it[id] = ID diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/filter/OrderBy.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/filter/OrderBy.kt index 15b557d60..68e57ff73 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/filter/OrderBy.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/filter/OrderBy.kt @@ -5,8 +5,9 @@ import eu.kanade.tachiyomi.source.model.Filter sealed class OrderBy(selection: Selection) : Filter.Sort( "Order by", arrayOf("Title", "Date"), - selection + selection, ) { class Popular() : OrderBy(Selection(0, true)) + class Latest() : OrderBy(Selection(1, false)) } diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/image/LocalCoverManager.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/image/LocalCoverManager.kt index 829d7f456..afd8e3ed0 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/image/LocalCoverManager.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/image/LocalCoverManager.kt @@ -9,9 +9,8 @@ import java.io.InputStream private const val DEFAULT_COVER_NAME = "cover.jpg" class LocalCoverManager( - private val fileSystem: LocalSourceFileSystem + private val fileSystem: LocalSourceFileSystem, ) { - fun find(mangaUrl: String): File? { return fileSystem.getFilesInMangaDirectory(mangaUrl) // Get all file whose names start with 'cover' @@ -24,7 +23,7 @@ class LocalCoverManager( fun update( manga: SManga, - inputStream: InputStream + inputStream: InputStream, ): File? { val directory = fileSystem.getMangaDirectory(manga.url) if (directory == null) { diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/io/Archive.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/io/Archive.kt index f3d061e83..87fec8722 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/io/Archive.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/io/Archive.kt @@ -3,10 +3,10 @@ package eu.kanade.tachiyomi.source.local.io import java.io.File object Archive { - private val SUPPORTED_ARCHIVE_TYPES = listOf("zip", "cbz", "rar", "cbr", "epub") - fun isSupported(file: File): Boolean = with(file) { - return extension.lowercase() in SUPPORTED_ARCHIVE_TYPES - } + fun isSupported(file: File): Boolean = + with(file) { + return extension.lowercase() in SUPPORTED_ARCHIVE_TYPES + } } diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/io/Format.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/io/Format.kt index 906a7f2f2..3a66b1523 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/io/Format.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/io/Format.kt @@ -4,22 +4,25 @@ import java.io.File sealed interface Format { data class Directory(val file: File) : Format + data class Zip(val file: File) : Format + data class Rar(val file: File) : Format + data class Epub(val file: File) : Format class UnknownFormatException : Exception() companion object { - - fun valueOf(file: File) = with(file) { - when { - isDirectory -> Directory(this) - extension.equals("zip", true) || extension.equals("cbz", true) -> Zip(this) - extension.equals("rar", true) || extension.equals("cbr", true) -> Rar(this) - extension.equals("epub", true) -> Epub(this) - else -> throw UnknownFormatException() + fun valueOf(file: File) = + with(file) { + when { + isDirectory -> Directory(this) + extension.equals("zip", true) || extension.equals("cbz", true) -> Zip(this) + extension.equals("rar", true) || extension.equals("cbr", true) -> Rar(this) + extension.equals("epub", true) -> Epub(this) + else -> throw UnknownFormatException() + } } - } } } diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/io/LocalSourceFileSystem.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/io/LocalSourceFileSystem.kt index 6ff42d192..44b3847df 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/io/LocalSourceFileSystem.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/io/LocalSourceFileSystem.kt @@ -4,9 +4,8 @@ import suwayomi.tachidesk.server.ApplicationDirs import java.io.File class LocalSourceFileSystem( - private val applicationDirs: ApplicationDirs + private val applicationDirs: ApplicationDirs, ) { - fun getBaseDirectories(): Sequence { return sequenceOf(File(applicationDirs.localMangaRoot)) } diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/loader/EpubPageLoader.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/loader/EpubPageLoader.kt index eda28932c..d0227b5e6 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/loader/EpubPageLoader.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/loader/EpubPageLoader.kt @@ -7,7 +7,6 @@ import java.io.File * Loader used to load a chapter from a .epub file. */ class EpubPageLoader(file: File) : PageLoader { - private val epub = EpubFile(file) override suspend fun getPages(): List { diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/loader/RarPageLoader.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/loader/RarPageLoader.kt index 298c6eaba..0c161dea4 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/loader/RarPageLoader.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/loader/RarPageLoader.kt @@ -13,7 +13,6 @@ import java.io.PipedOutputStream * Loader used to load a chapter from a .rar or .cbr file. */ class RarPageLoader(file: File) : PageLoader { - private val rar = Archive(file) override suspend fun getPages(): List { @@ -35,7 +34,10 @@ class RarPageLoader(file: File) : PageLoader { /** * Returns an input stream for the given [header]. */ - private fun getStream(rar: Archive, header: FileHeader): InputStream { + private fun getStream( + rar: Archive, + header: FileHeader, + ): InputStream { val pipeIn = PipedInputStream() val pipeOut = PipedOutputStream(pipeIn) synchronized(this) { diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/loader/ReaderPage.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/loader/ReaderPage.kt index 8a39befb8..aa8f32ae1 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/loader/ReaderPage.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/loader/ReaderPage.kt @@ -7,5 +7,5 @@ class ReaderPage( index: Int, url: String = "", imageUrl: String? = null, - var stream: (() -> InputStream)? = null + var stream: (() -> InputStream)? = null, ) : Page(index, url, imageUrl, null) diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/loader/ZipPageLoader.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/loader/ZipPageLoader.kt index 4fcdbe1b3..acd36ac2d 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/loader/ZipPageLoader.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/loader/ZipPageLoader.kt @@ -9,7 +9,6 @@ import java.io.File * Loader used to load a chapter from a .zip or .cbz file. */ class ZipPageLoader(file: File) : PageLoader { - private val zip = ZipFile(file) override suspend fun getPages(): List { diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/metadata/ComicInfo.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/metadata/ComicInfo.kt index f2ce77fe9..d129d017e 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/metadata/ComicInfo.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/metadata/ComicInfo.kt @@ -16,7 +16,7 @@ fun SManga.copyFromComicInfo(comicInfo: ComicInfo) { listOfNotNull( comicInfo.genre?.value, comicInfo.tags?.value, - comicInfo.categories?.value + comicInfo.categories?.value, ) .distinct() .joinToString(", ") { it.trim() } @@ -28,7 +28,7 @@ fun SManga.copyFromComicInfo(comicInfo: ComicInfo) { comicInfo.inker?.value, comicInfo.colorist?.value, comicInfo.letterer?.value, - comicInfo.coverArtist?.value + comicInfo.coverArtist?.value, ) .flatMap { it.split(", ") } .distinct() @@ -57,7 +57,7 @@ data class ComicInfo( val tags: Tags?, val web: Web?, val publishingStatus: PublishingStatusTachiyomi?, - val categories: CategoriesTachiyomi? + val categories: CategoriesTachiyomi?, ) { @Suppress("UNUSED") @XmlElement(false) @@ -71,73 +71,105 @@ data class ComicInfo( @Serializable @XmlSerialName("Title", "", "") - data class Title(@XmlValue(true) val value: String = "") + data class Title( + @XmlValue(true) val value: String = "", + ) @Serializable @XmlSerialName("Series", "", "") - data class Series(@XmlValue(true) val value: String = "") + data class Series( + @XmlValue(true) val value: String = "", + ) @Serializable @XmlSerialName("Number", "", "") - data class Number(@XmlValue(true) val value: String = "") + data class Number( + @XmlValue(true) val value: String = "", + ) @Serializable @XmlSerialName("Summary", "", "") - data class Summary(@XmlValue(true) val value: String = "") + data class Summary( + @XmlValue(true) val value: String = "", + ) @Serializable @XmlSerialName("Writer", "", "") - data class Writer(@XmlValue(true) val value: String = "") + data class Writer( + @XmlValue(true) val value: String = "", + ) @Serializable @XmlSerialName("Penciller", "", "") - data class Penciller(@XmlValue(true) val value: String = "") + data class Penciller( + @XmlValue(true) val value: String = "", + ) @Serializable @XmlSerialName("Inker", "", "") - data class Inker(@XmlValue(true) val value: String = "") + data class Inker( + @XmlValue(true) val value: String = "", + ) @Serializable @XmlSerialName("Colorist", "", "") - data class Colorist(@XmlValue(true) val value: String = "") + data class Colorist( + @XmlValue(true) val value: String = "", + ) @Serializable @XmlSerialName("Letterer", "", "") - data class Letterer(@XmlValue(true) val value: String = "") + data class Letterer( + @XmlValue(true) val value: String = "", + ) @Serializable @XmlSerialName("CoverArtist", "", "") - data class CoverArtist(@XmlValue(true) val value: String = "") + data class CoverArtist( + @XmlValue(true) val value: String = "", + ) @Serializable @XmlSerialName("Translator", "", "") - data class Translator(@XmlValue(true) val value: String = "") + data class Translator( + @XmlValue(true) val value: String = "", + ) @Serializable @XmlSerialName("Genre", "", "") - data class Genre(@XmlValue(true) val value: String = "") + data class Genre( + @XmlValue(true) val value: String = "", + ) @Serializable @XmlSerialName("Tags", "", "") - data class Tags(@XmlValue(true) val value: String = "") + data class Tags( + @XmlValue(true) val value: String = "", + ) @Serializable @XmlSerialName("Web", "", "") - data class Web(@XmlValue(true) val value: String = "") + data class Web( + @XmlValue(true) val value: String = "", + ) // The spec doesn't have a good field for this @Serializable @XmlSerialName("PublishingStatusTachiyomi", "http://www.w3.org/2001/XMLSchema", "ty") - data class PublishingStatusTachiyomi(@XmlValue(true) val value: String = "") + data class PublishingStatusTachiyomi( + @XmlValue(true) val value: String = "", + ) @Serializable @XmlSerialName("Categories", "http://www.w3.org/2001/XMLSchema", "ty") - data class CategoriesTachiyomi(@XmlValue(true) val value: String = "") + data class CategoriesTachiyomi( + @XmlValue(true) val value: String = "", + ) } enum class ComicInfoPublishingStatus( val comicInfoValue: String, - val sMangaModelValue: Int + val sMangaModelValue: Int, ) { ONGOING("Ongoing", SManga.ONGOING), COMPLETED("Completed", SManga.COMPLETED), @@ -145,7 +177,7 @@ enum class ComicInfoPublishingStatus( PUBLISHING_FINISHED("Publishing finished", SManga.PUBLISHING_FINISHED), CANCELLED("Cancelled", SManga.CANCELLED), ON_HIATUS("On hiatus", SManga.ON_HIATUS), - UNKNOWN("Unknown", SManga.UNKNOWN) + UNKNOWN("Unknown", SManga.UNKNOWN), ; companion object { diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/metadata/MangaDetails.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/metadata/MangaDetails.kt index 6e2f1d0df..d4f7d7090 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/metadata/MangaDetails.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/metadata/MangaDetails.kt @@ -9,5 +9,5 @@ class MangaDetails( val artist: String? = null, val description: String? = null, val genre: List? = null, - val status: Int? = null + val status: Int? = null, ) diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/Filter.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/Filter.kt index 81c2a4bd5..1def3d56b 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/Filter.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/Filter.kt @@ -4,15 +4,22 @@ package eu.kanade.tachiyomi.source.model // sealed class Filter(val name: String, var state: T) { open class Filter(val name: String, var state: T) { open class Header(name: String) : Filter(name, 0) + open class Separator(name: String = "") : Filter(name, 0) + abstract class Select(name: String, val values: Array, state: Int = 0) : Filter(name, state) { val displayValues get() = values.map { it.toString() } } + abstract class Text(name: String, state: String = "") : Filter(name, state) + abstract class CheckBox(name: String, state: Boolean = false) : Filter(name, state) + abstract class TriState(name: String, state: Int = STATE_IGNORE) : Filter(name, state) { fun isIgnored() = state == STATE_IGNORE + fun isIncluded() = state == STATE_INCLUDE + fun isExcluded() = state == STATE_EXCLUDE companion object { diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/FilterList.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/FilterList.kt index 42b6bc74b..c05951fa0 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/FilterList.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/FilterList.kt @@ -1,6 +1,5 @@ package eu.kanade.tachiyomi.source.model data class FilterList(val list: List>) : List> by list { - constructor(vararg fs: Filter<*>) : this(if (fs.isNotEmpty()) fs.asList() else emptyList()) } diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/Page.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/Page.kt index 2784c438c..5bebace06 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/Page.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/Page.kt @@ -9,18 +9,23 @@ open class Page( val index: Int, val url: String = "", var imageUrl: String? = null, - @Transient var uri: Uri? = null // Deprecated but can't be deleted due to extensions + // Deprecated but can't be deleted due to extensions + @Transient var uri: Uri? = null, ) : ProgressListener { - private val _progress = MutableStateFlow(0) val progress = _progress.asStateFlow() - override fun update(bytesRead: Long, contentLength: Long, done: Boolean) { - _progress.value = if (contentLength > 0) { - (100 * bytesRead / contentLength).toInt() - } else { - -1 - } + override fun update( + bytesRead: Long, + contentLength: Long, + done: Boolean, + ) { + _progress.value = + if (contentLength > 0) { + (100 * bytesRead / contentLength).toInt() + } else { + -1 + } } companion object { diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/SChapter.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/SChapter.kt index f53bbe8f0..d1d5243c4 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/SChapter.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/SChapter.kt @@ -1,9 +1,10 @@ +@file:Suppress("ktlint:standard:property-naming") + package eu.kanade.tachiyomi.source.model import java.io.Serializable interface SChapter : Serializable { - var url: String var name: String diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/SChapterImpl.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/SChapterImpl.kt index 4d5e43f1e..7c96902d2 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/SChapterImpl.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/SChapterImpl.kt @@ -1,7 +1,8 @@ +@file:Suppress("ktlint:standard:property-naming") + package eu.kanade.tachiyomi.source.model class SChapterImpl : SChapter { - override lateinit var url: String override lateinit var name: String diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/SManga.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/SManga.kt index 22e9962f8..da53e3db4 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/SManga.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/SManga.kt @@ -1,9 +1,10 @@ +@file:Suppress("ktlint:standard:property-naming") + package eu.kanade.tachiyomi.source.model import java.io.Serializable interface SManga : Serializable { - var url: String var title: String diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/SMangaImpl.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/SMangaImpl.kt index 91a7711cc..fa696dd19 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/SMangaImpl.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/SMangaImpl.kt @@ -1,7 +1,8 @@ +@file:Suppress("ktlint:standard:property-naming") + package eu.kanade.tachiyomi.source.model class SMangaImpl : SManga { - override lateinit var url: String override lateinit var title: String diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/UpdateStrategy.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/UpdateStrategy.kt index aa1d70181..2ebd485c8 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/UpdateStrategy.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/UpdateStrategy.kt @@ -2,5 +2,5 @@ package eu.kanade.tachiyomi.source.model enum class UpdateStrategy { ALWAYS_UPDATE, - ONLY_FETCH_ONCE + ONLY_FETCH_ONCE, } diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/source/online/HttpSource.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/source/online/HttpSource.kt index 165c3693e..ab172bdb9 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/source/online/HttpSource.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/source/online/HttpSource.kt @@ -19,7 +19,6 @@ import okhttp3.Response import rx.Observable import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle import uy.kohesive.injekt.injectLazy -// import uy.kohesive.injekt.injectLazy import java.net.URI import java.net.URISyntaxException import java.security.MessageDigest @@ -28,7 +27,6 @@ import java.security.MessageDigest * A simple implementation for sources from a website. */ abstract class HttpSource : CatalogueSource { - /** * Network service. */ @@ -91,7 +89,11 @@ abstract class HttpSource : CatalogueSource { * @param versionId [Int] the version ID of the source * @return a unique ID for the source */ - protected fun generateId(name: String, lang: String, versionId: Int): Long { + protected fun generateId( + name: String, + lang: String, + versionId: Int, + ): Long { val key = "${name.lowercase()}/$lang/$versionId" val bytes = MessageDigest.getInstance("MD5").digest(key.toByteArray()) return (0..7).map { bytes[it].toLong() and 0xff shl 8 * (7 - it) }.reduce(Long::or) and Long.MAX_VALUE @@ -100,9 +102,10 @@ abstract class HttpSource : CatalogueSource { /** * Headers builder for requests. Implementations can override this method for custom headers. */ - protected open fun headersBuilder() = Headers.Builder().apply { - add("User-Agent", DEFAULT_USER_AGENT) - } + protected open fun headersBuilder() = + Headers.Builder().apply { + add("User-Agent", DEFAULT_USER_AGENT) + } /** * Visible name of the source. @@ -147,7 +150,11 @@ abstract class HttpSource : CatalogueSource { * @param filters the list of filters to apply. */ @Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getSearchManga")) - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { + override fun fetchSearchManga( + page: Int, + query: String, + filters: FilterList, + ): Observable { return client.newCall(searchMangaRequest(page, query, filters)) .asObservableSuccess() .map { response -> @@ -162,7 +169,11 @@ abstract class HttpSource : CatalogueSource { * @param query the search query. * @param filters the list of filters to apply. */ - protected abstract fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request + protected abstract fun searchMangaRequest( + page: Int, + query: String, + filters: FilterList, + ): Request /** * Parses the response from the site and returns a [MangasPage] object. @@ -450,7 +461,10 @@ abstract class HttpSource : CatalogueSource { * @param chapter the chapter to be added. * @param manga the manga of the chapter. */ - open fun prepareNewChapter(chapter: SChapter, manga: SManga) {} + open fun prepareNewChapter( + chapter: SChapter, + manga: SManga, + ) {} /** * Returns the list of filters for the source. diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/source/online/ParsedHttpSource.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/source/online/ParsedHttpSource.kt index 941a3167a..319a639a4 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/source/online/ParsedHttpSource.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/source/online/ParsedHttpSource.kt @@ -13,7 +13,6 @@ import org.jsoup.nodes.Element * A simple implementation for sources from a website using Jsoup, an HTML parser. */ abstract class ParsedHttpSource : HttpSource() { - /** * Parses the response from the site and returns a [MangasPage] object. * @@ -22,13 +21,15 @@ abstract class ParsedHttpSource : HttpSource() { override fun popularMangaParse(response: Response): MangasPage { val document = response.asJsoup() - val mangas = document.select(popularMangaSelector()).map { element -> - popularMangaFromElement(element) - } + val mangas = + document.select(popularMangaSelector()).map { element -> + popularMangaFromElement(element) + } - val hasNextPage = popularMangaNextPageSelector()?.let { selector -> - document.select(selector).first() - } != null + val hasNextPage = + popularMangaNextPageSelector()?.let { selector -> + document.select(selector).first() + } != null return MangasPage(mangas, hasNextPage) } @@ -60,13 +61,15 @@ abstract class ParsedHttpSource : HttpSource() { override fun searchMangaParse(response: Response): MangasPage { val document = response.asJsoup() - val mangas = document.select(searchMangaSelector()).map { element -> - searchMangaFromElement(element) - } + val mangas = + document.select(searchMangaSelector()).map { element -> + searchMangaFromElement(element) + } - val hasNextPage = searchMangaNextPageSelector()?.let { selector -> - document.select(selector).first() - } != null + val hasNextPage = + searchMangaNextPageSelector()?.let { selector -> + document.select(selector).first() + } != null return MangasPage(mangas, hasNextPage) } @@ -98,13 +101,15 @@ abstract class ParsedHttpSource : HttpSource() { override fun latestUpdatesParse(response: Response): MangasPage { val document = response.asJsoup() - val mangas = document.select(latestUpdatesSelector()).map { element -> - latestUpdatesFromElement(element) - } + val mangas = + document.select(latestUpdatesSelector()).map { element -> + latestUpdatesFromElement(element) + } - val hasNextPage = latestUpdatesNextPageSelector()?.let { selector -> - document.select(selector).first() - } != null + val hasNextPage = + latestUpdatesNextPageSelector()?.let { selector -> + document.select(selector).first() + } != null return MangasPage(mangas, hasNextPage) } diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/source/online/ResolvableSource.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/source/online/ResolvableSource.kt index 6f785d714..0e9431692 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/source/online/ResolvableSource.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/source/online/ResolvableSource.kt @@ -10,7 +10,6 @@ import eu.kanade.tachiyomi.source.model.SManga */ @Suppress("unused") interface ResolvableSource : Source { - /** * Whether this source may potentially handle the given URI. * diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/util/JsoupExtensions.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/util/JsoupExtensions.kt index 6c166448a..652450319 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/util/JsoupExtensions.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/util/JsoupExtensions.kt @@ -5,11 +5,17 @@ import org.jsoup.Jsoup import org.jsoup.nodes.Document import org.jsoup.nodes.Element -fun Element.selectText(css: String, defaultValue: String? = null): String? { +fun Element.selectText( + css: String, + defaultValue: String? = null, +): String? { return select(css).first()?.text() ?: defaultValue } -fun Element.selectInt(css: String, defaultValue: Int = 0): Int { +fun Element.selectInt( + css: String, + defaultValue: Int = 0, +): Int { return select(css).first()?.text()?.toInt() ?: defaultValue } diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/util/chapter/ChapterRecognition.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/util/chapter/ChapterRecognition.kt index 5d935adb6..04fcedaa7 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/util/chapter/ChapterRecognition.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/util/chapter/ChapterRecognition.kt @@ -4,7 +4,6 @@ package eu.kanade.tachiyomi.util.chapter * -R> = regex conversion. */ object ChapterRecognition { - private const val NUMBER_PATTERN = """([0-9]+)(\.[0-9]+)?(\.?[a-z]+)?""" /** @@ -30,7 +29,11 @@ object ChapterRecognition { */ private val unwantedWhiteSpace = Regex("""\s(?=extra|special|omake)""") - fun parseChapterNumber(mangaTitle: String, chapterName: String, chapterNumber: Double? = null): Double { + fun parseChapterNumber( + mangaTitle: String, + chapterName: String, + chapterNumber: Double? = null, + ): Double { // If chapter number is known return. if (chapterNumber != null && (chapterNumber == -2.0 || chapterNumber > -1.0)) { return chapterNumber @@ -81,7 +84,10 @@ object ChapterRecognition { * @param alpha alpha value of regex * @return decimal/alpha float value */ - private fun checkForDecimal(decimal: String?, alpha: String?): Double { + private fun checkForDecimal( + decimal: String?, + alpha: String?, + ): Double { if (!decimal.isNullOrEmpty()) { return decimal.toDouble() } diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/util/lang/Hash.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/util/lang/Hash.kt index a89063208..8e289b3a2 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/util/lang/Hash.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/util/lang/Hash.kt @@ -3,11 +3,11 @@ package eu.kanade.tachiyomi.util.lang import java.security.MessageDigest object Hash { - - private val chars = charArrayOf( - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', - 'a', 'b', 'c', 'd', 'e', 'f' - ) + private val chars = + charArrayOf( + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'a', 'b', 'c', 'd', 'e', 'f', + ) private val MD5 get() = MessageDigest.getInstance("MD5") diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/util/lang/StringExtensions.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/util/lang/StringExtensions.kt index ab8c1c933..52a5a0617 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/util/lang/StringExtensions.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/util/lang/StringExtensions.kt @@ -7,7 +7,10 @@ import kotlin.math.floor * Replaces the given string to have at most [count] characters using [replacement] at its end. * If [replacement] is longer than [count] an exception will be thrown when `length > count`. */ -fun String.chop(count: Int, replacement: String = "…"): String { +fun String.chop( + count: Int, + replacement: String = "…", +): String { return if (length > count) { take(count - replacement.length) + replacement } else { @@ -19,7 +22,10 @@ fun String.chop(count: Int, replacement: String = "…"): String { * Replaces the given string to have at most [count] characters using [replacement] near the center. * If [replacement] is longer than [count] an exception will be thrown when `length > count`. */ -fun String.truncateCenter(count: Int, replacement: String = "..."): String { +fun String.truncateCenter( + count: Int, + replacement: String = "...", +): String { if (length <= count) { return this } diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/util/storage/EpubFile.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/util/storage/EpubFile.kt index d73d98fdb..f72b9455b 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/util/storage/EpubFile.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/util/storage/EpubFile.kt @@ -12,7 +12,6 @@ import java.io.InputStream * Wrapper over ZipFile to load files in epub format. */ class EpubFile(file: File) : Closeable { - /** * Zip file of this epub. */ @@ -81,9 +80,10 @@ class EpubFile(file: File) : Closeable { * Returns all the pages from the epub. */ fun getPagesFromDocument(document: Document): List { - val pages = document.select("manifest > item") - .filter { node -> "application/xhtml+xml" == node.attr("media-type") } - .associateBy { it.attr("id") } + val pages = + document.select("manifest > item") + .filter { node -> "application/xhtml+xml" == node.attr("media-type") } + .associateBy { it.attr("id") } val spine = document.select("spine > itemref").map { it.attr("idref") } return spine.mapNotNull { pages[it] }.map { it.attr("href") } @@ -92,7 +92,10 @@ class EpubFile(file: File) : Closeable { /** * Returns all the images contained in every page from the epub. */ - private fun getImagesFromPages(pages: List, packageHref: String): List { + private fun getImagesFromPages( + pages: List, + packageHref: String, + ): List { val result = mutableListOf() val basePath = getParentDirectory(packageHref) pages.forEach { page -> @@ -128,7 +131,10 @@ class EpubFile(file: File) : Closeable { /** * Resolves a zip path from base and relative components and a path separator. */ - private fun resolveZipPath(basePath: String, relativePath: String): String { + private fun resolveZipPath( + basePath: String, + relativePath: String, + ): String { if (relativePath.startsWith(pathSeparator)) { // Path is absolute, so return as-is. return relativePath diff --git a/server/src/main/kotlin/suwayomi/tachidesk/global/controller/GlobalMetaController.kt b/server/src/main/kotlin/suwayomi/tachidesk/global/controller/GlobalMetaController.kt index 178a8ed39..3916a557b 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/global/controller/GlobalMetaController.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/global/controller/GlobalMetaController.kt @@ -15,39 +15,41 @@ import suwayomi.tachidesk.server.util.withOperation object GlobalMetaController { /** used to modify a category's meta parameters */ - val getMeta = handler( - documentWith = { - withOperation { - summary("Server level meta mapping") - description("Get a list of globally stored key-value mapping, you can set values for whatever you want inside it.") - } - }, - behaviorOf = { ctx -> - ctx.json(GlobalMeta.getMetaMap()) - ctx.status(200) - }, - withResults = { - httpCode(HttpCode.OK) - } - ) + val getMeta = + handler( + documentWith = { + withOperation { + summary("Server level meta mapping") + description("Get a list of globally stored key-value mapping, you can set values for whatever you want inside it.") + } + }, + behaviorOf = { ctx -> + ctx.json(GlobalMeta.getMetaMap()) + ctx.status(200) + }, + withResults = { + httpCode(HttpCode.OK) + }, + ) /** used to modify global meta parameters */ - val modifyMeta = handler( - formParam("key"), - formParam("value"), - documentWith = { - withOperation { - summary("Add meta data to the global meta mapping") - description("A simple Key-Value stored at server global level, you can set values for whatever you want inside it.") - } - }, - behaviorOf = { ctx, key, value -> - GlobalMeta.modifyMeta(key, value) - ctx.status(200) - }, - withResults = { - httpCode(HttpCode.OK) - httpCode(HttpCode.NOT_FOUND) - } - ) + val modifyMeta = + handler( + formParam("key"), + formParam("value"), + documentWith = { + withOperation { + summary("Add meta data to the global meta mapping") + description("A simple Key-Value stored at server global level, you can set values for whatever you want inside it.") + } + }, + behaviorOf = { ctx, key, value -> + GlobalMeta.modifyMeta(key, value) + ctx.status(200) + }, + withResults = { + httpCode(HttpCode.OK) + httpCode(HttpCode.NOT_FOUND) + }, + ) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/global/controller/SettingsController.kt b/server/src/main/kotlin/suwayomi/tachidesk/global/controller/SettingsController.kt index 883285047..dfd37d435 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/global/controller/SettingsController.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/global/controller/SettingsController.kt @@ -19,36 +19,38 @@ import suwayomi.tachidesk.server.util.withOperation /** Settings Page/Screen */ object SettingsController { /** returns some static info about the current app build */ - val about = handler( - documentWith = { - withOperation { - summary("About Tachidesk") - description("Returns some static info about the current app build") - } - }, - behaviorOf = { ctx -> - ctx.json(About.getAbout()) - }, - withResults = { - json(HttpCode.OK) - } - ) + val about = + handler( + documentWith = { + withOperation { + summary("About Tachidesk") + description("Returns some static info about the current app build") + } + }, + behaviorOf = { ctx -> + ctx.json(About.getAbout()) + }, + withResults = { + json(HttpCode.OK) + }, + ) /** check for app updates */ - val checkUpdate = handler( - documentWith = { - withOperation { - summary("Tachidesk update check") - description("Check for app updates") - } - }, - behaviorOf = { ctx -> - ctx.future( - future { AppUpdate.checkUpdate() } - ) - }, - withResults = { - json>(HttpCode.OK) - } - ) + val checkUpdate = + handler( + documentWith = { + withOperation { + summary("Tachidesk update check") + description("Check for app updates") + } + }, + behaviorOf = { ctx -> + ctx.future( + future { AppUpdate.checkUpdate() }, + ) + }, + withResults = { + json>(HttpCode.OK) + }, + ) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/global/impl/About.kt b/server/src/main/kotlin/suwayomi/tachidesk/global/impl/About.kt index 8bc79b649..a125a2243 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/global/impl/About.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/global/impl/About.kt @@ -7,7 +7,7 @@ package suwayomi.tachidesk.global.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 suwayomi.tachidesk.server.BuildConfig +import suwayomi.tachidesk.server.generated.BuildConfig data class AboutDataClass( val name: String, @@ -16,7 +16,7 @@ data class AboutDataClass( val buildType: String, val buildTime: Long, val github: String, - val discord: String + val discord: String, ) object About { @@ -28,7 +28,7 @@ object About { BuildConfig.BUILD_TYPE, BuildConfig.BUILD_TIME, BuildConfig.GITHUB, - BuildConfig.DISCORD + BuildConfig.DISCORD, ) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/global/impl/AppUpdate.kt b/server/src/main/kotlin/suwayomi/tachidesk/global/impl/AppUpdate.kt index 8286af5d9..0a2d93120 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/global/impl/AppUpdate.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/global/impl/AppUpdate.kt @@ -19,7 +19,7 @@ data class UpdateDataClass( /** [channel] mirrors [suwayomi.tachidesk.server.BuildConfig.BUILD_TYPE] */ val channel: String, val tag: String, - val url: String + val url: String, ) object AppUpdate { @@ -30,29 +30,31 @@ object AppUpdate { private val network: NetworkHelper by injectLazy() suspend fun checkUpdate(): List { - val stableJson = json.parseToJsonElement( - network.client.newCall( - GET(LATEST_STABLE_CHANNEL_URL) - ).await().body.string() - ).jsonObject - - val previewJson = json.parseToJsonElement( - network.client.newCall( - GET(LATEST_PREVIEW_CHANNEL_URL) - ).await().body.string() - ).jsonObject + val stableJson = + json.parseToJsonElement( + network.client.newCall( + GET(LATEST_STABLE_CHANNEL_URL), + ).await().body.string(), + ).jsonObject + + val previewJson = + json.parseToJsonElement( + network.client.newCall( + GET(LATEST_PREVIEW_CHANNEL_URL), + ).await().body.string(), + ).jsonObject return listOf( UpdateDataClass( "Stable", stableJson["tag_name"]!!.jsonPrimitive.content, - stableJson["html_url"]!!.jsonPrimitive.content + stableJson["html_url"]!!.jsonPrimitive.content, ), UpdateDataClass( "Preview", previewJson["tag_name"]!!.jsonPrimitive.content, - previewJson["html_url"]!!.jsonPrimitive.content - ) + previewJson["html_url"]!!.jsonPrimitive.content, + ), ) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/global/impl/GlobalMeta.kt b/server/src/main/kotlin/suwayomi/tachidesk/global/impl/GlobalMeta.kt index fe300f84c..e4c368f8d 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/global/impl/GlobalMeta.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/global/impl/GlobalMeta.kt @@ -15,11 +15,15 @@ import suwayomi.tachidesk.global.model.table.GlobalMetaTable * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ object GlobalMeta { - fun modifyMeta(key: String, value: String) { + fun modifyMeta( + key: String, + value: String, + ) { transaction { - val meta = transaction { - GlobalMetaTable.select { GlobalMetaTable.key eq key } - }.firstOrNull() + val meta = + transaction { + GlobalMetaTable.select { GlobalMetaTable.key eq key } + }.firstOrNull() if (meta == null) { GlobalMetaTable.insert { diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/controller/GraphQLController.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/controller/GraphQLController.kt index 91d1ca34a..68e669316 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/controller/GraphQLController.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/controller/GraphQLController.kt @@ -23,7 +23,7 @@ object GraphQLController { ctx.future( future { server.execute(ctx) - } + }, ) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/dataLoaders/CategoryDataLoader.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/dataLoaders/CategoryDataLoader.kt index 6b36a7090..190fe6343 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/dataLoaders/CategoryDataLoader.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/dataLoaders/CategoryDataLoader.kt @@ -23,48 +23,56 @@ import suwayomi.tachidesk.server.JavalinSetup.future class CategoryDataLoader : KotlinDataLoader { override val dataLoaderName = "CategoryDataLoader" - override fun getDataLoader(): DataLoader = DataLoaderFactory.newDataLoader { ids -> - future { - transaction { - addLogger(Slf4jSqlDebugLogger) - val categories = CategoryTable.select { CategoryTable.id inList ids } - .map { CategoryType(it) } - .associateBy { it.id } - ids.map { categories[it] } + + override fun getDataLoader(): DataLoader = + DataLoaderFactory.newDataLoader { ids -> + future { + transaction { + addLogger(Slf4jSqlDebugLogger) + val categories = + CategoryTable.select { CategoryTable.id inList ids } + .map { CategoryType(it) } + .associateBy { it.id } + ids.map { categories[it] } + } } } - } } class CategoryForIdsDataLoader : KotlinDataLoader, CategoryNodeList> { override val dataLoaderName = "CategoryForIdsDataLoader" - override fun getDataLoader(): DataLoader, CategoryNodeList> = DataLoaderFactory.newDataLoader { categoryIds -> - future { - transaction { - addLogger(Slf4jSqlDebugLogger) - val ids = categoryIds.flatten().distinct() - val categories = CategoryTable.select { CategoryTable.id inList ids }.map { CategoryType(it) } - categoryIds.map { categoryIds -> - categories.filter { it.id in categoryIds }.toNodeList() + + override fun getDataLoader(): DataLoader, CategoryNodeList> = + DataLoaderFactory.newDataLoader { categoryIds -> + future { + transaction { + addLogger(Slf4jSqlDebugLogger) + val ids = categoryIds.flatten().distinct() + val categories = CategoryTable.select { CategoryTable.id inList ids }.map { CategoryType(it) } + categoryIds.map { categoryIds -> + categories.filter { it.id in categoryIds }.toNodeList() + } } } } - } } class CategoriesForMangaDataLoader : KotlinDataLoader { override val dataLoaderName = "CategoriesForMangaDataLoader" - override fun getDataLoader(): DataLoader = DataLoaderFactory.newDataLoader { ids -> - future { - transaction { - addLogger(Slf4jSqlDebugLogger) - val itemsByRef = CategoryMangaTable.innerJoin(CategoryTable) - .select { CategoryMangaTable.manga inList ids } - .map { Pair(it[CategoryMangaTable.manga].value, CategoryType(it)) } - .groupBy { it.first } - .mapValues { it.value.map { pair -> pair.second } } - ids.map { (itemsByRef[it] ?: emptyList()).toNodeList() } + + override fun getDataLoader(): DataLoader = + DataLoaderFactory.newDataLoader { ids -> + future { + transaction { + addLogger(Slf4jSqlDebugLogger) + val itemsByRef = + CategoryMangaTable.innerJoin(CategoryTable) + .select { CategoryMangaTable.manga inList ids } + .map { Pair(it[CategoryMangaTable.manga].value, CategoryType(it)) } + .groupBy { it.first } + .mapValues { it.value.map { pair -> pair.second } } + ids.map { (itemsByRef[it] ?: emptyList()).toNodeList() } + } } } - } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/dataLoaders/ChapterDataLoader.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/dataLoaders/ChapterDataLoader.kt index 4013a508e..bca01095d 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/dataLoaders/ChapterDataLoader.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/dataLoaders/ChapterDataLoader.kt @@ -25,82 +25,95 @@ import suwayomi.tachidesk.server.JavalinSetup.future class ChapterDataLoader : KotlinDataLoader { override val dataLoaderName = "ChapterDataLoader" - override fun getDataLoader(): DataLoader = DataLoaderFactory.newDataLoader { ids -> - future { - transaction { - addLogger(Slf4jSqlDebugLogger) - val chapters = ChapterTable.select { ChapterTable.id inList ids } - .map { ChapterType(it) } - .associateBy { it.id } - ids.map { chapters[it] } + + override fun getDataLoader(): DataLoader = + DataLoaderFactory.newDataLoader { ids -> + future { + transaction { + addLogger(Slf4jSqlDebugLogger) + val chapters = + ChapterTable.select { ChapterTable.id inList ids } + .map { ChapterType(it) } + .associateBy { it.id } + ids.map { chapters[it] } + } } } - } } class ChaptersForMangaDataLoader : KotlinDataLoader { override val dataLoaderName = "ChaptersForMangaDataLoader" - override fun getDataLoader(): DataLoader = DataLoaderFactory.newDataLoader { ids -> - future { - transaction { - addLogger(Slf4jSqlDebugLogger) - val chaptersByMangaId = ChapterTable.select { ChapterTable.manga inList ids } - .map { ChapterType(it) } - .groupBy { it.mangaId } - ids.map { (chaptersByMangaId[it] ?: emptyList()).toNodeList() } + + override fun getDataLoader(): DataLoader = + DataLoaderFactory.newDataLoader { ids -> + future { + transaction { + addLogger(Slf4jSqlDebugLogger) + val chaptersByMangaId = + ChapterTable.select { ChapterTable.manga inList ids } + .map { ChapterType(it) } + .groupBy { it.mangaId } + ids.map { (chaptersByMangaId[it] ?: emptyList()).toNodeList() } + } } } - } } class DownloadedChapterCountForMangaDataLoader : KotlinDataLoader { override val dataLoaderName = "DownloadedChapterCountForMangaDataLoader" - override fun getDataLoader(): DataLoader = DataLoaderFactory.newDataLoader { ids -> - future { - transaction { - addLogger(Slf4jSqlDebugLogger) - val downloadedChapterCountByMangaId = - ChapterTable - .slice(ChapterTable.manga, ChapterTable.isDownloaded.count()) - .select { (ChapterTable.manga inList ids) and (ChapterTable.isDownloaded eq true) } - .groupBy(ChapterTable.manga) - .associate { it[ChapterTable.manga].value to it[ChapterTable.isDownloaded.count()] } - ids.map { downloadedChapterCountByMangaId[it]?.toInt() ?: 0 } + + override fun getDataLoader(): DataLoader = + DataLoaderFactory.newDataLoader { ids -> + future { + transaction { + addLogger(Slf4jSqlDebugLogger) + val downloadedChapterCountByMangaId = + ChapterTable + .slice(ChapterTable.manga, ChapterTable.isDownloaded.count()) + .select { (ChapterTable.manga inList ids) and (ChapterTable.isDownloaded eq true) } + .groupBy(ChapterTable.manga) + .associate { it[ChapterTable.manga].value to it[ChapterTable.isDownloaded.count()] } + ids.map { downloadedChapterCountByMangaId[it]?.toInt() ?: 0 } + } } } - } } class UnreadChapterCountForMangaDataLoader : KotlinDataLoader { override val dataLoaderName = "UnreadChapterCountForMangaDataLoader" - override fun getDataLoader(): DataLoader = DataLoaderFactory.newDataLoader { ids -> - future { - transaction { - addLogger(Slf4jSqlDebugLogger) - val unreadChapterCountByMangaId = - ChapterTable - .slice(ChapterTable.manga, ChapterTable.isRead.count()) - .select { (ChapterTable.manga inList ids) and (ChapterTable.isRead eq false) } - .groupBy(ChapterTable.manga) - .associate { it[ChapterTable.manga].value to it[ChapterTable.isRead.count()] } - ids.map { unreadChapterCountByMangaId[it]?.toInt() ?: 0 } + + override fun getDataLoader(): DataLoader = + DataLoaderFactory.newDataLoader { ids -> + future { + transaction { + addLogger(Slf4jSqlDebugLogger) + val unreadChapterCountByMangaId = + ChapterTable + .slice(ChapterTable.manga, ChapterTable.isRead.count()) + .select { (ChapterTable.manga inList ids) and (ChapterTable.isRead eq false) } + .groupBy(ChapterTable.manga) + .associate { it[ChapterTable.manga].value to it[ChapterTable.isRead.count()] } + ids.map { unreadChapterCountByMangaId[it]?.toInt() ?: 0 } + } } } - } } class LastReadChapterForMangaDataLoader : KotlinDataLoader { override val dataLoaderName = "LastReadChapterForMangaDataLoader" - override fun getDataLoader(): DataLoader = DataLoaderFactory.newDataLoader { ids -> - future { - transaction { - addLogger(Slf4jSqlDebugLogger) - val lastReadChaptersByMangaId = ChapterTable - .select { (ChapterTable.manga inList ids) and (ChapterTable.isRead eq true) } - .orderBy(ChapterTable.sourceOrder to SortOrder.DESC) - .groupBy { it[ChapterTable.manga].value } - ids.map { id -> lastReadChaptersByMangaId[id]?.let { chapters -> ChapterType(chapters.first()) } } + + override fun getDataLoader(): DataLoader = + DataLoaderFactory.newDataLoader { ids -> + future { + transaction { + addLogger(Slf4jSqlDebugLogger) + val lastReadChaptersByMangaId = + ChapterTable + .select { (ChapterTable.manga inList ids) and (ChapterTable.isRead eq true) } + .orderBy(ChapterTable.sourceOrder to SortOrder.DESC) + .groupBy { it[ChapterTable.manga].value } + ids.map { id -> lastReadChaptersByMangaId[id]?.let { chapters -> ChapterType(chapters.first()) } } + } } } - } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/dataLoaders/ExtensionDataLoader.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/dataLoaders/ExtensionDataLoader.kt index 7e8c0763c..6f1433f06 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/dataLoaders/ExtensionDataLoader.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/dataLoaders/ExtensionDataLoader.kt @@ -21,43 +21,50 @@ import suwayomi.tachidesk.server.JavalinSetup.future class ExtensionDataLoader : KotlinDataLoader { override val dataLoaderName = "ExtensionDataLoader" - override fun getDataLoader(): DataLoader = DataLoaderFactory.newDataLoader { ids -> - future { - transaction { - addLogger(Slf4jSqlDebugLogger) - val extensions = ExtensionTable.select { ExtensionTable.pkgName inList ids } - .map { ExtensionType(it) } - .associateBy { it.pkgName } - ids.map { extensions[it] } + + override fun getDataLoader(): DataLoader = + DataLoaderFactory.newDataLoader { ids -> + future { + transaction { + addLogger(Slf4jSqlDebugLogger) + val extensions = + ExtensionTable.select { ExtensionTable.pkgName inList ids } + .map { ExtensionType(it) } + .associateBy { it.pkgName } + ids.map { extensions[it] } + } } } - } } class ExtensionForSourceDataLoader : KotlinDataLoader { override val dataLoaderName = "ExtensionForSourceDataLoader" - override fun getDataLoader(): DataLoader = DataLoaderFactory.newDataLoader { ids -> - future { - transaction { - addLogger(Slf4jSqlDebugLogger) - val extensions = ExtensionTable.innerJoin(SourceTable) - .select { SourceTable.id inList ids } - .toList() - .map { Triple(it[SourceTable.id].value, it[ExtensionTable.pkgName], it) } - .let { triples -> - val sources = buildMap { - triples.forEach { - if (!containsKey(it.second)) { - put(it.second, ExtensionType(it.third)) + + override fun getDataLoader(): DataLoader = + DataLoaderFactory.newDataLoader { ids -> + future { + transaction { + addLogger(Slf4jSqlDebugLogger) + val extensions = + ExtensionTable.innerJoin(SourceTable) + .select { SourceTable.id inList ids } + .toList() + .map { Triple(it[SourceTable.id].value, it[ExtensionTable.pkgName], it) } + .let { triples -> + val sources = + buildMap { + triples.forEach { + if (!containsKey(it.second)) { + put(it.second, ExtensionType(it.third)) + } + } + } + triples.associate { + it.first to sources[it.second] } } - } - triples.associate { - it.first to sources[it.second] - } - } - ids.map { extensions[it] } + ids.map { extensions[it] } + } } } - } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/dataLoaders/MangaDataLoader.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/dataLoaders/MangaDataLoader.kt index ecd586b4d..fe4cb199a 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/dataLoaders/MangaDataLoader.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/dataLoaders/MangaDataLoader.kt @@ -24,76 +24,89 @@ import suwayomi.tachidesk.server.JavalinSetup.future class MangaDataLoader : KotlinDataLoader { override val dataLoaderName = "MangaDataLoader" - override fun getDataLoader(): DataLoader = DataLoaderFactory.newDataLoader { ids -> - future { - transaction { - addLogger(Slf4jSqlDebugLogger) - val manga = MangaTable.select { MangaTable.id inList ids } - .map { MangaType(it) } - .associateBy { it.id } - ids.map { manga[it] } + + override fun getDataLoader(): DataLoader = + DataLoaderFactory.newDataLoader { ids -> + future { + transaction { + addLogger(Slf4jSqlDebugLogger) + val manga = + MangaTable.select { MangaTable.id inList ids } + .map { MangaType(it) } + .associateBy { it.id } + ids.map { manga[it] } + } } } - } } class MangaForCategoryDataLoader : KotlinDataLoader { override val dataLoaderName = "MangaForCategoryDataLoader" - override fun getDataLoader(): DataLoader = DataLoaderFactory.newDataLoader { ids -> - future { - transaction { - addLogger(Slf4jSqlDebugLogger) - val itemsByRef = if (ids.contains(0)) { - MangaTable - .leftJoin(CategoryMangaTable) - .select { MangaTable.inLibrary eq true } - .andWhere { CategoryMangaTable.manga.isNull() } - .map { MangaType(it) } - .let { - mapOf(0 to it) - } - } else { - emptyMap() - } + CategoryMangaTable.innerJoin(MangaTable) - .select { CategoryMangaTable.category inList ids } - .map { Pair(it[CategoryMangaTable.category].value, MangaType(it)) } - .groupBy { it.first } - .mapValues { it.value.map { pair -> pair.second } } - ids.map { (itemsByRef[it] ?: emptyList()).toNodeList() } + override fun getDataLoader(): DataLoader = + DataLoaderFactory.newDataLoader { ids -> + future { + transaction { + addLogger(Slf4jSqlDebugLogger) + val itemsByRef = + if (ids.contains(0)) { + MangaTable + .leftJoin(CategoryMangaTable) + .select { MangaTable.inLibrary eq true } + .andWhere { CategoryMangaTable.manga.isNull() } + .map { MangaType(it) } + .let { + mapOf(0 to it) + } + } else { + emptyMap() + } + + CategoryMangaTable.innerJoin(MangaTable) + .select { CategoryMangaTable.category inList ids } + .map { Pair(it[CategoryMangaTable.category].value, MangaType(it)) } + .groupBy { it.first } + .mapValues { it.value.map { pair -> pair.second } } + + ids.map { (itemsByRef[it] ?: emptyList()).toNodeList() } + } } } - } } class MangaForSourceDataLoader : KotlinDataLoader { override val dataLoaderName = "MangaForSourceDataLoader" - override fun getDataLoader(): DataLoader = DataLoaderFactory.newDataLoader { ids -> - future { - transaction { - addLogger(Slf4jSqlDebugLogger) - val mangaBySourceId = MangaTable.select { MangaTable.sourceReference inList ids } - .map { MangaType(it) } - .groupBy { it.sourceId } - ids.map { (mangaBySourceId[it] ?: emptyList()).toNodeList() } + + override fun getDataLoader(): DataLoader = + DataLoaderFactory.newDataLoader { ids -> + future { + transaction { + addLogger(Slf4jSqlDebugLogger) + val mangaBySourceId = + MangaTable.select { MangaTable.sourceReference inList ids } + .map { MangaType(it) } + .groupBy { it.sourceId } + ids.map { (mangaBySourceId[it] ?: emptyList()).toNodeList() } + } } } - } } class MangaForIdsDataLoader : KotlinDataLoader, MangaNodeList> { override val dataLoaderName = "MangaForIdsDataLoader" - override fun getDataLoader(): DataLoader, MangaNodeList> = DataLoaderFactory.newDataLoader { mangaIds -> - future { - transaction { - addLogger(Slf4jSqlDebugLogger) - val ids = mangaIds.flatten().distinct() - val manga = MangaTable.select { MangaTable.id inList ids } - .map { MangaType(it) } - mangaIds.map { mangaIds -> - manga.filter { it.id in mangaIds }.toNodeList() + + override fun getDataLoader(): DataLoader, MangaNodeList> = + DataLoaderFactory.newDataLoader { mangaIds -> + future { + transaction { + addLogger(Slf4jSqlDebugLogger) + val ids = mangaIds.flatten().distinct() + val manga = + MangaTable.select { MangaTable.id inList ids } + .map { MangaType(it) } + mangaIds.map { mangaIds -> + manga.filter { it.id in mangaIds }.toNodeList() + } } } } - } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/dataLoaders/MetaDataLoader.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/dataLoaders/MetaDataLoader.kt index 662967dc9..d0b8c594b 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/dataLoaders/MetaDataLoader.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/dataLoaders/MetaDataLoader.kt @@ -19,60 +19,72 @@ import suwayomi.tachidesk.server.JavalinSetup.future class GlobalMetaDataLoader : KotlinDataLoader { override val dataLoaderName = "GlobalMetaDataLoader" - override fun getDataLoader(): DataLoader = DataLoaderFactory.newDataLoader { ids -> - future { - transaction { - addLogger(Slf4jSqlDebugLogger) - val metasByRefId = GlobalMetaTable.select { GlobalMetaTable.key inList ids } - .map { GlobalMetaType(it) } - .associateBy { it.key } - ids.map { metasByRefId[it] } + + override fun getDataLoader(): DataLoader = + DataLoaderFactory.newDataLoader { ids -> + future { + transaction { + addLogger(Slf4jSqlDebugLogger) + val metasByRefId = + GlobalMetaTable.select { GlobalMetaTable.key inList ids } + .map { GlobalMetaType(it) } + .associateBy { it.key } + ids.map { metasByRefId[it] } + } } } - } } class ChapterMetaDataLoader : KotlinDataLoader> { override val dataLoaderName = "ChapterMetaDataLoader" - override fun getDataLoader(): DataLoader> = DataLoaderFactory.newDataLoader> { ids -> - future { - transaction { - addLogger(Slf4jSqlDebugLogger) - val metasByRefId = ChapterMetaTable.select { ChapterMetaTable.ref inList ids } - .map { ChapterMetaType(it) } - .groupBy { it.chapterId } - ids.map { metasByRefId[it].orEmpty() } + + override fun getDataLoader(): DataLoader> = + DataLoaderFactory.newDataLoader> { ids -> + future { + transaction { + addLogger(Slf4jSqlDebugLogger) + val metasByRefId = + ChapterMetaTable.select { ChapterMetaTable.ref inList ids } + .map { ChapterMetaType(it) } + .groupBy { it.chapterId } + ids.map { metasByRefId[it].orEmpty() } + } } } - } } class MangaMetaDataLoader : KotlinDataLoader> { override val dataLoaderName = "MangaMetaDataLoader" - override fun getDataLoader(): DataLoader> = DataLoaderFactory.newDataLoader> { ids -> - future { - transaction { - addLogger(Slf4jSqlDebugLogger) - val metasByRefId = MangaMetaTable.select { MangaMetaTable.ref inList ids } - .map { MangaMetaType(it) } - .groupBy { it.mangaId } - ids.map { metasByRefId[it].orEmpty() } + + override fun getDataLoader(): DataLoader> = + DataLoaderFactory.newDataLoader> { ids -> + future { + transaction { + addLogger(Slf4jSqlDebugLogger) + val metasByRefId = + MangaMetaTable.select { MangaMetaTable.ref inList ids } + .map { MangaMetaType(it) } + .groupBy { it.mangaId } + ids.map { metasByRefId[it].orEmpty() } + } } } - } } class CategoryMetaDataLoader : KotlinDataLoader> { override val dataLoaderName = "CategoryMetaDataLoader" - override fun getDataLoader(): DataLoader> = DataLoaderFactory.newDataLoader> { ids -> - future { - transaction { - addLogger(Slf4jSqlDebugLogger) - val metasByRefId = CategoryMetaTable.select { CategoryMetaTable.ref inList ids } - .map { CategoryMetaType(it) } - .groupBy { it.categoryId } - ids.map { metasByRefId[it].orEmpty() } + + override fun getDataLoader(): DataLoader> = + DataLoaderFactory.newDataLoader> { ids -> + future { + transaction { + addLogger(Slf4jSqlDebugLogger) + val metasByRefId = + CategoryMetaTable.select { CategoryMetaTable.ref inList ids } + .map { CategoryMetaType(it) } + .groupBy { it.categoryId } + ids.map { metasByRefId[it].orEmpty() } + } } } - } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/dataLoaders/SourceDataLoader.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/dataLoaders/SourceDataLoader.kt index bc58ac6f6..eb29de9e1 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/dataLoaders/SourceDataLoader.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/dataLoaders/SourceDataLoader.kt @@ -23,34 +23,40 @@ import suwayomi.tachidesk.server.JavalinSetup.future class SourceDataLoader : KotlinDataLoader { override val dataLoaderName = "SourceDataLoader" - override fun getDataLoader(): DataLoader = DataLoaderFactory.newDataLoader { ids -> - future { - transaction { - addLogger(Slf4jSqlDebugLogger) - val source = SourceTable.select { SourceTable.id inList ids } - .mapNotNull { SourceType(it) } - .associateBy { it.id } - ids.map { source[it] } + + override fun getDataLoader(): DataLoader = + DataLoaderFactory.newDataLoader { ids -> + future { + transaction { + addLogger(Slf4jSqlDebugLogger) + val source = + SourceTable.select { SourceTable.id inList ids } + .mapNotNull { SourceType(it) } + .associateBy { it.id } + ids.map { source[it] } + } } } - } } class SourcesForExtensionDataLoader : KotlinDataLoader { override val dataLoaderName = "SourcesForExtensionDataLoader" - override fun getDataLoader(): DataLoader = DataLoaderFactory.newDataLoader { ids -> - future { - transaction { - addLogger(Slf4jSqlDebugLogger) - - val sourcesByExtensionPkg = SourceTable.innerJoin(ExtensionTable) - .select { ExtensionTable.pkgName inList ids } - .map { Pair(it[ExtensionTable.pkgName], SourceType(it)) } - .groupBy { it.first } - .mapValues { it.value.mapNotNull { pair -> pair.second } } - - ids.map { (sourcesByExtensionPkg[it] ?: emptyList()).toNodeList() } + + override fun getDataLoader(): DataLoader = + DataLoaderFactory.newDataLoader { ids -> + future { + transaction { + addLogger(Slf4jSqlDebugLogger) + + val sourcesByExtensionPkg = + SourceTable.innerJoin(ExtensionTable) + .select { ExtensionTable.pkgName inList ids } + .map { Pair(it[ExtensionTable.pkgName], SourceType(it)) } + .groupBy { it.first } + .mapValues { it.value.mapNotNull { pair -> pair.second } } + + ids.map { (sourcesByExtensionPkg[it] ?: emptyList()).toNodeList() } + } } } - } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/BackupMutation.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/BackupMutation.kt index 156724ca5..054c975df 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/BackupMutation.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/BackupMutation.kt @@ -20,17 +20,16 @@ import kotlin.time.Duration.Companion.seconds class BackupMutation { data class RestoreBackupInput( val clientMutationId: String? = null, - val backup: UploadedFile + val backup: UploadedFile, ) + data class RestoreBackupPayload( val clientMutationId: String?, - val status: BackupRestoreStatus + val status: BackupRestoreStatus, ) @OptIn(DelicateCoroutinesApi::class) - fun restoreBackup( - input: RestoreBackupInput - ): CompletableFuture { + fun restoreBackup(input: RestoreBackupInput): CompletableFuture { val (clientMutationId, backup) = input return future { @@ -38,11 +37,12 @@ class BackupMutation { ProtoBackupImport.performRestore(backup.content) } - val status = withTimeout(10.seconds) { - ProtoBackupImport.backupRestoreState.first { - it != ProtoBackupImport.BackupRestoreState.Idle - }.toStatus() - } + val status = + withTimeout(10.seconds) { + ProtoBackupImport.backupRestoreState.first { + it != ProtoBackupImport.BackupRestoreState.Idle + }.toStatus() + } RestoreBackupPayload(clientMutationId, status) } @@ -51,32 +51,33 @@ class BackupMutation { data class CreateBackupInput( val clientMutationId: String? = null, val includeChapters: Boolean? = null, - val includeCategories: Boolean? = null + val includeCategories: Boolean? = null, ) + data class CreateBackupPayload( val clientMutationId: String?, - val url: String + val url: String, ) - fun createBackup( - input: CreateBackupInput? = null - ): CreateBackupPayload { + + fun createBackup(input: CreateBackupInput? = null): CreateBackupPayload { val filename = ProtoBackupExport.getBackupFilename() - val backup = ProtoBackupExport.createBackup( - BackupFlags( - includeManga = true, - includeCategories = input?.includeCategories ?: true, - includeChapters = input?.includeChapters ?: true, - includeTracking = true, - includeHistory = true + val backup = + ProtoBackupExport.createBackup( + BackupFlags( + includeManga = true, + includeCategories = input?.includeCategories ?: true, + includeChapters = input?.includeChapters ?: true, + includeTracking = true, + includeHistory = true, + ), ) - ) TemporaryFileStorage.saveFile(filename, backup) return CreateBackupPayload( clientMutationId = input?.clientMutationId, - url = "/api/graphql/files/backup/$filename" + url = "/api/graphql/files/backup/$filename", ) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/CategoryMutation.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/CategoryMutation.kt index 161990cdf..311e32242 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/CategoryMutation.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/CategoryMutation.kt @@ -27,15 +27,15 @@ import suwayomi.tachidesk.manga.model.table.MangaTable class CategoryMutation { data class SetCategoryMetaInput( val clientMutationId: String? = null, - val meta: CategoryMetaType + val meta: CategoryMetaType, ) + data class SetCategoryMetaPayload( val clientMutationId: String?, - val meta: CategoryMetaType + val meta: CategoryMetaType, ) - fun setCategoryMeta( - input: SetCategoryMetaInput - ): SetCategoryMetaPayload { + + fun setCategoryMeta(input: SetCategoryMetaInput): SetCategoryMetaPayload { val (clientMutationId, meta) = input Category.modifyMeta(meta.categoryId, meta.key, meta.value) @@ -46,34 +46,37 @@ class CategoryMutation { data class DeleteCategoryMetaInput( val clientMutationId: String? = null, val categoryId: Int, - val key: String + val key: String, ) + data class DeleteCategoryMetaPayload( val clientMutationId: String?, val meta: CategoryMetaType?, - val category: CategoryType + val category: CategoryType, ) - fun deleteCategoryMeta( - input: DeleteCategoryMetaInput - ): DeleteCategoryMetaPayload { + + fun deleteCategoryMeta(input: DeleteCategoryMetaInput): DeleteCategoryMetaPayload { val (clientMutationId, categoryId, key) = input - val (meta, category) = transaction { - val meta = CategoryMetaTable.select { (CategoryMetaTable.ref eq categoryId) and (CategoryMetaTable.key eq key) } - .firstOrNull() + val (meta, category) = + transaction { + val meta = + CategoryMetaTable.select { (CategoryMetaTable.ref eq categoryId) and (CategoryMetaTable.key eq key) } + .firstOrNull() - CategoryMetaTable.deleteWhere { (CategoryMetaTable.ref eq categoryId) and (CategoryMetaTable.key eq key) } + CategoryMetaTable.deleteWhere { (CategoryMetaTable.ref eq categoryId) and (CategoryMetaTable.key eq key) } - val category = transaction { - CategoryType(CategoryTable.select { CategoryTable.id eq categoryId }.first()) - } + val category = + transaction { + CategoryType(CategoryTable.select { CategoryTable.id eq categoryId }.first()) + } - if (meta != null) { - CategoryMetaType(meta) - } else { - null - } to category - } + if (meta != null) { + CategoryMetaType(meta) + } else { + null + } to category + } return DeleteCategoryMetaPayload(clientMutationId, meta, category) } @@ -81,30 +84,35 @@ class CategoryMutation { data class UpdateCategoryPatch( val name: String? = null, val default: Boolean? = null, - val includeInUpdate: IncludeInUpdate? = null + val includeInUpdate: IncludeInUpdate? = null, ) data class UpdateCategoryPayload( val clientMutationId: String?, - val category: CategoryType + val category: CategoryType, ) + data class UpdateCategoryInput( val clientMutationId: String? = null, val id: Int, - val patch: UpdateCategoryPatch + val patch: UpdateCategoryPatch, ) data class UpdateCategoriesPayload( val clientMutationId: String?, - val categories: List + val categories: List, ) + data class UpdateCategoriesInput( val clientMutationId: String? = null, val ids: List, - val patch: UpdateCategoryPatch + val patch: UpdateCategoryPatch, ) - private fun updateCategories(ids: List, patch: UpdateCategoryPatch) { + private fun updateCategories( + ids: List, + patch: UpdateCategoryPatch, + ) { transaction { if (patch.name != null) { CategoryTable.update({ CategoryTable.id inList ids }) { update -> @@ -135,13 +143,14 @@ class CategoryMutation { updateCategories(listOf(id), patch) - val category = transaction { - CategoryType(CategoryTable.select { CategoryTable.id eq id }.first()) - } + val category = + transaction { + CategoryType(CategoryTable.select { CategoryTable.id eq id }.first()) + } return UpdateCategoryPayload( clientMutationId = clientMutationId, - category = category + category = category, ) } @@ -150,24 +159,26 @@ class CategoryMutation { updateCategories(ids, patch) - val categories = transaction { - CategoryTable.select { CategoryTable.id inList ids }.map { CategoryType(it) } - } + val categories = + transaction { + CategoryTable.select { CategoryTable.id inList ids }.map { CategoryType(it) } + } return UpdateCategoriesPayload( clientMutationId = clientMutationId, - categories = categories + categories = categories, ) } data class UpdateCategoryOrderPayload( val clientMutationId: String?, - val categories: List + val categories: List, ) + data class UpdateCategoryOrderInput( val clientMutationId: String? = null, val id: Int, - val position: Int + val position: Int, ) fun updateCategoryOrder(input: UpdateCategoryOrderInput): UpdateCategoryOrderPayload { @@ -177,9 +188,10 @@ class CategoryMutation { } transaction { - val currentOrder = CategoryTable - .select { CategoryTable.id eq categoryId } - .first()[CategoryTable.order] + val currentOrder = + CategoryTable + .select { CategoryTable.id eq categoryId } + .first()[CategoryTable.order] if (currentOrder != position) { if (position < currentOrder) { @@ -200,13 +212,14 @@ class CategoryMutation { Category.normalizeCategories() - val categories = transaction { - CategoryTable.selectAll().orderBy(CategoryTable.order).map { CategoryType(it) } - } + val categories = + transaction { + CategoryTable.selectAll().orderBy(CategoryTable.order).map { CategoryType(it) } + } return UpdateCategoryOrderPayload( clientMutationId = clientMutationId, - categories = categories + categories = categories, ) } @@ -215,15 +228,15 @@ class CategoryMutation { val name: String, val order: Int? = null, val default: Boolean? = null, - val includeInUpdate: IncludeInUpdate? = null + val includeInUpdate: IncludeInUpdate? = null, ) + data class CreateCategoryPayload( val clientMutationId: String?, - val category: CategoryType + val category: CategoryType, ) - fun createCategory( - input: CreateCategoryInput - ): CreateCategoryPayload { + + fun createCategory(input: CreateCategoryInput): CreateCategoryPayload { val (clientMutationId, name, order, default, includeInUpdate) = input transaction { require(CategoryTable.select { CategoryTable.name eq input.name }.isEmpty()) { @@ -239,73 +252,78 @@ class CategoryMutation { } } - val category = transaction { - if (order != null) { - CategoryTable.update({ CategoryTable.order greaterEq order }) { - it[CategoryTable.order] = CategoryTable.order + 1 + val category = + transaction { + if (order != null) { + CategoryTable.update({ CategoryTable.order greaterEq order }) { + it[CategoryTable.order] = CategoryTable.order + 1 + } } - } - val id = CategoryTable.insertAndGetId { - it[CategoryTable.name] = input.name - it[CategoryTable.order] = order ?: Int.MAX_VALUE - if (default != null) { - it[CategoryTable.isDefault] = default - } - if (includeInUpdate != null) { - it[CategoryTable.includeInUpdate] = includeInUpdate.value - } - } + val id = + CategoryTable.insertAndGetId { + it[CategoryTable.name] = input.name + it[CategoryTable.order] = order ?: Int.MAX_VALUE + if (default != null) { + it[CategoryTable.isDefault] = default + } + if (includeInUpdate != null) { + it[CategoryTable.includeInUpdate] = includeInUpdate.value + } + } - Category.normalizeCategories() + Category.normalizeCategories() - CategoryType(CategoryTable.select { CategoryTable.id eq id }.first()) - } + CategoryType(CategoryTable.select { CategoryTable.id eq id }.first()) + } return CreateCategoryPayload(clientMutationId, category) } data class DeleteCategoryInput( val clientMutationId: String? = null, - val categoryId: Int + val categoryId: Int, ) + data class DeleteCategoryPayload( val clientMutationId: String?, val category: CategoryType?, - val mangas: List + val mangas: List, ) - fun deleteCategory( - input: DeleteCategoryInput - ): DeleteCategoryPayload { + + fun deleteCategory(input: DeleteCategoryInput): DeleteCategoryPayload { val (clientMutationId, categoryId) = input if (categoryId == 0) { // Don't delete default category return DeleteCategoryPayload( clientMutationId, null, - emptyList() + emptyList(), ) } - val (category, mangas) = transaction { - val category = CategoryTable.select { CategoryTable.id eq categoryId } - .firstOrNull() - - val mangas = transaction { - MangaTable.innerJoin(CategoryMangaTable) - .select { CategoryMangaTable.category eq categoryId } - .map { MangaType(it) } - } + val (category, mangas) = + transaction { + val category = + CategoryTable.select { CategoryTable.id eq categoryId } + .firstOrNull() + + val mangas = + transaction { + MangaTable.innerJoin(CategoryMangaTable) + .select { CategoryMangaTable.category eq categoryId } + .map { MangaType(it) } + } - CategoryTable.deleteWhere { CategoryTable.id eq categoryId } + CategoryTable.deleteWhere { CategoryTable.id eq categoryId } - Category.normalizeCategories() + Category.normalizeCategories() - if (category != null) { - CategoryType(category) - } else { - null - } to mangas - } + if (category != null) { + CategoryType(category) + } else { + null + } to mangas + } return DeleteCategoryPayload(clientMutationId, category, mangas) } @@ -313,30 +331,35 @@ class CategoryMutation { data class UpdateMangaCategoriesPatch( val clearCategories: Boolean? = null, val addToCategories: List? = null, - val removeFromCategories: List? = null + val removeFromCategories: List? = null, ) data class UpdateMangaCategoriesPayload( val clientMutationId: String?, - val manga: MangaType + val manga: MangaType, ) + data class UpdateMangaCategoriesInput( val clientMutationId: String? = null, val id: Int, - val patch: UpdateMangaCategoriesPatch + val patch: UpdateMangaCategoriesPatch, ) data class UpdateMangasCategoriesPayload( val clientMutationId: String?, - val mangas: List + val mangas: List, ) + data class UpdateMangasCategoriesInput( val clientMutationId: String? = null, val ids: List, - val patch: UpdateMangaCategoriesPatch + val patch: UpdateMangaCategoriesPatch, ) - private fun updateMangas(ids: List, patch: UpdateMangaCategoriesPatch) { + private fun updateMangas( + ids: List, + patch: UpdateMangaCategoriesPatch, + ) { transaction { if (patch.clearCategories == true) { CategoryMangaTable.deleteWhere { CategoryMangaTable.manga inList ids } @@ -346,19 +369,21 @@ class CategoryMutation { } } if (!patch.addToCategories.isNullOrEmpty()) { - val newCategories = buildList { - ids.forEach { mangaId -> - patch.addToCategories.forEach { categoryId -> - val existingMapping = CategoryMangaTable.select { - (CategoryMangaTable.manga eq mangaId) and (CategoryMangaTable.category eq categoryId) - }.isNotEmpty() - - if (!existingMapping) { - add(mangaId to categoryId) + val newCategories = + buildList { + ids.forEach { mangaId -> + patch.addToCategories.forEach { categoryId -> + val existingMapping = + CategoryMangaTable.select { + (CategoryMangaTable.manga eq mangaId) and (CategoryMangaTable.category eq categoryId) + }.isNotEmpty() + + if (!existingMapping) { + add(mangaId to categoryId) + } } } } - } CategoryMangaTable.batchInsert(newCategories) { (manga, category) -> this[CategoryMangaTable.manga] = manga @@ -373,13 +398,14 @@ class CategoryMutation { updateMangas(listOf(id), patch) - val manga = transaction { - MangaType(MangaTable.select { MangaTable.id eq id }.first()) - } + val manga = + transaction { + MangaType(MangaTable.select { MangaTable.id eq id }.first()) + } return UpdateMangaCategoriesPayload( clientMutationId = clientMutationId, - manga = manga + manga = manga, ) } @@ -388,13 +414,14 @@ class CategoryMutation { updateMangas(ids, patch) - val mangas = transaction { - MangaTable.select { MangaTable.id inList ids }.map { MangaType(it) } - } + val mangas = + transaction { + MangaTable.select { MangaTable.id inList ids }.map { MangaType(it) } + } return UpdateMangasCategoriesPayload( clientMutationId = clientMutationId, - mangas = mangas + mangas = mangas, ) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/ChapterMutation.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/ChapterMutation.kt index df9bae194..5ad52d488 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/ChapterMutation.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/ChapterMutation.kt @@ -25,30 +25,35 @@ class ChapterMutation { data class UpdateChapterPatch( val isBookmarked: Boolean? = null, val isRead: Boolean? = null, - val lastPageRead: Int? = null + val lastPageRead: Int? = null, ) data class UpdateChapterPayload( val clientMutationId: String?, - val chapter: ChapterType + val chapter: ChapterType, ) + data class UpdateChapterInput( val clientMutationId: String? = null, val id: Int, - val patch: UpdateChapterPatch + val patch: UpdateChapterPatch, ) data class UpdateChaptersPayload( val clientMutationId: String?, - val chapters: List + val chapters: List, ) + data class UpdateChaptersInput( val clientMutationId: String? = null, val ids: List, - val patch: UpdateChapterPatch + val patch: UpdateChapterPatch, ) - private fun updateChapters(ids: List, patch: UpdateChapterPatch) { + private fun updateChapters( + ids: List, + patch: UpdateChapterPatch, + ) { transaction { if (patch.isRead != null || patch.isBookmarked != null || patch.lastPageRead != null) { val now = Instant.now().epochSecond @@ -68,81 +73,79 @@ class ChapterMutation { } } - fun updateChapter( - input: UpdateChapterInput - ): UpdateChapterPayload { + fun updateChapter(input: UpdateChapterInput): UpdateChapterPayload { val (clientMutationId, id, patch) = input updateChapters(listOf(id), patch) - val chapter = transaction { - ChapterType(ChapterTable.select { ChapterTable.id eq id }.first()) - } + val chapter = + transaction { + ChapterType(ChapterTable.select { ChapterTable.id eq id }.first()) + } return UpdateChapterPayload( clientMutationId = clientMutationId, - chapter = chapter + chapter = chapter, ) } - fun updateChapters( - input: UpdateChaptersInput - ): UpdateChaptersPayload { + fun updateChapters(input: UpdateChaptersInput): UpdateChaptersPayload { val (clientMutationId, ids, patch) = input updateChapters(ids, patch) - val chapters = transaction { - ChapterTable.select { ChapterTable.id inList ids }.map { ChapterType(it) } - } + val chapters = + transaction { + ChapterTable.select { ChapterTable.id inList ids }.map { ChapterType(it) } + } return UpdateChaptersPayload( clientMutationId = clientMutationId, - chapters = chapters + chapters = chapters, ) } data class FetchChaptersInput( val clientMutationId: String? = null, - val mangaId: Int + val mangaId: Int, ) + data class FetchChaptersPayload( val clientMutationId: String?, - val chapters: List + val chapters: List, ) - fun fetchChapters( - input: FetchChaptersInput - ): CompletableFuture { + fun fetchChapters(input: FetchChaptersInput): CompletableFuture { val (clientMutationId, mangaId) = input return future { Chapter.fetchChapterList(mangaId) }.thenApply { - val chapters = transaction { - ChapterTable.select { ChapterTable.manga eq mangaId } - .orderBy(ChapterTable.sourceOrder) - .map { ChapterType(it) } - } + val chapters = + transaction { + ChapterTable.select { ChapterTable.manga eq mangaId } + .orderBy(ChapterTable.sourceOrder) + .map { ChapterType(it) } + } FetchChaptersPayload( clientMutationId = clientMutationId, - chapters = chapters + chapters = chapters, ) } } data class SetChapterMetaInput( val clientMutationId: String? = null, - val meta: ChapterMetaType + val meta: ChapterMetaType, ) + data class SetChapterMetaPayload( val clientMutationId: String?, - val meta: ChapterMetaType + val meta: ChapterMetaType, ) - fun setChapterMeta( - input: SetChapterMetaInput - ): SetChapterMetaPayload { + + fun setChapterMeta(input: SetChapterMetaInput): SetChapterMetaPayload { val (clientMutationId, meta) = input Chapter.modifyChapterMeta(meta.chapterId, meta.key, meta.value) @@ -153,50 +156,53 @@ class ChapterMutation { data class DeleteChapterMetaInput( val clientMutationId: String? = null, val chapterId: Int, - val key: String + val key: String, ) + data class DeleteChapterMetaPayload( val clientMutationId: String?, val meta: ChapterMetaType?, - val chapter: ChapterType + val chapter: ChapterType, ) - fun deleteChapterMeta( - input: DeleteChapterMetaInput - ): DeleteChapterMetaPayload { + + fun deleteChapterMeta(input: DeleteChapterMetaInput): DeleteChapterMetaPayload { val (clientMutationId, chapterId, key) = input - val (meta, chapter) = transaction { - val meta = ChapterMetaTable.select { (ChapterMetaTable.ref eq chapterId) and (ChapterMetaTable.key eq key) } - .firstOrNull() + val (meta, chapter) = + transaction { + val meta = + ChapterMetaTable.select { (ChapterMetaTable.ref eq chapterId) and (ChapterMetaTable.key eq key) } + .firstOrNull() - ChapterMetaTable.deleteWhere { (ChapterMetaTable.ref eq chapterId) and (ChapterMetaTable.key eq key) } + ChapterMetaTable.deleteWhere { (ChapterMetaTable.ref eq chapterId) and (ChapterMetaTable.key eq key) } - val chapter = transaction { - ChapterType(ChapterTable.select { ChapterTable.id eq chapterId }.first()) - } + val chapter = + transaction { + ChapterType(ChapterTable.select { ChapterTable.id eq chapterId }.first()) + } - if (meta != null) { - ChapterMetaType(meta) - } else { - null - } to chapter - } + if (meta != null) { + ChapterMetaType(meta) + } else { + null + } to chapter + } return DeleteChapterMetaPayload(clientMutationId, meta, chapter) } data class FetchChapterPagesInput( val clientMutationId: String? = null, - val chapterId: Int + val chapterId: Int, ) + data class FetchChapterPagesPayload( val clientMutationId: String?, val pages: List, - val chapter: ChapterType + val chapter: ChapterType, ) - fun fetchChapterPages( - input: FetchChapterPagesInput - ): CompletableFuture { + + fun fetchChapterPages(input: FetchChapterPagesInput): CompletableFuture { val (clientMutationId, chapterId) = input return future { @@ -204,10 +210,11 @@ class ChapterMutation { }.thenApply { chapter -> FetchChapterPagesPayload( clientMutationId = clientMutationId, - pages = List(chapter.pageCount) { index -> - "/api/v1/manga/${chapter.mangaId}/chapter/${chapter.index}/page/$index" - }, - chapter = ChapterType(chapter) + pages = + List(chapter.pageCount) { index -> + "/api/v1/manga/${chapter.mangaId}/chapter/${chapter.index}/page/$index" + }, + chapter = ChapterType(chapter), ) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/DownloadMutation.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/DownloadMutation.kt index dfc94b518..974e30524 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/DownloadMutation.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/DownloadMutation.kt @@ -16,14 +16,14 @@ import java.util.concurrent.CompletableFuture import kotlin.time.Duration.Companion.seconds class DownloadMutation { - data class DeleteDownloadedChaptersInput( val clientMutationId: String? = null, - val ids: List + val ids: List, ) + data class DeleteDownloadedChaptersPayload( val clientMutationId: String?, - val chapters: List + val chapters: List, ) fun deleteDownloadedChapters(input: DeleteDownloadedChaptersInput): DeleteDownloadedChaptersPayload { @@ -33,20 +33,22 @@ class DownloadMutation { return DeleteDownloadedChaptersPayload( clientMutationId = clientMutationId, - chapters = transaction { - ChapterTable.select { ChapterTable.id inList chapters } - .map { ChapterType(it) } - } + chapters = + transaction { + ChapterTable.select { ChapterTable.id inList chapters } + .map { ChapterType(it) } + }, ) } data class DeleteDownloadedChapterInput( val clientMutationId: String? = null, - val id: Int + val id: Int, ) + data class DeleteDownloadedChapterPayload( val clientMutationId: String?, - val chapters: ChapterType + val chapters: ChapterType, ) fun deleteDownloadedChapter(input: DeleteDownloadedChapterInput): DeleteDownloadedChapterPayload { @@ -56,24 +58,24 @@ class DownloadMutation { return DeleteDownloadedChapterPayload( clientMutationId = clientMutationId, - chapters = transaction { - ChapterType(ChapterTable.select { ChapterTable.id eq chapter }.first()) - } + chapters = + transaction { + ChapterType(ChapterTable.select { ChapterTable.id eq chapter }.first()) + }, ) } data class EnqueueChapterDownloadsInput( val clientMutationId: String? = null, - val ids: List + val ids: List, ) + data class EnqueueChapterDownloadsPayload( val clientMutationId: String?, - val downloadStatus: DownloadStatus + val downloadStatus: DownloadStatus, ) - fun enqueueChapterDownloads( - input: EnqueueChapterDownloadsInput - ): CompletableFuture { + fun enqueueChapterDownloads(input: EnqueueChapterDownloadsInput): CompletableFuture { val (clientMutationId, chapters) = input DownloadManager.enqueue(DownloadManager.EnqueueInput(chapters)) @@ -81,25 +83,25 @@ class DownloadMutation { return future { EnqueueChapterDownloadsPayload( clientMutationId = clientMutationId, - downloadStatus = withTimeout(30.seconds) { - DownloadStatus(DownloadManager.status.first { it.queue.any { it.chapter.id in chapters } }) - } + downloadStatus = + withTimeout(30.seconds) { + DownloadStatus(DownloadManager.status.first { it.queue.any { it.chapter.id in chapters } }) + }, ) } } data class EnqueueChapterDownloadInput( val clientMutationId: String? = null, - val id: Int + val id: Int, ) + data class EnqueueChapterDownloadPayload( val clientMutationId: String?, - val downloadStatus: DownloadStatus + val downloadStatus: DownloadStatus, ) - fun enqueueChapterDownload( - input: EnqueueChapterDownloadInput - ): CompletableFuture { + fun enqueueChapterDownload(input: EnqueueChapterDownloadInput): CompletableFuture { val (clientMutationId, chapter) = input DownloadManager.enqueue(DownloadManager.EnqueueInput(listOf(chapter))) @@ -107,25 +109,25 @@ class DownloadMutation { return future { EnqueueChapterDownloadPayload( clientMutationId = clientMutationId, - downloadStatus = withTimeout(30.seconds) { - DownloadStatus(DownloadManager.status.first { it.queue.any { it.chapter.id == chapter } }) - } + downloadStatus = + withTimeout(30.seconds) { + DownloadStatus(DownloadManager.status.first { it.queue.any { it.chapter.id == chapter } }) + }, ) } } data class DequeueChapterDownloadsInput( val clientMutationId: String? = null, - val ids: List + val ids: List, ) + data class DequeueChapterDownloadsPayload( val clientMutationId: String?, - val downloadStatus: DownloadStatus + val downloadStatus: DownloadStatus, ) - fun dequeueChapterDownloads( - input: DequeueChapterDownloadsInput - ): CompletableFuture { + fun dequeueChapterDownloads(input: DequeueChapterDownloadsInput): CompletableFuture { val (clientMutationId, chapters) = input DownloadManager.dequeue(DownloadManager.EnqueueInput(chapters)) @@ -133,25 +135,25 @@ class DownloadMutation { return future { DequeueChapterDownloadsPayload( clientMutationId = clientMutationId, - downloadStatus = withTimeout(30.seconds) { - DownloadStatus(DownloadManager.status.first { it.queue.none { it.chapter.id in chapters } }) - } + downloadStatus = + withTimeout(30.seconds) { + DownloadStatus(DownloadManager.status.first { it.queue.none { it.chapter.id in chapters } }) + }, ) } } data class DequeueChapterDownloadInput( val clientMutationId: String? = null, - val id: Int + val id: Int, ) + data class DequeueChapterDownloadPayload( val clientMutationId: String?, - val downloadStatus: DownloadStatus + val downloadStatus: DownloadStatus, ) - fun dequeueChapterDownload( - input: DequeueChapterDownloadInput - ): CompletableFuture { + fun dequeueChapterDownload(input: DequeueChapterDownloadInput): CompletableFuture { val (clientMutationId, chapter) = input DownloadManager.dequeue(DownloadManager.EnqueueInput(listOf(chapter))) @@ -159,19 +161,21 @@ class DownloadMutation { return future { DequeueChapterDownloadPayload( clientMutationId = clientMutationId, - downloadStatus = withTimeout(30.seconds) { - DownloadStatus(DownloadManager.status.first { it.queue.none { it.chapter.id == chapter } }) - } + downloadStatus = + withTimeout(30.seconds) { + DownloadStatus(DownloadManager.status.first { it.queue.none { it.chapter.id == chapter } }) + }, ) } } data class StartDownloaderInput( - val clientMutationId: String? = null + val clientMutationId: String? = null, ) + data class StartDownloaderPayload( val clientMutationId: String?, - val downloadStatus: DownloadStatus + val downloadStatus: DownloadStatus, ) fun startDownloader(input: StartDownloaderInput): CompletableFuture { @@ -180,21 +184,23 @@ class DownloadMutation { return future { StartDownloaderPayload( input.clientMutationId, - downloadStatus = withTimeout(30.seconds) { - DownloadStatus( - DownloadManager.status.first { it.status == Status.Started } - ) - } + downloadStatus = + withTimeout(30.seconds) { + DownloadStatus( + DownloadManager.status.first { it.status == Status.Started }, + ) + }, ) } } data class StopDownloaderInput( - val clientMutationId: String? = null + val clientMutationId: String? = null, ) + data class StopDownloaderPayload( val clientMutationId: String?, - val downloadStatus: DownloadStatus + val downloadStatus: DownloadStatus, ) fun stopDownloader(input: StopDownloaderInput): CompletableFuture { @@ -202,21 +208,23 @@ class DownloadMutation { DownloadManager.stop() StopDownloaderPayload( input.clientMutationId, - downloadStatus = withTimeout(30.seconds) { - DownloadStatus( - DownloadManager.status.first { it.status == Status.Stopped } - ) - } + downloadStatus = + withTimeout(30.seconds) { + DownloadStatus( + DownloadManager.status.first { it.status == Status.Stopped }, + ) + }, ) } } data class ClearDownloaderInput( - val clientMutationId: String? = null + val clientMutationId: String? = null, ) + data class ClearDownloaderPayload( val clientMutationId: String?, - val downloadStatus: DownloadStatus + val downloadStatus: DownloadStatus, ) fun clearDownloader(input: ClearDownloaderInput): CompletableFuture { @@ -224,11 +232,12 @@ class DownloadMutation { DownloadManager.clear() ClearDownloaderPayload( input.clientMutationId, - downloadStatus = withTimeout(30.seconds) { - DownloadStatus( - DownloadManager.status.first { it.status == Status.Stopped && it.queue.isEmpty() } - ) - } + downloadStatus = + withTimeout(30.seconds) { + DownloadStatus( + DownloadManager.status.first { it.status == Status.Stopped && it.queue.isEmpty() }, + ) + }, ) } } @@ -236,11 +245,12 @@ class DownloadMutation { data class ReorderChapterDownloadInput( val clientMutationId: String? = null, val chapterId: Int, - val to: Int + val to: Int, ) + data class ReorderChapterDownloadPayload( val clientMutationId: String?, - val downloadStatus: DownloadStatus + val downloadStatus: DownloadStatus, ) fun reorderChapterDownload(input: ReorderChapterDownloadInput): CompletableFuture { @@ -250,11 +260,12 @@ class DownloadMutation { return future { ReorderChapterDownloadPayload( clientMutationId, - downloadStatus = withTimeout(30.seconds) { - DownloadStatus( - DownloadManager.status.first { it.queue.indexOfFirst { it.chapter.id == chapter } <= to } - ) - } + downloadStatus = + withTimeout(30.seconds) { + DownloadStatus( + DownloadManager.status.first { it.queue.indexOfFirst { it.chapter.id == chapter } <= to }, + ) + }, ) } } @@ -262,7 +273,7 @@ class DownloadMutation { data class DownloadAheadInput( val clientMutationId: String? = null, val mangaIds: List = emptyList(), - val latestReadChapterIds: List? = null + val latestReadChapterIds: List? = null, ) data class DownloadAheadPayload(val clientMutationId: String?) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/ExtensionMutation.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/ExtensionMutation.kt index b993b38a1..15c874712 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/ExtensionMutation.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/ExtensionMutation.kt @@ -15,34 +15,40 @@ class ExtensionMutation { data class UpdateExtensionPatch( val install: Boolean? = null, val update: Boolean? = null, - val uninstall: Boolean? = null + val uninstall: Boolean? = null, ) data class UpdateExtensionPayload( val clientMutationId: String?, - val extension: ExtensionType + val extension: ExtensionType, ) + data class UpdateExtensionInput( val clientMutationId: String? = null, val id: String, - val patch: UpdateExtensionPatch + val patch: UpdateExtensionPatch, ) data class UpdateExtensionsPayload( val clientMutationId: String?, - val extensions: List + val extensions: List, ) + data class UpdateExtensionsInput( val clientMutationId: String? = null, val ids: List, - val patch: UpdateExtensionPatch + val patch: UpdateExtensionPatch, ) - private suspend fun updateExtensions(ids: List, patch: UpdateExtensionPatch) { - val extensions = transaction { - ExtensionTable.select { ExtensionTable.pkgName inList ids } - .map { ExtensionType(it) } - } + private suspend fun updateExtensions( + ids: List, + patch: UpdateExtensionPatch, + ) { + val extensions = + transaction { + ExtensionTable.select { ExtensionTable.pkgName inList ids } + .map { ExtensionType(it) } + } if (patch.update == true) { extensions.filter { it.hasUpdate }.forEach { @@ -69,13 +75,14 @@ class ExtensionMutation { return future { updateExtensions(listOf(id), patch) }.thenApply { - val extension = transaction { - ExtensionType(ExtensionTable.select { ExtensionTable.pkgName eq id }.first()) - } + val extension = + transaction { + ExtensionType(ExtensionTable.select { ExtensionTable.pkgName eq id }.first()) + } UpdateExtensionPayload( clientMutationId = clientMutationId, - extension = extension + extension = extension, ) } } @@ -86,54 +93,55 @@ class ExtensionMutation { return future { updateExtensions(ids, patch) }.thenApply { - val extensions = transaction { - ExtensionTable.select { ExtensionTable.pkgName inList ids } - .map { ExtensionType(it) } - } + val extensions = + transaction { + ExtensionTable.select { ExtensionTable.pkgName inList ids } + .map { ExtensionType(it) } + } UpdateExtensionsPayload( clientMutationId = clientMutationId, - extensions = extensions + extensions = extensions, ) } } data class FetchExtensionsInput( - val clientMutationId: String? = null + val clientMutationId: String? = null, ) + data class FetchExtensionsPayload( val clientMutationId: String?, - val extensions: List + val extensions: List, ) - fun fetchExtensions( - input: FetchExtensionsInput - ): CompletableFuture { + fun fetchExtensions(input: FetchExtensionsInput): CompletableFuture { val (clientMutationId) = input return future { ExtensionsList.fetchExtensions() }.thenApply { - val extensions = transaction { - ExtensionTable.select { ExtensionTable.name neq LocalSource.EXTENSION_NAME } - .map { ExtensionType(it) } - } + val extensions = + transaction { + ExtensionTable.select { ExtensionTable.name neq LocalSource.EXTENSION_NAME } + .map { ExtensionType(it) } + } FetchExtensionsPayload( clientMutationId = clientMutationId, - extensions = extensions + extensions = extensions, ) } } data class InstallExternalExtensionInput( val clientMutationId: String? = null, - val extensionFile: UploadedFile + val extensionFile: UploadedFile, ) data class InstallExternalExtensionPayload( val clientMutationId: String?, - val extension: ExtensionType + val extension: ExtensionType, ) fun installExternalExtension(input: InstallExternalExtensionInput): CompletableFuture { @@ -146,7 +154,7 @@ class ExtensionMutation { InstallExternalExtensionPayload( clientMutationId, - extension = ExtensionType(dbExtension) + extension = ExtensionType(dbExtension), ) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/InfoMutation.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/InfoMutation.kt index f484cb130..17214936b 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/InfoMutation.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/InfoMutation.kt @@ -14,12 +14,12 @@ import kotlin.time.Duration.Companion.seconds class InfoMutation { data class WebUIUpdateInput( - val clientMutationId: String? = null + val clientMutationId: String? = null, ) data class WebUIUpdatePayload( val clientMutationId: String?, - val updateStatus: WebUIUpdateStatus + val updateStatus: WebUIUpdateStatus, ) fun updateWebUI(input: WebUIUpdateInput): CompletableFuture { @@ -35,14 +35,15 @@ class InfoMutation { return@withTimeout WebUIUpdatePayload( input.clientMutationId, WebUIUpdateStatus( - info = WebUIUpdateInfo( - channel = serverConfig.webUIChannel.value, - tag = version, - updateAvailable - ), + info = + WebUIUpdateInfo( + channel = serverConfig.webUIChannel.value, + tag = version, + updateAvailable, + ), state = STOPPED, - progress = 0 - ) + progress = 0, + ), ) } try { @@ -53,7 +54,7 @@ class InfoMutation { WebUIUpdatePayload( input.clientMutationId, - updateStatus = WebInterfaceManager.status.first { it.state == DOWNLOADING } + updateStatus = WebInterfaceManager.status.first { it.state == DOWNLOADING }, ) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/MangaMutation.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/MangaMutation.kt index 8c6f21b38..16895ea06 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/MangaMutation.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/MangaMutation.kt @@ -22,30 +22,35 @@ import java.util.concurrent.CompletableFuture */ class MangaMutation { data class UpdateMangaPatch( - val inLibrary: Boolean? = null + val inLibrary: Boolean? = null, ) data class UpdateMangaPayload( val clientMutationId: String?, - val manga: MangaType + val manga: MangaType, ) + data class UpdateMangaInput( val clientMutationId: String? = null, val id: Int, - val patch: UpdateMangaPatch + val patch: UpdateMangaPatch, ) data class UpdateMangasPayload( val clientMutationId: String?, - val mangas: List + val mangas: List, ) + data class UpdateMangasInput( val clientMutationId: String? = null, val ids: List, - val patch: UpdateMangaPatch + val patch: UpdateMangaPatch, ) - private suspend fun updateMangas(ids: List, patch: UpdateMangaPatch) { + private suspend fun updateMangas( + ids: List, + patch: UpdateMangaPatch, + ) { transaction { if (patch.inLibrary != null) { MangaTable.update({ MangaTable.id inList ids }) { update -> @@ -69,13 +74,14 @@ class MangaMutation { return future { updateMangas(listOf(id), patch) }.thenApply { - val manga = transaction { - MangaType(MangaTable.select { MangaTable.id eq id }.first()) - } + val manga = + transaction { + MangaType(MangaTable.select { MangaTable.id eq id }.first()) + } UpdateMangaPayload( clientMutationId = clientMutationId, - manga = manga + manga = manga, ) } } @@ -86,55 +92,56 @@ class MangaMutation { return future { updateMangas(ids, patch) }.thenApply { - val mangas = transaction { - MangaTable.select { MangaTable.id inList ids }.map { MangaType(it) } - } + val mangas = + transaction { + MangaTable.select { MangaTable.id inList ids }.map { MangaType(it) } + } UpdateMangasPayload( clientMutationId = clientMutationId, - mangas = mangas + mangas = mangas, ) } } data class FetchMangaInput( val clientMutationId: String? = null, - val id: Int + val id: Int, ) + data class FetchMangaPayload( val clientMutationId: String?, - val manga: MangaType + val manga: MangaType, ) - fun fetchManga( - input: FetchMangaInput - ): CompletableFuture { + fun fetchManga(input: FetchMangaInput): CompletableFuture { val (clientMutationId, id) = input return future { Manga.fetchManga(id) }.thenApply { - val manga = transaction { - MangaTable.select { MangaTable.id eq id }.first() - } + val manga = + transaction { + MangaTable.select { MangaTable.id eq id }.first() + } FetchMangaPayload( clientMutationId = clientMutationId, - manga = MangaType(manga) + manga = MangaType(manga), ) } } data class SetMangaMetaInput( val clientMutationId: String? = null, - val meta: MangaMetaType + val meta: MangaMetaType, ) + data class SetMangaMetaPayload( val clientMutationId: String?, - val meta: MangaMetaType + val meta: MangaMetaType, ) - fun setMangaMeta( - input: SetMangaMetaInput - ): SetMangaMetaPayload { + + fun setMangaMeta(input: SetMangaMetaInput): SetMangaMetaPayload { val (clientMutationId, meta) = input Manga.modifyMangaMeta(meta.mangaId, meta.key, meta.value) @@ -145,34 +152,37 @@ class MangaMutation { data class DeleteMangaMetaInput( val clientMutationId: String? = null, val mangaId: Int, - val key: String + val key: String, ) + data class DeleteMangaMetaPayload( val clientMutationId: String?, val meta: MangaMetaType?, - val manga: MangaType + val manga: MangaType, ) - fun deleteMangaMeta( - input: DeleteMangaMetaInput - ): DeleteMangaMetaPayload { + + fun deleteMangaMeta(input: DeleteMangaMetaInput): DeleteMangaMetaPayload { val (clientMutationId, mangaId, key) = input - val (meta, manga) = transaction { - val meta = MangaMetaTable.select { (MangaMetaTable.ref eq mangaId) and (MangaMetaTable.key eq key) } - .firstOrNull() + val (meta, manga) = + transaction { + val meta = + MangaMetaTable.select { (MangaMetaTable.ref eq mangaId) and (MangaMetaTable.key eq key) } + .firstOrNull() - MangaMetaTable.deleteWhere { (MangaMetaTable.ref eq mangaId) and (MangaMetaTable.key eq key) } + MangaMetaTable.deleteWhere { (MangaMetaTable.ref eq mangaId) and (MangaMetaTable.key eq key) } - val manga = transaction { - MangaType(MangaTable.select { MangaTable.id eq mangaId }.first()) - } + val manga = + transaction { + MangaType(MangaTable.select { MangaTable.id eq mangaId }.first()) + } - if (meta != null) { - MangaMetaType(meta) - } else { - null - } to manga - } + if (meta != null) { + MangaMetaType(meta) + } else { + null + } to manga + } return DeleteMangaMetaPayload(clientMutationId, meta, manga) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/MetaMutation.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/MetaMutation.kt index 57c4238bf..dbe1c96ac 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/MetaMutation.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/MetaMutation.kt @@ -9,18 +9,17 @@ import suwayomi.tachidesk.global.model.table.GlobalMetaTable import suwayomi.tachidesk.graphql.types.GlobalMetaType class MetaMutation { - data class SetGlobalMetaInput( val clientMutationId: String? = null, - val meta: GlobalMetaType + val meta: GlobalMetaType, ) + data class SetGlobalMetaPayload( val clientMutationId: String?, - val meta: GlobalMetaType + val meta: GlobalMetaType, ) - fun setGlobalMeta( - input: SetGlobalMetaInput - ): SetGlobalMetaPayload { + + fun setGlobalMeta(input: SetGlobalMetaInput): SetGlobalMetaPayload { val (clientMutationId, meta) = input GlobalMeta.modifyMeta(meta.key, meta.value) @@ -30,29 +29,31 @@ class MetaMutation { data class DeleteGlobalMetaInput( val clientMutationId: String? = null, - val key: String + val key: String, ) + data class DeleteGlobalMetaPayload( val clientMutationId: String?, - val meta: GlobalMetaType? + val meta: GlobalMetaType?, ) - fun deleteGlobalMeta( - input: DeleteGlobalMetaInput - ): DeleteGlobalMetaPayload { + + fun deleteGlobalMeta(input: DeleteGlobalMetaInput): DeleteGlobalMetaPayload { val (clientMutationId, key) = input - val meta = transaction { - val meta = GlobalMetaTable.select { GlobalMetaTable.key eq key } - .firstOrNull() + val meta = + transaction { + val meta = + GlobalMetaTable.select { GlobalMetaTable.key eq key } + .firstOrNull() - GlobalMetaTable.deleteWhere { GlobalMetaTable.key eq key } + GlobalMetaTable.deleteWhere { GlobalMetaTable.key eq key } - if (meta != null) { - GlobalMetaType(meta) - } else { - null + if (meta != null) { + GlobalMetaType(meta) + } else { + null + } } - } return DeleteGlobalMetaPayload(clientMutationId, meta) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/SettingsMutation.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/SettingsMutation.kt index a398c51b0..8c3fa9fd3 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/SettingsMutation.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/SettingsMutation.kt @@ -11,53 +11,107 @@ import xyz.nulldev.ts.config.GlobalConfigManager class SettingsMutation { data class SetSettingsInput( val clientMutationId: String? = null, - val settings: PartialSettingsType + val settings: PartialSettingsType, ) data class SetSettingsPayload( val clientMutationId: String?, - val settings: SettingsType + val settings: SettingsType, ) private fun updateSettings(settings: Settings) { if (settings.ip != null) serverConfig.ip.value = settings.ip!! if (settings.port != null) serverConfig.port.value = settings.port!! - if (settings.socksProxyEnabled != null) serverConfig.socksProxyEnabled.value = settings.socksProxyEnabled!! - if (settings.socksProxyHost != null) serverConfig.socksProxyHost.value = settings.socksProxyHost!! - if (settings.socksProxyPort != null) serverConfig.socksProxyPort.value = settings.socksProxyPort!! - - if (settings.webUIFlavor != null) serverConfig.webUIFlavor.value = settings.webUIFlavor!!.uiName - if (settings.initialOpenInBrowserEnabled != null) serverConfig.initialOpenInBrowserEnabled.value = settings.initialOpenInBrowserEnabled!! - if (settings.webUIInterface != null) serverConfig.webUIInterface.value = settings.webUIInterface!!.name.lowercase() - if (settings.electronPath != null) serverConfig.electronPath.value = settings.electronPath!! - if (settings.webUIChannel != null) serverConfig.webUIChannel.value = settings.webUIChannel!!.name.lowercase() - if (settings.webUIUpdateCheckInterval != null) serverConfig.webUIUpdateCheckInterval.value = settings.webUIUpdateCheckInterval!! - - if (settings.downloadAsCbz != null) serverConfig.downloadAsCbz.value = settings.downloadAsCbz!! - if (settings.downloadsPath != null) serverConfig.downloadsPath.value = settings.downloadsPath!! - if (settings.autoDownloadNewChapters != null) serverConfig.autoDownloadNewChapters.value = settings.autoDownloadNewChapters!! - - if (settings.maxSourcesInParallel != null) serverConfig.maxSourcesInParallel.value = settings.maxSourcesInParallel!! - - if (settings.excludeUnreadChapters != null) serverConfig.excludeUnreadChapters.value = settings.excludeUnreadChapters!! - if (settings.excludeNotStarted != null) serverConfig.excludeNotStarted.value = settings.excludeNotStarted!! - if (settings.excludeCompleted != null) serverConfig.excludeCompleted.value = settings.excludeCompleted!! - if (settings.globalUpdateInterval != null) serverConfig.globalUpdateInterval.value = settings.globalUpdateInterval!! - - if (settings.basicAuthEnabled != null) serverConfig.basicAuthEnabled.value = settings.basicAuthEnabled!! - if (settings.basicAuthUsername != null) serverConfig.basicAuthUsername.value = settings.basicAuthUsername!! - if (settings.basicAuthPassword != null) serverConfig.basicAuthPassword.value = settings.basicAuthPassword!! - - if (settings.debugLogsEnabled != null) serverConfig.debugLogsEnabled.value = settings.debugLogsEnabled!! - if (settings.systemTrayEnabled != null) serverConfig.systemTrayEnabled.value = settings.systemTrayEnabled!! - - if (settings.backupPath != null) serverConfig.backupPath.value = settings.backupPath!! - if (settings.backupTime != null) serverConfig.backupTime.value = settings.backupTime!! - if (settings.backupInterval != null) serverConfig.backupInterval.value = settings.backupInterval!! - if (settings.backupTTL != null) serverConfig.backupTTL.value = settings.backupTTL!! - - if (settings.localSourcePath != null) serverConfig.localSourcePath.value = settings.localSourcePath!! + if (settings.socksProxyEnabled != null) { + serverConfig.socksProxyEnabled.value = settings.socksProxyEnabled!! + } + if (settings.socksProxyHost != null) { + serverConfig.socksProxyHost.value = settings.socksProxyHost!! + } + if (settings.socksProxyPort != null) { + serverConfig.socksProxyPort.value = settings.socksProxyPort!! + } + + if (settings.webUIFlavor != null) { + serverConfig.webUIFlavor.value = settings.webUIFlavor!!.uiName + } + if (settings.initialOpenInBrowserEnabled != null) { + serverConfig.initialOpenInBrowserEnabled.value = settings.initialOpenInBrowserEnabled!! + } + if (settings.webUIInterface != null) { + serverConfig.webUIInterface.value = settings.webUIInterface!!.name.lowercase() + } + if (settings.electronPath != null) { + serverConfig.electronPath.value = settings.electronPath!! + } + if (settings.webUIChannel != null) { + serverConfig.webUIChannel.value = settings.webUIChannel!!.name.lowercase() + } + if (settings.webUIUpdateCheckInterval != null) { + serverConfig.webUIUpdateCheckInterval.value = settings.webUIUpdateCheckInterval!! + } + + if (settings.downloadAsCbz != null) { + serverConfig.downloadAsCbz.value = settings.downloadAsCbz!! + } + if (settings.downloadsPath != null) { + serverConfig.downloadsPath.value = settings.downloadsPath!! + } + if (settings.autoDownloadNewChapters != null) { + serverConfig.autoDownloadNewChapters.value = settings.autoDownloadNewChapters!! + } + + if (settings.maxSourcesInParallel != null) { + serverConfig.maxSourcesInParallel.value = settings.maxSourcesInParallel!! + } + + if (settings.excludeUnreadChapters != null) { + serverConfig.excludeUnreadChapters.value = settings.excludeUnreadChapters!! + } + if (settings.excludeNotStarted != null) { + serverConfig.excludeNotStarted.value = settings.excludeNotStarted!! + } + if (settings.excludeCompleted != null) { + serverConfig.excludeCompleted.value = settings.excludeCompleted!! + } + if (settings.globalUpdateInterval != null) { + serverConfig.globalUpdateInterval.value = settings.globalUpdateInterval!! + } + + if (settings.basicAuthEnabled != null) { + serverConfig.basicAuthEnabled.value = settings.basicAuthEnabled!! + } + if (settings.basicAuthUsername != null) { + serverConfig.basicAuthUsername.value = settings.basicAuthUsername!! + } + if (settings.basicAuthPassword != null) { + serverConfig.basicAuthPassword.value = settings.basicAuthPassword!! + } + + if (settings.debugLogsEnabled != null) { + serverConfig.debugLogsEnabled.value = settings.debugLogsEnabled!! + } + if (settings.systemTrayEnabled != null) { + serverConfig.systemTrayEnabled.value = settings.systemTrayEnabled!! + } + + if (settings.backupPath != null) { + serverConfig.backupPath.value = settings.backupPath!! + } + if (settings.backupTime != null) { + serverConfig.backupTime.value = settings.backupTime!! + } + if (settings.backupInterval != null) { + serverConfig.backupInterval.value = settings.backupInterval!! + } + if (settings.backupTTL != null) { + serverConfig.backupTTL.value = settings.backupTTL!! + } + + if (settings.localSourcePath != null) { + serverConfig.localSourcePath.value = settings.localSourcePath!! + } } fun setSettings(input: SetSettingsInput): SetSettingsPayload { @@ -72,7 +126,7 @@ class SettingsMutation { data class ResetSettingsPayload( val clientMutationId: String?, - val settings: SettingsType + val settings: SettingsType, ) fun resetSettings(input: ResetSettingsInput): ResetSettingsPayload { diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/SourceMutation.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/SourceMutation.kt index a4811337a..a7911b8c3 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/SourceMutation.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/SourceMutation.kt @@ -20,63 +20,64 @@ import suwayomi.tachidesk.server.JavalinSetup.future import java.util.concurrent.CompletableFuture class SourceMutation { - enum class FetchSourceMangaType { SEARCH, POPULAR, - LATEST + LATEST, } + data class FetchSourceMangaInput( val clientMutationId: String? = null, val source: Long, val type: FetchSourceMangaType, val page: Int, val query: String? = null, - val filters: List? = null + val filters: List? = null, ) + data class FetchSourceMangaPayload( val clientMutationId: String?, val mangas: List, - val hasNextPage: Boolean + val hasNextPage: Boolean, ) - fun fetchSourceManga( - input: FetchSourceMangaInput - ): CompletableFuture { + fun fetchSourceManga(input: FetchSourceMangaInput): CompletableFuture { val (clientMutationId, sourceId, type, page, query, filters) = input return future { val source = GetCatalogueSource.getCatalogueSourceOrNull(sourceId)!! - val mangasPage = when (type) { - FetchSourceMangaType.SEARCH -> { - source.getSearchManga( - page = page, - query = query.orEmpty(), - filters = updateFilterList(source, filters) - ) - } - FetchSourceMangaType.POPULAR -> { - source.getPopularManga(page) + val mangasPage = + when (type) { + FetchSourceMangaType.SEARCH -> { + source.getSearchManga( + page = page, + query = query.orEmpty(), + filters = updateFilterList(source, filters), + ) + } + FetchSourceMangaType.POPULAR -> { + source.getPopularManga(page) + } + FetchSourceMangaType.LATEST -> { + if (!source.supportsLatest) throw Exception("Source does not support latest") + source.getLatestUpdates(page) + } } - FetchSourceMangaType.LATEST -> { - if (!source.supportsLatest) throw Exception("Source does not support latest") - source.getLatestUpdates(page) - } - } val mangaIds = mangasPage.insertOrGet(sourceId) - val mangas = transaction { - MangaTable.select { MangaTable.id inList mangaIds } - .map { MangaType(it) } - }.sortedBy { - mangaIds.indexOf(it.id) - } + val mangas = + transaction { + MangaTable.select { MangaTable.id inList mangaIds } + .map { MangaType(it) } + }.sortedBy { + mangaIds.indexOf(it.id) + } FetchSourceMangaPayload( clientMutationId = clientMutationId, mangas = mangas, - hasNextPage = mangasPage.hasNextPage + hasNextPage = mangasPage.hasNextPage, ) } } @@ -87,21 +88,21 @@ class SourceMutation { val checkBoxState: Boolean? = null, val editTextState: String? = null, val listState: String? = null, - val multiSelectState: List? = null + val multiSelectState: List? = null, ) + data class UpdateSourcePreferenceInput( val clientMutationId: String? = null, val source: Long, - val change: SourcePreferenceChange + val change: SourcePreferenceChange, ) + data class UpdateSourcePreferencePayload( val clientMutationId: String?, - val preferences: List + val preferences: List, ) - fun updateSourcePreference( - input: UpdateSourcePreferenceInput - ): UpdateSourcePreferencePayload { + fun updateSourcePreference(input: UpdateSourcePreferenceInput): UpdateSourcePreferencePayload { val (clientMutationId, sourceId, change) = input Source.setSourcePreference(sourceId, change.position, "") { preference -> @@ -117,7 +118,7 @@ class SourceMutation { return UpdateSourcePreferencePayload( clientMutationId = clientMutationId, - preferences = Source.getSourcePreferencesRaw(sourceId).map { preferenceOf(it) } + preferences = Source.getSourcePreferencesRaw(sourceId).map { preferenceOf(it) }, ) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/UpdateMutation.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/UpdateMutation.kt index 7a626c3da..b7ce3eaa7 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/UpdateMutation.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/UpdateMutation.kt @@ -15,18 +15,19 @@ class UpdateMutation { private val updater by DI.global.instance() data class UpdateLibraryMangaInput( - val clientMutationId: String? = null + val clientMutationId: String? = null, ) + data class UpdateLibraryMangaPayload( val clientMutationId: String?, - val updateStatus: UpdateStatus + val updateStatus: UpdateStatus, ) fun updateLibraryManga(input: UpdateLibraryMangaInput): UpdateLibraryMangaPayload { updater.addCategoriesToUpdateQueue( Category.getCategoryList(), clear = true, - forceAll = false + forceAll = false, ) return UpdateLibraryMangaPayload(input.clientMutationId, UpdateStatus(updater.status.value)) @@ -34,32 +35,35 @@ class UpdateMutation { data class UpdateCategoryMangaInput( val clientMutationId: String? = null, - val categories: List + val categories: List, ) + data class UpdateCategoryMangaPayload( val clientMutationId: String?, - val updateStatus: UpdateStatus + val updateStatus: UpdateStatus, ) fun updateCategoryManga(input: UpdateCategoryMangaInput): UpdateCategoryMangaPayload { - val categories = transaction { - CategoryTable.select { CategoryTable.id inList input.categories }.map { - CategoryTable.toDataClass(it) + val categories = + transaction { + CategoryTable.select { CategoryTable.id inList input.categories }.map { + CategoryTable.toDataClass(it) + } } - } updater.addCategoriesToUpdateQueue(categories, clear = true, forceAll = true) return UpdateCategoryMangaPayload( clientMutationId = input.clientMutationId, - updateStatus = UpdateStatus(updater.status.value) + updateStatus = UpdateStatus(updater.status.value), ) } data class UpdateStopInput( - val clientMutationId: String? = null + val clientMutationId: String? = null, ) + data class UpdateStopPayload( - val clientMutationId: String? + val clientMutationId: String?, ) fun updateStop(input: UpdateStopInput): UpdateStopPayload { diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/BackupQuery.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/BackupQuery.kt index f5fa35078..e5aa5f10a 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/BackupQuery.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/BackupQuery.kt @@ -8,21 +8,22 @@ import suwayomi.tachidesk.manga.impl.backup.proto.ProtoBackupValidator class BackupQuery { data class ValidateBackupInput( - val backup: UploadedFile + val backup: UploadedFile, ) + data class ValidateBackupSource( val id: Long, - val name: String + val name: String, ) + data class ValidateBackupResult( - val missingSources: List + val missingSources: List, ) - fun validateBackup( - input: ValidateBackupInput - ): ValidateBackupResult { + + fun validateBackup(input: ValidateBackupInput): ValidateBackupResult { val result = ProtoBackupValidator.validate(input.backup.content) return ValidateBackupResult( - result.missingSourceIds.map { ValidateBackupSource(it.first, it.second) } + result.missingSourceIds.map { ValidateBackupSource(it.first, it.second) }, ) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/CategoryQuery.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/CategoryQuery.kt index 61d5521e8..9d6a393c2 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/CategoryQuery.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/CategoryQuery.kt @@ -46,14 +46,18 @@ import suwayomi.tachidesk.manga.model.table.CategoryTable import java.util.concurrent.CompletableFuture class CategoryQuery { - fun category(dataFetchingEnvironment: DataFetchingEnvironment, id: Int): CompletableFuture { + fun category( + dataFetchingEnvironment: DataFetchingEnvironment, + id: Int, + ): CompletableFuture { return dataFetchingEnvironment.getValueFromDataLoader("CategoryDataLoader", id) } enum class CategoryOrderBy(override val column: Column>) : OrderBy { ID(CategoryTable.id), NAME(CategoryTable.name), - ORDER(CategoryTable.order); + ORDER(CategoryTable.order), + ; override fun greater(cursor: Cursor): Op { return when (this) { @@ -72,11 +76,12 @@ class CategoryQuery { } override fun asCursor(type: CategoryType): Cursor { - val value = when (this) { - ID -> type.id.toString() - NAME -> type.id.toString() + "-" + type.name - ORDER -> type.id.toString() + "-" + type.order - } + val value = + when (this) { + ID -> type.id.toString() + NAME -> type.id.toString() + "-" + type.name + ORDER -> type.id.toString() + "-" + type.order + } return Cursor(value) } } @@ -85,7 +90,7 @@ class CategoryQuery { val id: Int? = null, val order: Int? = null, val name: String? = null, - val default: Boolean? = null + val default: Boolean? = null, ) : HasGetOp { override fun getOp(): Op? { val opAnd = OpAnd() @@ -105,14 +110,14 @@ class CategoryQuery { val default: BooleanFilter? = null, override val and: List? = null, override val or: List? = null, - override val not: CategoryFilter? = null + override val not: CategoryFilter? = null, ) : Filter { override fun getOpList(): List> { return listOfNotNull( andFilterWithCompareEntity(CategoryTable.id, id), andFilterWithCompare(CategoryTable.order, order), andFilterWithCompareString(CategoryTable.name, name), - andFilterWithCompare(CategoryTable.isDefault, default) + andFilterWithCompare(CategoryTable.isDefault, default), ) } } @@ -126,55 +131,56 @@ class CategoryQuery { after: Cursor? = null, first: Int? = null, last: Int? = null, - offset: Int? = null + offset: Int? = null, ): CategoryNodeList { - val queryResults = transaction { - val res = CategoryTable.selectAll() - - res.applyOps(condition, filter) - - if (orderBy != null || (last != null || before != null)) { - val orderByColumn = orderBy?.column ?: CategoryTable.id - val orderType = orderByType.maybeSwap(last ?: before) - - if (orderBy == CategoryOrderBy.ID || orderBy == null) { - res.orderBy(orderByColumn to orderType) - } else { - res.orderBy( - orderByColumn to orderType, - CategoryTable.id to SortOrder.ASC - ) + val queryResults = + transaction { + val res = CategoryTable.selectAll() + + res.applyOps(condition, filter) + + if (orderBy != null || (last != null || before != null)) { + val orderByColumn = orderBy?.column ?: CategoryTable.id + val orderType = orderByType.maybeSwap(last ?: before) + + if (orderBy == CategoryOrderBy.ID || orderBy == null) { + res.orderBy(orderByColumn to orderType) + } else { + res.orderBy( + orderByColumn to orderType, + CategoryTable.id to SortOrder.ASC, + ) + } } - } - val total = res.count() - val firstResult = res.firstOrNull()?.get(CategoryTable.id)?.value - val lastResult = res.lastOrNull()?.get(CategoryTable.id)?.value + val total = res.count() + val firstResult = res.firstOrNull()?.get(CategoryTable.id)?.value + val lastResult = res.lastOrNull()?.get(CategoryTable.id)?.value - if (after != null) { - res.andWhere { - when (orderByType) { - DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: CategoryOrderBy.ID).less(after) - null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: CategoryOrderBy.ID).greater(after) + if (after != null) { + res.andWhere { + when (orderByType) { + DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: CategoryOrderBy.ID).less(after) + null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: CategoryOrderBy.ID).greater(after) + } } - } - } else if (before != null) { - res.andWhere { - when (orderByType) { - DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: CategoryOrderBy.ID).greater(before) - null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: CategoryOrderBy.ID).less(before) + } else if (before != null) { + res.andWhere { + when (orderByType) { + DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: CategoryOrderBy.ID).greater(before) + null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: CategoryOrderBy.ID).less(before) + } } } - } - if (first != null) { - res.limit(first, offset?.toLong() ?: 0) - } else if (last != null) { - res.limit(last) - } + if (first != null) { + res.limit(first, offset?.toLong() ?: 0) + } else if (last != null) { + res.limit(last) + } - QueryResults(total, firstResult, lastResult, res.toList()) - } + QueryResults(total, firstResult, lastResult, res.toList()) + } val getAsCursor: (CategoryType) -> Cursor = (orderBy ?: CategoryOrderBy.ID)::asCursor @@ -189,24 +195,25 @@ class CategoryQuery { resultsAsType.firstOrNull()?.let { CategoryNodeList.CategoryEdge( getAsCursor(it), - it + it, ) }, resultsAsType.lastOrNull()?.let { CategoryNodeList.CategoryEdge( getAsCursor(it), - it + it, ) - } + }, ) }, - pageInfo = PageInfo( - hasNextPage = queryResults.lastKey != resultsAsType.lastOrNull()?.id, - hasPreviousPage = queryResults.firstKey != resultsAsType.firstOrNull()?.id, - startCursor = resultsAsType.firstOrNull()?.let { getAsCursor(it) }, - endCursor = resultsAsType.lastOrNull()?.let { getAsCursor(it) } - ), - totalCount = queryResults.total.toInt() + pageInfo = + PageInfo( + hasNextPage = queryResults.lastKey != resultsAsType.lastOrNull()?.id, + hasPreviousPage = queryResults.firstKey != resultsAsType.firstOrNull()?.id, + startCursor = resultsAsType.firstOrNull()?.let { getAsCursor(it) }, + endCursor = resultsAsType.lastOrNull()?.let { getAsCursor(it) }, + ), + totalCount = queryResults.total.toInt(), ) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/ChapterQuery.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/ChapterQuery.kt index b2cc0d468..307746516 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/ChapterQuery.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/ChapterQuery.kt @@ -55,7 +55,10 @@ import java.util.concurrent.CompletableFuture * - Get page list? */ class ChapterQuery { - fun chapter(dataFetchingEnvironment: DataFetchingEnvironment, id: Int): CompletableFuture { + fun chapter( + dataFetchingEnvironment: DataFetchingEnvironment, + id: Int, + ): CompletableFuture { return dataFetchingEnvironment.getValueFromDataLoader("ChapterDataLoader", id) } @@ -66,7 +69,8 @@ class ChapterQuery { UPLOAD_DATE(ChapterTable.date_upload), CHAPTER_NUMBER(ChapterTable.chapter_number), LAST_READ_AT(ChapterTable.lastReadAt), - FETCHED_AT(ChapterTable.fetchedAt); + FETCHED_AT(ChapterTable.fetchedAt), + ; override fun greater(cursor: Cursor): Op { return when (this) { @@ -93,15 +97,16 @@ class ChapterQuery { } override fun asCursor(type: ChapterType): Cursor { - val value = when (this) { - ID -> type.id.toString() - SOURCE_ORDER -> type.id.toString() + "-" + type.sourceOrder - NAME -> type.id.toString() + "-" + type.name - UPLOAD_DATE -> type.id.toString() + "-" + type.uploadDate - CHAPTER_NUMBER -> type.id.toString() + "-" + type.chapterNumber - LAST_READ_AT -> type.id.toString() + "-" + type.lastReadAt - FETCHED_AT -> type.id.toString() + "-" + type.fetchedAt - } + val value = + when (this) { + ID -> type.id.toString() + SOURCE_ORDER -> type.id.toString() + "-" + type.sourceOrder + NAME -> type.id.toString() + "-" + type.name + UPLOAD_DATE -> type.id.toString() + "-" + type.uploadDate + CHAPTER_NUMBER -> type.id.toString() + "-" + type.chapterNumber + LAST_READ_AT -> type.id.toString() + "-" + type.lastReadAt + FETCHED_AT -> type.id.toString() + "-" + type.fetchedAt + } return Cursor(value) } } @@ -122,7 +127,7 @@ class ChapterQuery { val realUrl: String? = null, val fetchedAt: Long? = null, val isDownloaded: Boolean? = null, - val pageCount: Int? = null + val pageCount: Int? = null, ) : HasGetOp { override fun getOp(): Op? { val opAnd = OpAnd() @@ -167,7 +172,7 @@ class ChapterQuery { val inLibrary: BooleanFilter? = null, override val and: List? = null, override val or: List? = null, - override val not: ChapterFilter? = null + override val not: ChapterFilter? = null, ) : Filter { override fun getOpList(): List> { return listOfNotNull( @@ -186,7 +191,7 @@ class ChapterQuery { andFilterWithCompareString(ChapterTable.realUrl, realUrl), andFilterWithCompare(ChapterTable.fetchedAt, fetchedAt), andFilterWithCompare(ChapterTable.isDownloaded, isDownloaded), - andFilterWithCompare(ChapterTable.pageCount, pageCount) + andFilterWithCompare(ChapterTable.pageCount, pageCount), ) } @@ -202,63 +207,64 @@ class ChapterQuery { after: Cursor? = null, first: Int? = null, last: Int? = null, - offset: Int? = null + offset: Int? = null, ): ChapterNodeList { - val queryResults = transaction { - val res = ChapterTable.selectAll() + val queryResults = + transaction { + val res = ChapterTable.selectAll() - val libraryOp = filter?.getLibraryOp() - if (libraryOp != null) { - res.adjustColumnSet { - innerJoin(MangaTable) + val libraryOp = filter?.getLibraryOp() + if (libraryOp != null) { + res.adjustColumnSet { + innerJoin(MangaTable) + } + res.andWhere { libraryOp } } - res.andWhere { libraryOp } - } - res.applyOps(condition, filter) + res.applyOps(condition, filter) - if (orderBy != null || (last != null || before != null)) { - val orderByColumn = orderBy?.column ?: ChapterTable.id - val orderType = orderByType.maybeSwap(last ?: before) + if (orderBy != null || (last != null || before != null)) { + val orderByColumn = orderBy?.column ?: ChapterTable.id + val orderType = orderByType.maybeSwap(last ?: before) - if (orderBy == ChapterOrderBy.ID || orderBy == null) { - res.orderBy(orderByColumn to orderType) - } else { - res.orderBy( - orderByColumn to orderType, - ChapterTable.id to SortOrder.ASC - ) + if (orderBy == ChapterOrderBy.ID || orderBy == null) { + res.orderBy(orderByColumn to orderType) + } else { + res.orderBy( + orderByColumn to orderType, + ChapterTable.id to SortOrder.ASC, + ) + } } - } - val total = res.count() - val firstResult = res.firstOrNull()?.get(ChapterTable.id)?.value - val lastResult = res.lastOrNull()?.get(ChapterTable.id)?.value + val total = res.count() + val firstResult = res.firstOrNull()?.get(ChapterTable.id)?.value + val lastResult = res.lastOrNull()?.get(ChapterTable.id)?.value - if (after != null) { - res.andWhere { - when (orderByType) { - DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: ID).less(after) - null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: ID).greater(after) + if (after != null) { + res.andWhere { + when (orderByType) { + DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: ID).less(after) + null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: ID).greater(after) + } } - } - } else if (before != null) { - res.andWhere { - when (orderByType) { - DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: ID).greater(before) - null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: ID).less(before) + } else if (before != null) { + res.andWhere { + when (orderByType) { + DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: ID).greater(before) + null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: ID).less(before) + } } } - } - if (first != null) { - res.limit(first, offset?.toLong() ?: 0) - } else if (last != null) { - res.limit(last) - } + if (first != null) { + res.limit(first, offset?.toLong() ?: 0) + } else if (last != null) { + res.limit(last) + } - QueryResults(total, firstResult, lastResult, res.toList()) - } + QueryResults(total, firstResult, lastResult, res.toList()) + } val getAsCursor: (ChapterType) -> Cursor = (orderBy ?: ChapterOrderBy.ID)::asCursor @@ -273,24 +279,25 @@ class ChapterQuery { resultsAsType.firstOrNull()?.let { ChapterNodeList.ChapterEdge( getAsCursor(it), - it + it, ) }, resultsAsType.lastOrNull()?.let { ChapterNodeList.ChapterEdge( getAsCursor(it), - it + it, ) - } + }, ) }, - pageInfo = PageInfo( - hasNextPage = queryResults.lastKey != resultsAsType.lastOrNull()?.id, - hasPreviousPage = queryResults.firstKey != resultsAsType.firstOrNull()?.id, - startCursor = resultsAsType.firstOrNull()?.let { getAsCursor(it) }, - endCursor = resultsAsType.lastOrNull()?.let { getAsCursor(it) } - ), - totalCount = queryResults.total.toInt() + pageInfo = + PageInfo( + hasNextPage = queryResults.lastKey != resultsAsType.lastOrNull()?.id, + hasPreviousPage = queryResults.firstKey != resultsAsType.firstOrNull()?.id, + startCursor = resultsAsType.firstOrNull()?.let { getAsCursor(it) }, + endCursor = resultsAsType.lastOrNull()?.let { getAsCursor(it) }, + ), + totalCount = queryResults.total.toInt(), ) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/DownloadQuery.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/DownloadQuery.kt index 3cb75d790..f799a0973 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/DownloadQuery.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/DownloadQuery.kt @@ -7,7 +7,6 @@ import suwayomi.tachidesk.server.JavalinSetup.future import java.util.concurrent.CompletableFuture class DownloadQuery { - fun downloadStatus(): CompletableFuture { return future { DownloadStatus(DownloadManager.status.first()) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/ExtensionQuery.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/ExtensionQuery.kt index 58beae5d3..44141b1c7 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/ExtensionQuery.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/ExtensionQuery.kt @@ -47,14 +47,18 @@ import suwayomi.tachidesk.manga.model.table.ExtensionTable import java.util.concurrent.CompletableFuture class ExtensionQuery { - fun extension(dataFetchingEnvironment: DataFetchingEnvironment, pkgName: String): CompletableFuture { + fun extension( + dataFetchingEnvironment: DataFetchingEnvironment, + pkgName: String, + ): CompletableFuture { return dataFetchingEnvironment.getValueFromDataLoader("ExtensionDataLoader", pkgName) } enum class ExtensionOrderBy(override val column: Column>) : OrderBy { PKG_NAME(ExtensionTable.pkgName), NAME(ExtensionTable.name), - APK_NAME(ExtensionTable.apkName); + APK_NAME(ExtensionTable.apkName), + ; override fun greater(cursor: Cursor): Op { return when (this) { @@ -73,11 +77,12 @@ class ExtensionQuery { } override fun asCursor(type: ExtensionType): Cursor { - val value = when (this) { - PKG_NAME -> type.pkgName - NAME -> type.pkgName + "\\-" + type.name - APK_NAME -> type.pkgName + "\\-" + type.apkName - } + val value = + when (this) { + PKG_NAME -> type.pkgName + NAME -> type.pkgName + "\\-" + type.name + APK_NAME -> type.pkgName + "\\-" + type.apkName + } return Cursor(value) } } @@ -93,7 +98,7 @@ class ExtensionQuery { val isNsfw: Boolean? = null, val isInstalled: Boolean? = null, val hasUpdate: Boolean? = null, - val isObsolete: Boolean? = null + val isObsolete: Boolean? = null, ) : HasGetOp { override fun getOp(): Op? { val opAnd = OpAnd() @@ -126,7 +131,7 @@ class ExtensionQuery { val isObsolete: BooleanFilter? = null, override val and: List? = null, override val or: List? = null, - override val not: ExtensionFilter? = null + override val not: ExtensionFilter? = null, ) : Filter { override fun getOpList(): List> { return listOfNotNull( @@ -140,7 +145,7 @@ class ExtensionQuery { andFilterWithCompare(ExtensionTable.isNsfw, isNsfw), andFilterWithCompare(ExtensionTable.isInstalled, isInstalled), andFilterWithCompare(ExtensionTable.hasUpdate, hasUpdate), - andFilterWithCompare(ExtensionTable.isObsolete, isObsolete) + andFilterWithCompare(ExtensionTable.isObsolete, isObsolete), ) } } @@ -154,57 +159,58 @@ class ExtensionQuery { after: Cursor? = null, first: Int? = null, last: Int? = null, - offset: Int? = null + offset: Int? = null, ): ExtensionNodeList { - val queryResults = transaction { - val res = ExtensionTable.selectAll() + val queryResults = + transaction { + val res = ExtensionTable.selectAll() - res.adjustWhere { ExtensionTable.name neq LocalSource.EXTENSION_NAME } + res.adjustWhere { ExtensionTable.name neq LocalSource.EXTENSION_NAME } - res.applyOps(condition, filter) + res.applyOps(condition, filter) - if (orderBy != null || (last != null || before != null)) { - val orderByColumn = orderBy?.column ?: ExtensionTable.pkgName - val orderType = orderByType.maybeSwap(last ?: before) + if (orderBy != null || (last != null || before != null)) { + val orderByColumn = orderBy?.column ?: ExtensionTable.pkgName + val orderType = orderByType.maybeSwap(last ?: before) - if (orderBy == ExtensionOrderBy.PKG_NAME || orderBy == null) { - res.orderBy(orderByColumn to orderType) - } else { - res.orderBy( - orderByColumn to orderType, - ExtensionTable.pkgName to SortOrder.ASC - ) + if (orderBy == ExtensionOrderBy.PKG_NAME || orderBy == null) { + res.orderBy(orderByColumn to orderType) + } else { + res.orderBy( + orderByColumn to orderType, + ExtensionTable.pkgName to SortOrder.ASC, + ) + } } - } - val total = res.count() - val firstResult = res.firstOrNull()?.get(ExtensionTable.pkgName) - val lastResult = res.lastOrNull()?.get(ExtensionTable.pkgName) + val total = res.count() + val firstResult = res.firstOrNull()?.get(ExtensionTable.pkgName) + val lastResult = res.lastOrNull()?.get(ExtensionTable.pkgName) - if (after != null) { - res.andWhere { - when (orderByType) { - DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: ExtensionOrderBy.PKG_NAME).less(after) - null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: ExtensionOrderBy.PKG_NAME).greater(after) + if (after != null) { + res.andWhere { + when (orderByType) { + DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: ExtensionOrderBy.PKG_NAME).less(after) + null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: ExtensionOrderBy.PKG_NAME).greater(after) + } } - } - } else if (before != null) { - res.andWhere { - when (orderByType) { - DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: ExtensionOrderBy.PKG_NAME).greater(before) - null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: ExtensionOrderBy.PKG_NAME).less(before) + } else if (before != null) { + res.andWhere { + when (orderByType) { + DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: ExtensionOrderBy.PKG_NAME).greater(before) + null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: ExtensionOrderBy.PKG_NAME).less(before) + } } } - } - if (first != null) { - res.limit(first, offset?.toLong() ?: 0) - } else if (last != null) { - res.limit(last) - } + if (first != null) { + res.limit(first, offset?.toLong() ?: 0) + } else if (last != null) { + res.limit(last) + } - QueryResults(total, firstResult, lastResult, res.toList()) - } + QueryResults(total, firstResult, lastResult, res.toList()) + } val getAsCursor: (ExtensionType) -> Cursor = (orderBy ?: ExtensionOrderBy.PKG_NAME)::asCursor @@ -219,24 +225,25 @@ class ExtensionQuery { resultsAsType.firstOrNull()?.let { ExtensionNodeList.ExtensionEdge( getAsCursor(it), - it + it, ) }, resultsAsType.lastOrNull()?.let { ExtensionNodeList.ExtensionEdge( getAsCursor(it), - it + it, ) - } + }, ) }, - pageInfo = PageInfo( - hasNextPage = queryResults.lastKey != resultsAsType.lastOrNull()?.pkgName, - hasPreviousPage = queryResults.firstKey != resultsAsType.firstOrNull()?.pkgName, - startCursor = resultsAsType.firstOrNull()?.let { getAsCursor(it) }, - endCursor = resultsAsType.lastOrNull()?.let { getAsCursor(it) } - ), - totalCount = queryResults.total.toInt() + pageInfo = + PageInfo( + hasNextPage = queryResults.lastKey != resultsAsType.lastOrNull()?.pkgName, + hasPreviousPage = queryResults.firstKey != resultsAsType.firstOrNull()?.pkgName, + startCursor = resultsAsType.firstOrNull()?.let { getAsCursor(it) }, + endCursor = resultsAsType.lastOrNull()?.let { getAsCursor(it) }, + ), + totalCount = queryResults.total.toInt(), ) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/InfoQuery.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/InfoQuery.kt index 45ed95fc3..c2b7dfca0 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/InfoQuery.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/InfoQuery.kt @@ -3,8 +3,8 @@ package suwayomi.tachidesk.graphql.queries import suwayomi.tachidesk.global.impl.AppUpdate import suwayomi.tachidesk.graphql.types.WebUIUpdateInfo import suwayomi.tachidesk.graphql.types.WebUIUpdateStatus -import suwayomi.tachidesk.server.BuildConfig import suwayomi.tachidesk.server.JavalinSetup.future +import suwayomi.tachidesk.server.generated.BuildConfig import suwayomi.tachidesk.server.serverConfig import suwayomi.tachidesk.server.util.WebInterfaceManager import java.util.concurrent.CompletableFuture @@ -17,7 +17,7 @@ class InfoQuery { val buildType: String, val buildTime: Long, val github: String, - val discord: String + val discord: String, ) fun about(): AboutPayload { @@ -28,7 +28,7 @@ class InfoQuery { BuildConfig.BUILD_TYPE, BuildConfig.BUILD_TIME, BuildConfig.GITHUB, - BuildConfig.DISCORD + BuildConfig.DISCORD, ) } @@ -36,7 +36,7 @@ class InfoQuery { /** [channel] mirrors [suwayomi.tachidesk.server.BuildConfig.BUILD_TYPE] */ val channel: String, val tag: String, - val url: String + val url: String, ) fun checkForServerUpdates(): CompletableFuture> { @@ -45,7 +45,7 @@ class InfoQuery { CheckForServerUpdatesPayload( channel = it.channel, tag = it.tag, - url = it.url + url = it.url, ) } } @@ -57,7 +57,7 @@ class InfoQuery { WebUIUpdateInfo( channel = serverConfig.webUIChannel.value, tag = version, - updateAvailable + updateAvailable, ) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/MangaQuery.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/MangaQuery.kt index 1fcbb71a1..af5aa8ad3 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/MangaQuery.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/MangaQuery.kt @@ -49,7 +49,10 @@ import suwayomi.tachidesk.manga.model.table.MangaTable import java.util.concurrent.CompletableFuture class MangaQuery { - fun manga(dataFetchingEnvironment: DataFetchingEnvironment, id: Int): CompletableFuture { + fun manga( + dataFetchingEnvironment: DataFetchingEnvironment, + id: Int, + ): CompletableFuture { return dataFetchingEnvironment.getValueFromDataLoader("MangaDataLoader", id) } @@ -57,7 +60,8 @@ class MangaQuery { ID(MangaTable.id), TITLE(MangaTable.title), IN_LIBRARY_AT(MangaTable.inLibraryAt), - LAST_FETCHED_AT(MangaTable.lastFetchedAt); + LAST_FETCHED_AT(MangaTable.lastFetchedAt), + ; override fun greater(cursor: Cursor): Op { return when (this) { @@ -78,12 +82,13 @@ class MangaQuery { } override fun asCursor(type: MangaType): Cursor { - val value = when (this) { - ID -> type.id.toString() - TITLE -> type.id.toString() + "-" + type.title - IN_LIBRARY_AT -> type.id.toString() + "-" + type.inLibraryAt.toString() - LAST_FETCHED_AT -> type.id.toString() + "-" + type.lastFetchedAt.toString() - } + val value = + when (this) { + ID -> type.id.toString() + TITLE -> type.id.toString() + "-" + type.title + IN_LIBRARY_AT -> type.id.toString() + "-" + type.inLibraryAt.toString() + LAST_FETCHED_AT -> type.id.toString() + "-" + type.lastFetchedAt.toString() + } return Cursor(value) } } @@ -104,7 +109,7 @@ class MangaQuery { val inLibraryAt: Long? = null, val realUrl: String? = null, val lastFetchedAt: Long? = null, - val chaptersLastFetchedAt: Long? = null + val chaptersLastFetchedAt: Long? = null, ) : HasGetOp { override fun getOp(): Op? { val opAnd = OpAnd() @@ -140,21 +145,21 @@ class MangaQuery { override val lessThan: MangaStatus? = null, override val lessThanOrEqualTo: MangaStatus? = null, override val greaterThan: MangaStatus? = null, - override val greaterThanOrEqualTo: MangaStatus? = null + override val greaterThanOrEqualTo: MangaStatus? = null, ) : ComparableScalarFilter { - fun asIntFilter() = IntFilter( - equalTo = equalTo?.value, - notEqualTo = notEqualTo?.value, - distinctFrom = distinctFrom?.value, - notDistinctFrom = notDistinctFrom?.value, - `in` = `in`?.map { it.value }, - notIn = notIn?.map { it.value }, - lessThan = lessThan?.value, - lessThanOrEqualTo = lessThanOrEqualTo?.value, - greaterThan = greaterThan?.value, - greaterThanOrEqualTo = greaterThanOrEqualTo?.value - - ) + fun asIntFilter() = + IntFilter( + equalTo = equalTo?.value, + notEqualTo = notEqualTo?.value, + distinctFrom = distinctFrom?.value, + notDistinctFrom = notDistinctFrom?.value, + `in` = `in`?.map { it.value }, + notIn = notIn?.map { it.value }, + lessThan = lessThan?.value, + lessThanOrEqualTo = lessThanOrEqualTo?.value, + greaterThan = greaterThan?.value, + greaterThanOrEqualTo = greaterThanOrEqualTo?.value, + ) } data class MangaFilter( @@ -176,7 +181,7 @@ class MangaQuery { val chaptersLastFetchedAt: LongFilter? = null, override val and: List? = null, override val or: List? = null, - override val not: MangaFilter? = null + override val not: MangaFilter? = null, ) : Filter { override fun getOpList(): List> { return listOfNotNull( @@ -194,7 +199,7 @@ class MangaQuery { andFilterWithCompare(MangaTable.inLibraryAt, inLibraryAt), andFilterWithCompareString(MangaTable.realUrl, realUrl), andFilterWithCompare(MangaTable.lastFetchedAt, lastFetchedAt), - andFilterWithCompare(MangaTable.chaptersLastFetchedAt, chaptersLastFetchedAt) + andFilterWithCompare(MangaTable.chaptersLastFetchedAt, chaptersLastFetchedAt), ) } } @@ -208,55 +213,56 @@ class MangaQuery { after: Cursor? = null, first: Int? = null, last: Int? = null, - offset: Int? = null + offset: Int? = null, ): MangaNodeList { - val queryResults = transaction { - val res = MangaTable.selectAll() + val queryResults = + transaction { + val res = MangaTable.selectAll() - res.applyOps(condition, filter) + res.applyOps(condition, filter) - if (orderBy != null || (last != null || before != null)) { - val orderByColumn = orderBy?.column ?: MangaTable.id - val orderType = orderByType.maybeSwap(last ?: before) + if (orderBy != null || (last != null || before != null)) { + val orderByColumn = orderBy?.column ?: MangaTable.id + val orderType = orderByType.maybeSwap(last ?: before) - if (orderBy == MangaOrderBy.ID || orderBy == null) { - res.orderBy(orderByColumn to orderType) - } else { - res.orderBy( - orderByColumn to orderType, - MangaTable.id to SortOrder.ASC - ) + if (orderBy == MangaOrderBy.ID || orderBy == null) { + res.orderBy(orderByColumn to orderType) + } else { + res.orderBy( + orderByColumn to orderType, + MangaTable.id to SortOrder.ASC, + ) + } } - } - val total = res.count() - val firstResult = res.firstOrNull()?.get(MangaTable.id)?.value - val lastResult = res.lastOrNull()?.get(MangaTable.id)?.value + val total = res.count() + val firstResult = res.firstOrNull()?.get(MangaTable.id)?.value + val lastResult = res.lastOrNull()?.get(MangaTable.id)?.value - if (after != null) { - res.andWhere { - when (orderByType) { - DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: MangaOrderBy.ID).less(after) - null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: MangaOrderBy.ID).greater(after) + if (after != null) { + res.andWhere { + when (orderByType) { + DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: MangaOrderBy.ID).less(after) + null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: MangaOrderBy.ID).greater(after) + } } - } - } else if (before != null) { - res.andWhere { - when (orderByType) { - DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: MangaOrderBy.ID).greater(before) - null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: MangaOrderBy.ID).less(before) + } else if (before != null) { + res.andWhere { + when (orderByType) { + DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: MangaOrderBy.ID).greater(before) + null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: MangaOrderBy.ID).less(before) + } } } - } - if (first != null) { - res.limit(first, offset?.toLong() ?: 0) - } else if (last != null) { - res.limit(last) - } + if (first != null) { + res.limit(first, offset?.toLong() ?: 0) + } else if (last != null) { + res.limit(last) + } - QueryResults(total, firstResult, lastResult, res.toList()) - } + QueryResults(total, firstResult, lastResult, res.toList()) + } val getAsCursor: (MangaType) -> Cursor = (orderBy ?: MangaOrderBy.ID)::asCursor @@ -271,24 +277,25 @@ class MangaQuery { resultsAsType.firstOrNull()?.let { MangaNodeList.MangaEdge( getAsCursor(it), - it + it, ) }, resultsAsType.lastOrNull()?.let { MangaNodeList.MangaEdge( getAsCursor(it), - it + it, ) - } + }, ) }, - pageInfo = PageInfo( - hasNextPage = queryResults.lastKey != resultsAsType.lastOrNull()?.id, - hasPreviousPage = queryResults.firstKey != resultsAsType.firstOrNull()?.id, - startCursor = resultsAsType.firstOrNull()?.let { getAsCursor(it) }, - endCursor = resultsAsType.lastOrNull()?.let { getAsCursor(it) } - ), - totalCount = queryResults.total.toInt() + pageInfo = + PageInfo( + hasNextPage = queryResults.lastKey != resultsAsType.lastOrNull()?.id, + hasPreviousPage = queryResults.firstKey != resultsAsType.firstOrNull()?.id, + startCursor = resultsAsType.firstOrNull()?.let { getAsCursor(it) }, + endCursor = resultsAsType.lastOrNull()?.let { getAsCursor(it) }, + ), + totalCount = queryResults.total.toInt(), ) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/MetaQuery.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/MetaQuery.kt index 7566c4e51..15d5adf06 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/MetaQuery.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/MetaQuery.kt @@ -42,13 +42,17 @@ import suwayomi.tachidesk.graphql.types.GlobalMetaType import java.util.concurrent.CompletableFuture class MetaQuery { - fun meta(dataFetchingEnvironment: DataFetchingEnvironment, key: String): CompletableFuture { + fun meta( + dataFetchingEnvironment: DataFetchingEnvironment, + key: String, + ): CompletableFuture { return dataFetchingEnvironment.getValueFromDataLoader("GlobalMetaDataLoader", key) } enum class MetaOrderBy(override val column: Column>) : OrderBy { KEY(GlobalMetaTable.key), - VALUE(GlobalMetaTable.value); + VALUE(GlobalMetaTable.value), + ; override fun greater(cursor: Cursor): Op { return when (this) { @@ -65,17 +69,18 @@ class MetaQuery { } override fun asCursor(type: GlobalMetaType): Cursor { - val value = when (this) { - KEY -> type.key - VALUE -> type.key + "\\-" + type.value - } + val value = + when (this) { + KEY -> type.key + VALUE -> type.key + "\\-" + type.value + } return Cursor(value) } } data class MetaCondition( val key: String? = null, - val value: String? = null + val value: String? = null, ) : HasGetOp { override fun getOp(): Op? { val opAnd = OpAnd() @@ -91,12 +96,12 @@ class MetaQuery { val value: StringFilter? = null, override val and: List? = null, override val or: List? = null, - override val not: MetaFilter? = null + override val not: MetaFilter? = null, ) : Filter { override fun getOpList(): List> { return listOfNotNull( andFilterWithCompareString(GlobalMetaTable.key, key), - andFilterWithCompareString(GlobalMetaTable.value, value) + andFilterWithCompareString(GlobalMetaTable.value, value), ) } } @@ -110,55 +115,56 @@ class MetaQuery { after: Cursor? = null, first: Int? = null, last: Int? = null, - offset: Int? = null + offset: Int? = null, ): GlobalMetaNodeList { - val queryResults = transaction { - val res = GlobalMetaTable.selectAll() - - res.applyOps(condition, filter) - - if (orderBy != null || (last != null || before != null)) { - val orderByColumn = orderBy?.column ?: GlobalMetaTable.key - val orderType = orderByType.maybeSwap(last ?: before) - - if (orderBy == MetaOrderBy.KEY || orderBy == null) { - res.orderBy(orderByColumn to orderType) - } else { - res.orderBy( - orderByColumn to orderType, - GlobalMetaTable.key to SortOrder.ASC - ) + val queryResults = + transaction { + val res = GlobalMetaTable.selectAll() + + res.applyOps(condition, filter) + + if (orderBy != null || (last != null || before != null)) { + val orderByColumn = orderBy?.column ?: GlobalMetaTable.key + val orderType = orderByType.maybeSwap(last ?: before) + + if (orderBy == MetaOrderBy.KEY || orderBy == null) { + res.orderBy(orderByColumn to orderType) + } else { + res.orderBy( + orderByColumn to orderType, + GlobalMetaTable.key to SortOrder.ASC, + ) + } } - } - val total = res.count() - val firstResult = res.firstOrNull()?.get(GlobalMetaTable.key) - val lastResult = res.lastOrNull()?.get(GlobalMetaTable.key) + val total = res.count() + val firstResult = res.firstOrNull()?.get(GlobalMetaTable.key) + val lastResult = res.lastOrNull()?.get(GlobalMetaTable.key) - if (after != null) { - res.andWhere { - when (orderByType) { - DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: MetaOrderBy.KEY).less(after) - null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: MetaOrderBy.KEY).greater(after) + if (after != null) { + res.andWhere { + when (orderByType) { + DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: MetaOrderBy.KEY).less(after) + null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: MetaOrderBy.KEY).greater(after) + } } - } - } else if (before != null) { - res.andWhere { - when (orderByType) { - DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: MetaOrderBy.KEY).greater(before) - null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: MetaOrderBy.KEY).less(before) + } else if (before != null) { + res.andWhere { + when (orderByType) { + DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: MetaOrderBy.KEY).greater(before) + null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: MetaOrderBy.KEY).less(before) + } } } - } - if (first != null) { - res.limit(first, offset?.toLong() ?: 0) - } else if (last != null) { - res.limit(last) - } + if (first != null) { + res.limit(first, offset?.toLong() ?: 0) + } else if (last != null) { + res.limit(last) + } - QueryResults(total, firstResult, lastResult, res.toList()) - } + QueryResults(total, firstResult, lastResult, res.toList()) + } val getAsCursor: (GlobalMetaType) -> Cursor = (orderBy ?: MetaOrderBy.KEY)::asCursor @@ -173,24 +179,25 @@ class MetaQuery { resultsAsType.firstOrNull()?.let { GlobalMetaNodeList.MetaEdge( getAsCursor(it), - it + it, ) }, resultsAsType.lastOrNull()?.let { GlobalMetaNodeList.MetaEdge( getAsCursor(it), - it + it, ) - } + }, ) }, - pageInfo = PageInfo( - hasNextPage = queryResults.lastKey != resultsAsType.lastOrNull()?.key, - hasPreviousPage = queryResults.firstKey != resultsAsType.firstOrNull()?.key, - startCursor = resultsAsType.firstOrNull()?.let { getAsCursor(it) }, - endCursor = resultsAsType.lastOrNull()?.let { getAsCursor(it) } - ), - totalCount = queryResults.total.toInt() + pageInfo = + PageInfo( + hasNextPage = queryResults.lastKey != resultsAsType.lastOrNull()?.key, + hasPreviousPage = queryResults.firstKey != resultsAsType.firstOrNull()?.key, + startCursor = resultsAsType.firstOrNull()?.let { getAsCursor(it) }, + endCursor = resultsAsType.lastOrNull()?.let { getAsCursor(it) }, + ), + totalCount = queryResults.total.toInt(), ) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/SourceQuery.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/SourceQuery.kt index aad508ec3..a98fc9bbb 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/SourceQuery.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/SourceQuery.kt @@ -46,14 +46,18 @@ import suwayomi.tachidesk.manga.model.table.SourceTable import java.util.concurrent.CompletableFuture class SourceQuery { - fun source(dataFetchingEnvironment: DataFetchingEnvironment, id: Long): CompletableFuture { + fun source( + dataFetchingEnvironment: DataFetchingEnvironment, + id: Long, + ): CompletableFuture { return dataFetchingEnvironment.getValueFromDataLoader("SourceDataLoader", id) } enum class SourceOrderBy(override val column: Column>) : OrderBy { ID(SourceTable.id), NAME(SourceTable.name), - LANG(SourceTable.lang); + LANG(SourceTable.lang), + ; override fun greater(cursor: Cursor): Op { return when (this) { @@ -72,11 +76,12 @@ class SourceQuery { } override fun asCursor(type: SourceType): Cursor { - val value = when (this) { - ID -> type.id.toString() - NAME -> type.id.toString() + "-" + type.name - LANG -> type.id.toString() + "-" + type.lang - } + val value = + when (this) { + ID -> type.id.toString() + NAME -> type.id.toString() + "-" + type.name + LANG -> type.id.toString() + "-" + type.lang + } return Cursor(value) } } @@ -85,7 +90,7 @@ class SourceQuery { val id: Long? = null, val name: String? = null, val lang: String? = null, - val isNsfw: Boolean? = null + val isNsfw: Boolean? = null, ) : HasGetOp { override fun getOp(): Op? { val opAnd = OpAnd() @@ -105,14 +110,14 @@ class SourceQuery { val isNsfw: BooleanFilter? = null, override val and: List? = null, override val or: List? = null, - override val not: SourceFilter? = null + override val not: SourceFilter? = null, ) : Filter { override fun getOpList(): List> { return listOfNotNull( andFilterWithCompareEntity(SourceTable.id, id), andFilterWithCompareString(SourceTable.name, name), andFilterWithCompareString(SourceTable.lang, lang), - andFilterWithCompare(SourceTable.isNsfw, isNsfw) + andFilterWithCompare(SourceTable.isNsfw, isNsfw), ) } } @@ -126,57 +131,58 @@ class SourceQuery { after: Cursor? = null, first: Int? = null, last: Int? = null, - offset: Int? = null + offset: Int? = null, ): SourceNodeList { - val (queryResults, resultsAsType) = transaction { - val res = SourceTable.selectAll() - - res.applyOps(condition, filter) - - if (orderBy != null || (last != null || before != null)) { - val orderByColumn = orderBy?.column ?: SourceTable.id - val orderType = orderByType.maybeSwap(last ?: before) - - if (orderBy == SourceOrderBy.ID || orderBy == null) { - res.orderBy(orderByColumn to orderType) - } else { - res.orderBy( - orderByColumn to orderType, - SourceTable.id to SortOrder.ASC - ) + val (queryResults, resultsAsType) = + transaction { + val res = SourceTable.selectAll() + + res.applyOps(condition, filter) + + if (orderBy != null || (last != null || before != null)) { + val orderByColumn = orderBy?.column ?: SourceTable.id + val orderType = orderByType.maybeSwap(last ?: before) + + if (orderBy == SourceOrderBy.ID || orderBy == null) { + res.orderBy(orderByColumn to orderType) + } else { + res.orderBy( + orderByColumn to orderType, + SourceTable.id to SortOrder.ASC, + ) + } } - } - val total = res.count() - val firstResult = res.firstOrNull()?.get(SourceTable.id)?.value - val lastResult = res.lastOrNull()?.get(SourceTable.id)?.value + val total = res.count() + val firstResult = res.firstOrNull()?.get(SourceTable.id)?.value + val lastResult = res.lastOrNull()?.get(SourceTable.id)?.value - if (after != null) { - res.andWhere { - when (orderByType) { - DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: SourceOrderBy.ID).less(after) - null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: SourceOrderBy.ID).greater(after) + if (after != null) { + res.andWhere { + when (orderByType) { + DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: SourceOrderBy.ID).less(after) + null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: SourceOrderBy.ID).greater(after) + } } - } - } else if (before != null) { - res.andWhere { - when (orderByType) { - DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: SourceOrderBy.ID).greater(before) - null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: SourceOrderBy.ID).less(before) + } else if (before != null) { + res.andWhere { + when (orderByType) { + DESC, DESC_NULLS_FIRST, DESC_NULLS_LAST -> (orderBy ?: SourceOrderBy.ID).greater(before) + null, ASC, ASC_NULLS_FIRST, ASC_NULLS_LAST -> (orderBy ?: SourceOrderBy.ID).less(before) + } } } - } - if (first != null) { - res.limit(first, offset?.toLong() ?: 0) - } else if (last != null) { - res.limit(last) - } + if (first != null) { + res.limit(first, offset?.toLong() ?: 0) + } else if (last != null) { + res.limit(last) + } - QueryResults(total, firstResult, lastResult, res.toList()).let { - it to it.results.mapNotNull { SourceType(it) } + QueryResults(total, firstResult, lastResult, res.toList()).let { + it to it.results.mapNotNull { SourceType(it) } + } } - } val getAsCursor: (SourceType) -> Cursor = (orderBy ?: SourceOrderBy.ID)::asCursor @@ -189,24 +195,25 @@ class SourceQuery { resultsAsType.firstOrNull()?.let { SourceNodeList.SourceEdge( getAsCursor(it), - it + it, ) }, resultsAsType.lastOrNull()?.let { SourceNodeList.SourceEdge( getAsCursor(it), - it + it, ) - } + }, ) }, - pageInfo = PageInfo( - hasNextPage = queryResults.lastKey != resultsAsType.lastOrNull()?.id, - hasPreviousPage = queryResults.firstKey != resultsAsType.firstOrNull()?.id, - startCursor = resultsAsType.firstOrNull()?.let { getAsCursor(it) }, - endCursor = resultsAsType.lastOrNull()?.let { getAsCursor(it) } - ), - totalCount = queryResults.total.toInt() + pageInfo = + PageInfo( + hasNextPage = queryResults.lastKey != resultsAsType.lastOrNull()?.id, + hasPreviousPage = queryResults.firstKey != resultsAsType.firstOrNull()?.id, + startCursor = resultsAsType.firstOrNull()?.let { getAsCursor(it) }, + endCursor = resultsAsType.lastOrNull()?.let { getAsCursor(it) }, + ), + totalCount = queryResults.total.toInt(), ) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/filter/Filter.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/filter/Filter.kt index 531c9eca1..6c4824325 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/filter/Filter.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/filter/Filter.kt @@ -17,7 +17,11 @@ import org.jetbrains.exposed.sql.or import org.jetbrains.exposed.sql.stringParam import org.jetbrains.exposed.sql.upperCase -class ILikeEscapeOp(expr1: Expression<*>, expr2: Expression<*>, like: Boolean, val escapeChar: Char?) : ComparisonOp(expr1, expr2, if (like) "ILIKE" else "NOT ILIKE") { +class ILikeEscapeOp(expr1: Expression<*>, expr2: Expression<*>, like: Boolean, val escapeChar: Char?) : ComparisonOp( + expr1, + expr2, + if (like) "ILIKE" else "NOT ILIKE", +) { override fun toQueryBuilder(queryBuilder: QueryBuilder) { super.toQueryBuilder(queryBuilder) if (escapeChar != null) { @@ -29,43 +33,93 @@ class ILikeEscapeOp(expr1: Expression<*>, expr2: Expression<*>, like: Boolean, v } companion object { - fun iLike(expression: Expression, pattern: String): ILikeEscapeOp = iLike(expression, LikePattern(pattern)) - fun iNotLike(expression: Expression, pattern: String): ILikeEscapeOp = iNotLike(expression, LikePattern(pattern)) - fun iLike(expression: Expression, pattern: LikePattern): ILikeEscapeOp = ILikeEscapeOp(expression, stringParam(pattern.pattern), true, pattern.escapeChar) - fun iNotLike(expression: Expression, pattern: LikePattern): ILikeEscapeOp = ILikeEscapeOp(expression, stringParam(pattern.pattern), false, pattern.escapeChar) + fun iLike( + expression: Expression, + pattern: String, + ): ILikeEscapeOp = iLike(expression, LikePattern(pattern)) + + fun iNotLike( + expression: Expression, + pattern: String, + ): ILikeEscapeOp = iNotLike(expression, LikePattern(pattern)) + + fun iLike( + expression: Expression, + pattern: LikePattern, + ): ILikeEscapeOp = + ILikeEscapeOp( + expression, + stringParam(pattern.pattern), + true, + pattern.escapeChar, + ) + + fun iNotLike( + expression: Expression, + pattern: LikePattern, + ): ILikeEscapeOp = + ILikeEscapeOp( + expression, + stringParam(pattern.pattern), + false, + pattern.escapeChar, + ) } } -class DistinctFromOp(expr1: Expression<*>, expr2: Expression<*>, not: Boolean) : ComparisonOp(expr1, expr2, if (not) "IS NOT DISTINCT FROM" else "IS DISTINCT FROM") { +class DistinctFromOp(expr1: Expression<*>, expr2: Expression<*>, not: Boolean) : ComparisonOp( + expr1, + expr2, + if (not) "IS NOT DISTINCT FROM" else "IS DISTINCT FROM", +) { companion object { - fun distinctFrom(expression: ExpressionWithColumnType, t: T): DistinctFromOp = DistinctFromOp( - expression, - with(SqlExpressionBuilder) { - expression.wrap(t) - }, - false - ) - fun notDistinctFrom(expression: ExpressionWithColumnType, t: T): DistinctFromOp = DistinctFromOp( - expression, - with(SqlExpressionBuilder) { - expression.wrap(t) - }, - true - ) - fun > distinctFrom(expression: ExpressionWithColumnType>, t: T): DistinctFromOp = DistinctFromOp( - expression, - with(SqlExpressionBuilder) { - expression.wrap(t) - }, - false - ) - fun > notDistinctFrom(expression: ExpressionWithColumnType>, t: T): DistinctFromOp = DistinctFromOp( - expression, - with(SqlExpressionBuilder) { - expression.wrap(t) - }, - true - ) + fun distinctFrom( + expression: ExpressionWithColumnType, + t: T, + ): DistinctFromOp = + DistinctFromOp( + expression, + with(SqlExpressionBuilder) { + expression.wrap(t) + }, + false, + ) + + fun notDistinctFrom( + expression: ExpressionWithColumnType, + t: T, + ): DistinctFromOp = + DistinctFromOp( + expression, + with(SqlExpressionBuilder) { + expression.wrap(t) + }, + true, + ) + + fun > distinctFrom( + expression: ExpressionWithColumnType>, + t: T, + ): DistinctFromOp = + DistinctFromOp( + expression, + with(SqlExpressionBuilder) { + expression.wrap(t) + }, + false, + ) + + fun > notDistinctFrom( + expression: ExpressionWithColumnType>, + t: T, + ): DistinctFromOp = + DistinctFromOp( + expression, + with(SqlExpressionBuilder) { + expression.wrap(t) + }, + true, + ) } } @@ -88,9 +142,10 @@ interface Filter> : HasGetOp { override fun getOp(): Op? { var op: Op? = null + fun newOp( otherOp: Op?, - operator: (Op, Op) -> Op + operator: (Op, Op) -> Op, ) { when { op == null && otherOp == null -> Unit @@ -99,9 +154,11 @@ interface Filter> : HasGetOp { op != null && otherOp != null -> op = operator(op!!, otherOp) } } + fun andOp(andOp: Op?) { newOp(andOp, Op::and) } + fun orOp(orOp: Op?) { newOp(orOp, Op::or) } @@ -127,6 +184,8 @@ interface ScalarFilter { val notEqualTo: T? val distinctFrom: T? val notDistinctFrom: T? + + @Suppress("ktlint:standard:property-naming") val `in`: List? val notIn: List? } @@ -155,7 +214,7 @@ data class LongFilter( override val lessThan: Long? = null, override val lessThanOrEqualTo: Long? = null, override val greaterThan: Long? = null, - override val greaterThanOrEqualTo: Long? = null + override val greaterThanOrEqualTo: Long? = null, ) : ComparableScalarFilter data class BooleanFilter( @@ -169,7 +228,7 @@ data class BooleanFilter( override val lessThan: Boolean? = null, override val lessThanOrEqualTo: Boolean? = null, override val greaterThan: Boolean? = null, - override val greaterThanOrEqualTo: Boolean? = null + override val greaterThanOrEqualTo: Boolean? = null, ) : ComparableScalarFilter data class IntFilter( @@ -183,7 +242,7 @@ data class IntFilter( override val lessThan: Int? = null, override val lessThanOrEqualTo: Int? = null, override val greaterThan: Int? = null, - override val greaterThanOrEqualTo: Int? = null + override val greaterThanOrEqualTo: Int? = null, ) : ComparableScalarFilter data class FloatFilter( @@ -197,7 +256,7 @@ data class FloatFilter( override val lessThan: Float? = null, override val lessThanOrEqualTo: Float? = null, override val greaterThan: Float? = null, - override val greaterThanOrEqualTo: Float? = null + override val greaterThanOrEqualTo: Float? = null, ) : ComparableScalarFilter data class StringFilter( @@ -235,7 +294,7 @@ data class StringFilter( val lessThanInsensitive: String? = null, val lessThanOrEqualToInsensitive: String? = null, val greaterThanInsensitive: String? = null, - val greaterThanOrEqualToInsensitive: String? = null + val greaterThanOrEqualToInsensitive: String? = null, ) : ComparableScalarFilter data class StringListFilter( @@ -251,13 +310,13 @@ data class StringListFilter( override val hasNone: List? = null, val hasAnyInsensitive: List? = null, val hasAllInsensitive: List? = null, - val hasNoneInsensitive: List? = null + val hasNoneInsensitive: List? = null, ) : ListScalarFilter> @Suppress("UNCHECKED_CAST") fun andFilterWithCompareString( column: Column, - filter: StringFilter? + filter: StringFilter?, ): Op? { filter ?: return null val opAnd = OpAnd() @@ -314,19 +373,29 @@ fun andFilterWithCompareString( } class OpAnd(var op: Op? = null) { - fun andWhere(value: T?, andPart: SqlExpressionBuilder.(T & Any) -> Op) { + fun andWhere( + value: T?, + andPart: SqlExpressionBuilder.(T & Any) -> Op, + ) { value ?: return val expr = Op.build { andPart(value) } op = if (op == null) expr else (op!! and expr) } - fun eq(value: T?, column: Column) = andWhere(value) { column eq it } - fun > eq(value: T?, column: Column>) = andWhere(value) { column eq it } + fun eq( + value: T?, + column: Column, + ) = andWhere(value) { column eq it } + + fun > eq( + value: T?, + column: Column>, + ) = andWhere(value) { column eq it } } fun > andFilterWithCompare( column: Column, - filter: ComparableScalarFilter? + filter: ComparableScalarFilter?, ): Op? { filter ?: return null val opAnd = OpAnd(andFilter(column, filter)) @@ -341,7 +410,7 @@ fun > andFilterWithCompare( fun > andFilterWithCompareEntity( column: Column>, - filter: ComparableScalarFilter? + filter: ComparableScalarFilter?, ): Op? { filter ?: return null val opAnd = OpAnd(andFilterEntity(column, filter)) @@ -356,7 +425,7 @@ fun > andFilterWithCompareEntity( fun > andFilter( column: Column, - filter: ScalarFilter? + filter: ScalarFilter?, ): Op? { filter ?: return null val opAnd = OpAnd() @@ -377,7 +446,7 @@ fun > andFilter( fun > andFilterEntity( column: Column>, - filter: ScalarFilter? + filter: ScalarFilter?, ): Op? { filter ?: return null val opAnd = OpAnd() diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/JavalinGraphQLRequestParser.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/JavalinGraphQLRequestParser.kt index d55d047ef..f8f419ba9 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/JavalinGraphQLRequestParser.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/JavalinGraphQLRequestParser.kt @@ -17,36 +17,39 @@ import io.javalin.plugin.json.jsonMapper import java.io.IOException class JavalinGraphQLRequestParser : GraphQLRequestParser { - @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE", "UNCHECKED_CAST") override suspend fun parseRequest(context: Context): GraphQLServerRequest? { return try { - val formParam = context.formParam("operations") - ?: return context.bodyAsClass(GraphQLServerRequest::class.java) + val formParam = + context.formParam("operations") + ?: return context.bodyAsClass(GraphQLServerRequest::class.java) - val request = context.jsonMapper().fromJsonString( - formParam, - GraphQLServerRequest::class.java - ) - val map = context.formParam("map")?.let { + val request = context.jsonMapper().fromJsonString( - it, - Map::class.java as Class>> + formParam, + GraphQLServerRequest::class.java, ) - }.orEmpty() - - val mapItems = map.flatMap { (key, variables) -> - val file = context.uploadedFile(key) - variables.map { fullVariable -> - val variable = fullVariable.removePrefix("variables.").substringBefore('.') - val listIndex = fullVariable.substringAfterLast('.').toIntOrNull() - MapItem( - variable, - listIndex, - file + val map = + context.formParam("map")?.let { + context.jsonMapper().fromJsonString( + it, + Map::class.java as Class>>, ) - } - }.groupBy { it.variable } + }.orEmpty() + + val mapItems = + map.flatMap { (key, variables) -> + val file = context.uploadedFile(key) + variables.map { fullVariable -> + val variable = fullVariable.removePrefix("variables.").substringBefore('.') + val listIndex = fullVariable.substringAfterLast('.').toIntOrNull() + MapItem( + variable, + listIndex, + file, + ) + } + }.groupBy { it.variable } when (request) { is GraphQLRequest -> { @@ -54,11 +57,12 @@ class JavalinGraphQLRequestParser : GraphQLRequestParser { } is GraphQLBatchRequest -> { request.copy( - requests = request.requests.map { - it.copy( - variables = it.variables?.modifyFiles(mapItems) - ) - } + requests = + request.requests.map { + it.copy( + variables = it.variables?.modifyFiles(mapItems), + ) + }, ) } } @@ -70,7 +74,7 @@ class JavalinGraphQLRequestParser : GraphQLRequestParser { data class MapItem( val variable: String, val listIndex: Int?, - val file: UploadedFile? + val file: UploadedFile?, ) /** diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/TachideskDataLoaderRegistryFactory.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/TachideskDataLoaderRegistryFactory.kt index 5d3e44134..c9fa7ab86 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/TachideskDataLoaderRegistryFactory.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/TachideskDataLoaderRegistryFactory.kt @@ -52,7 +52,7 @@ class TachideskDataLoaderRegistryFactory { SourceDataLoader(), SourcesForExtensionDataLoader(), ExtensionDataLoader(), - ExtensionForSourceDataLoader() + ExtensionForSourceDataLoader(), ) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/TachideskGraphQLContextFactory.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/TachideskGraphQLContextFactory.kt index 34147ae8d..4eed39b9c 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/TachideskGraphQLContextFactory.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/TachideskGraphQLContextFactory.kt @@ -37,5 +37,4 @@ class TachideskGraphQLContextFactory : GraphQLContextFactory.toGraphQLContext(): graphql.GraphQLContext = - graphql.GraphQLContext.of(this) +fun Map<*, Any?>.toGraphQLContext(): graphql.GraphQLContext = graphql.GraphQLContext.of(this) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/TachideskGraphQLSchema.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/TachideskGraphQLSchema.kt index 24b552436..b0d350482 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/TachideskGraphQLSchema.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/TachideskGraphQLSchema.kt @@ -46,49 +46,55 @@ import kotlin.reflect.KClass import kotlin.reflect.KType class CustomSchemaGeneratorHooks : FlowSubscriptionSchemaGeneratorHooks() { - override fun willGenerateGraphQLType(type: KType): GraphQLType? = when (type.classifier as? KClass<*>) { - Long::class -> GraphQLLongAsString // encode to string for JS - Cursor::class -> GraphQLCursor - UploadedFile::class -> GraphQLUpload - else -> super.willGenerateGraphQLType(type) - } + override fun willGenerateGraphQLType(type: KType): GraphQLType? = + when (type.classifier as? KClass<*>) { + Long::class -> GraphQLLongAsString // encode to string for JS + Cursor::class -> GraphQLCursor + UploadedFile::class -> GraphQLUpload + else -> super.willGenerateGraphQLType(type) + } } -val schema = toSchema( - config = SchemaGeneratorConfig( - supportedPackages = listOf("suwayomi.tachidesk.graphql"), - introspectionEnabled = true, - hooks = CustomSchemaGeneratorHooks() - ), - queries = listOf( - TopLevelObject(BackupQuery()), - TopLevelObject(CategoryQuery()), - TopLevelObject(ChapterQuery()), - TopLevelObject(DownloadQuery()), - TopLevelObject(ExtensionQuery()), - TopLevelObject(InfoQuery()), - TopLevelObject(MangaQuery()), - TopLevelObject(MetaQuery()), - TopLevelObject(SettingsQuery()), - TopLevelObject(SourceQuery()), - TopLevelObject(UpdateQuery()) - ), - mutations = listOf( - TopLevelObject(BackupMutation()), - TopLevelObject(CategoryMutation()), - TopLevelObject(ChapterMutation()), - TopLevelObject(DownloadMutation()), - TopLevelObject(ExtensionMutation()), - TopLevelObject(InfoMutation()), - TopLevelObject(MangaMutation()), - TopLevelObject(MetaMutation()), - TopLevelObject(SettingsMutation()), - TopLevelObject(SourceMutation()), - TopLevelObject(UpdateMutation()) - ), - subscriptions = listOf( - TopLevelObject(DownloadSubscription()), - TopLevelObject(InfoSubscription()), - TopLevelObject(UpdateSubscription()) +val schema = + toSchema( + config = + SchemaGeneratorConfig( + supportedPackages = listOf("suwayomi.tachidesk.graphql"), + introspectionEnabled = true, + hooks = CustomSchemaGeneratorHooks(), + ), + queries = + listOf( + TopLevelObject(BackupQuery()), + TopLevelObject(CategoryQuery()), + TopLevelObject(ChapterQuery()), + TopLevelObject(DownloadQuery()), + TopLevelObject(ExtensionQuery()), + TopLevelObject(InfoQuery()), + TopLevelObject(MangaQuery()), + TopLevelObject(MetaQuery()), + TopLevelObject(SettingsQuery()), + TopLevelObject(SourceQuery()), + TopLevelObject(UpdateQuery()), + ), + mutations = + listOf( + TopLevelObject(BackupMutation()), + TopLevelObject(CategoryMutation()), + TopLevelObject(ChapterMutation()), + TopLevelObject(DownloadMutation()), + TopLevelObject(ExtensionMutation()), + TopLevelObject(InfoMutation()), + TopLevelObject(MangaMutation()), + TopLevelObject(MetaMutation()), + TopLevelObject(SettingsMutation()), + TopLevelObject(SourceMutation()), + TopLevelObject(UpdateMutation()), + ), + subscriptions = + listOf( + TopLevelObject(DownloadSubscription()), + TopLevelObject(InfoSubscription()), + TopLevelObject(UpdateSubscription()), + ), ) -) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/TachideskGraphQLServer.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/TachideskGraphQLServer.kt index 47d3e29eb..3f2c84c9d 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/TachideskGraphQLServer.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/TachideskGraphQLServer.kt @@ -25,7 +25,7 @@ class TachideskGraphQLServer( requestParser: JavalinGraphQLRequestParser, contextFactory: TachideskGraphQLContextFactory, requestHandler: GraphQLRequestHandler, - subscriptionHandler: GraphQLSubscriptionHandler + subscriptionHandler: GraphQLSubscriptionHandler, ) : GraphQLServer(requestParser, contextFactory, requestHandler) { private val objectMapper = jacksonObjectMapper() private val subscriptionProtocolHandler = ApolloSubscriptionProtocolHandler(contextFactory, subscriptionHandler, objectMapper) @@ -42,9 +42,10 @@ class TachideskGraphQLServer( } companion object { - private fun getGraphQLObject(): GraphQL = GraphQL.newGraphQL(schema) - .subscriptionExecutionStrategy(FlowSubscriptionExecutionStrategy()) - .build() + private fun getGraphQLObject(): GraphQL = + GraphQL.newGraphQL(schema) + .subscriptionExecutionStrategy(FlowSubscriptionExecutionStrategy()) + .build() fun create(): TachideskGraphQLServer { val graphQL = getGraphQLObject() diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/TemporaryFileStorage.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/TemporaryFileStorage.kt index 34648663d..3129cf1d0 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/TemporaryFileStorage.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/TemporaryFileStorage.kt @@ -22,12 +22,15 @@ object TemporaryFileStorage { Runtime.getRuntime().addShutdownHook( thread(start = false) { folder.deleteRecursively() - } + }, ) } @OptIn(DelicateCoroutinesApi::class) - fun saveFile(name: String, content: InputStream) { + fun saveFile( + name: String, + content: InputStream, + ) { val file = folder.resolve(name) content.use { inStream -> file.outputStream().use { diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/primitives/Cursor.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/primitives/Cursor.kt index eb07e4705..eac7019fd 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/primitives/Cursor.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/primitives/Cursor.kt @@ -14,36 +14,45 @@ import java.util.Locale data class Cursor(val value: String) -val GraphQLCursor: GraphQLScalarType = GraphQLScalarType.newScalar() - .name("Cursor").description("A location in a connection that can be used for resuming pagination.").coercing(GraphqlCursorCoercing()).build() +val GraphQLCursor: GraphQLScalarType = + GraphQLScalarType.newScalar() + .name( + "Cursor", + ).description("A location in a connection that can be used for resuming pagination.").coercing(GraphqlCursorCoercing()).build() private class GraphqlCursorCoercing : Coercing { private fun toStringImpl(input: Any): String? { return (input as? Cursor)?.value } - private fun parseValueImpl(input: Any, locale: Locale): Cursor { + private fun parseValueImpl( + input: Any, + locale: Locale, + ): Cursor { if (input !is String) { throw CoercingParseValueException( CoercingUtil.i18nMsg( locale, "String.unexpectedRawValueType", - CoercingUtil.typeName(input) - ) + CoercingUtil.typeName(input), + ), ) } return Cursor(input) } - private fun parseLiteralImpl(input: Any, locale: Locale): Cursor { + private fun parseLiteralImpl( + input: Any, + locale: Locale, + ): Cursor { if (input !is StringValue) { throw CoercingParseLiteralException( CoercingUtil.i18nMsg( locale, "Scalar.unexpectedAstType", "StringValue", - CoercingUtil.typeName(input) - ) + CoercingUtil.typeName(input), + ), ) } return Cursor(input.value) @@ -59,8 +68,8 @@ private class GraphqlCursorCoercing : Coercing { CoercingUtil.i18nMsg( Locale.getDefault(), "String.unexpectedRawValueType", - CoercingUtil.typeName(dataFetcherResult) - ) + CoercingUtil.typeName(dataFetcherResult), + ), ) } @@ -68,14 +77,14 @@ private class GraphqlCursorCoercing : Coercing { override fun serialize( dataFetcherResult: Any, graphQLContext: GraphQLContext, - locale: Locale + locale: Locale, ): String { return toStringImpl(dataFetcherResult) ?: throw CoercingSerializeException( CoercingUtil.i18nMsg( locale, "String.unexpectedRawValueType", - CoercingUtil.typeName(dataFetcherResult) - ) + CoercingUtil.typeName(dataFetcherResult), + ), ) } @@ -85,7 +94,11 @@ private class GraphqlCursorCoercing : Coercing { } @Throws(CoercingParseValueException::class) - override fun parseValue(input: Any, graphQLContext: GraphQLContext, locale: Locale): Cursor { + override fun parseValue( + input: Any, + graphQLContext: GraphQLContext, + locale: Locale, + ): Cursor { return parseValueImpl(input, locale) } @@ -99,7 +112,7 @@ private class GraphqlCursorCoercing : Coercing { input: Value<*>, variables: CoercedVariables, graphQLContext: GraphQLContext, - locale: Locale + locale: Locale, ): Cursor { return parseLiteralImpl(input, locale) } @@ -112,7 +125,7 @@ private class GraphqlCursorCoercing : Coercing { override fun valueToLiteral( input: Any, graphQLContext: GraphQLContext, - locale: Locale + locale: Locale, ): Value<*> { return valueToLiteralImpl(input) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/primitives/LongAsString.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/primitives/LongAsString.kt index e5157ee62..ff89c0e8c 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/primitives/LongAsString.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/primitives/LongAsString.kt @@ -12,36 +12,43 @@ import graphql.schema.CoercingSerializeException import graphql.schema.GraphQLScalarType import java.util.Locale -val GraphQLLongAsString: GraphQLScalarType = GraphQLScalarType.newScalar() - .name("LongString").description("A 64-bit signed integer as a String").coercing(GraphqlLongAsStringCoercing()).build() +val GraphQLLongAsString: GraphQLScalarType = + GraphQLScalarType.newScalar() + .name("LongString").description("A 64-bit signed integer as a String").coercing(GraphqlLongAsStringCoercing()).build() private class GraphqlLongAsStringCoercing : Coercing { private fun toStringImpl(input: Any): String { return input.toString() } - private fun parseValueImpl(input: Any, locale: Locale): Long { + private fun parseValueImpl( + input: Any, + locale: Locale, + ): Long { if (input !is String) { throw CoercingParseValueException( CoercingUtil.i18nMsg( locale, "String.unexpectedRawValueType", - CoercingUtil.typeName(input) - ) + CoercingUtil.typeName(input), + ), ) } return input.toLong() } - private fun parseLiteralImpl(input: Any, locale: Locale): Long { + private fun parseLiteralImpl( + input: Any, + locale: Locale, + ): Long { if (input !is StringValue) { throw CoercingParseLiteralException( CoercingUtil.i18nMsg( locale, "Scalar.unexpectedAstType", "StringValue", - CoercingUtil.typeName(input) - ) + CoercingUtil.typeName(input), + ), ) } return input.value.toLong() @@ -60,7 +67,7 @@ private class GraphqlLongAsStringCoercing : Coercing { override fun serialize( dataFetcherResult: Any, graphQLContext: GraphQLContext, - locale: Locale + locale: Locale, ): String { return toStringImpl(dataFetcherResult) } @@ -71,7 +78,11 @@ private class GraphqlLongAsStringCoercing : Coercing { } @Throws(CoercingParseValueException::class) - override fun parseValue(input: Any, graphQLContext: GraphQLContext, locale: Locale): Long { + override fun parseValue( + input: Any, + graphQLContext: GraphQLContext, + locale: Locale, + ): Long { return parseValueImpl(input, locale) } @@ -85,7 +96,7 @@ private class GraphqlLongAsStringCoercing : Coercing { input: Value<*>, variables: CoercedVariables, graphQLContext: GraphQLContext, - locale: Locale + locale: Locale, ): Long { return parseLiteralImpl(input, locale) } @@ -98,7 +109,7 @@ private class GraphqlLongAsStringCoercing : Coercing { override fun valueToLiteral( input: Any, graphQLContext: GraphQLContext, - locale: Locale + locale: Locale, ): Value<*> { return valueToLiteralImpl(input) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/primitives/NodeList.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/primitives/NodeList.kt index ae4ac253a..4b2a130be 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/primitives/NodeList.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/primitives/NodeList.kt @@ -26,7 +26,7 @@ data class PageInfo( @GraphQLDescription("When paginating backwards, the cursor to continue.") val startCursor: Cursor?, @GraphQLDescription("When paginating forwards, the cursor to continue.") - val endCursor: Cursor? + val endCursor: Cursor?, ) abstract class Edge { diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/primitives/OrderBy.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/primitives/OrderBy.kt index 4cf89215f..d267fb116 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/primitives/OrderBy.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/primitives/OrderBy.kt @@ -41,7 +41,7 @@ fun > greaterNotUnique( column: Column, idColumn: Column>, cursor: Cursor, - toValue: (String) -> T + toValue: (String) -> T, ): Op { return greaterNotUniqueImpl(column, idColumn, cursor, String::toInt, toValue) } @@ -51,7 +51,7 @@ fun > greaterNotUnique( column: Column, idColumn: Column>, cursor: Cursor, - toValue: (String) -> T + toValue: (String) -> T, ): Op { return greaterNotUniqueImpl(column, idColumn, cursor, String::toLong, toValue) } @@ -61,7 +61,7 @@ private fun , V : Comparable> greaterNotUniqueImpl( idColumn: Column>, cursor: Cursor, toKey: (String) -> K, - toValue: (String) -> V + toValue: (String) -> V, ): Op { val id = toKey(cursor.value.substringBefore('-')) val value = toValue(cursor.value.substringAfter('-')) @@ -73,7 +73,7 @@ fun > greaterNotUnique( column: Column, idColumn: Column, cursor: Cursor, - toValue: (String) -> T + toValue: (String) -> T, ): Op { val id = cursor.value.substringBefore("\\-") val value = toValue(cursor.value.substringAfter("\\-")) @@ -85,7 +85,7 @@ fun > lessNotUnique( column: Column, idColumn: Column>, cursor: Cursor, - toValue: (String) -> T + toValue: (String) -> T, ): Op { return lessNotUniqueImpl(column, idColumn, cursor, String::toInt, toValue) } @@ -95,7 +95,7 @@ fun > lessNotUnique( column: Column, idColumn: Column>, cursor: Cursor, - toValue: (String) -> T + toValue: (String) -> T, ): Op { return lessNotUniqueImpl(column, idColumn, cursor, String::toLong, toValue) } @@ -105,7 +105,7 @@ private fun , V : Comparable> lessNotUniqueImpl( idColumn: Column>, cursor: Cursor, toKey: (String) -> K, - toValue: (String) -> V + toValue: (String) -> V, ): Op { val id = toKey(cursor.value.substringBefore('-')) val value = toValue(cursor.value.substringAfter('-')) @@ -117,7 +117,7 @@ fun > lessNotUnique( column: Column, idColumn: Column, cursor: Cursor, - toValue: (String) -> T + toValue: (String) -> T, ): Op { val id = cursor.value.substringBefore("\\-") val value = toValue(cursor.value.substringAfter("\\-")) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/primitives/Upload.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/primitives/Upload.kt index e4e4572be..31a2aa6aa 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/primitives/Upload.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/primitives/Upload.kt @@ -9,21 +9,25 @@ import graphql.schema.GraphQLScalarType import io.javalin.http.UploadedFile import java.util.Locale -val GraphQLUpload = GraphQLScalarType.newScalar() - .name("Upload") - .description("A file part in a multipart request") - .coercing(GraphqlUploadCoercing()) - .build() +val GraphQLUpload = + GraphQLScalarType.newScalar() + .name("Upload") + .description("A file part in a multipart request") + .coercing(GraphqlUploadCoercing()) + .build() private class GraphqlUploadCoercing : Coercing { - private fun parseValueImpl(input: Any, locale: Locale): UploadedFile { + private fun parseValueImpl( + input: Any, + locale: Locale, + ): UploadedFile { if (input !is UploadedFile) { throw CoercingParseValueException( CoercingUtil.i18nMsg( locale, "String.unexpectedRawValueType", - CoercingUtil.typeName(input) - ) + CoercingUtil.typeName(input), + ), ) } return input @@ -38,7 +42,7 @@ private class GraphqlUploadCoercing : Coercing { override fun serialize( dataFetcherResult: Any, graphQLContext: GraphQLContext, - locale: Locale + locale: Locale, ): Void? { throw CoercingSerializeException("Upload is an input-only type") } @@ -49,7 +53,11 @@ private class GraphqlUploadCoercing : Coercing { } @Throws(CoercingParseValueException::class) - override fun parseValue(input: Any, graphQLContext: GraphQLContext, locale: Locale): UploadedFile { + override fun parseValue( + input: Any, + graphQLContext: GraphQLContext, + locale: Locale, + ): UploadedFile { return parseValueImpl(input, locale) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/subscriptions/ApolloSubscriptionProtocolHandler.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/subscriptions/ApolloSubscriptionProtocolHandler.kt index f195843d7..1fedc7afa 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/subscriptions/ApolloSubscriptionProtocolHandler.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/subscriptions/ApolloSubscriptionProtocolHandler.kt @@ -45,7 +45,7 @@ import suwayomi.tachidesk.server.serverConfig class ApolloSubscriptionProtocolHandler( private val contextFactory: TachideskGraphQLContextFactory, private val subscriptionHandler: GraphQLSubscriptionHandler, - private val objectMapper: ObjectMapper + private val objectMapper: ObjectMapper, ) { private val sessionState = ApolloSubscriptionSessionState() private val logger = KotlinLogging.logger {} @@ -68,13 +68,13 @@ class ApolloSubscriptionProtocolHandler( val operationMessage = convertToMessageOrNull(context.message()) ?: return flowOf(basicConnectionErrorMessage) logger.debug { "GraphQL subscription client message, sessionId=${context.sessionId} type=${operationMessage.type} operationName=${ - getOperationName(operationMessage.payload) + getOperationName(operationMessage.payload) } ${ - if (serverConfig.gqlDebugLogsEnabled.value) { - "operationMessage=$operationMessage" - } else { - "" - } + if (serverConfig.gqlDebugLogsEnabled.value) { + "operationMessage=$operationMessage" + } else { + "" + } }" } @@ -108,7 +108,7 @@ class ApolloSubscriptionProtocolHandler( @Suppress("Detekt.TooGenericExceptionCaught") private fun startSubscription( operationMessage: SubscriptionOperationMessage, - context: WsContext + context: WsContext, ): Flow { if (operationMessage.id == null) { logger.error("GraphQL subscription operation id is required") @@ -149,7 +149,10 @@ class ApolloSubscriptionProtocolHandler( } } - private fun onInit(operationMessage: SubscriptionOperationMessage, context: WsContext): Flow { + private fun onInit( + operationMessage: SubscriptionOperationMessage, + context: WsContext, + ): Flow { saveContext(operationMessage, context) return flowOf(acknowledgeMessage) } @@ -157,7 +160,10 @@ class ApolloSubscriptionProtocolHandler( /** * Generate the context and save it for all future messages. */ - private fun saveContext(operationMessage: SubscriptionOperationMessage, context: WsContext) { + private fun saveContext( + operationMessage: SubscriptionOperationMessage, + context: WsContext, + ) { runBlocking { val graphQLContext = contextFactory.generateContextMap(context).toGraphQLContext() sessionState.saveContext(context, graphQLContext) @@ -169,7 +175,7 @@ class ApolloSubscriptionProtocolHandler( */ private fun onComplete( operationMessage: SubscriptionOperationMessage, - context: WsContext + context: WsContext, ): Flow { return sessionState.completeOperation(operationMessage) } @@ -183,7 +189,10 @@ class ApolloSubscriptionProtocolHandler( return emptyFlow() } - private fun onUnknownOperation(operationMessage: SubscriptionOperationMessage, context: WsContext): Flow { + private fun onUnknownOperation( + operationMessage: SubscriptionOperationMessage, + context: WsContext, + ): Flow { logger.error("Unknown subscription operation $operationMessage") sessionState.completeOperation(operationMessage) return emptyFlow() diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/subscriptions/ApolloSubscriptionSessionState.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/subscriptions/ApolloSubscriptionSessionState.kt index 7576ee2c4..ea58b56af 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/subscriptions/ApolloSubscriptionSessionState.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/subscriptions/ApolloSubscriptionSessionState.kt @@ -19,7 +19,6 @@ import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.CopyOnWriteArrayList internal class ApolloSubscriptionSessionState { - // Operations are saved by web socket session id, then operation id internal val activeOperations = ConcurrentHashMap() @@ -33,21 +32,29 @@ internal class ApolloSubscriptionSessionState { * This allows us to include some initial state to be used when handling all the messages. * This will be removed in [terminateSession]. */ - fun saveContext(context: WsContext, graphQLContext: GraphQLContext) { + fun saveContext( + context: WsContext, + graphQLContext: GraphQLContext, + ) { cachedGraphQLContext[context.sessionId] = graphQLContext } /** * Return the graphQL context for this session. */ - fun getGraphQLContext(context: WsContext): GraphQLContext = cachedGraphQLContext[context.sessionId] ?: emptyMap().toGraphQLContext() + fun getGraphQLContext(context: WsContext): GraphQLContext = + cachedGraphQLContext[context.sessionId] ?: emptyMap().toGraphQLContext() /** * Save the operation that is sending data to the client. * This will override values without cancelling the subscription so it is the responsibility of the consumer to cancel. * These messages will be stopped on [stopOperation]. */ - fun saveOperation(context: WsContext, operationMessage: SubscriptionOperationMessage, subscription: Job) { + fun saveOperation( + context: WsContext, + operationMessage: SubscriptionOperationMessage, + subscription: Job, + ) { val id = operationMessage.id if (id != null) { activeOperations[id] = subscription @@ -78,7 +85,10 @@ internal class ApolloSubscriptionSessionState { /** * Terminate the session, cancelling the keep alive messages and all operations active for this session. */ - fun terminateSession(context: WsContext, code: CloseStatus) { + fun terminateSession( + context: WsContext, + code: CloseStatus, + ) { sessionToOperationId.remove(context.sessionId)?.forEach { activeOperations[it]?.cancel() } @@ -89,6 +99,5 @@ internal class ApolloSubscriptionSessionState { /** * Looks up the operation for the client, to check if it already exists */ - fun doesOperationExist(operationMessage: SubscriptionOperationMessage): Boolean = - activeOperations.containsKey(operationMessage.id) + fun doesOperationExist(operationMessage: SubscriptionOperationMessage): Boolean = activeOperations.containsKey(operationMessage.id) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/subscriptions/GraphQLSubscriptionHandler.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/subscriptions/GraphQLSubscriptionHandler.kt index a402e6421..6f54ed0a9 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/subscriptions/GraphQLSubscriptionHandler.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/subscriptions/GraphQLSubscriptionHandler.kt @@ -23,11 +23,11 @@ import kotlinx.coroutines.flow.map open class GraphQLSubscriptionHandler( private val graphQL: GraphQL, - private val dataLoaderRegistryFactory: KotlinDataLoaderRegistryFactory? = null + private val dataLoaderRegistryFactory: KotlinDataLoaderRegistryFactory? = null, ) { open fun executeSubscription( graphQLRequest: GraphQLRequest, - graphQLContext: GraphQLContext = GraphQLContext.of(emptyMap()) + graphQLContext: GraphQLContext = GraphQLContext.of(emptyMap()), ): Flow> { val dataLoaderRegistry = dataLoaderRegistryFactory?.generate() val input = graphQLRequest.toExecutionInput(dataLoaderRegistry, graphQLContext) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/subscriptions/SubscriptionOperationMessage.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/subscriptions/SubscriptionOperationMessage.kt index ae8dac1ac..06c250585 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/subscriptions/SubscriptionOperationMessage.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/server/subscriptions/SubscriptionOperationMessage.kt @@ -21,22 +21,22 @@ import com.fasterxml.jackson.annotation.JsonInclude data class SubscriptionOperationMessage( val type: String, val id: String? = null, - val payload: Any? = null + val payload: Any? = null, ) { enum class CommonMessages(val type: String) { GQL_PING("ping"), GQL_PONG("pong"), - GQL_COMPLETE("complete") + GQL_COMPLETE("complete"), } enum class ClientMessages(val type: String) { GQL_CONNECTION_INIT("connection_init"), - GQL_SUBSCRIBE("subscribe") + GQL_SUBSCRIBE("subscribe"), } enum class ServerMessages(val type: String) { GQL_CONNECTION_ACK("connection_ack"), GQL_NEXT("next"), - GQL_ERROR("error") + GQL_ERROR("error"), } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/subscriptions/DownloadSubscription.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/subscriptions/DownloadSubscription.kt index 15b01c1b3..8a55d5a10 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/subscriptions/DownloadSubscription.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/subscriptions/DownloadSubscription.kt @@ -13,7 +13,6 @@ import suwayomi.tachidesk.graphql.types.DownloadStatus import suwayomi.tachidesk.manga.impl.download.DownloadManager class DownloadSubscription { - fun downloadChanged(): Flow { return DownloadManager.status.map { downloadStatus -> DownloadStatus(downloadStatus) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/BackupTypes.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/BackupTypes.kt index a30b9852b..12ba53a67 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/BackupTypes.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/BackupTypes.kt @@ -5,31 +5,34 @@ import suwayomi.tachidesk.manga.impl.backup.proto.ProtoBackupImport enum class BackupRestoreState { IDLE, RESTORING_CATEGORIES, - RESTORING_MANGA + RESTORING_MANGA, } data class BackupRestoreStatus( val state: BackupRestoreState, val totalManga: Int, - val mangaProgress: Int + val mangaProgress: Int, ) fun ProtoBackupImport.BackupRestoreState.toStatus(): BackupRestoreStatus { return when (this) { - ProtoBackupImport.BackupRestoreState.Idle -> BackupRestoreStatus( - state = BackupRestoreState.IDLE, - totalManga = 0, - mangaProgress = 0 - ) - is ProtoBackupImport.BackupRestoreState.RestoringCategories -> BackupRestoreStatus( - state = BackupRestoreState.RESTORING_CATEGORIES, - totalManga = totalManga, - mangaProgress = 0 - ) - is ProtoBackupImport.BackupRestoreState.RestoringManga -> BackupRestoreStatus( - state = BackupRestoreState.RESTORING_MANGA, - totalManga = totalManga, - mangaProgress = current - ) + ProtoBackupImport.BackupRestoreState.Idle -> + BackupRestoreStatus( + state = BackupRestoreState.IDLE, + totalManga = 0, + mangaProgress = 0, + ) + is ProtoBackupImport.BackupRestoreState.RestoringCategories -> + BackupRestoreStatus( + state = BackupRestoreState.RESTORING_CATEGORIES, + totalManga = totalManga, + mangaProgress = 0, + ) + is ProtoBackupImport.BackupRestoreState.RestoringManga -> + BackupRestoreStatus( + state = BackupRestoreState.RESTORING_MANGA, + totalManga = totalManga, + mangaProgress = current, + ) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/CategoryType.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/CategoryType.kt index 03c9ccd27..2c9e99fdd 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/CategoryType.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/CategoryType.kt @@ -24,14 +24,14 @@ class CategoryType( val order: Int, val name: String, val default: Boolean, - val includeInUpdate: IncludeInUpdate + val includeInUpdate: IncludeInUpdate, ) : Node { constructor(row: ResultRow) : this( row[CategoryTable.id].value, row[CategoryTable.order], row[CategoryTable.name], row[CategoryTable.isDefault], - IncludeInUpdate.fromValue(row[CategoryTable.includeInUpdate]) + IncludeInUpdate.fromValue(row[CategoryTable.includeInUpdate]), ) fun mangas(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture { @@ -47,11 +47,11 @@ data class CategoryNodeList( override val nodes: List, override val edges: List, override val pageInfo: PageInfo, - override val totalCount: Int + override val totalCount: Int, ) : NodeList() { data class CategoryEdge( override val cursor: Cursor, - override val node: CategoryType + override val node: CategoryType, ) : Edge() companion object { @@ -59,13 +59,14 @@ data class CategoryNodeList( return CategoryNodeList( nodes = this, edges = getEdges(), - pageInfo = PageInfo( - hasNextPage = false, - hasPreviousPage = false, - startCursor = Cursor(0.toString()), - endCursor = Cursor(lastIndex.toString()) - ), - totalCount = size + pageInfo = + PageInfo( + hasNextPage = false, + hasPreviousPage = false, + startCursor = Cursor(0.toString()), + endCursor = Cursor(lastIndex.toString()), + ), + totalCount = size, ) } @@ -74,12 +75,12 @@ data class CategoryNodeList( return listOf( CategoryEdge( cursor = Cursor("0"), - node = first() + node = first(), ), CategoryEdge( cursor = Cursor(lastIndex.toString()), - node = last() - ) + node = last(), + ), ) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/ChapterType.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/ChapterType.kt index 9010bcaad..c354e52bb 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/ChapterType.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/ChapterType.kt @@ -35,7 +35,7 @@ class ChapterType( val realUrl: String?, val fetchedAt: Long, val isDownloaded: Boolean, - val pageCount: Int + val pageCount: Int, // val chapterCount: Int?, ) : Node { constructor(row: ResultRow) : this( @@ -54,7 +54,7 @@ class ChapterType( row[ChapterTable.realUrl], row[ChapterTable.fetchedAt], row[ChapterTable.isDownloaded], - row[ChapterTable.pageCount] + row[ChapterTable.pageCount], // transaction { ChapterTable.select { manga eq chapterEntry[manga].value }.count().toInt() }, ) @@ -74,7 +74,7 @@ class ChapterType( dataClass.realUrl, dataClass.fetchedAt, dataClass.downloaded, - dataClass.pageCount + dataClass.pageCount, ) fun manga(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture { @@ -90,11 +90,11 @@ data class ChapterNodeList( override val nodes: List, override val edges: List, override val pageInfo: PageInfo, - override val totalCount: Int + override val totalCount: Int, ) : NodeList() { data class ChapterEdge( override val cursor: Cursor, - override val node: ChapterType + override val node: ChapterType, ) : Edge() companion object { @@ -102,13 +102,14 @@ data class ChapterNodeList( return ChapterNodeList( nodes = this, edges = getEdges(), - pageInfo = PageInfo( - hasNextPage = false, - hasPreviousPage = false, - startCursor = Cursor(0.toString()), - endCursor = Cursor(lastIndex.toString()) - ), - totalCount = size + pageInfo = + PageInfo( + hasNextPage = false, + hasPreviousPage = false, + startCursor = Cursor(0.toString()), + endCursor = Cursor(lastIndex.toString()), + ), + totalCount = size, ) } @@ -117,12 +118,12 @@ data class ChapterNodeList( return listOf( ChapterEdge( cursor = Cursor("0"), - node = first() + node = first(), ), ChapterEdge( cursor = Cursor(lastIndex.toString()), - node = last() - ) + node = last(), + ), ) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/DownloadType.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/DownloadType.kt index 9acddd377..f19218112 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/DownloadType.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/DownloadType.kt @@ -23,14 +23,14 @@ import suwayomi.tachidesk.manga.impl.download.model.DownloadState as OtherDownlo data class DownloadStatus( val state: DownloaderState, - val queue: List + val queue: List, ) { constructor(downloadStatus: DownloadStatus) : this( when (downloadStatus.status) { Status.Stopped -> DownloaderState.STOPPED Status.Started -> DownloaderState.STARTED }, - downloadStatus.queue.map { DownloadType(it) } + downloadStatus.queue.map { DownloadType(it) }, ) } @@ -41,7 +41,7 @@ class DownloadType( val mangaId: Int, val state: DownloadState, val progress: Float, - val tries: Int + val tries: Int, ) : Node { constructor(downloadChapter: DownloadChapter) : this( downloadChapter.chapter.id, @@ -53,7 +53,7 @@ class DownloadType( OtherDownloadState.Error -> DownloadState.ERROR }, downloadChapter.progress, - downloadChapter.tries + downloadChapter.tries, ) fun manga(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture { @@ -69,23 +69,23 @@ enum class DownloadState { QUEUED, DOWNLOADING, FINISHED, - ERROR + ERROR, } enum class DownloaderState { STARTED, - STOPPED + STOPPED, } data class DownloadNodeList( override val nodes: List, override val edges: List, override val pageInfo: PageInfo, - override val totalCount: Int + override val totalCount: Int, ) : NodeList() { data class DownloadEdge( override val cursor: Cursor, - override val node: DownloadType + override val node: DownloadType, ) : Edge() companion object { @@ -93,13 +93,14 @@ data class DownloadNodeList( return DownloadNodeList( nodes = this, edges = getEdges(), - pageInfo = PageInfo( - hasNextPage = false, - hasPreviousPage = false, - startCursor = Cursor(0.toString()), - endCursor = Cursor(lastIndex.toString()) - ), - totalCount = size + pageInfo = + PageInfo( + hasNextPage = false, + hasPreviousPage = false, + startCursor = Cursor(0.toString()), + endCursor = Cursor(lastIndex.toString()), + ), + totalCount = size, ) } @@ -108,12 +109,12 @@ data class DownloadNodeList( return listOf( DownloadEdge( cursor = Cursor("0"), - node = first() + node = first(), ), DownloadEdge( cursor = Cursor(lastIndex.toString()), - node = last() - ) + node = last(), + ), ) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/ExtensionType.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/ExtensionType.kt index b34345daa..4b0d350ca 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/ExtensionType.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/ExtensionType.kt @@ -22,17 +22,15 @@ import java.util.concurrent.CompletableFuture class ExtensionType( val apkName: String, val iconUrl: String, - val name: String, val pkgName: String, val versionName: String, val versionCode: Int, val lang: String, val isNsfw: Boolean, - val isInstalled: Boolean, val hasUpdate: Boolean, - val isObsolete: Boolean + val isObsolete: Boolean, ) : Node { constructor(row: ResultRow) : this( apkName = row[ExtensionTable.apkName], @@ -45,7 +43,7 @@ class ExtensionType( isNsfw = row[ExtensionTable.isNsfw], isInstalled = row[ExtensionTable.isInstalled], hasUpdate = row[ExtensionTable.hasUpdate], - isObsolete = row[ExtensionTable.isObsolete] + isObsolete = row[ExtensionTable.isObsolete], ) fun source(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture { @@ -57,11 +55,11 @@ data class ExtensionNodeList( override val nodes: List, override val edges: List, override val pageInfo: PageInfo, - override val totalCount: Int + override val totalCount: Int, ) : NodeList() { data class ExtensionEdge( override val cursor: Cursor, - override val node: ExtensionType + override val node: ExtensionType, ) : Edge() companion object { @@ -69,13 +67,14 @@ data class ExtensionNodeList( return ExtensionNodeList( nodes = this, edges = getEdges(), - pageInfo = PageInfo( - hasNextPage = false, - hasPreviousPage = false, - startCursor = Cursor(0.toString()), - endCursor = Cursor(lastIndex.toString()) - ), - totalCount = size + pageInfo = + PageInfo( + hasNextPage = false, + hasPreviousPage = false, + startCursor = Cursor(0.toString()), + endCursor = Cursor(lastIndex.toString()), + ), + totalCount = size, ) } @@ -84,12 +83,12 @@ data class ExtensionNodeList( return listOf( ExtensionEdge( cursor = Cursor("0"), - node = first() + node = first(), ), ExtensionEdge( cursor = Cursor(lastIndex.toString()), - node = last() - ) + node = last(), + ), ) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/MangaType.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/MangaType.kt index ceb80b405..d006f82ee 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/MangaType.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/MangaType.kt @@ -39,7 +39,7 @@ class MangaType( val inLibraryAt: Long, val realUrl: String?, var lastFetchedAt: Long?, // todo - var chaptersLastFetchedAt: Long? // todo + var chaptersLastFetchedAt: Long?, // todo ) : Node { constructor(row: ResultRow) : this( row[MangaTable.id].value, @@ -57,7 +57,7 @@ class MangaType( row[MangaTable.inLibraryAt], row[MangaTable.realUrl], row[MangaTable.lastFetchedAt], - row[MangaTable.chaptersLastFetchedAt] + row[MangaTable.chaptersLastFetchedAt], ) constructor(dataClass: MangaDataClass) : this( @@ -76,7 +76,7 @@ class MangaType( dataClass.inLibraryAt, dataClass.realUrl, dataClass.lastFetchedAt, - dataClass.chaptersLastFetchedAt + dataClass.chaptersLastFetchedAt, ) fun downloadCount(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture { @@ -123,11 +123,11 @@ data class MangaNodeList( override val nodes: List, override val edges: List, override val pageInfo: PageInfo, - override val totalCount: Int + override val totalCount: Int, ) : NodeList() { data class MangaEdge( override val cursor: Cursor, - override val node: MangaType + override val node: MangaType, ) : Edge() companion object { @@ -135,13 +135,14 @@ data class MangaNodeList( return MangaNodeList( nodes = this, edges = getEdges(), - pageInfo = PageInfo( - hasNextPage = false, - hasPreviousPage = false, - startCursor = Cursor(0.toString()), - endCursor = Cursor(lastIndex.toString()) - ), - totalCount = size + pageInfo = + PageInfo( + hasNextPage = false, + hasPreviousPage = false, + startCursor = Cursor(0.toString()), + endCursor = Cursor(lastIndex.toString()), + ), + totalCount = size, ) } @@ -150,12 +151,12 @@ data class MangaNodeList( return listOf( MangaEdge( cursor = Cursor("0"), - node = first() + node = first(), ), MangaEdge( cursor = Cursor(lastIndex.toString()), - node = last() - ) + node = last(), + ), ) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/MetaType.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/MetaType.kt index 6d5fee8d9..8c2fe8593 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/MetaType.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/MetaType.kt @@ -22,12 +22,12 @@ interface MetaType : Node { class ChapterMetaType( override val key: String, override val value: String, - val chapterId: Int + val chapterId: Int, ) : MetaType { constructor(row: ResultRow) : this( key = row[ChapterMetaTable.key], value = row[ChapterMetaTable.value], - chapterId = row[ChapterMetaTable.ref].value + chapterId = row[ChapterMetaTable.ref].value, ) fun chapter(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture { @@ -38,12 +38,12 @@ class ChapterMetaType( class MangaMetaType( override val key: String, override val value: String, - val mangaId: Int + val mangaId: Int, ) : MetaType { constructor(row: ResultRow) : this( key = row[MangaMetaTable.key], value = row[MangaMetaTable.value], - mangaId = row[MangaMetaTable.ref].value + mangaId = row[MangaMetaTable.ref].value, ) fun manga(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture { @@ -54,12 +54,12 @@ class MangaMetaType( class CategoryMetaType( override val key: String, override val value: String, - val categoryId: Int + val categoryId: Int, ) : MetaType { constructor(row: ResultRow) : this( key = row[CategoryMetaTable.key], value = row[CategoryMetaTable.value], - categoryId = row[CategoryMetaTable.ref].value + categoryId = row[CategoryMetaTable.ref].value, ) fun category(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture { @@ -69,11 +69,11 @@ class CategoryMetaType( class GlobalMetaType( override val key: String, - override val value: String + override val value: String, ) : MetaType { constructor(row: ResultRow) : this( key = row[GlobalMetaTable.key], - value = row[GlobalMetaTable.value] + value = row[GlobalMetaTable.value], ) } @@ -81,11 +81,11 @@ data class GlobalMetaNodeList( override val nodes: List, override val edges: List, override val pageInfo: PageInfo, - override val totalCount: Int + override val totalCount: Int, ) : NodeList() { data class MetaEdge( override val cursor: Cursor, - override val node: GlobalMetaType + override val node: GlobalMetaType, ) : Edge() companion object { @@ -93,13 +93,14 @@ data class GlobalMetaNodeList( return GlobalMetaNodeList( nodes = this, edges = getEdges(), - pageInfo = PageInfo( - hasNextPage = false, - hasPreviousPage = false, - startCursor = Cursor(0.toString()), - endCursor = Cursor(lastIndex.toString()) - ), - totalCount = size + pageInfo = + PageInfo( + hasNextPage = false, + hasPreviousPage = false, + startCursor = Cursor(0.toString()), + endCursor = Cursor(lastIndex.toString()), + ), + totalCount = size, ) } @@ -108,12 +109,12 @@ data class GlobalMetaNodeList( return listOf( MetaEdge( cursor = Cursor("0"), - node = first() + node = first(), ), MetaEdge( cursor = Cursor(lastIndex.toString()), - node = last() - ) + node = last(), + ), ) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/SettingsType.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/SettingsType.kt index 22a426ab5..3d16415e6 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/SettingsType.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/SettingsType.kt @@ -69,12 +69,10 @@ interface Settings : Node { data class PartialSettingsType( override val ip: String?, override val port: Int?, - // proxy override val socksProxyEnabled: Boolean?, override val socksProxyHost: String?, override val socksProxyPort: String?, - // webUI override val webUIFlavor: WebUIFlavor?, override val initialOpenInBrowserEnabled: Boolean?, @@ -82,49 +80,40 @@ data class PartialSettingsType( override val electronPath: String?, override val webUIChannel: WebUIChannel?, override val webUIUpdateCheckInterval: Double?, - // downloader override val downloadAsCbz: Boolean?, override val downloadsPath: String?, override val autoDownloadNewChapters: Boolean?, - // requests override val maxSourcesInParallel: Int?, - // updater override val excludeUnreadChapters: Boolean?, override val excludeNotStarted: Boolean?, override val excludeCompleted: Boolean?, override val globalUpdateInterval: Double?, - // Authentication override val basicAuthEnabled: Boolean?, override val basicAuthUsername: String?, override val basicAuthPassword: String?, - // misc override val debugLogsEnabled: Boolean?, override val systemTrayEnabled: Boolean?, - // backup override val backupPath: String?, override val backupTime: String?, override val backupInterval: Int?, override val backupTTL: Int?, - // local source - override val localSourcePath: String? + override val localSourcePath: String?, ) : Settings class SettingsType( override val ip: String, override val port: Int, - // proxy override val socksProxyEnabled: Boolean, override val socksProxyHost: String, override val socksProxyPort: String, - // webUI override val webUIFlavor: WebUIFlavor, override val initialOpenInBrowserEnabled: Boolean, @@ -132,77 +121,61 @@ class SettingsType( override val electronPath: String, override val webUIChannel: WebUIChannel, override val webUIUpdateCheckInterval: Double, - // downloader override val downloadAsCbz: Boolean, override val downloadsPath: String, override val autoDownloadNewChapters: Boolean, - // requests override val maxSourcesInParallel: Int, - // updater override val excludeUnreadChapters: Boolean, override val excludeNotStarted: Boolean, override val excludeCompleted: Boolean, override val globalUpdateInterval: Double, - // Authentication override val basicAuthEnabled: Boolean, override val basicAuthUsername: String, override val basicAuthPassword: String, - // misc override val debugLogsEnabled: Boolean, override val systemTrayEnabled: Boolean, - // backup override val backupPath: String, override val backupTime: String, override val backupInterval: Int, override val backupTTL: Int, - // local source - override val localSourcePath: String + override val localSourcePath: String, ) : Settings { constructor(config: ServerConfig = serverConfig) : this( config.ip.value, config.port.value, - config.socksProxyEnabled.value, config.socksProxyHost.value, config.socksProxyPort.value, - WebUIFlavor.from(config.webUIFlavor.value), config.initialOpenInBrowserEnabled.value, WebUIInterface.from(config.webUIInterface.value), config.electronPath.value, WebUIChannel.from(config.webUIChannel.value), config.webUIUpdateCheckInterval.value, - config.downloadAsCbz.value, config.downloadsPath.value, config.autoDownloadNewChapters.value, - config.maxSourcesInParallel.value, - config.excludeUnreadChapters.value, config.excludeNotStarted.value, config.excludeCompleted.value, config.globalUpdateInterval.value, - config.basicAuthEnabled.value, config.basicAuthUsername.value, config.basicAuthPassword.value, - config.debugLogsEnabled.value, config.systemTrayEnabled.value, - config.backupPath.value, config.backupTime.value, config.backupInterval.value, config.backupTTL.value, - - config.localSourcePath.value + config.localSourcePath.value, ) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/SourceType.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/SourceType.kt index df38728c5..5e0203f0b 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/SourceType.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/SourceType.kt @@ -43,7 +43,7 @@ class SourceType( val supportsLatest: Boolean, val isConfigurable: Boolean, val isNsfw: Boolean, - val displayName: String + val displayName: String, ) : Node { constructor(source: SourceDataClass) : this( id = source.id.toLong(), @@ -53,7 +53,7 @@ class SourceType( supportsLatest = source.supportsLatest, isConfigurable = source.isConfigurable, isNsfw = source.isNsfw, - displayName = source.displayName + displayName = source.displayName, ) constructor(row: ResultRow, sourceExtension: ResultRow, catalogueSource: CatalogueSource) : this( @@ -64,7 +64,7 @@ class SourceType( supportsLatest = catalogueSource.supportsLatest, isConfigurable = catalogueSource is ConfigurableSource, isNsfw = row[SourceTable.isNsfw], - displayName = catalogueSource.toString() + displayName = catalogueSource.toString(), ) fun manga(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture { @@ -84,17 +84,20 @@ class SourceType( } } +@Suppress("ktlint:standard:function-naming") fun SourceType(row: ResultRow): SourceType? { - val catalogueSource = GetCatalogueSource - .getCatalogueSourceOrNull(row[SourceTable.id].value) - ?: return null - val sourceExtension = if (row.hasValue(ExtensionTable.id)) { - row - } else { - ExtensionTable - .select { ExtensionTable.id eq row[SourceTable.extension] } - .first() - } + val catalogueSource = + GetCatalogueSource + .getCatalogueSourceOrNull(row[SourceTable.id].value) + ?: return null + val sourceExtension = + if (row.hasValue(ExtensionTable.id)) { + row + } else { + ExtensionTable + .select { ExtensionTable.id eq row[SourceTable.extension] } + .first() + } return SourceType(row, sourceExtension, catalogueSource) } @@ -103,11 +106,11 @@ data class SourceNodeList( override val nodes: List, override val edges: List, override val pageInfo: PageInfo, - override val totalCount: Int + override val totalCount: Int, ) : NodeList() { data class SourceEdge( override val cursor: Cursor, - override val node: SourceType + override val node: SourceType, ) : Edge() companion object { @@ -115,13 +118,14 @@ data class SourceNodeList( return SourceNodeList( nodes = this, edges = getEdges(), - pageInfo = PageInfo( - hasNextPage = false, - hasPreviousPage = false, - startCursor = Cursor(0.toString()), - endCursor = Cursor(lastIndex.toString()) - ), - totalCount = size + pageInfo = + PageInfo( + hasNextPage = false, + hasPreviousPage = false, + startCursor = Cursor(0.toString()), + endCursor = Cursor(lastIndex.toString()), + ), + totalCount = size, ) } @@ -130,12 +134,12 @@ data class SourceNodeList( return listOf( SourceEdge( cursor = Cursor("0"), - node = first() + node = first(), ), SourceEdge( cursor = Cursor(lastIndex.toString()), - node = last() - ) + node = last(), + ), ) } } @@ -156,7 +160,7 @@ data class CheckBoxFilter(val name: String, val default: Boolean) : Filter enum class TriState { IGNORE, INCLUDE, - EXCLUDE + EXCLUDE, } data class TriStateFilter(val name: String, val default: TriState) : Filter @@ -177,18 +181,20 @@ fun filterOf(filter: SourceFilter<*>): Filter { is SourceFilter.Select<*> -> SelectFilter(filter.name, filter.displayValues, filter.state) is SourceFilter.Text -> TextFilter(filter.name, filter.state) is SourceFilter.CheckBox -> CheckBoxFilter(filter.name, filter.state) - is SourceFilter.TriState -> TriStateFilter( - filter.name, - when (filter.state) { - SourceFilter.TriState.STATE_INCLUDE -> TriState.INCLUDE - SourceFilter.TriState.STATE_EXCLUDE -> TriState.EXCLUDE - else -> TriState.IGNORE - } - ) - is SourceFilter.Group<*> -> GroupFilter( - filter.name, - filter.state.map { filterOf(it as SourceFilter<*>) } - ) + is SourceFilter.TriState -> + TriStateFilter( + filter.name, + when (filter.state) { + SourceFilter.TriState.STATE_INCLUDE -> TriState.INCLUDE + SourceFilter.TriState.STATE_EXCLUDE -> TriState.EXCLUDE + else -> TriState.IGNORE + }, + ) + is SourceFilter.Group<*> -> + GroupFilter( + filter.name, + filter.state.map { filterOf(it as SourceFilter<*>) }, + ) is SourceFilter.Sort -> SortFilter(filter.name, filter.values.asList(), filter.state?.let(SortFilter::SortSelection)) else -> throw RuntimeException("sealed class cannot have more subtypes!") } @@ -239,10 +245,13 @@ data class FilterChange( val checkBoxState: Boolean? = null, val triState: TriState? = null, val sortState: SortFilter.SortSelection? = null, - val groupChange: FilterChange? = null + val groupChange: FilterChange? = null, ) -fun updateFilterList(source: CatalogueSource, changes: List?): FilterList { +fun updateFilterList( + source: CatalogueSource, + changes: List?, +): FilterList { val filterList = source.getFilterList() changes?.forEach { change -> @@ -254,32 +263,42 @@ fun updateFilterList(source: CatalogueSource, changes: List?): Fil // NOOP } is SourceFilter.Select<*> -> { - filter.state = change.selectState ?: throw Exception("Expected select state change at position ${change.position}") + filter.state = change.selectState + ?: throw Exception("Expected select state change at position ${change.position}") } is SourceFilter.Text -> { - filter.state = change.textState ?: throw Exception("Expected text state change at position ${change.position}") + filter.state = change.textState + ?: throw Exception("Expected text state change at position ${change.position}") } is SourceFilter.CheckBox -> { - filter.state = change.checkBoxState ?: throw Exception("Expected checkbox state change at position ${change.position}") + filter.state = change.checkBoxState + ?: throw Exception("Expected checkbox state change at position ${change.position}") } is SourceFilter.TriState -> { - filter.state = change.triState?.ordinal ?: throw Exception("Expected tri state change at position ${change.position}") + filter.state = change.triState?.ordinal + ?: throw Exception("Expected tri state change at position ${change.position}") } is SourceFilter.Group<*> -> { - val groupChange = change.groupChange ?: throw Exception("Expected group change at position ${change.position}") + val groupChange = + change.groupChange + ?: throw Exception("Expected group change at position ${change.position}") when (val groupFilter = filter.state[groupChange.position]) { is SourceFilter.CheckBox -> { - groupFilter.state = groupChange.checkBoxState ?: throw Exception("Expected checkbox state change at position ${change.position}") + groupFilter.state = groupChange.checkBoxState + ?: throw Exception("Expected checkbox state change at position ${change.position}") } is SourceFilter.TriState -> { - groupFilter.state = groupChange.triState?.ordinal ?: throw Exception("Expected tri state change at position ${change.position}") + groupFilter.state = groupChange.triState?.ordinal + ?: throw Exception("Expected tri state change at position ${change.position}") } is SourceFilter.Text -> { - groupFilter.state = groupChange.textState ?: throw Exception("Expected text state change at position ${change.position}") + groupFilter.state = groupChange.textState + ?: throw Exception("Expected text state change at position ${change.position}") } is SourceFilter.Select<*> -> { - groupFilter.state = groupChange.selectState ?: throw Exception("Expected select state change at position ${change.position}") + groupFilter.state = groupChange.selectState + ?: throw Exception("Expected select state change at position ${change.position}") } } } @@ -301,7 +320,7 @@ data class SwitchPreference( val summary: String?, val visible: Boolean, val currentValue: Boolean?, - val default: Boolean + val default: Boolean, ) : Preference data class CheckBoxPreference( @@ -310,7 +329,7 @@ data class CheckBoxPreference( val summary: String?, val visible: Boolean, val currentValue: Boolean?, - val default: Boolean + val default: Boolean, ) : Preference data class EditTextPreference( @@ -322,7 +341,7 @@ data class EditTextPreference( val default: String?, val dialogTitle: String?, val dialogMessage: String?, - val text: String? + val text: String?, ) : Preference data class ListPreference( @@ -333,7 +352,7 @@ data class ListPreference( val currentValue: String?, val default: String?, val entries: List, - val entryValues: List + val entryValues: List, ) : Preference data class MultiSelectListPreference( @@ -346,60 +365,65 @@ data class MultiSelectListPreference( val dialogTitle: String?, val dialogMessage: String?, val entries: List, - val entryValues: List + val entryValues: List, ) : Preference fun preferenceOf(preference: SourcePreference): Preference { return when (preference) { - is SourceSwitchPreference -> SwitchPreference( - preference.key, - preference.title.toString(), - preference.summary?.toString(), - preference.visible, - preference.currentValue as Boolean, - preference.defaultValue as Boolean, - ) - is SourceCheckBoxPreference -> CheckBoxPreference( - preference.key, - preference.title.toString(), - preference.summary?.toString(), - preference.visible, - preference.currentValue as Boolean, - preference.defaultValue as Boolean - ) - is SourceEditTextPreference -> EditTextPreference( - preference.key, - preference.title?.toString(), - preference.summary?.toString(), - preference.visible, - (preference.currentValue as CharSequence?)?.toString(), - (preference.defaultValue as CharSequence?)?.toString(), - preference.dialogTitle?.toString(), - preference.dialogMessage?.toString(), - preference.text - ) - is SourceListPreference -> ListPreference( - preference.key, - preference.title?.toString(), - preference.summary?.toString(), - preference.visible, - (preference.currentValue as CharSequence?)?.toString(), - (preference.defaultValue as CharSequence?)?.toString(), - preference.entries.map { it.toString() }, - preference.entryValues.map { it.toString() } - ) - is SourceMultiSelectListPreference -> MultiSelectListPreference( - preference.key, - preference.title?.toString(), - preference.summary?.toString(), - preference.visible, - (preference.currentValue as Collection<*>?)?.map { it.toString() }, - (preference.defaultValue as Collection<*>?)?.map { it.toString() }, - preference.dialogTitle?.toString(), - preference.dialogMessage?.toString(), - preference.entries.map { it.toString() }, - preference.entryValues.map { it.toString() } - ) + is SourceSwitchPreference -> + SwitchPreference( + preference.key, + preference.title.toString(), + preference.summary?.toString(), + preference.visible, + preference.currentValue as Boolean, + preference.defaultValue as Boolean, + ) + is SourceCheckBoxPreference -> + CheckBoxPreference( + preference.key, + preference.title.toString(), + preference.summary?.toString(), + preference.visible, + preference.currentValue as Boolean, + preference.defaultValue as Boolean, + ) + is SourceEditTextPreference -> + EditTextPreference( + preference.key, + preference.title?.toString(), + preference.summary?.toString(), + preference.visible, + (preference.currentValue as CharSequence?)?.toString(), + (preference.defaultValue as CharSequence?)?.toString(), + preference.dialogTitle?.toString(), + preference.dialogMessage?.toString(), + preference.text, + ) + is SourceListPreference -> + ListPreference( + preference.key, + preference.title?.toString(), + preference.summary?.toString(), + preference.visible, + (preference.currentValue as CharSequence?)?.toString(), + (preference.defaultValue as CharSequence?)?.toString(), + preference.entries.map { it.toString() }, + preference.entryValues.map { it.toString() }, + ) + is SourceMultiSelectListPreference -> + MultiSelectListPreference( + preference.key, + preference.title?.toString(), + preference.summary?.toString(), + preference.visible, + (preference.currentValue as Collection<*>?)?.map { it.toString() }, + (preference.defaultValue as Collection<*>?)?.map { it.toString() }, + preference.dialogTitle?.toString(), + preference.dialogMessage?.toString(), + preference.entries.map { it.toString() }, + preference.entryValues.map { it.toString() }, + ) else -> throw RuntimeException("sealed class cannot have more subtypes!") } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/UpdateType.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/UpdateType.kt index 410089ec8..e91291d11 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/UpdateType.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/UpdateType.kt @@ -16,7 +16,7 @@ class UpdateStatus( val runningJobs: UpdateStatusType, val completeJobs: UpdateStatusType, val failedJobs: UpdateStatusType, - val skippedJobs: UpdateStatusType + val skippedJobs: UpdateStatusType, ) { constructor(status: UpdateStatus) : this( isRunning = status.running, @@ -26,13 +26,13 @@ class UpdateStatus( runningJobs = UpdateStatusType(status.mangaStatusMap[JobStatus.RUNNING]?.map { it.id }.orEmpty()), completeJobs = UpdateStatusType(status.mangaStatusMap[JobStatus.COMPLETE]?.map { it.id }.orEmpty()), failedJobs = UpdateStatusType(status.mangaStatusMap[JobStatus.FAILED]?.map { it.id }.orEmpty()), - skippedJobs = UpdateStatusType(status.mangaStatusMap[JobStatus.SKIPPED]?.map { it.id }.orEmpty()) + skippedJobs = UpdateStatusType(status.mangaStatusMap[JobStatus.SKIPPED]?.map { it.id }.orEmpty()), ) } class UpdateStatusCategoryType( @get:GraphQLIgnore - val categoryIds: List + val categoryIds: List, ) { fun categories(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture { return dataFetchingEnvironment.getValueFromDataLoader("CategoryForIdsDataLoader", categoryIds) @@ -41,7 +41,7 @@ class UpdateStatusCategoryType( class UpdateStatusType( @get:GraphQLIgnore - val mangaIds: List + val mangaIds: List, ) { fun mangas(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture { return dataFetchingEnvironment.getValueFromDataLoader, MangaNodeList>("MangaForIdsDataLoader", mangaIds) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/WebUIUpdateType.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/WebUIUpdateType.kt index 28e1081e1..ba5e112df 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/WebUIUpdateType.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/WebUIUpdateType.kt @@ -3,18 +3,18 @@ package suwayomi.tachidesk.graphql.types data class WebUIUpdateInfo( val channel: String, val tag: String, - val updateAvailable: Boolean + val updateAvailable: Boolean, ) enum class UpdateState { STOPPED, DOWNLOADING, FINISHED, - ERROR + ERROR, } data class WebUIUpdateStatus( val info: WebUIUpdateInfo, val state: UpdateState, - val progress: Int + val progress: Int, ) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/BackupController.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/BackupController.kt index 471575695..b6858ef0d 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/BackupController.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/BackupController.kt @@ -17,154 +17,163 @@ import suwayomi.tachidesk.server.util.withOperation * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ object BackupController { - /** expects a Tachiyomi protobuf backup in the body */ - val protobufImport = handler( - documentWith = { - withOperation { - summary("Restore a backup") - description("Expects a Tachiyomi protobuf backup in the body") - } - }, - behaviorOf = { ctx -> - ctx.future( - future { - ProtoBackupImport.performRestore(ctx.bodyAsInputStream()) + val protobufImport = + handler( + documentWith = { + withOperation { + summary("Restore a backup") + description("Expects a Tachiyomi protobuf backup in the body") } - ) - }, - withResults = { - httpCode(HttpCode.OK) - } - ) + }, + behaviorOf = { ctx -> + ctx.future( + future { + ProtoBackupImport.performRestore(ctx.bodyAsInputStream()) + }, + ) + }, + withResults = { + httpCode(HttpCode.OK) + }, + ) /** expects a Tachiyomi protobuf backup as a file upload, the file must be named "backup.proto.gz" */ - val protobufImportFile = handler( - documentWith = { - withOperation { - summary("Restore a backup file") - description("Expects a Tachiyomi protobuf backup as a file upload, the file must be named \"backup.proto.gz\"") - } - uploadedFile("backup.proto.gz") { - it.description("Protobuf backup") - it.required(true) - } - }, - behaviorOf = { ctx -> - // TODO: rewrite this with ctx.uploadedFiles(), don't call the multipart field "backup.proto.gz" - ctx.future( - future { - ProtoBackupImport.performRestore(ctx.uploadedFile("backup.proto.gz")!!.content) + val protobufImportFile = + handler( + documentWith = { + withOperation { + summary("Restore a backup file") + description("Expects a Tachiyomi protobuf backup as a file upload, the file must be named \"backup.proto.gz\"") + } + uploadedFile("backup.proto.gz") { + it.description("Protobuf backup") + it.required(true) } - ) - }, - withResults = { - httpCode(HttpCode.OK) - httpCode(HttpCode.NOT_FOUND) - } - ) + }, + behaviorOf = { ctx -> + // TODO: rewrite this with ctx.uploadedFiles(), don't call the multipart field "backup.proto.gz" + ctx.future( + future { + ProtoBackupImport.performRestore(ctx.uploadedFile("backup.proto.gz")!!.content) + }, + ) + }, + withResults = { + httpCode(HttpCode.OK) + httpCode(HttpCode.NOT_FOUND) + }, + ) /** returns a Tachiyomi protobuf backup created from the current database as a body */ - val protobufExport = handler( - documentWith = { - withOperation { - summary("Create a backup") - description("Returns a Tachiyomi protobuf backup created from the current database as a body") - } - }, - behaviorOf = { ctx -> - ctx.contentType("application/octet-stream") - ctx.future( - future { - ProtoBackupExport.createBackup( - BackupFlags( - includeManga = true, - includeCategories = true, - includeChapters = true, - includeTracking = true, - includeHistory = true - ) - ) + val protobufExport = + handler( + documentWith = { + withOperation { + summary("Create a backup") + description("Returns a Tachiyomi protobuf backup created from the current database as a body") } - ) - }, - withResults = { - stream(HttpCode.OK) - } - ) + }, + behaviorOf = { ctx -> + ctx.contentType("application/octet-stream") + ctx.future( + future { + ProtoBackupExport.createBackup( + BackupFlags( + includeManga = true, + includeCategories = true, + includeChapters = true, + includeTracking = true, + includeHistory = true, + ), + ) + }, + ) + }, + withResults = { + stream(HttpCode.OK) + }, + ) /** returns a Tachiyomi protobuf backup created from the current database as a file */ - val protobufExportFile = handler( - documentWith = { - withOperation { - summary("Create a backup file") - description("Returns a Tachiyomi protobuf backup created from the current database as a file") - } - }, - behaviorOf = { ctx -> - ctx.contentType("application/octet-stream") + val protobufExportFile = + handler( + documentWith = { + withOperation { + summary("Create a backup file") + description("Returns a Tachiyomi protobuf backup created from the current database as a file") + } + }, + behaviorOf = { ctx -> + ctx.contentType("application/octet-stream") - ctx.header("Content-Disposition", """attachment; filename="${ProtoBackupExport.getBackupFilename()}"""") - ctx.future( - future { - ProtoBackupExport.createBackup( - BackupFlags( - includeManga = true, - includeCategories = true, - includeChapters = true, - includeTracking = true, - includeHistory = true + ctx.header("Content-Disposition", """attachment; filename="${ProtoBackupExport.getBackupFilename()}"""") + ctx.future( + future { + ProtoBackupExport.createBackup( + BackupFlags( + includeManga = true, + includeCategories = true, + includeChapters = true, + includeTracking = true, + includeHistory = true, + ), ) - ) - } - ) - }, - withResults = { - stream(HttpCode.OK) - } - ) + }, + ) + }, + withResults = { + stream(HttpCode.OK) + }, + ) /** Reports missing sources and trackers, expects a Tachiyomi protobuf backup in the body */ - val protobufValidate = handler( - documentWith = { - withOperation { - summary("Validate a backup") - description("Reports missing sources and trackers, expects a Tachiyomi protobuf backup in the body") - } - }, - behaviorOf = { ctx -> - ctx.future( - future { - ProtoBackupValidator.validate(ctx.bodyAsInputStream()) + val protobufValidate = + handler( + documentWith = { + withOperation { + summary("Validate a backup") + description("Reports missing sources and trackers, expects a Tachiyomi protobuf backup in the body") } - ) - }, - withResults = { - json(HttpCode.OK) - } - ) + }, + behaviorOf = { ctx -> + ctx.future( + future { + ProtoBackupValidator.validate(ctx.bodyAsInputStream()) + }, + ) + }, + withResults = { + json(HttpCode.OK) + }, + ) /** Reports missing sources and trackers, expects a Tachiyomi protobuf backup as a file upload, the file must be named "backup.proto.gz" */ - val protobufValidateFile = handler( - documentWith = { - withOperation { - summary("Validate a backup file") - description("Reports missing sources and trackers, expects a Tachiyomi protobuf backup as a file upload, the file must be named \"backup.proto.gz\"") - } - uploadedFile("backup.proto.gz") { - it.description("Protobuf backup") - it.required(true) - } - }, - behaviorOf = { ctx -> - ctx.future( - future { - ProtoBackupValidator.validate(ctx.uploadedFile("backup.proto.gz")!!.content) + val protobufValidateFile = + handler( + documentWith = { + withOperation { + summary("Validate a backup file") + description( + "Reports missing sources and trackers, " + + "expects a Tachiyomi protobuf backup as a file upload, " + + "the file must be named \"backup.proto.gz\"", + ) + } + uploadedFile("backup.proto.gz") { + it.description("Protobuf backup") + it.required(true) } - ) - }, - withResults = { - json(HttpCode.OK) - } - ) + }, + behaviorOf = { ctx -> + ctx.future( + future { + ProtoBackupValidator.validate(ctx.uploadedFile("backup.proto.gz")!!.content) + }, + ) + }, + withResults = { + json(HttpCode.OK) + }, + ) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/CategoryController.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/CategoryController.kt index c4ec3ef3f..d96a6f5bb 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/CategoryController.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/CategoryController.kt @@ -19,136 +19,143 @@ import suwayomi.tachidesk.server.util.withOperation object CategoryController { /** category list */ - val categoryList = handler( - documentWith = { - withOperation { - summary("Category list") - description("get a list of categories") - } - }, - behaviorOf = { ctx -> - ctx.json(Category.getCategoryList()) - }, - withResults = { - json>(HttpCode.OK) - } - ) + val categoryList = + handler( + documentWith = { + withOperation { + summary("Category list") + description("get a list of categories") + } + }, + behaviorOf = { ctx -> + ctx.json(Category.getCategoryList()) + }, + withResults = { + json>(HttpCode.OK) + }, + ) /** category create */ - val categoryCreate = handler( - formParam("name"), - documentWith = { - withOperation { - summary("Category create") - description("Create a category") - } - }, - behaviorOf = { ctx, name -> - if (Category.createCategory(name) != -1) { - ctx.status(200) - } else { - ctx.status(HttpCode.BAD_REQUEST) - } - }, - withResults = { - httpCode(HttpCode.OK) - httpCode(HttpCode.BAD_REQUEST) - } - ) + val categoryCreate = + handler( + formParam("name"), + documentWith = { + withOperation { + summary("Category create") + description("Create a category") + } + }, + behaviorOf = { ctx, name -> + if (Category.createCategory(name) != -1) { + ctx.status(200) + } else { + ctx.status(HttpCode.BAD_REQUEST) + } + }, + withResults = { + httpCode(HttpCode.OK) + httpCode(HttpCode.BAD_REQUEST) + }, + ) /** category modification */ - val categoryModify = handler( - pathParam("categoryId"), - formParam("name"), - formParam("default"), - formParam("includeInUpdate"), - documentWith = { - withOperation { - summary("Category modify") - description("Modify a category") - } - }, - behaviorOf = { ctx, categoryId, name, isDefault, includeInUpdate -> - Category.updateCategory(categoryId, name, isDefault, includeInUpdate) - ctx.status(200) - }, - withResults = { - httpCode(HttpCode.OK) - } - ) + val categoryModify = + handler( + pathParam("categoryId"), + formParam("name"), + formParam("default"), + formParam("includeInUpdate"), + documentWith = { + withOperation { + summary("Category modify") + description("Modify a category") + } + }, + behaviorOf = { ctx, categoryId, name, isDefault, includeInUpdate -> + Category.updateCategory(categoryId, name, isDefault, includeInUpdate) + ctx.status(200) + }, + withResults = { + httpCode(HttpCode.OK) + }, + ) /** category delete */ - val categoryDelete = handler( - pathParam("categoryId"), - documentWith = { - withOperation { - summary("Category delete") - description("Delete a category") - } - }, - behaviorOf = { ctx, categoryId -> - Category.removeCategory(categoryId) - ctx.status(200) - }, - withResults = { - httpCode(HttpCode.OK) - } - ) + val categoryDelete = + handler( + pathParam("categoryId"), + documentWith = { + withOperation { + summary("Category delete") + description("Delete a category") + } + }, + behaviorOf = { ctx, categoryId -> + Category.removeCategory(categoryId) + ctx.status(200) + }, + withResults = { + httpCode(HttpCode.OK) + }, + ) /** returns the manga list associated with a category */ - val categoryMangas = handler( - pathParam("categoryId"), - documentWith = { - withOperation { - summary("Category manga") - description("Returns the manga list associated with a category") - } - }, - behaviorOf = { ctx, categoryId -> - ctx.json(CategoryManga.getCategoryMangaList(categoryId)) - }, - withResults = { - json>(HttpCode.OK) - } - ) + val categoryMangas = + handler( + pathParam("categoryId"), + documentWith = { + withOperation { + summary("Category manga") + description("Returns the manga list associated with a category") + } + }, + behaviorOf = { ctx, categoryId -> + ctx.json(CategoryManga.getCategoryMangaList(categoryId)) + }, + withResults = { + json>(HttpCode.OK) + }, + ) /** category re-ordering */ - val categoryReorder = handler( - formParam("from"), - formParam("to"), - documentWith = { - withOperation { - summary("Category re-ordering") - description("Re-order a category") - } - }, - behaviorOf = { ctx, from, to -> - Category.reorderCategory(from, to) - ctx.status(200) - }, - withResults = { - httpCode(HttpCode.OK) - } - ) + val categoryReorder = + handler( + formParam("from"), + formParam("to"), + documentWith = { + withOperation { + summary("Category re-ordering") + description("Re-order a category") + } + }, + behaviorOf = { ctx, from, to -> + Category.reorderCategory(from, to) + ctx.status(200) + }, + withResults = { + httpCode(HttpCode.OK) + }, + ) /** used to modify a category's meta parameters */ - val meta = handler( - pathParam("categoryId"), - formParam("key"), - formParam("value"), - documentWith = { - withOperation { - summary("Add meta data to category") - description("A simple Key-Value storage in the manga object, you can set values for whatever you want inside it.") - } - }, - behaviorOf = { ctx, categoryId, key, value -> - Category.modifyMeta(categoryId, key, value) - ctx.status(200) - }, - withResults = { - httpCode(HttpCode.OK) - httpCode(HttpCode.NOT_FOUND) - } - ) + val meta = + handler( + pathParam("categoryId"), + formParam("key"), + formParam("value"), + documentWith = { + withOperation { + summary("Add meta data to category") + description("A simple Key-Value storage in the manga object, you can set values for whatever you want inside it.") + } + }, + behaviorOf = { ctx, categoryId, key, value -> + Category.modifyMeta(categoryId, key, value) + ctx.status(200) + }, + withResults = { + httpCode(HttpCode.OK) + httpCode(HttpCode.NOT_FOUND) + }, + ) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/DownloadController.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/DownloadController.kt index 6dac4cb3f..6dd26d366 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/DownloadController.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/DownloadController.kt @@ -39,159 +39,167 @@ object DownloadController { } /** Start the downloader */ - val start = handler( - documentWith = { - withOperation { - summary("Downloader start") - description("Start the downloader") - } - }, - behaviorOf = { - DownloadManager.start() - }, - withResults = { - httpCode(HttpCode.OK) - } - ) + val start = + handler( + documentWith = { + withOperation { + summary("Downloader start") + description("Start the downloader") + } + }, + behaviorOf = { + DownloadManager.start() + }, + withResults = { + httpCode(HttpCode.OK) + }, + ) /** Stop the downloader */ - val stop = handler( - documentWith = { - withOperation { - summary("Downloader stop") - description("Stop the downloader") - } - }, - behaviorOf = { ctx -> - ctx.future( - future { DownloadManager.stop() } - ) - }, - withResults = { - httpCode(HttpCode.OK) - } - ) + val stop = + handler( + documentWith = { + withOperation { + summary("Downloader stop") + description("Stop the downloader") + } + }, + behaviorOf = { ctx -> + ctx.future( + future { DownloadManager.stop() }, + ) + }, + withResults = { + httpCode(HttpCode.OK) + }, + ) /** clear download queue */ - val clear = handler( - documentWith = { - withOperation { - summary("Downloader clear") - description("Clear download queue") - } - }, - behaviorOf = { ctx -> - ctx.future( - future { DownloadManager.clear() } - ) - }, - withResults = { - httpCode(HttpCode.OK) - } - ) + val clear = + handler( + documentWith = { + withOperation { + summary("Downloader clear") + description("Clear download queue") + } + }, + behaviorOf = { ctx -> + ctx.future( + future { DownloadManager.clear() }, + ) + }, + withResults = { + httpCode(HttpCode.OK) + }, + ) /** Queue single chapter for download */ - val queueChapter = handler( - pathParam("chapterIndex"), - pathParam("mangaId"), - documentWith = { - withOperation { - summary("Downloader add single chapter") - description("Queue single chapter for download") - } - }, - behaviorOf = { ctx, chapterIndex, mangaId -> - ctx.future( - future { - DownloadManager.enqueueWithChapterIndex(mangaId, chapterIndex) + val queueChapter = + handler( + pathParam("chapterIndex"), + pathParam("mangaId"), + documentWith = { + withOperation { + summary("Downloader add single chapter") + description("Queue single chapter for download") } - ) - }, - withResults = { - httpCode(HttpCode.OK) - httpCode(HttpCode.NOT_FOUND) - } - ) + }, + behaviorOf = { ctx, chapterIndex, mangaId -> + ctx.future( + future { + DownloadManager.enqueueWithChapterIndex(mangaId, chapterIndex) + }, + ) + }, + withResults = { + httpCode(HttpCode.OK) + httpCode(HttpCode.NOT_FOUND) + }, + ) - val queueChapters = handler( - documentWith = { - withOperation { - summary("Downloader add multiple chapters") - description("Queue multiple chapters for download") - } - body() - }, - behaviorOf = { ctx -> - val inputs = json.decodeFromString(ctx.body()) - ctx.future( - future { - DownloadManager.enqueue(inputs) + val queueChapters = + handler( + documentWith = { + withOperation { + summary("Downloader add multiple chapters") + description("Queue multiple chapters for download") } - ) - }, - withResults = { - httpCode(HttpCode.OK) - } - ) + body() + }, + behaviorOf = { ctx -> + val inputs = json.decodeFromString(ctx.body()) + ctx.future( + future { + DownloadManager.enqueue(inputs) + }, + ) + }, + withResults = { + httpCode(HttpCode.OK) + }, + ) /** delete multiple chapters from download queue */ - val unqueueChapters = handler( - documentWith = { - withOperation { - summary("Downloader remove multiple downloads") - description("Remove multiple chapters downloads from queue") - } - body() - }, - behaviorOf = { ctx -> - val input = json.decodeFromString(ctx.body()) - ctx.future( - future { - DownloadManager.dequeue(input) + val unqueueChapters = + handler( + documentWith = { + withOperation { + summary("Downloader remove multiple downloads") + description("Remove multiple chapters downloads from queue") } - ) - }, - withResults = { - httpCode(HttpCode.OK) - } - ) + body() + }, + behaviorOf = { ctx -> + val input = json.decodeFromString(ctx.body()) + ctx.future( + future { + DownloadManager.dequeue(input) + }, + ) + }, + withResults = { + httpCode(HttpCode.OK) + }, + ) /** delete chapter from download queue */ - val unqueueChapter = handler( - pathParam("chapterIndex"), - pathParam("mangaId"), - documentWith = { - withOperation { - summary("Downloader remove chapter") - description("Delete chapter from download queue") - } - }, - behaviorOf = { ctx, chapterIndex, mangaId -> - DownloadManager.dequeue(chapterIndex, mangaId) + val unqueueChapter = + handler( + pathParam("chapterIndex"), + pathParam("mangaId"), + documentWith = { + withOperation { + summary("Downloader remove chapter") + description("Delete chapter from download queue") + } + }, + behaviorOf = { ctx, chapterIndex, mangaId -> + DownloadManager.dequeue(chapterIndex, mangaId) - ctx.status(200) - }, - withResults = { - httpCode(HttpCode.OK) - } - ) + ctx.status(200) + }, + withResults = { + httpCode(HttpCode.OK) + }, + ) /** clear download queue */ - val reorderChapter = handler( - pathParam("chapterIndex"), - pathParam("mangaId"), - pathParam("to"), - documentWith = { - withOperation { - summary("Downloader reorder chapter") - description("Reorder chapter in download queue") - } - }, - behaviorOf = { _, chapterIndex, mangaId, to -> - DownloadManager.reorder(chapterIndex, mangaId, to) - }, - withResults = { - httpCode(HttpCode.OK) - } - ) + val reorderChapter = + handler( + pathParam("chapterIndex"), + pathParam("mangaId"), + pathParam("to"), + documentWith = { + withOperation { + summary("Downloader reorder chapter") + description("Reorder chapter in download queue") + } + }, + behaviorOf = { _, chapterIndex, mangaId, to -> + DownloadManager.reorder(chapterIndex, mangaId, to) + }, + withResults = { + httpCode(HttpCode.OK) + }, + ) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/ExtensionController.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/ExtensionController.kt index 4d736f166..605f20740 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/ExtensionController.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/ExtensionController.kt @@ -21,143 +21,149 @@ object ExtensionController { private val logger = KotlinLogging.logger {} /** list all extensions */ - val list = handler( - documentWith = { - withOperation { - summary("Extension list") - description("List all extensions") - } - }, - behaviorOf = { ctx -> - ctx.future( - future { - ExtensionsList.getExtensionList() + val list = + handler( + documentWith = { + withOperation { + summary("Extension list") + description("List all extensions") } - ) - }, - withResults = { - json>(HttpCode.OK) - } - ) + }, + behaviorOf = { ctx -> + ctx.future( + future { + ExtensionsList.getExtensionList() + }, + ) + }, + withResults = { + json>(HttpCode.OK) + }, + ) /** install extension identified with "pkgName" */ - val install = handler( - pathParam("pkgName"), - documentWith = { - withOperation { - summary("Extension install") - description("install extension identified with \"pkgName\"") - } - }, - behaviorOf = { ctx, pkgName -> - ctx.future( - future { - Extension.installExtension(pkgName) + val install = + handler( + pathParam("pkgName"), + documentWith = { + withOperation { + summary("Extension install") + description("install extension identified with \"pkgName\"") } - ) - }, - withResults = { - httpCode(HttpCode.CREATED) - httpCode(HttpCode.FOUND) - httpCode(HttpCode.INTERNAL_SERVER_ERROR) - } - ) + }, + behaviorOf = { ctx, pkgName -> + ctx.future( + future { + Extension.installExtension(pkgName) + }, + ) + }, + withResults = { + httpCode(HttpCode.CREATED) + httpCode(HttpCode.FOUND) + httpCode(HttpCode.INTERNAL_SERVER_ERROR) + }, + ) /** install the uploaded apk file */ - val installFile = handler( - documentWith = { - withOperation { - summary("Extension install apk") - description("Install the uploaded apk file") - } - uploadedFile("file") { - it.description("Extension apk") - it.required(true) - } - }, - behaviorOf = { ctx -> - val uploadedFile = ctx.uploadedFile("file")!! - logger.debug { "Uploaded extension file name: " + uploadedFile.filename } - - ctx.future( - future { - Extension.installExternalExtension(uploadedFile.content, uploadedFile.filename) + val installFile = + handler( + documentWith = { + withOperation { + summary("Extension install apk") + description("Install the uploaded apk file") + } + uploadedFile("file") { + it.description("Extension apk") + it.required(true) } - ) - }, - withResults = { - httpCode(HttpCode.CREATED) - httpCode(HttpCode.FOUND) - httpCode(HttpCode.INTERNAL_SERVER_ERROR) - } - ) + }, + behaviorOf = { ctx -> + val uploadedFile = ctx.uploadedFile("file")!! + logger.debug { "Uploaded extension file name: " + uploadedFile.filename } + + ctx.future( + future { + Extension.installExternalExtension(uploadedFile.content, uploadedFile.filename) + }, + ) + }, + withResults = { + httpCode(HttpCode.CREATED) + httpCode(HttpCode.FOUND) + httpCode(HttpCode.INTERNAL_SERVER_ERROR) + }, + ) /** update extension identified with "pkgName" */ - val update = handler( - pathParam("pkgName"), - documentWith = { - withOperation { - summary("Extension update") - description("Update extension identified with \"pkgName\"") - } - }, - behaviorOf = { ctx, pkgName -> - ctx.future( - future { - Extension.updateExtension(pkgName) + val update = + handler( + pathParam("pkgName"), + documentWith = { + withOperation { + summary("Extension update") + description("Update extension identified with \"pkgName\"") } - ) - }, - withResults = { - httpCode(HttpCode.CREATED) - httpCode(HttpCode.FOUND) - httpCode(HttpCode.NOT_FOUND) - httpCode(HttpCode.INTERNAL_SERVER_ERROR) - } - ) + }, + behaviorOf = { ctx, pkgName -> + ctx.future( + future { + Extension.updateExtension(pkgName) + }, + ) + }, + withResults = { + httpCode(HttpCode.CREATED) + httpCode(HttpCode.FOUND) + httpCode(HttpCode.NOT_FOUND) + httpCode(HttpCode.INTERNAL_SERVER_ERROR) + }, + ) /** uninstall extension identified with "pkgName" */ - val uninstall = handler( - pathParam("pkgName"), - documentWith = { - withOperation { - summary("Extension uninstall") - description("Uninstall extension identified with \"pkgName\"") - } - }, - behaviorOf = { ctx, pkgName -> - Extension.uninstallExtension(pkgName) - ctx.status(200) - }, - withResults = { - httpCode(HttpCode.CREATED) - httpCode(HttpCode.FOUND) - httpCode(HttpCode.NOT_FOUND) - httpCode(HttpCode.INTERNAL_SERVER_ERROR) - } - ) + val uninstall = + handler( + pathParam("pkgName"), + documentWith = { + withOperation { + summary("Extension uninstall") + description("Uninstall extension identified with \"pkgName\"") + } + }, + behaviorOf = { ctx, pkgName -> + Extension.uninstallExtension(pkgName) + ctx.status(200) + }, + withResults = { + httpCode(HttpCode.CREATED) + httpCode(HttpCode.FOUND) + httpCode(HttpCode.NOT_FOUND) + httpCode(HttpCode.INTERNAL_SERVER_ERROR) + }, + ) /** icon for extension named `apkName` */ - val icon = handler( - pathParam("apkName"), - documentWith = { - withOperation { - summary("Extension icon") - description("Icon for extension named `apkName`") - } - }, - behaviorOf = { ctx, apkName -> - ctx.future( - future { Extension.getExtensionIcon(apkName) } - .thenApply { - ctx.header("content-type", it.second) - it.first - } - ) - }, - withResults = { - image(HttpCode.OK) - httpCode(HttpCode.NOT_FOUND) - } - ) + val icon = + handler( + pathParam("apkName"), + documentWith = { + withOperation { + summary("Extension icon") + description("Icon for extension named `apkName`") + } + }, + behaviorOf = { ctx, apkName -> + ctx.future( + future { Extension.getExtensionIcon(apkName) } + .thenApply { + ctx.header("content-type", it.second) + it.first + }, + ) + }, + withResults = { + image(HttpCode.OK) + httpCode(HttpCode.NOT_FOUND) + }, + ) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/MangaController.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/MangaController.kt index b984c87a5..b9f1f594c 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/MangaController.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/MangaController.kt @@ -32,367 +32,390 @@ import kotlin.time.Duration.Companion.days object MangaController { private val json by DI.global.instance() - val retrieve = handler( - pathParam("mangaId"), - queryParam("onlineFetch", false), - documentWith = { - withOperation { - summary("Get manga info") - description("Get a manga from the database using a specific id.") - } - }, - behaviorOf = { ctx, mangaId, onlineFetch -> - ctx.future( - future { - Manga.getManga(mangaId, onlineFetch) + val retrieve = + handler( + pathParam("mangaId"), + queryParam("onlineFetch", false), + documentWith = { + withOperation { + summary("Get manga info") + description("Get a manga from the database using a specific id.") } - ) - }, - withResults = { - json(HttpCode.OK) - httpCode(HttpCode.NOT_FOUND) - } - ) + }, + behaviorOf = { ctx, mangaId, onlineFetch -> + ctx.future( + future { + Manga.getManga(mangaId, onlineFetch) + }, + ) + }, + withResults = { + json(HttpCode.OK) + httpCode(HttpCode.NOT_FOUND) + }, + ) /** get manga info with all data filled in */ - val retrieveFull = handler( - pathParam("mangaId"), - queryParam("onlineFetch", false), - documentWith = { - withOperation { - summary("Get manga info with all data filled in") - description("Get a manga from the database using a specific id.") - } - }, - behaviorOf = { ctx, mangaId, onlineFetch -> - ctx.future( - future { - Manga.getMangaFull(mangaId, onlineFetch) + val retrieveFull = + handler( + pathParam("mangaId"), + queryParam("onlineFetch", false), + documentWith = { + withOperation { + summary("Get manga info with all data filled in") + description("Get a manga from the database using a specific id.") } - ) - }, - withResults = { - json(HttpCode.OK) - httpCode(HttpCode.NOT_FOUND) - } - ) + }, + behaviorOf = { ctx, mangaId, onlineFetch -> + ctx.future( + future { + Manga.getMangaFull(mangaId, onlineFetch) + }, + ) + }, + withResults = { + json(HttpCode.OK) + httpCode(HttpCode.NOT_FOUND) + }, + ) /** manga thumbnail */ - val thumbnail = handler( - pathParam("mangaId"), - documentWith = { - withOperation { - summary("Get a manga thumbnail") - description("Get a manga thumbnail from the source or the cache.") - } - }, - behaviorOf = { ctx, mangaId -> - ctx.future( - future { Manga.getMangaThumbnail(mangaId) } - .thenApply { - ctx.header("content-type", it.second) - val httpCacheSeconds = 1.days.inWholeSeconds - ctx.header("cache-control", "max-age=$httpCacheSeconds") - it.first - } - ) - }, - withResults = { - image(HttpCode.OK) - httpCode(HttpCode.NOT_FOUND) - } - ) + val thumbnail = + handler( + pathParam("mangaId"), + documentWith = { + withOperation { + summary("Get a manga thumbnail") + description("Get a manga thumbnail from the source or the cache.") + } + }, + behaviorOf = { ctx, mangaId -> + ctx.future( + future { Manga.getMangaThumbnail(mangaId) } + .thenApply { + ctx.header("content-type", it.second) + val httpCacheSeconds = 1.days.inWholeSeconds + ctx.header("cache-control", "max-age=$httpCacheSeconds") + it.first + }, + ) + }, + withResults = { + image(HttpCode.OK) + httpCode(HttpCode.NOT_FOUND) + }, + ) /** adds the manga to library */ - val addToLibrary = handler( - pathParam("mangaId"), - documentWith = { - withOperation { - summary("Add manga to library") - description("Use a manga id to add the manga to your library.\nWill do nothing if manga is already in your library.") - } - }, - behaviorOf = { ctx, mangaId -> - ctx.future( - future { Library.addMangaToLibrary(mangaId) } - ) - }, - withResults = { - httpCode(HttpCode.OK) - httpCode(HttpCode.NOT_FOUND) - } - ) + val addToLibrary = + handler( + pathParam("mangaId"), + documentWith = { + withOperation { + summary("Add manga to library") + description("Use a manga id to add the manga to your library.\nWill do nothing if manga is already in your library.") + } + }, + behaviorOf = { ctx, mangaId -> + ctx.future( + future { Library.addMangaToLibrary(mangaId) }, + ) + }, + withResults = { + httpCode(HttpCode.OK) + httpCode(HttpCode.NOT_FOUND) + }, + ) /** removes the manga from the library */ - val removeFromLibrary = handler( - pathParam("mangaId"), - documentWith = { - withOperation { - summary("Remove manga to library") - description("Use a manga id to remove the manga to your library.\nWill do nothing if manga not in your library.") - } - }, - behaviorOf = { ctx, mangaId -> - ctx.future( - future { Library.removeMangaFromLibrary(mangaId) } - ) - }, - withResults = { - httpCode(HttpCode.OK) - httpCode(HttpCode.NOT_FOUND) - } - ) + val removeFromLibrary = + handler( + pathParam("mangaId"), + documentWith = { + withOperation { + summary("Remove manga to library") + description("Use a manga id to remove the manga to your library.\nWill do nothing if manga not in your library.") + } + }, + behaviorOf = { ctx, mangaId -> + ctx.future( + future { Library.removeMangaFromLibrary(mangaId) }, + ) + }, + withResults = { + httpCode(HttpCode.OK) + httpCode(HttpCode.NOT_FOUND) + }, + ) /** list manga's categories */ - val categoryList = handler( - pathParam("mangaId"), - documentWith = { - withOperation { - summary("Get a manga's categories") - description("Get the list of categories for this manga") - } - }, - behaviorOf = { ctx, mangaId -> - ctx.json(CategoryManga.getMangaCategories(mangaId)) - }, - withResults = { - json>(HttpCode.OK) - } - ) + val categoryList = + handler( + pathParam("mangaId"), + documentWith = { + withOperation { + summary("Get a manga's categories") + description("Get the list of categories for this manga") + } + }, + behaviorOf = { ctx, mangaId -> + ctx.json(CategoryManga.getMangaCategories(mangaId)) + }, + withResults = { + json>(HttpCode.OK) + }, + ) /** adds the manga to category */ - val addToCategory = handler( - pathParam("mangaId"), - pathParam("categoryId"), - documentWith = { - withOperation { - summary("Add manga to category") - description("Add a manga to a category using their ids.") - } - }, - behaviorOf = { ctx, mangaId, categoryId -> - CategoryManga.addMangaToCategory(mangaId, categoryId) - ctx.status(200) - }, - withResults = { - httpCode(HttpCode.OK) - } - ) + val addToCategory = + handler( + pathParam("mangaId"), + pathParam("categoryId"), + documentWith = { + withOperation { + summary("Add manga to category") + description("Add a manga to a category using their ids.") + } + }, + behaviorOf = { ctx, mangaId, categoryId -> + CategoryManga.addMangaToCategory(mangaId, categoryId) + ctx.status(200) + }, + withResults = { + httpCode(HttpCode.OK) + }, + ) /** removes the manga from the category */ - val removeFromCategory = handler( - pathParam("mangaId"), - pathParam("categoryId"), - documentWith = { - withOperation { - summary("Remove manga from category") - description("Remove a manga from a category using their ids.") - } - }, - behaviorOf = { ctx, mangaId, categoryId -> - CategoryManga.removeMangaFromCategory(mangaId, categoryId) - ctx.status(200) - }, - withResults = { - httpCode(HttpCode.OK) - } - ) + val removeFromCategory = + handler( + pathParam("mangaId"), + pathParam("categoryId"), + documentWith = { + withOperation { + summary("Remove manga from category") + description("Remove a manga from a category using their ids.") + } + }, + behaviorOf = { ctx, mangaId, categoryId -> + CategoryManga.removeMangaFromCategory(mangaId, categoryId) + ctx.status(200) + }, + withResults = { + httpCode(HttpCode.OK) + }, + ) /** used to modify a manga's meta parameters */ - val meta = handler( - pathParam("mangaId"), - formParam("key"), - formParam("value"), - documentWith = { - withOperation { - summary("Add data to manga") - description("A simple Key-Value storage in the manga object, you can set values for whatever you want inside it.") - } - }, - behaviorOf = { ctx, mangaId, key, value -> - Manga.modifyMangaMeta(mangaId, key, value) - ctx.status(200) - }, - withResults = { - httpCode(HttpCode.OK) - httpCode(HttpCode.NOT_FOUND) - } - ) + val meta = + handler( + pathParam("mangaId"), + formParam("key"), + formParam("value"), + documentWith = { + withOperation { + summary("Add data to manga") + description("A simple Key-Value storage in the manga object, you can set values for whatever you want inside it.") + } + }, + behaviorOf = { ctx, mangaId, key, value -> + Manga.modifyMangaMeta(mangaId, key, value) + ctx.status(200) + }, + withResults = { + httpCode(HttpCode.OK) + httpCode(HttpCode.NOT_FOUND) + }, + ) /** get chapter list when showing a manga */ - val chapterList = handler( - pathParam("mangaId"), - queryParam("onlineFetch", false), - documentWith = { - withOperation { - summary("Get manga chapter list") - description("Get the manga chapter list from the database or online. If there is no chapters in the database it fetches the chapters online. Use onlineFetch to update chapter list.") - } - }, - behaviorOf = { ctx, mangaId, onlineFetch -> - ctx.future(future { Chapter.getChapterList(mangaId, onlineFetch) }) - }, - withResults = { - json>(HttpCode.OK) - httpCode(HttpCode.NOT_FOUND) - } - ) + val chapterList = + handler( + pathParam("mangaId"), + queryParam("onlineFetch", false), + documentWith = { + withOperation { + summary("Get manga chapter list") + description( + "Get the manga chapter list from the database or online. " + + "If there is no chapters in the database it fetches the chapters online. " + + "Use onlineFetch to update chapter list.", + ) + } + }, + behaviorOf = { ctx, mangaId, onlineFetch -> + ctx.future(future { Chapter.getChapterList(mangaId, onlineFetch) }) + }, + withResults = { + json>(HttpCode.OK) + httpCode(HttpCode.NOT_FOUND) + }, + ) /** batch edit chapters of single manga */ - val chapterBatch = handler( - pathParam("mangaId"), - documentWith = { - withOperation { - summary("Chapters update multiple") - description("Update multiple chapters of single manga. For batch marking as read, or bookmarking") - } - body() - }, - behaviorOf = { ctx, mangaId -> - val input = json.decodeFromString(ctx.body()) - Chapter.modifyChapters(input, mangaId) - }, - withResults = { - httpCode(HttpCode.OK) - } - ) + val chapterBatch = + handler( + pathParam("mangaId"), + documentWith = { + withOperation { + summary("Chapters update multiple") + description("Update multiple chapters of single manga. For batch marking as read, or bookmarking") + } + body() + }, + behaviorOf = { ctx, mangaId -> + val input = json.decodeFromString(ctx.body()) + Chapter.modifyChapters(input, mangaId) + }, + withResults = { + httpCode(HttpCode.OK) + }, + ) /** batch edit chapters from multiple manga */ - val anyChapterBatch = handler( - documentWith = { - withOperation { - summary("Chapters update multiple") - description("Update multiple chapters on any manga. For batch marking as read, or bookmarking") - } - body() - }, - behaviorOf = { ctx -> - val input = json.decodeFromString(ctx.body()) - Chapter.modifyChapters( - Chapter.MangaChapterBatchEditInput( - input.chapterIds, - null, - input.change + val anyChapterBatch = + handler( + documentWith = { + withOperation { + summary("Chapters update multiple") + description("Update multiple chapters on any manga. For batch marking as read, or bookmarking") + } + body() + }, + behaviorOf = { ctx -> + val input = json.decodeFromString(ctx.body()) + Chapter.modifyChapters( + Chapter.MangaChapterBatchEditInput( + input.chapterIds, + null, + input.change, + ), ) - ) - }, - withResults = { - httpCode(HttpCode.OK) - } - ) + }, + withResults = { + httpCode(HttpCode.OK) + }, + ) /** used to display a chapter, get a chapter in order to show its pages */ - val chapterRetrieve = handler( - pathParam("mangaId"), - pathParam("chapterIndex"), - documentWith = { - withOperation { - summary("Get a chapter") - description("Get the chapter from the manga id and chapter index. It will also retrieve the pages for this chapter.") - } - }, - behaviorOf = { ctx, mangaId, chapterIndex -> - ctx.future(future { getChapterDownloadReadyByIndex(chapterIndex, mangaId) }) - }, - withResults = { - json(HttpCode.OK) - httpCode(HttpCode.NOT_FOUND) - } - ) + val chapterRetrieve = + handler( + pathParam("mangaId"), + pathParam("chapterIndex"), + documentWith = { + withOperation { + summary("Get a chapter") + description("Get the chapter from the manga id and chapter index. It will also retrieve the pages for this chapter.") + } + }, + behaviorOf = { ctx, mangaId, chapterIndex -> + ctx.future(future { getChapterDownloadReadyByIndex(chapterIndex, mangaId) }) + }, + withResults = { + json(HttpCode.OK) + httpCode(HttpCode.NOT_FOUND) + }, + ) /** used to modify a chapter's parameters */ - val chapterModify = handler( - pathParam("mangaId"), - pathParam("chapterIndex"), - formParam("read"), - formParam("bookmarked"), - formParam("markPrevRead"), - formParam("lastPageRead"), - documentWith = { - withOperation { - summary("Modify a chapter") - description("Update user info for a given chapter, such as read status, bookmarked, and more.") - } - }, - behaviorOf = { ctx, mangaId, chapterIndex, read, bookmarked, markPrevRead, lastPageRead -> - Chapter.modifyChapter(mangaId, chapterIndex, read, bookmarked, markPrevRead, lastPageRead) + val chapterModify = + handler( + pathParam("mangaId"), + pathParam("chapterIndex"), + formParam("read"), + formParam("bookmarked"), + formParam("markPrevRead"), + formParam("lastPageRead"), + documentWith = { + withOperation { + summary("Modify a chapter") + description("Update user info for a given chapter, such as read status, bookmarked, and more.") + } + }, + behaviorOf = { ctx, mangaId, chapterIndex, read, bookmarked, markPrevRead, lastPageRead -> + Chapter.modifyChapter(mangaId, chapterIndex, read, bookmarked, markPrevRead, lastPageRead) - ctx.status(200) - }, - withResults = { - httpCode(HttpCode.OK) - } - ) + ctx.status(200) + }, + withResults = { + httpCode(HttpCode.OK) + }, + ) /** delete a downloaded chapter */ - val chapterDelete = handler( - pathParam("mangaId"), - pathParam("chapterIndex"), - documentWith = { - withOperation { - summary("Delete a chapter download") - description("Delete the downloaded chapter and its files.") - } - }, - behaviorOf = { ctx, mangaId, chapterIndex -> - Chapter.deleteChapter(mangaId, chapterIndex) + val chapterDelete = + handler( + pathParam("mangaId"), + pathParam("chapterIndex"), + documentWith = { + withOperation { + summary("Delete a chapter download") + description("Delete the downloaded chapter and its files.") + } + }, + behaviorOf = { ctx, mangaId, chapterIndex -> + Chapter.deleteChapter(mangaId, chapterIndex) - ctx.status(200) - }, - withResults = { - httpCode(HttpCode.OK) - httpCode(HttpCode.NOT_FOUND) - } - ) + ctx.status(200) + }, + withResults = { + httpCode(HttpCode.OK) + httpCode(HttpCode.NOT_FOUND) + }, + ) /** used to modify a chapter's meta parameters */ - val chapterMeta = handler( - pathParam("mangaId"), - pathParam("chapterIndex"), - formParam("key"), - formParam("value"), - documentWith = { - withOperation { - summary("Add data to chapter") - description("A simple Key-Value storage in the chapter object, you can set values for whatever you want inside it.") - } - }, - behaviorOf = { ctx, mangaId, chapterIndex, key, value -> - Chapter.modifyChapterMeta(mangaId, chapterIndex, key, value) + val chapterMeta = + handler( + pathParam("mangaId"), + pathParam("chapterIndex"), + formParam("key"), + formParam("value"), + documentWith = { + withOperation { + summary("Add data to chapter") + description("A simple Key-Value storage in the chapter object, you can set values for whatever you want inside it.") + } + }, + behaviorOf = { ctx, mangaId, chapterIndex, key, value -> + Chapter.modifyChapterMeta(mangaId, chapterIndex, key, value) - ctx.status(200) - }, - withResults = { - httpCode(HttpCode.OK) - httpCode(HttpCode.NOT_FOUND) - } - ) + ctx.status(200) + }, + withResults = { + httpCode(HttpCode.OK) + httpCode(HttpCode.NOT_FOUND) + }, + ) /** get page at index "index" */ - val pageRetrieve = handler( - pathParam("mangaId"), - pathParam("chapterIndex"), - pathParam("index"), - documentWith = { - withOperation { - summary("Get a chapter page") - description("Get a chapter page for a given index. Cache use can be disabled so it only retrieves it directly from the source.") - } - }, - behaviorOf = { ctx, mangaId, chapterIndex, index -> - ctx.future( - future { Page.getPageImage(mangaId, chapterIndex, index) } - .thenApply { - ctx.header("content-type", it.second) - val httpCacheSeconds = 1.days.inWholeSeconds - ctx.header("cache-control", "max-age=$httpCacheSeconds") - it.first - } - ) - }, - withResults = { - image(HttpCode.OK) - httpCode(HttpCode.NOT_FOUND) - } - ) + val pageRetrieve = + handler( + pathParam("mangaId"), + pathParam("chapterIndex"), + pathParam("index"), + documentWith = { + withOperation { + summary("Get a chapter page") + description( + "Get a chapter page for a given index. Cache use can be disabled so it only retrieves it directly from the source.", + ) + } + }, + behaviorOf = { ctx, mangaId, chapterIndex, index -> + ctx.future( + future { Page.getPageImage(mangaId, chapterIndex, index) } + .thenApply { + ctx.header("content-type", it.second) + val httpCacheSeconds = 1.days.inWholeSeconds + ctx.header("cache-control", "max-age=$httpCacheSeconds") + it.first + }, + ) + }, + withResults = { + image(HttpCode.OK) + httpCode(HttpCode.NOT_FOUND) + }, + ) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/SourceController.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/SourceController.kt index 6af9c8230..1bba233b7 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/SourceController.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/SourceController.kt @@ -29,217 +29,229 @@ import suwayomi.tachidesk.server.util.withOperation object SourceController { /** list of sources */ - val list = handler( - documentWith = { - withOperation { - summary("Sources list") - description("List of sources") - } - }, - behaviorOf = { ctx -> - ctx.json(Source.getSourceList()) - }, - withResults = { - json>(HttpCode.OK) - } - ) + val list = + handler( + documentWith = { + withOperation { + summary("Sources list") + description("List of sources") + } + }, + behaviorOf = { ctx -> + ctx.json(Source.getSourceList()) + }, + withResults = { + json>(HttpCode.OK) + }, + ) /** fetch source with id `sourceId` */ - val retrieve = handler( - pathParam("sourceId"), - documentWith = { - withOperation { - summary("Source fetch") - description("Fetch source with id `sourceId`") - } - }, - behaviorOf = { ctx, sourceId -> - ctx.json(Source.getSource(sourceId)!!) - }, - withResults = { - json(HttpCode.OK) - httpCode(HttpCode.NOT_FOUND) - } - ) + val retrieve = + handler( + pathParam("sourceId"), + documentWith = { + withOperation { + summary("Source fetch") + description("Fetch source with id `sourceId`") + } + }, + behaviorOf = { ctx, sourceId -> + ctx.json(Source.getSource(sourceId)!!) + }, + withResults = { + json(HttpCode.OK) + httpCode(HttpCode.NOT_FOUND) + }, + ) /** popular mangas from source with id `sourceId` */ - val popular = handler( - pathParam("sourceId"), - pathParam("pageNum"), - documentWith = { - withOperation { - summary("Source popular manga") - description("Popular mangas from source with id `sourceId`") - } - }, - behaviorOf = { ctx, sourceId, pageNum -> - ctx.future( - future { - MangaList.getMangaList(sourceId, pageNum, popular = true) + val popular = + handler( + pathParam("sourceId"), + pathParam("pageNum"), + documentWith = { + withOperation { + summary("Source popular manga") + description("Popular mangas from source with id `sourceId`") } - ) - }, - withResults = { - json(HttpCode.OK) - } - ) + }, + behaviorOf = { ctx, sourceId, pageNum -> + ctx.future( + future { + MangaList.getMangaList(sourceId, pageNum, popular = true) + }, + ) + }, + withResults = { + json(HttpCode.OK) + }, + ) /** latest mangas from source with id `sourceId` */ - val latest = handler( - pathParam("sourceId"), - pathParam("pageNum"), - documentWith = { - withOperation { - summary("Source latest manga") - description("Latest mangas from source with id `sourceId`") - } - }, - behaviorOf = { ctx, sourceId, pageNum -> - ctx.future( - future { - MangaList.getMangaList(sourceId, pageNum, popular = false) + val latest = + handler( + pathParam("sourceId"), + pathParam("pageNum"), + documentWith = { + withOperation { + summary("Source latest manga") + description("Latest mangas from source with id `sourceId`") } - ) - }, - withResults = { - json(HttpCode.OK) - } - ) + }, + behaviorOf = { ctx, sourceId, pageNum -> + ctx.future( + future { + MangaList.getMangaList(sourceId, pageNum, popular = false) + }, + ) + }, + withResults = { + json(HttpCode.OK) + }, + ) /** fetch preferences of source with id `sourceId` */ - val getPreferences = handler( - pathParam("sourceId"), - documentWith = { - withOperation { - summary("Source preferences") - description("Fetch preferences of source with id `sourceId`") - } - }, - behaviorOf = { ctx, sourceId -> - ctx.json(Source.getSourcePreferences(sourceId)) - }, - withResults = { - json>(HttpCode.OK) - } - ) + val getPreferences = + handler( + pathParam("sourceId"), + documentWith = { + withOperation { + summary("Source preferences") + description("Fetch preferences of source with id `sourceId`") + } + }, + behaviorOf = { ctx, sourceId -> + ctx.json(Source.getSourcePreferences(sourceId)) + }, + withResults = { + json>(HttpCode.OK) + }, + ) /** set one preference of source with id `sourceId` */ - val setPreference = handler( - pathParam("sourceId"), - documentWith = { - withOperation { - summary("Source preference set") - description("Set one preference of source with id `sourceId`") - } - body() - }, - behaviorOf = { ctx, sourceId -> - val preferenceChange = ctx.bodyAsClass(SourcePreferenceChange::class.java) - ctx.json(Source.setSourcePreference(sourceId, preferenceChange.position, preferenceChange.value)) - }, - withResults = { - httpCode(HttpCode.OK) - } - ) + val setPreference = + handler( + pathParam("sourceId"), + documentWith = { + withOperation { + summary("Source preference set") + description("Set one preference of source with id `sourceId`") + } + body() + }, + behaviorOf = { ctx, sourceId -> + val preferenceChange = ctx.bodyAsClass(SourcePreferenceChange::class.java) + ctx.json(Source.setSourcePreference(sourceId, preferenceChange.position, preferenceChange.value)) + }, + withResults = { + httpCode(HttpCode.OK) + }, + ) /** fetch filters of source with id `sourceId` */ - val getFilters = handler( - pathParam("sourceId"), - queryParam("reset", false), - documentWith = { - withOperation { - summary("Source filters") - description("Fetch filters of source with id `sourceId`") - } - }, - behaviorOf = { ctx, sourceId, reset -> - ctx.json(Search.getFilterList(sourceId, reset)) - }, - withResults = { - json>(HttpCode.OK) - } - ) + val getFilters = + handler( + pathParam("sourceId"), + queryParam("reset", false), + documentWith = { + withOperation { + summary("Source filters") + description("Fetch filters of source with id `sourceId`") + } + }, + behaviorOf = { ctx, sourceId, reset -> + ctx.json(Search.getFilterList(sourceId, reset)) + }, + withResults = { + json>(HttpCode.OK) + }, + ) private val json by DI.global.instance() /** change filters of source with id `sourceId` */ - val setFilters = handler( - pathParam("sourceId"), - documentWith = { - withOperation { - summary("Source filters set") - description("Change filters of source with id `sourceId`") - } - body() - body>() - }, - behaviorOf = { ctx, sourceId -> - val filterChange = try { - json.decodeFromString>(ctx.body()) - } catch (e: Exception) { - listOf(json.decodeFromString(ctx.body())) - } + val setFilters = + handler( + pathParam("sourceId"), + documentWith = { + withOperation { + summary("Source filters set") + description("Change filters of source with id `sourceId`") + } + body() + body>() + }, + behaviorOf = { ctx, sourceId -> + val filterChange = + try { + json.decodeFromString>(ctx.body()) + } catch (e: Exception) { + listOf(json.decodeFromString(ctx.body())) + } - ctx.json(Search.setFilter(sourceId, filterChange)) - }, - withResults = { - httpCode(HttpCode.OK) - } - ) + ctx.json(Search.setFilter(sourceId, filterChange)) + }, + withResults = { + httpCode(HttpCode.OK) + }, + ) /** single source search */ - val searchSingle = handler( - pathParam("sourceId"), - queryParam("searchTerm", ""), - queryParam("pageNum", 1), - documentWith = { - withOperation { - summary("Source search") - description("Single source search") - } - }, - behaviorOf = { ctx, sourceId, searchTerm, pageNum -> - ctx.future(future { Search.sourceSearch(sourceId, searchTerm, pageNum) }) - }, - withResults = { - json(HttpCode.OK) - } - ) + val searchSingle = + handler( + pathParam("sourceId"), + queryParam("searchTerm", ""), + queryParam("pageNum", 1), + documentWith = { + withOperation { + summary("Source search") + description("Single source search") + } + }, + behaviorOf = { ctx, sourceId, searchTerm, pageNum -> + ctx.future(future { Search.sourceSearch(sourceId, searchTerm, pageNum) }) + }, + withResults = { + json(HttpCode.OK) + }, + ) /** quick search single source filter */ - val quickSearchSingle = handler( - pathParam("sourceId"), - queryParam("pageNum", 1), - documentWith = { - withOperation { - summary("Source manga quick search") - description("Returns list of manga from source matching posted searchTerm and filter") - } - body() - }, - behaviorOf = { ctx, sourceId, pageNum -> - val filter = json.decodeFromString(ctx.body()) - ctx.future(future { Search.sourceFilter(sourceId, pageNum, filter) }) - }, - withResults = { - json(HttpCode.OK) - } - ) + val quickSearchSingle = + handler( + pathParam("sourceId"), + queryParam("pageNum", 1), + documentWith = { + withOperation { + summary("Source manga quick search") + description("Returns list of manga from source matching posted searchTerm and filter") + } + body() + }, + behaviorOf = { ctx, sourceId, pageNum -> + val filter = json.decodeFromString(ctx.body()) + ctx.future(future { Search.sourceFilter(sourceId, pageNum, filter) }) + }, + withResults = { + json(HttpCode.OK) + }, + ) /** all source search */ - val searchAll = handler( - pathParam("searchTerm"), - documentWith = { - withOperation { - summary("Source global search") - description("All source search") - } - }, - behaviorOf = { ctx, searchTerm -> // TODO - ctx.json(Search.sourceGlobalSearch(searchTerm)) - }, - withResults = { - httpCode(HttpCode.OK) - } - ) + val searchAll = + handler( + pathParam("searchTerm"), + documentWith = { + withOperation { + summary("Source global search") + description("All source search") + } + }, + behaviorOf = { ctx, searchTerm -> // TODO + ctx.json(Search.sourceGlobalSearch(searchTerm)) + }, + withResults = { + httpCode(HttpCode.OK) + }, + ) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/UpdateController.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/UpdateController.kt index 72ed09f11..7f762b09f 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/UpdateController.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/UpdateController.kt @@ -30,25 +30,26 @@ object UpdateController { private val logger = KotlinLogging.logger { } /** get recently updated manga chapters */ - val recentChapters = handler( - pathParam("pageNum"), - documentWith = { - withOperation { - summary("Updates fetch") - description("Get recently updated manga chapters") - } - }, - behaviorOf = { ctx, pageNum -> - ctx.future( - future { - Chapter.getRecentChapters(pageNum) + val recentChapters = + handler( + pathParam("pageNum"), + documentWith = { + withOperation { + summary("Updates fetch") + description("Get recently updated manga chapters") } - ) - }, - withResults = { - json(HttpCode.OK) - } - ) + }, + behaviorOf = { ctx, pageNum -> + ctx.future( + future { + Chapter.getRecentChapters(pageNum) + }, + ) + }, + withResults = { + json(HttpCode.OK) + }, + ) /** * Class made for handling return type in the documentation for [recentChapters], @@ -56,42 +57,43 @@ object UpdateController { */ private class PagedMangaChapterListDataClass : PaginatedList(emptyList(), false) - val categoryUpdate = handler( - formParam("categoryId"), - documentWith = { - withOperation { - summary("Updater start") - description("Starts the updater") - } - }, - behaviorOf = { ctx, categoryId -> - val updater by DI.global.instance() - if (categoryId == null) { - logger.info { "Adding Library to Update Queue" } - updater.addCategoriesToUpdateQueue( - Category.getCategoryList(), - clear = true, - forceAll = false - ) - } else { - val category = Category.getCategoryById(categoryId) - if (category != null) { + val categoryUpdate = + handler( + formParam("categoryId"), + documentWith = { + withOperation { + summary("Updater start") + description("Starts the updater") + } + }, + behaviorOf = { ctx, categoryId -> + val updater by DI.global.instance() + if (categoryId == null) { + logger.info { "Adding Library to Update Queue" } updater.addCategoriesToUpdateQueue( - listOf(category), + Category.getCategoryList(), clear = true, - forceAll = true + forceAll = false, ) } else { - logger.info { "No Category found" } - ctx.status(HttpCode.BAD_REQUEST) + val category = Category.getCategoryById(categoryId) + if (category != null) { + updater.addCategoriesToUpdateQueue( + listOf(category), + clear = true, + forceAll = true, + ) + } else { + logger.info { "No Category found" } + ctx.status(HttpCode.BAD_REQUEST) + } } - } - }, - withResults = { - httpCode(HttpCode.OK) - httpCode(HttpCode.BAD_REQUEST) - } - ) + }, + withResults = { + httpCode(HttpCode.OK) + httpCode(HttpCode.BAD_REQUEST) + }, + ) fun categoryUpdateWS(ws: WsConfig) { ws.onConnect { ctx -> @@ -105,42 +107,44 @@ object UpdateController { } } - val updateSummary = handler( - documentWith = { - withOperation { - summary("Updater summary") - description("Gets the latest updater summary") - } - }, - behaviorOf = { ctx -> - val updater by DI.global.instance() - ctx.json(updater.status.value) - }, - withResults = { - json(HttpCode.OK) - } - ) + val updateSummary = + handler( + documentWith = { + withOperation { + summary("Updater summary") + description("Gets the latest updater summary") + } + }, + behaviorOf = { ctx -> + val updater by DI.global.instance() + ctx.json(updater.status.value) + }, + withResults = { + json(HttpCode.OK) + }, + ) - val reset = handler( - documentWith = { - withOperation { - summary("Updater reset") - description("Stops and resets the Updater") - } - }, - behaviorOf = { ctx -> - val updater by DI.global.instance() - logger.info { "Resetting Updater" } - ctx.future( - future { - updater.reset() - }.thenApply { - ctx.status(HttpCode.OK) + val reset = + handler( + documentWith = { + withOperation { + summary("Updater reset") + description("Stops and resets the Updater") } - ) - }, - withResults = { - httpCode(HttpCode.OK) - } - ) + }, + behaviorOf = { ctx -> + val updater by DI.global.instance() + logger.info { "Resetting Updater" } + ctx.future( + future { + updater.reset() + }.thenApply { + ctx.status(HttpCode.OK) + }, + ) + }, + withResults = { + httpCode(HttpCode.OK) + }, + ) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Category.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Category.kt index f222a2358..0e04a2d1e 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Category.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Category.kt @@ -35,10 +35,11 @@ object Category { return transaction { if (CategoryTable.select { CategoryTable.name eq name }.firstOrNull() == null) { - val newCategoryId = CategoryTable.insertAndGetId { - it[CategoryTable.name] = name - it[CategoryTable.order] = Int.MAX_VALUE - }.value + val newCategoryId = + CategoryTable.insertAndGetId { + it[CategoryTable.name] = name + it[CategoryTable.order] = Int.MAX_VALUE + }.value normalizeCategories() @@ -49,10 +50,20 @@ object Category { } } - fun updateCategory(categoryId: Int, name: String?, isDefault: Boolean?, includeInUpdate: Int?) { + fun updateCategory( + categoryId: Int, + name: String?, + isDefault: Boolean?, + includeInUpdate: Int?, + ) { transaction { CategoryTable.update({ CategoryTable.id eq categoryId }) { - if (categoryId != DEFAULT_CATEGORY_ID && name != null && !name.equals(DEFAULT_CATEGORY_NAME, ignoreCase = true)) it[CategoryTable.name] = name + if ( + categoryId != DEFAULT_CATEGORY_ID && name != null && + !name.equals(DEFAULT_CATEGORY_NAME, ignoreCase = true) + ) { + it[CategoryTable.name] = name + } if (categoryId != DEFAULT_CATEGORY_ID && isDefault != null) it[CategoryTable.isDefault] = isDefault if (includeInUpdate != null) it[CategoryTable.includeInUpdate] = includeInUpdate } @@ -62,10 +73,16 @@ object Category { /** * Move the category from order number `from` to `to` */ - fun reorderCategory(from: Int, to: Int) { + fun reorderCategory( + from: Int, + to: Int, + ) { if (from == 0 || to == 0) return transaction { - val categories = CategoryTable.select { CategoryTable.id neq DEFAULT_CATEGORY_ID }.orderBy(CategoryTable.order to SortOrder.ASC).toMutableList() + val categories = + CategoryTable.select { + CategoryTable.id neq DEFAULT_CATEGORY_ID + }.orderBy(CategoryTable.order to SortOrder.ASC).toMutableList() categories.add(to - 1, categories.removeAt(from - 1)) categories.forEachIndexed { index, cat -> CategoryTable.update({ CategoryTable.id eq cat[CategoryTable.id].value }) { @@ -98,14 +115,15 @@ object Category { } } - private fun needsDefaultCategory() = transaction { - MangaTable - .leftJoin(CategoryMangaTable) - .select { MangaTable.inLibrary eq true } - .andWhere { CategoryMangaTable.manga.isNull() } - .empty() - .not() - } + private fun needsDefaultCategory() = + transaction { + MangaTable + .leftJoin(CategoryMangaTable) + .select { MangaTable.inLibrary eq true } + .andWhere { CategoryMangaTable.manga.isNull() } + .empty() + .not() + } const val DEFAULT_CATEGORY_ID = 0 const val DEFAULT_CATEGORY_NAME = "Default" @@ -156,11 +174,16 @@ object Category { } } - fun modifyMeta(categoryId: Int, key: String, value: String) { + fun modifyMeta( + categoryId: Int, + key: String, + value: String, + ) { transaction { - val meta = transaction { - CategoryMetaTable.select { (CategoryMetaTable.ref eq categoryId) and (CategoryMetaTable.key eq key) } - }.firstOrNull() + val meta = + transaction { + CategoryMetaTable.select { (CategoryMetaTable.ref eq categoryId) and (CategoryMetaTable.key eq key) } + }.firstOrNull() if (meta == null) { CategoryMetaTable.insert { diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/CategoryManga.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/CategoryManga.kt index db6dd1a9d..1c8b865b4 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/CategoryManga.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/CategoryManga.kt @@ -31,9 +31,16 @@ import suwayomi.tachidesk.manga.model.table.MangaTable import suwayomi.tachidesk.manga.model.table.toDataClass object CategoryManga { - fun addMangaToCategory(mangaId: Int, categoryId: Int) { + fun addMangaToCategory( + mangaId: Int, + categoryId: Int, + ) { if (categoryId == DEFAULT_CATEGORY_ID) return - fun notAlreadyInCategory() = CategoryMangaTable.select { (CategoryMangaTable.category eq categoryId) and (CategoryMangaTable.manga eq mangaId) }.isEmpty() + + fun notAlreadyInCategory() = + CategoryMangaTable.select { + (CategoryMangaTable.category eq categoryId) and (CategoryMangaTable.manga eq mangaId) + }.isEmpty() transaction { if (notAlreadyInCategory()) { @@ -45,7 +52,10 @@ object CategoryManga { } } - fun removeMangaFromCategory(mangaId: Int, categoryId: Int) { + fun removeMangaFromCategory( + mangaId: Int, + categoryId: Int, + ) { if (categoryId == DEFAULT_CATEGORY_ID) return transaction { CategoryMangaTable.deleteWhere { (CategoryMangaTable.category eq categoryId) and (CategoryMangaTable.manga eq mangaId) } @@ -57,12 +67,18 @@ object CategoryManga { */ fun getCategoryMangaList(categoryId: Int): List { // Select the required columns from the MangaTable and add the aggregate functions to compute unread, download, and chapter counts - val unreadCount = wrapAsExpression( - ChapterTable.slice(ChapterTable.id.count()).select((ChapterTable.isRead eq false) and (ChapterTable.manga eq MangaTable.id)) - ) - val downloadedCount = wrapAsExpression( - ChapterTable.slice(ChapterTable.id.count()).select((ChapterTable.isDownloaded eq true) and (ChapterTable.manga eq MangaTable.id)) - ) + val unreadCount = + wrapAsExpression( + ChapterTable.slice( + ChapterTable.id.count(), + ).select((ChapterTable.isRead eq false) and (ChapterTable.manga eq MangaTable.id)), + ) + val downloadedCount = + wrapAsExpression( + ChapterTable.slice( + ChapterTable.id.count(), + ).select((ChapterTable.isDownloaded eq true) and (ChapterTable.manga eq MangaTable.id)), + ) val chapterCount = ChapterTable.id.count().alias("chapter_count") val lastReadAt = ChapterTable.lastReadAt.max().alias("last_read_at") @@ -80,19 +96,20 @@ object CategoryManga { return transaction { // Fetch data from the MangaTable and join with the CategoryMangaTable, if a category is specified - val query = if (categoryId == DEFAULT_CATEGORY_ID) { - MangaTable - .leftJoin(ChapterTable, { MangaTable.id }, { ChapterTable.manga }) - .leftJoin(CategoryMangaTable) - .slice(columns = selectedColumns) - .select { (MangaTable.inLibrary eq true) and CategoryMangaTable.category.isNull() } - } else { - MangaTable - .innerJoin(CategoryMangaTable) - .leftJoin(ChapterTable, { MangaTable.id }, { ChapterTable.manga }) - .slice(columns = selectedColumns) - .select { (MangaTable.inLibrary eq true) and (CategoryMangaTable.category eq categoryId) } - } + val query = + if (categoryId == DEFAULT_CATEGORY_ID) { + MangaTable + .leftJoin(ChapterTable, { MangaTable.id }, { ChapterTable.manga }) + .leftJoin(CategoryMangaTable) + .slice(columns = selectedColumns) + .select { (MangaTable.inLibrary eq true) and CategoryMangaTable.category.isNull() } + } else { + MangaTable + .innerJoin(CategoryMangaTable) + .leftJoin(ChapterTable, { MangaTable.id }, { ChapterTable.manga }) + .slice(columns = selectedColumns) + .select { (MangaTable.inLibrary eq true) and (CategoryMangaTable.category eq categoryId) } + } // Join with the ChapterTable to fetch the last read chapter for each manga query.groupBy(*MangaTable.columns.toTypedArray()).map(transform) @@ -104,7 +121,9 @@ object CategoryManga { */ fun getMangaCategories(mangaId: Int): List { return transaction { - CategoryMangaTable.innerJoin(CategoryTable).select { CategoryMangaTable.manga eq mangaId }.orderBy(CategoryTable.order to SortOrder.ASC).map { + CategoryMangaTable.innerJoin(CategoryTable).select { + CategoryMangaTable.manga eq mangaId + }.orderBy(CategoryTable.order to SortOrder.ASC).map { CategoryTable.toDataClass(it) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Chapter.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Chapter.kt index 9cf2f9050..fdc8f8cce 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Chapter.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Chapter.kt @@ -45,7 +45,10 @@ object Chapter { private val logger = KotlinLogging.logger { } /** get chapter list when showing a manga */ - suspend fun getChapterList(mangaId: Int, onlineFetch: Boolean = false): List { + suspend fun getChapterList( + mangaId: Int, + onlineFetch: Boolean = false, + ): List { return if (onlineFetch) { getSourceChapters(mangaId) } else { @@ -68,10 +71,11 @@ object Chapter { private suspend fun getSourceChapters(mangaId: Int): List { val chapterList = fetchChapterList(mangaId) - val dbChapterMap = transaction { - ChapterTable.select { ChapterTable.manga eq mangaId } - .associateBy({ it[ChapterTable.url] }, { it }) - } + val dbChapterMap = + transaction { + ChapterTable.select { ChapterTable.manga eq mangaId } + .associateBy({ it[ChapterTable.url] }, { it }) + } val chapterIds = chapterList.map { dbChapterMap.getValue(it.url)[ChapterTable.id] } val chapterMetas = getChaptersMetaMaps(chapterIds) @@ -88,21 +92,17 @@ object Chapter { chapterNumber = it.chapter_number, scanlator = it.scanlator, mangaId = mangaId, - read = dbChapter[ChapterTable.isRead], bookmarked = dbChapter[ChapterTable.isBookmarked], lastPageRead = dbChapter[ChapterTable.lastPageRead], lastReadAt = dbChapter[ChapterTable.lastReadAt], - index = chapterList.size - index, fetchedAt = dbChapter[ChapterTable.fetchedAt], realUrl = dbChapter[ChapterTable.realUrl], downloaded = dbChapter[ChapterTable.isDownloaded], - pageCount = dbChapter[ChapterTable.pageCount], - chapterCount = chapterList.size, - meta = chapterMetas.getValue(dbChapter[ChapterTable.id]) + meta = chapterMetas.getValue(dbChapter[ChapterTable.id]), ) } } @@ -111,10 +111,11 @@ object Chapter { val manga = getManga(mangaId) val source = getCatalogueSourceOrStub(manga.sourceId.toLong()) - val sManga = SManga.create().apply { - title = manga.title - url = manga.url - } + val sManga = + SManga.create().apply { + title = manga.title + url = manga.url + } val numberOfCurrentChapters = getCountOfMangaChapters(mangaId) val chapterList = source.getChapterList(sManga) @@ -143,9 +144,10 @@ object Chapter { it[fetchedAt] = now++ it[ChapterTable.manga] = mangaId - it[realUrl] = runCatching { - (source as? HttpSource)?.getChapterUrl(fetchedChapter) - }.getOrNull() + it[realUrl] = + runCatching { + (source as? HttpSource)?.getChapterUrl(fetchedChapter) + }.getOrNull() } } else { ChapterTable.update({ ChapterTable.url eq fetchedChapter.url }) { @@ -157,9 +159,10 @@ object Chapter { it[sourceOrder] = index + 1 it[ChapterTable.manga] = mangaId - it[realUrl] = runCatching { - (source as? HttpSource)?.getChapterUrl(fetchedChapter) - }.getOrNull() + it[realUrl] = + runCatching { + (source as? HttpSource)?.getChapterUrl(fetchedChapter) + }.getOrNull() } } } @@ -169,18 +172,20 @@ object Chapter { } } - val newChapters = transaction { - ChapterTable.select { ChapterTable.manga eq mangaId } - .orderBy(ChapterTable.sourceOrder to SortOrder.DESC).toList() - } + val newChapters = + transaction { + ChapterTable.select { ChapterTable.manga eq mangaId } + .orderBy(ChapterTable.sourceOrder to SortOrder.DESC).toList() + } // clear any orphaned/duplicate chapters that are in the db but not in `chapterList` val dbChapterCount = newChapters.count() if (dbChapterCount > chapterList.size) { // we got some clean up due - val dbChapterList = transaction { - ChapterTable.select { ChapterTable.manga eq mangaId } - .orderBy(ChapterTable.url to ASC).toList() - } + val dbChapterList = + transaction { + ChapterTable.select { ChapterTable.manga eq mangaId } + .orderBy(ChapterTable.url to ASC).toList() + } val chapterUrls = chapterList.map { it.url }.toSet() dbChapterList.forEachIndexed { index, dbChapter -> @@ -203,7 +208,11 @@ object Chapter { return chapterList } - private fun downloadNewChapters(mangaId: Int, prevNumberOfChapters: Int, updatedChapterList: List) { + private fun downloadNewChapters( + mangaId: Int, + prevNumberOfChapters: Int, + updatedChapterList: List, + ) { // convert numbers to be index based val currentNumberOfChapters = (prevNumberOfChapters - 1).coerceAtLeast(0) val updatedNumberOfChapters = (updatedChapterList.size - 1).coerceAtLeast(0) @@ -224,8 +233,9 @@ object Chapter { // make sure to only consider the latest chapters. e.g. old unread chapters should be ignored val latestReadChapterIndex = updatedChapterList.indexOfFirst { it[ChapterTable.isRead] }.takeIf { it > -1 } ?: return - val unreadChapters = updatedChapterList.subList(numberOfNewChapters, latestReadChapterIndex) - .filter { !it[ChapterTable.isRead] } + val unreadChapters = + updatedChapterList.subList(numberOfNewChapters, latestReadChapterIndex) + .filter { !it[ChapterTable.isRead] } val skipDueToUnreadChapters = serverConfig.excludeEntryWithUnreadChapters.value && unreadChapters.isNotEmpty() if (skipDueToUnreadChapters) { @@ -235,9 +245,10 @@ object Chapter { val firstChapterToDownloadIndex = (numberOfNewChapters - serverConfig.autoDownloadAheadLimit.value).coerceAtLeast(0) - val chapterIdsToDownload = newChapters.subList(firstChapterToDownloadIndex, numberOfNewChapters) - .filter { !it[ChapterTable.isRead] && !it[ChapterTable.isDownloaded] } - .map { it[ChapterTable.id].value } + val chapterIdsToDownload = + newChapters.subList(firstChapterToDownloadIndex, numberOfNewChapters) + .filter { !it[ChapterTable.isRead] && !it[ChapterTable.isDownloaded] } + .map { it[ChapterTable.id].value } if (chapterIdsToDownload.isEmpty()) { return @@ -254,7 +265,7 @@ object Chapter { isRead: Boolean?, isBookmarked: Boolean?, markPrevRead: Boolean?, - lastPageRead: Int? + lastPageRead: Int?, ) { transaction { if (listOf(isRead, isBookmarked, lastPageRead).any { it != null }) { @@ -285,23 +296,26 @@ object Chapter { val isRead: Boolean? = null, val isBookmarked: Boolean? = null, val lastPageRead: Int? = null, - val delete: Boolean? = null + val delete: Boolean? = null, ) @Serializable data class MangaChapterBatchEditInput( val chapterIds: List? = null, val chapterIndexes: List? = null, - val change: ChapterChange? + val change: ChapterChange?, ) @Serializable data class ChapterBatchEditInput( val chapterIds: List? = null, - val change: ChapterChange? + val change: ChapterChange?, ) - fun modifyChapters(input: MangaChapterBatchEditInput, mangaId: Int? = null) { + fun modifyChapters( + input: MangaChapterBatchEditInput, + mangaId: Int? = null, + ) { // Make sure change is defined if (input.change == null) return val (isRead, isBookmarked, lastPageRead, delete) = input.change @@ -315,25 +329,26 @@ object Chapter { if (listOfNotNull(isRead, isBookmarked, lastPageRead).isEmpty()) return // Make sure some filter is defined - val condition = when { - mangaId != null -> - // mangaId is not null, scope query under manga - when { - input.chapterIds != null -> - Op.build { (ChapterTable.manga eq mangaId) and (ChapterTable.id inList input.chapterIds) } - input.chapterIndexes != null -> - Op.build { (ChapterTable.manga eq mangaId) and (ChapterTable.sourceOrder inList input.chapterIndexes) } - else -> null - } - else -> { - // mangaId is null, only chapterIndexes is valid for this case - when { - input.chapterIds != null -> - Op.build { (ChapterTable.id inList input.chapterIds) } - else -> null + val condition = + when { + mangaId != null -> + // mangaId is not null, scope query under manga + when { + input.chapterIds != null -> + Op.build { (ChapterTable.manga eq mangaId) and (ChapterTable.id inList input.chapterIds) } + input.chapterIndexes != null -> + Op.build { (ChapterTable.manga eq mangaId) and (ChapterTable.sourceOrder inList input.chapterIndexes) } + else -> null + } + else -> { + // mangaId is null, only chapterIndexes is valid for this case + when { + input.chapterIds != null -> + Op.build { (ChapterTable.id inList input.chapterIds) } + else -> null + } } - } - } ?: return + } ?: return transaction { val now = Instant.now().epochSecond @@ -368,7 +383,12 @@ object Chapter { } } - fun modifyChapterMeta(mangaId: Int, chapterIndex: Int, key: String, value: String) { + fun modifyChapterMeta( + mangaId: Int, + chapterIndex: Int, + key: String, + value: String, + ) { transaction { val chapterId = ChapterTable.select { (ChapterTable.manga eq mangaId) and (ChapterTable.sourceOrder eq chapterIndex) } @@ -377,7 +397,11 @@ object Chapter { } } - fun modifyChapterMeta(chapterId: Int, key: String, value: String) { + fun modifyChapterMeta( + chapterId: Int, + key: String, + value: String, + ) { transaction { val meta = ChapterMetaTable.select { (ChapterMetaTable.ref eq chapterId) and (ChapterMetaTable.key eq key) } @@ -397,7 +421,10 @@ object Chapter { } } - fun deleteChapter(mangaId: Int, chapterIndex: Int) { + fun deleteChapter( + mangaId: Int, + chapterIndex: Int, + ) { transaction { val chapterId = ChapterTable.select { (ChapterTable.manga eq mangaId) and (ChapterTable.sourceOrder eq chapterIndex) } @@ -411,19 +438,23 @@ object Chapter { } } - private fun deleteChapters(input: MangaChapterBatchEditInput, mangaId: Int? = null) { + private fun deleteChapters( + input: MangaChapterBatchEditInput, + mangaId: Int? = null, + ) { if (input.chapterIds != null) { deleteChapters(input.chapterIds) } else if (input.chapterIndexes != null && mangaId != null) { transaction { - val chapterIds = ChapterTable.slice(ChapterTable.manga, ChapterTable.id) - .select { (ChapterTable.sourceOrder inList input.chapterIndexes) and (ChapterTable.manga eq mangaId) } - .map { row -> - val chapterId = row[ChapterTable.id].value - ChapterDownloadHelper.delete(mangaId, chapterId) + val chapterIds = + ChapterTable.slice(ChapterTable.manga, ChapterTable.id) + .select { (ChapterTable.sourceOrder inList input.chapterIndexes) and (ChapterTable.manga eq mangaId) } + .map { row -> + val chapterId = row[ChapterTable.id].value + ChapterDownloadHelper.delete(mangaId, chapterId) - chapterId - } + chapterId + } ChapterTable.update({ ChapterTable.id inList chapterIds }) { it[isDownloaded] = false @@ -457,7 +488,7 @@ object Chapter { .map { MangaChapterDataClass( MangaTable.toDataClass(it), - ChapterTable.toDataClass(it) + ChapterTable.toDataClass(it), ) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/ChapterDownloadHelper.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/ChapterDownloadHelper.kt index 4c61a6c04..9a13adeb7 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/ChapterDownloadHelper.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/ChapterDownloadHelper.kt @@ -12,11 +12,18 @@ import java.io.File import java.io.InputStream object ChapterDownloadHelper { - fun getImage(mangaId: Int, chapterId: Int, index: Int): Pair { + fun getImage( + mangaId: Int, + chapterId: Int, + index: Int, + ): Pair { return provider(mangaId, chapterId).getImage().execute(index) } - fun delete(mangaId: Int, chapterId: Int): Boolean { + fun delete( + mangaId: Int, + chapterId: Int, + ): Boolean { return provider(mangaId, chapterId).delete() } @@ -25,13 +32,16 @@ object ChapterDownloadHelper { chapterId: Int, download: DownloadChapter, scope: CoroutineScope, - step: suspend (DownloadChapter?, Boolean) -> Unit + step: suspend (DownloadChapter?, Boolean) -> Unit, ): Boolean { return provider(mangaId, chapterId).download().execute(download, scope, step) } // return the appropriate provider based on how the download was saved. For the logic is simple but will evolve when new types of downloads are available - private fun provider(mangaId: Int, chapterId: Int): ChaptersFilesProvider { + private fun provider( + mangaId: Int, + chapterId: Int, + ): ChaptersFilesProvider { val chapterFolder = File(getChapterDownloadPath(mangaId, chapterId)) val cbzFile = File(getChapterCbzPath(mangaId, chapterId)) if (cbzFile.exists()) return ArchiveProvider(mangaId, chapterId) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Library.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Library.kt index 1a3bdb074..f5199bb67 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Library.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Library.kt @@ -29,9 +29,10 @@ object Library { val manga = getManga(mangaId) if (!manga.inLibrary) { transaction { - val defaultCategories = CategoryTable.select { - (CategoryTable.isDefault eq true) and (CategoryTable.id neq Category.DEFAULT_CATEGORY_ID) - }.toList() + val defaultCategories = + CategoryTable.select { + (CategoryTable.isDefault eq true) and (CategoryTable.id neq Category.DEFAULT_CATEGORY_ID) + }.toList() val existingCategories = CategoryMangaTable.select { CategoryMangaTable.manga eq mangaId }.toList() MangaTable.update({ MangaTable.id eq manga.id }) { @@ -66,7 +67,10 @@ object Library { } } - fun handleMangaThumbnail(mangaId: Int, inLibrary: Boolean) { + fun handleMangaThumbnail( + mangaId: Int, + inLibrary: Boolean, + ) { scope.launch { try { if (inLibrary) { diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Manga.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Manga.kt index 5c837cc72..9e4f5d910 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Manga.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Manga.kt @@ -58,7 +58,10 @@ import java.util.concurrent.ConcurrentHashMap private val logger = KotlinLogging.logger { } object Manga { - private fun truncate(text: String?, maxLength: Int): String? { + private fun truncate( + text: String?, + maxLength: Int, + ): String? { return if (text?.length ?: 0 > maxLength) { text?.take(maxLength - 3) + "..." } else { @@ -66,7 +69,10 @@ object Manga { } } - suspend fun getManga(mangaId: Int, onlineFetch: Boolean = false): MangaDataClass { + suspend fun getManga( + mangaId: Int, + onlineFetch: Boolean = false, + ): MangaDataClass { var mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() } return if (!onlineFetch && mangaEntry[MangaTable.initialized]) { @@ -79,14 +85,11 @@ object Manga { MangaDataClass( id = mangaId, sourceId = mangaEntry[MangaTable.sourceReference].toString(), - url = mangaEntry[MangaTable.url], title = mangaEntry[MangaTable.title], thumbnailUrl = proxyThumbnailUrl(mangaId), thumbnailUrlLastFetched = mangaEntry[MangaTable.thumbnailUrlLastFetched], - initialized = true, - artist = sManga.artist, author = sManga.author, description = sManga.description, @@ -100,7 +103,7 @@ object Manga { lastFetchedAt = mangaEntry[MangaTable.lastFetchedAt], chaptersLastFetchedAt = mangaEntry[MangaTable.chaptersLastFetchedAt], updateStrategy = UpdateStrategy.valueOf(mangaEntry[MangaTable.updateStrategy]), - freshData = true + freshData = true, ) } } @@ -108,12 +111,14 @@ object Manga { suspend fun fetchManga(mangaId: Int): SManga? { val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() } - val source = getCatalogueSourceOrNull(mangaEntry[MangaTable.sourceReference]) - ?: return null - val sManga = SManga.create().apply { - url = mangaEntry[MangaTable.url] - title = mangaEntry[MangaTable.title] - } + val source = + getCatalogueSourceOrNull(mangaEntry[MangaTable.sourceReference]) + ?: return null + val sManga = + SManga.create().apply { + url = mangaEntry[MangaTable.url] + title = mangaEntry[MangaTable.title] + } val networkManga = source.getMangaDetails(sManga) sManga.copyFrom(networkManga) @@ -139,9 +144,10 @@ object Manga { clearThumbnail(mangaId) } - it[MangaTable.realUrl] = runCatching { - (source as? HttpSource)?.getMangaUrl(sManga) - }.getOrNull() + it[MangaTable.realUrl] = + runCatching { + (source as? HttpSource)?.getMangaUrl(sManga) + }.getOrNull() it[MangaTable.lastFetchedAt] = Instant.now().epochSecond @@ -152,7 +158,10 @@ object Manga { return sManga } - suspend fun getMangaFull(mangaId: Int, onlineFetch: Boolean = false): MangaDataClass { + suspend fun getMangaFull( + mangaId: Int, + onlineFetch: Boolean = false, + ): MangaDataClass { val mangaDaaClass = getManga(mangaId, onlineFetch) return transaction { @@ -186,17 +195,17 @@ object Manga { } } - private fun getMangaDataClass(mangaId: Int, mangaEntry: ResultRow) = MangaDataClass( + private fun getMangaDataClass( + mangaId: Int, + mangaEntry: ResultRow, + ) = MangaDataClass( id = mangaId, sourceId = mangaEntry[MangaTable.sourceReference].toString(), - url = mangaEntry[MangaTable.url], title = mangaEntry[MangaTable.title], thumbnailUrl = proxyThumbnailUrl(mangaId), thumbnailUrlLastFetched = mangaEntry[MangaTable.thumbnailUrlLastFetched], - initialized = true, - artist = mangaEntry[MangaTable.artist], author = mangaEntry[MangaTable.author], description = mangaEntry[MangaTable.description], @@ -210,7 +219,7 @@ object Manga { lastFetchedAt = mangaEntry[MangaTable.lastFetchedAt], chaptersLastFetchedAt = mangaEntry[MangaTable.chaptersLastFetchedAt], updateStrategy = UpdateStrategy.valueOf(mangaEntry[MangaTable.updateStrategy]), - freshData = false + freshData = false, ) fun getMangaMetaMap(mangaId: Int): Map { @@ -220,7 +229,11 @@ object Manga { } } - fun modifyMangaMeta(mangaId: Int, key: String, value: String) { + fun modifyMangaMeta( + mangaId: Int, + key: String, + value: String, + ) { transaction { val meta = MangaMetaTable.select { (MangaMetaTable.ref eq mangaId) and (MangaMetaTable.key eq key) } @@ -251,45 +264,51 @@ object Manga { val sourceId = mangaEntry[MangaTable.sourceReference] return when (val source = getCatalogueSourceOrStub(sourceId)) { - is HttpSource -> getImageResponse(cacheSaveDir, fileName) { - val thumbnailUrl = mangaEntry[MangaTable.thumbnail_url] - ?: if (!mangaEntry[MangaTable.initialized]) { - // initialize then try again - getManga(mangaId) - transaction { - MangaTable.select { MangaTable.id eq mangaId }.first() - }[MangaTable.thumbnail_url]!! - } else { - // source provides no thumbnail url for this manga - throw NullPointerException("No thumbnail found") - } - - source.client.newCall( - GET(thumbnailUrl, source.headers) - ).await() - } + is HttpSource -> + getImageResponse(cacheSaveDir, fileName) { + val thumbnailUrl = + mangaEntry[MangaTable.thumbnail_url] + ?: if (!mangaEntry[MangaTable.initialized]) { + // initialize then try again + getManga(mangaId) + transaction { + MangaTable.select { MangaTable.id eq mangaId }.first() + }[MangaTable.thumbnail_url]!! + } else { + // source provides no thumbnail url for this manga + throw NullPointerException("No thumbnail found") + } + + source.client.newCall( + GET(thumbnailUrl, source.headers), + ).await() + } is LocalSource -> { - val imageFile = mangaEntry[MangaTable.thumbnail_url]?.let { - val file = File(it) - if (file.exists()) { - file - } else { - null - } - } ?: throw IOException("Thumbnail does not exist") - val contentType = ImageUtil.findImageType { imageFile.inputStream() }?.mime - ?: "image/jpeg" + val imageFile = + mangaEntry[MangaTable.thumbnail_url]?.let { + val file = File(it) + if (file.exists()) { + file + } else { + null + } + } ?: throw IOException("Thumbnail does not exist") + val contentType = + ImageUtil.findImageType { imageFile.inputStream() }?.mime + ?: "image/jpeg" imageFile.inputStream() to contentType } - is StubSource -> getImageResponse(cacheSaveDir, fileName) { - val thumbnailUrl = mangaEntry[MangaTable.thumbnail_url] - ?: throw NullPointerException("No thumbnail found") - network.client.newCall( - GET(thumbnailUrl) - ).await() - } + is StubSource -> + getImageResponse(cacheSaveDir, fileName) { + val thumbnailUrl = + mangaEntry[MangaTable.thumbnail_url] + ?: throw NullPointerException("No thumbnail found") + network.client.newCall( + GET(thumbnailUrl), + ).await() + } else -> throw IllegalArgumentException("Unknown source") } @@ -319,14 +338,18 @@ object Manga { private val downloadAheadQueue = ConcurrentHashMap>() private var downloadAheadTimer: Timer? = null - fun downloadAhead(mangaIds: List, latestReadChapterIds: List = emptyList()) { + + private const val MANGAS_KEY = "mangaIds" + private const val CHAPTERS_KEY = "chapterIds" + + fun downloadAhead( + mangaIds: List, + latestReadChapterIds: List = emptyList(), + ) { if (serverConfig.autoDownloadAheadLimit.value == 0) { return } - val MANGAS_KEY = "mangaIds" - val CHAPTERS_KEY = "chapterIds" - val updateDownloadAheadQueue = { key: String, ids: List -> val idSet = downloadAheadQueue[key] ?: ConcurrentHashMap.newKeySet() idSet.addAll(ids) @@ -339,17 +362,21 @@ object Manga { // handle cases where this function gets called multiple times in quick succession. // this could happen in case e.g. multiple chapters get marked as read without batching the operation downloadAheadTimer?.cancel() - downloadAheadTimer = Timer().apply { - schedule( - object : TimerTask() { - override fun run() { - downloadAheadChapters(downloadAheadQueue[MANGAS_KEY]!!.toList(), downloadAheadQueue[CHAPTERS_KEY]!!.toList()) - downloadAheadQueue.clear() - } - }, - 5000 - ) - } + downloadAheadTimer = + Timer().apply { + schedule( + object : TimerTask() { + override fun run() { + downloadAheadChapters( + downloadAheadQueue[MANGAS_KEY]?.toList().orEmpty(), + downloadAheadQueue[CHAPTERS_KEY]?.toList().orEmpty(), + ) + downloadAheadQueue.clear() + } + }, + 5000, + ) + } } /** @@ -370,29 +397,38 @@ object Manga { * * will download the unread chapters starting from chapter 15 */ - private fun downloadAheadChapters(mangaIds: List, latestReadChapterIds: List) { - val mangaToLatestReadChapterIndex = transaction { - ChapterTable.select { (ChapterTable.manga inList mangaIds) and (ChapterTable.isRead eq true) } - .orderBy(ChapterTable.sourceOrder to SortOrder.DESC).groupBy { it[ChapterTable.manga].value } - }.mapValues { (_, chapters) -> chapters.firstOrNull()?.let { it[ChapterTable.sourceOrder] } ?: 0 } - - val mangaToUnreadChaptersMap = transaction { - ChapterTable.select { (ChapterTable.manga inList mangaIds) and (ChapterTable.isRead eq false) } - .orderBy(ChapterTable.sourceOrder to SortOrder.DESC) - .groupBy { it[ChapterTable.manga].value } - } + private fun downloadAheadChapters( + mangaIds: List, + latestReadChapterIds: List, + ) { + val mangaToLatestReadChapterIndex = + transaction { + ChapterTable.select { (ChapterTable.manga inList mangaIds) and (ChapterTable.isRead eq true) } + .orderBy(ChapterTable.sourceOrder to SortOrder.DESC).groupBy { it[ChapterTable.manga].value } + }.mapValues { (_, chapters) -> chapters.firstOrNull()?.let { it[ChapterTable.sourceOrder] } ?: 0 } + + val mangaToUnreadChaptersMap = + transaction { + ChapterTable.select { (ChapterTable.manga inList mangaIds) and (ChapterTable.isRead eq false) } + .orderBy(ChapterTable.sourceOrder to SortOrder.DESC) + .groupBy { it[ChapterTable.manga].value } + } - val chapterIdsToDownload = mangaToUnreadChaptersMap.map { (mangaId, unreadChapters) -> - val latestReadChapterIndex = mangaToLatestReadChapterIndex[mangaId] ?: 0 - val lastChapterToDownloadIndex = - unreadChapters.indexOfLast { it[ChapterTable.sourceOrder] > latestReadChapterIndex && it[ChapterTable.id].value !in latestReadChapterIds } - val unreadChaptersToConsider = unreadChapters.subList(0, lastChapterToDownloadIndex + 1) - val firstChapterToDownloadIndex = - (unreadChaptersToConsider.size - serverConfig.autoDownloadAheadLimit.value).coerceAtLeast(0) - unreadChaptersToConsider.subList(firstChapterToDownloadIndex, lastChapterToDownloadIndex + 1) - .filter { !it[ChapterTable.isDownloaded] } - .map { it[ChapterTable.id].value } - }.flatten() + val chapterIdsToDownload = + mangaToUnreadChaptersMap.map { (mangaId, unreadChapters) -> + val latestReadChapterIndex = mangaToLatestReadChapterIndex[mangaId] ?: 0 + val lastChapterToDownloadIndex = + unreadChapters.indexOfLast { + it[ChapterTable.sourceOrder] > latestReadChapterIndex && + it[ChapterTable.id].value !in latestReadChapterIds + } + val unreadChaptersToConsider = unreadChapters.subList(0, lastChapterToDownloadIndex + 1) + val firstChapterToDownloadIndex = + (unreadChaptersToConsider.size - serverConfig.autoDownloadAheadLimit.value).coerceAtLeast(0) + unreadChaptersToConsider.subList(firstChapterToDownloadIndex, lastChapterToDownloadIndex + 1) + .filter { !it[ChapterTable.isDownloaded] } + .map { it[ChapterTable.id].value } + }.flatten() logger.info { "downloadAheadChapters: download chapters [${chapterIdsToDownload.joinToString(", ")}]" } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/MangaList.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/MangaList.kt index c1e7b22dd..1dc780d65 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/MangaList.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/MangaList.kt @@ -26,29 +26,35 @@ object MangaList { return "/api/v1/manga/$mangaId/thumbnail" } - suspend fun getMangaList(sourceId: Long, pageNum: Int = 1, popular: Boolean): PagedMangaListDataClass { + suspend fun getMangaList( + sourceId: Long, + pageNum: Int = 1, + popular: Boolean, + ): PagedMangaListDataClass { require(pageNum > 0) { "pageNum = $pageNum is not in valid range" } val source = getCatalogueSourceOrStub(sourceId) - val mangasPage = if (popular) { - source.getPopularManga(pageNum) - } else { - if (source.supportsLatest) { - source.getLatestUpdates(pageNum) + val mangasPage = + if (popular) { + source.getPopularManga(pageNum) } else { - throw Exception("Source $source doesn't support latest") + if (source.supportsLatest) { + source.getLatestUpdates(pageNum) + } else { + throw Exception("Source $source doesn't support latest") + } } - } return mangasPage.processEntries(sourceId) } fun MangasPage.insertOrGet(sourceId: Long): List { return transaction { mangas.map { manga -> - val mangaEntry = MangaTable.select { - (MangaTable.url eq manga.url) and (MangaTable.sourceReference eq sourceId) - }.firstOrNull() + val mangaEntry = + MangaTable.select { + (MangaTable.url eq manga.url) and (MangaTable.sourceReference eq sourceId) + }.firstOrNull() if (mangaEntry == null) { // create manga entry MangaTable.insertAndGetId { it[url] = manga.url @@ -73,89 +79,87 @@ object MangaList { fun MangasPage.processEntries(sourceId: Long): PagedMangaListDataClass { val mangasPage = this - val mangaList = transaction { - return@transaction mangasPage.mangas.map { manga -> - var mangaEntry = MangaTable.select { - (MangaTable.url eq manga.url) and (MangaTable.sourceReference eq sourceId) - }.firstOrNull() - if (mangaEntry == null) { // create manga entry - val mangaId = MangaTable.insertAndGetId { - it[url] = manga.url - it[title] = manga.title - - it[artist] = manga.artist - it[author] = manga.author - it[description] = manga.description - it[genre] = manga.genre - it[status] = manga.status - it[thumbnail_url] = manga.thumbnail_url - it[updateStrategy] = manga.update_strategy.name - - it[sourceReference] = sourceId - }.value - - mangaEntry = MangaTable.select { - (MangaTable.url eq manga.url) and (MangaTable.sourceReference eq sourceId) - }.first() - - MangaDataClass( - id = mangaId, - sourceId = sourceId.toString(), - - url = manga.url, - title = manga.title, - thumbnailUrl = proxyThumbnailUrl(mangaId), - thumbnailUrlLastFetched = mangaEntry[MangaTable.thumbnailUrlLastFetched], - - initialized = manga.initialized, - - artist = manga.artist, - author = manga.author, - description = manga.description, - genre = manga.genre.toGenreList(), - status = MangaStatus.valueOf(manga.status).name, - inLibrary = false, // It's a new manga entry - inLibraryAt = 0, - meta = getMangaMetaMap(mangaId), - realUrl = mangaEntry[MangaTable.realUrl], - lastFetchedAt = mangaEntry[MangaTable.lastFetchedAt], - chaptersLastFetchedAt = mangaEntry[MangaTable.chaptersLastFetchedAt], - updateStrategy = UpdateStrategy.valueOf(mangaEntry[MangaTable.updateStrategy]), - freshData = true - ) - } else { - val mangaId = mangaEntry[MangaTable.id].value - MangaDataClass( - id = mangaId, - sourceId = sourceId.toString(), - - url = manga.url, - title = manga.title, - thumbnailUrl = proxyThumbnailUrl(mangaId), - thumbnailUrlLastFetched = mangaEntry[MangaTable.thumbnailUrlLastFetched], - - initialized = true, - - artist = mangaEntry[MangaTable.artist], - author = mangaEntry[MangaTable.author], - description = mangaEntry[MangaTable.description], - genre = mangaEntry[MangaTable.genre].toGenreList(), - status = MangaStatus.valueOf(mangaEntry[MangaTable.status]).name, - inLibrary = mangaEntry[MangaTable.inLibrary], - inLibraryAt = mangaEntry[MangaTable.inLibraryAt], - meta = getMangaMetaMap(mangaId), - realUrl = mangaEntry[MangaTable.realUrl], - lastFetchedAt = mangaEntry[MangaTable.lastFetchedAt], - chaptersLastFetchedAt = mangaEntry[MangaTable.chaptersLastFetchedAt], - updateStrategy = UpdateStrategy.valueOf(mangaEntry[MangaTable.updateStrategy]), - freshData = false - ) + val mangaList = + transaction { + return@transaction mangasPage.mangas.map { manga -> + var mangaEntry = + MangaTable.select { + (MangaTable.url eq manga.url) and (MangaTable.sourceReference eq sourceId) + }.firstOrNull() + if (mangaEntry == null) { // create manga entry + val mangaId = + MangaTable.insertAndGetId { + it[url] = manga.url + it[title] = manga.title + + it[artist] = manga.artist + it[author] = manga.author + it[description] = manga.description + it[genre] = manga.genre + it[status] = manga.status + it[thumbnail_url] = manga.thumbnail_url + it[updateStrategy] = manga.update_strategy.name + + it[sourceReference] = sourceId + }.value + + mangaEntry = + MangaTable.select { + (MangaTable.url eq manga.url) and (MangaTable.sourceReference eq sourceId) + }.first() + + MangaDataClass( + id = mangaId, + sourceId = sourceId.toString(), + url = manga.url, + title = manga.title, + thumbnailUrl = proxyThumbnailUrl(mangaId), + thumbnailUrlLastFetched = mangaEntry[MangaTable.thumbnailUrlLastFetched], + initialized = manga.initialized, + artist = manga.artist, + author = manga.author, + description = manga.description, + genre = manga.genre.toGenreList(), + status = MangaStatus.valueOf(manga.status).name, + inLibrary = false, // It's a new manga entry + inLibraryAt = 0, + meta = getMangaMetaMap(mangaId), + realUrl = mangaEntry[MangaTable.realUrl], + lastFetchedAt = mangaEntry[MangaTable.lastFetchedAt], + chaptersLastFetchedAt = mangaEntry[MangaTable.chaptersLastFetchedAt], + updateStrategy = UpdateStrategy.valueOf(mangaEntry[MangaTable.updateStrategy]), + freshData = true, + ) + } else { + val mangaId = mangaEntry[MangaTable.id].value + MangaDataClass( + id = mangaId, + sourceId = sourceId.toString(), + url = manga.url, + title = manga.title, + thumbnailUrl = proxyThumbnailUrl(mangaId), + thumbnailUrlLastFetched = mangaEntry[MangaTable.thumbnailUrlLastFetched], + initialized = true, + artist = mangaEntry[MangaTable.artist], + author = mangaEntry[MangaTable.author], + description = mangaEntry[MangaTable.description], + genre = mangaEntry[MangaTable.genre].toGenreList(), + status = MangaStatus.valueOf(mangaEntry[MangaTable.status]).name, + inLibrary = mangaEntry[MangaTable.inLibrary], + inLibraryAt = mangaEntry[MangaTable.inLibraryAt], + meta = getMangaMetaMap(mangaId), + realUrl = mangaEntry[MangaTable.realUrl], + lastFetchedAt = mangaEntry[MangaTable.lastFetchedAt], + chaptersLastFetchedAt = mangaEntry[MangaTable.chaptersLastFetchedAt], + updateStrategy = UpdateStrategy.valueOf(mangaEntry[MangaTable.updateStrategy]), + freshData = false, + ) + } } } - } return PagedMangaListDataClass( mangaList, - mangasPage.hasNextPage + mangasPage.hasNextPage, ) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Page.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Page.kt index 8f96c6ee1..26d47e4bb 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Page.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Page.kt @@ -31,21 +31,30 @@ object Page { * A page might have a imageUrl ready from the get go, or we might need to * go an extra step and call fetchImageUrl to get it. */ - suspend fun getTrueImageUrl(page: Page, source: HttpSource): String { + suspend fun getTrueImageUrl( + page: Page, + source: HttpSource, + ): String { if (page.imageUrl == null) { page.imageUrl = source.getImageUrl(page) } return page.imageUrl!! } - suspend fun getPageImage(mangaId: Int, chapterIndex: Int, index: Int, progressFlow: ((StateFlow) -> Unit)? = null): Pair { + suspend fun getPageImage( + mangaId: Int, + chapterIndex: Int, + index: Int, + progressFlow: ((StateFlow) -> Unit)? = null, + ): Pair { val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() } val source = getCatalogueSourceOrStub(mangaEntry[MangaTable.sourceReference]) - val chapterEntry = transaction { - ChapterTable.select { - (ChapterTable.sourceOrder eq chapterIndex) and (ChapterTable.manga eq mangaId) - }.first() - } + val chapterEntry = + transaction { + ChapterTable.select { + (ChapterTable.sourceOrder eq chapterIndex) and (ChapterTable.manga eq mangaId) + }.first() + } val chapterId = chapterEntry[ChapterTable.id].value val pageEntry = @@ -54,11 +63,12 @@ object Page { .orderBy(PageTable.index to SortOrder.ASC) .limit(1, index.toLong()).first() } - val tachiyomiPage = Page( - pageEntry[PageTable.index], - pageEntry[PageTable.url], - pageEntry[PageTable.imageUrl] - ) + val tachiyomiPage = + Page( + pageEntry[PageTable.index], + pageEntry[PageTable.url], + pageEntry[PageTable.imageUrl], + ) progressFlow?.invoke(tachiyomiPage.progress) // we treat Local source differently diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Search.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Search.kt index 36c18ed99..e2c07cdf7 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Search.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Search.kt @@ -20,13 +20,21 @@ import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogue import suwayomi.tachidesk.manga.model.dataclass.PagedMangaListDataClass object Search { - suspend fun sourceSearch(sourceId: Long, searchTerm: String, pageNum: Int): PagedMangaListDataClass { + suspend fun sourceSearch( + sourceId: Long, + searchTerm: String, + pageNum: Int, + ): PagedMangaListDataClass { val source = getCatalogueSourceOrStub(sourceId) val searchManga = source.getSearchManga(pageNum, searchTerm, getFilterListOf(source)) return searchManga.processEntries(sourceId) } - suspend fun sourceFilter(sourceId: Long, pageNum: Int, filter: FilterData): PagedMangaListDataClass { + suspend fun sourceFilter( + sourceId: Long, + pageNum: Int, + filter: FilterData, + ): PagedMangaListDataClass { 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) @@ -35,14 +43,20 @@ object Search { private val filterListCache = mutableMapOf() - private fun getFilterListOf(source: CatalogueSource, reset: Boolean = false): FilterList { + private fun getFilterListOf( + source: CatalogueSource, + reset: Boolean = false, + ): FilterList { if (reset || !filterListCache.containsKey(source.id)) { filterListCache[source.id] = source.getFilterList() } return filterListCache[source.id]!! } - fun getFilterList(sourceId: Long, reset: Boolean): List { + fun getFilterList( + sourceId: Long, + reset: Boolean, + ): List { val source = getCatalogueSourceOrStub(sourceId) return getFilterListOf(source, reset).list.map { @@ -70,30 +84,37 @@ object Search { is Filter.Select<*> -> FilterObject("Select", item) else -> throw RuntimeException("Illegal Group item type!") } - } + }, ) } else -> it - } + }, ) } } private fun Filter.Select<*>.getValuesType(): String = values::class.java.componentType!!.simpleName + class SerializableGroup(name: String, state: List) : Filter>(name, state) data class FilterObject( val type: String, - val filter: Filter<*> + val filter: Filter<*>, ) - fun setFilter(sourceId: Long, changes: List) { + fun setFilter( + sourceId: Long, + changes: List, + ) { val source = getCatalogueSourceOrStub(sourceId) val filterList = getFilterListOf(source, false) updateFilterList(filterList, changes) } - private fun updateFilterList(filterList: FilterList, changes: List): FilterList { + private fun updateFilterList( + filterList: FilterList, + changes: List, + ): FilterList { changes.forEach { change -> when (val filter = filterList[change.position]) { is Filter.Header -> { @@ -124,7 +145,10 @@ object Search { return filterList } - fun buildFilterList(sourceId: Long, changes: List): FilterList { + fun buildFilterList( + sourceId: Long, + changes: List, + ): FilterList { val source = getCatalogueSourceOrStub(sourceId) val filterList = source.getFilterList() return updateFilterList(filterList, changes) @@ -135,13 +159,13 @@ object Search { @Serializable data class FilterChange( val position: Int, - val state: String + val state: String, ) @Serializable data class FilterData( val searchTerm: String?, - val filter: List? + val filter: List?, ) @Suppress("UNUSED_PARAMETER") diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Source.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Source.kt index 0242b7d4f..d70f1dbda 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Source.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Source.kt @@ -46,7 +46,7 @@ object Source { catalogueSource.supportsLatest, catalogueSource is ConfigurableSource, it[SourceTable.isNsfw], - catalogueSource.toString() + catalogueSource.toString(), ) } } @@ -63,12 +63,12 @@ object Source { source[SourceTable.name], source[SourceTable.lang], getExtensionIconUrl( - extension[ExtensionTable.apkName] + extension[ExtensionTable.apkName], ), catalogueSource.supportsLatest, catalogueSource is ConfigurableSource, source[SourceTable.isNsfw], - catalogueSource.toString() + catalogueSource.toString(), ) } } @@ -85,7 +85,7 @@ object Source { */ data class PreferenceObject( val type: String, - val props: Any + val props: Any, ) var preferenceScreenMap: MutableMap = mutableMapOf() @@ -119,7 +119,7 @@ object Source { data class SourcePreferenceChange( val position: Int, - val value: String + val value: String, ) private val jsonMapper by DI.global.instance() @@ -137,7 +137,7 @@ object Source { "Set" -> jsonMapper.fromJsonString(value, List::class.java as Class>).toSet() else -> throw RuntimeException("Unsupported type conversion") } - } + }, ) { val screen = preferenceScreenMap[sourceId]!! val pref = screen.preferences[position] diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/BackupFlags.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/BackupFlags.kt index d06b61674..05c69546b 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/BackupFlags.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/BackupFlags.kt @@ -12,5 +12,5 @@ data class BackupFlags( val includeCategories: Boolean, val includeChapters: Boolean, val includeTracking: Boolean, - val includeHistory: Boolean + val includeHistory: Boolean, ) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/Category.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/Category.kt deleted file mode 100644 index f4ece8d1c..000000000 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/Category.kt +++ /dev/null @@ -1,23 +0,0 @@ -package suwayomi.tachidesk.manga.impl.backup.models - -import java.io.Serializable - -interface Category : Serializable { - - var id: Int? - - var name: String - - var order: Int - - var flags: Int - - companion object { - - fun create(name: String): Category = CategoryImpl().apply { - this.name = name - } - - fun createDefault(): Category = create("Default").apply { id = 0 } - } -} diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/CategoryImpl.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/CategoryImpl.kt deleted file mode 100644 index 813c00d2a..000000000 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/CategoryImpl.kt +++ /dev/null @@ -1,24 +0,0 @@ -package suwayomi.tachidesk.manga.impl.backup.models - -class CategoryImpl : Category { - - override var id: Int? = null - - override lateinit var name: String - - override var order: Int = 0 - - override var flags: Int = 0 - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other == null || javaClass != other.javaClass) return false - - val category = other as Category - return name == category.name - } - - override fun hashCode(): Int { - return name.hashCode() - } -} diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/Chapter.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/Chapter.kt index f5f0a12a7..42cc20fb7 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/Chapter.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/Chapter.kt @@ -1,10 +1,11 @@ +@file:Suppress("ktlint:standard:property-naming") + package suwayomi.tachidesk.manga.impl.backup.models import eu.kanade.tachiyomi.source.model.SChapter import java.io.Serializable interface Chapter : SChapter, Serializable { - var id: Long? var manga_id: Long? @@ -23,9 +24,9 @@ interface Chapter : SChapter, Serializable { get() = chapter_number >= 0f companion object { - - fun create(): Chapter = ChapterImpl().apply { - chapter_number = -1f - } + fun create(): Chapter = + ChapterImpl().apply { + chapter_number = -1f + } } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/ChapterImpl.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/ChapterImpl.kt index d30ba6b87..c218b2c18 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/ChapterImpl.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/ChapterImpl.kt @@ -1,10 +1,11 @@ +@file:Suppress("ktlint:standard:property-naming") + package suwayomi.tachidesk.manga.impl.backup.models import org.jetbrains.exposed.sql.ResultRow import suwayomi.tachidesk.manga.model.table.ChapterTable class ChapterImpl : Chapter { - override var id: Long? = null override var manga_id: Long? = null diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/History.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/History.kt deleted file mode 100644 index 3c60b6be3..000000000 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/History.kt +++ /dev/null @@ -1,42 +0,0 @@ -package suwayomi.tachidesk.manga.impl.backup.models - -import java.io.Serializable - -/** - * Object containing the history statistics of a chapter - */ -interface History : Serializable { - - /** - * Id of history object. - */ - var id: Long? - - /** - * Chapter id of history object. - */ - var chapter_id: Long - - /** - * Last time chapter was read in time long format - */ - var last_read: Long - - /** - * Total time chapter was read - todo not yet implemented - */ - var time_read: Long - - companion object { - - /** - * History constructor - * - * @param chapter chapter object - * @return history object - */ - fun create(chapter: Chapter): History = HistoryImpl().apply { - this.chapter_id = chapter.id!! - } - } -} diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/HistoryImpl.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/HistoryImpl.kt deleted file mode 100644 index f0ee2ced0..000000000 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/HistoryImpl.kt +++ /dev/null @@ -1,27 +0,0 @@ -package suwayomi.tachidesk.manga.impl.backup.models - -/** - * Object containing the history statistics of a chapter - */ -class HistoryImpl : History { - - /** - * Id of history object. - */ - override var id: Long? = null - - /** - * Chapter id of history object. - */ - override var chapter_id: Long = 0 - - /** - * Last time chapter was read in time long format - */ - override var last_read: Long = 0 - - /** - * Total time chapter was read - todo not yet implemented - */ - override var time_read: Long = 0 -} diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/Manga.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/Manga.kt index b20782b58..5b9886e0a 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/Manga.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/Manga.kt @@ -1,3 +1,5 @@ +@file:Suppress("ktlint:standard:property-naming") + package suwayomi.tachidesk.manga.impl.backup.models import eu.kanade.tachiyomi.source.model.SManga @@ -15,7 +17,6 @@ object ReadingModeType { } interface Manga : SManga { - var id: Long? var source: Long @@ -48,11 +49,17 @@ interface Manga : SManga { return genre?.split(", ")?.map { it.trim() } } - private fun setChapterFlags(flag: Int, mask: Int) { + private fun setChapterFlags( + flag: Int, + mask: Int, + ) { chapter_flags = chapter_flags and mask.inv() or (flag and mask) } - private fun setViewerFlags(flag: Int, mask: Int) { + private fun setViewerFlags( + flag: Int, + mask: Int, + ) { viewer_flags = viewer_flags and mask.inv() or (flag and mask) } @@ -86,7 +93,6 @@ interface Manga : SManga { set(rotationType) = setViewerFlags(rotationType, OrientationType.MASK) companion object { - // Generic filter that does not filter anything const val SHOW_ALL = 0x00000000 @@ -115,15 +121,21 @@ interface Manga : SManga { const val CHAPTER_DISPLAY_NUMBER = 0x00100000 const val CHAPTER_DISPLAY_MASK = 0x00100000 - fun create(source: Long): Manga = MangaImpl().apply { - this.source = source - } - - fun create(pathUrl: String, title: String, source: Long = 0): Manga = MangaImpl().apply { - url = pathUrl - this.title = title - this.source = source - } + fun create(source: Long): Manga = + MangaImpl().apply { + this.source = source + } + + fun create( + pathUrl: String, + title: String, + source: Long = 0, + ): Manga = + MangaImpl().apply { + url = pathUrl + this.title = title + this.source = source + } } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/MangaCategory.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/MangaCategory.kt deleted file mode 100644 index 9afba3576..000000000 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/MangaCategory.kt +++ /dev/null @@ -1,20 +0,0 @@ -package suwayomi.tachidesk.manga.impl.backup.models - -class MangaCategory { - - var id: Long? = null - - var manga_id: Long = 0 - - var category_id: Int = 0 - - companion object { - - fun create(manga: Manga, category: Category): MangaCategory { - val mc = MangaCategory() - mc.manga_id = manga.id!! - mc.category_id = category.id!! - return mc - } - } -} diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/MangaChapter.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/MangaChapter.kt deleted file mode 100644 index 9afc27c07..000000000 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/MangaChapter.kt +++ /dev/null @@ -1,3 +0,0 @@ -package suwayomi.tachidesk.manga.impl.backup.models - -class MangaChapter(val manga: Manga, val chapter: Chapter) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/MangaChapterHistory.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/MangaChapterHistory.kt deleted file mode 100644 index f93bfcf22..000000000 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/MangaChapterHistory.kt +++ /dev/null @@ -1,10 +0,0 @@ -package suwayomi.tachidesk.manga.impl.backup.models - -/** - * Object containing manga, chapter and history - * - * @param manga object containing manga - * @param chapter object containing chater - * @param history object containing history - */ -data class MangaChapterHistory(val manga: Manga, val chapter: Chapter, val history: History) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/MangaImpl.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/MangaImpl.kt index c1b7ac535..c24e6ab3c 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/MangaImpl.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/MangaImpl.kt @@ -1,3 +1,5 @@ +@file:Suppress("ktlint:standard:property-naming") + package suwayomi.tachidesk.manga.impl.backup.models import eu.kanade.tachiyomi.source.model.UpdateStrategy @@ -5,7 +7,6 @@ import org.jetbrains.exposed.sql.ResultRow import suwayomi.tachidesk.manga.model.table.MangaTable open class MangaImpl : Manga { - override var id: Long? = null override var source: Long = -1 diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/Track.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/Track.kt index c9b9e15e7..685912546 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/Track.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/Track.kt @@ -1,9 +1,10 @@ +@file:Suppress("ktlint:standard:property-naming") + package suwayomi.tachidesk.manga.impl.backup.models import java.io.Serializable interface Track : Serializable { - var id: Long? var manga_id: Long @@ -39,8 +40,9 @@ interface Track : Serializable { } companion object { - fun create(serviceId: Int): Track = TrackImpl().apply { - sync_id = serviceId - } + fun create(serviceId: Int): Track = + TrackImpl().apply { + sync_id = serviceId + } } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/TrackImpl.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/TrackImpl.kt index 84e035bdc..c02663e54 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/TrackImpl.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/TrackImpl.kt @@ -1,7 +1,8 @@ +@file:Suppress("ktlint:standard:property-naming") + package suwayomi.tachidesk.manga.impl.backup.models class TrackImpl : Track { - override var id: Long? = null override var manga_id: Long = 0 diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/ProtoBackupExport.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/ProtoBackupExport.kt index 2ebb4aa7e..14a7d9569 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/ProtoBackupExport.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/ProtoBackupExport.kt @@ -51,7 +51,7 @@ object ProtoBackupExport : ProtoBackupBase() { private val logger = KotlinLogging.logger { } private val applicationDirs by DI.global.instance() private var backupSchedulerJobId: String = "" - private const val lastAutomatedBackupKey = "lastAutomatedBackupKey" + private const val LAST_AUTOMATED_BACKUP_KEY = "lastAutomatedBackupKey" private val preferences = Preferences.userNodeForPackage(ProtoBackupExport::class.java) init { @@ -59,10 +59,10 @@ object ProtoBackupExport : ProtoBackupBase() { combine(serverConfig.backupInterval, serverConfig.backupTime) { interval, timeOfDay -> Pair( interval, - timeOfDay + timeOfDay, ) }, - ::scheduleAutomatedBackupTask + ::scheduleAutomatedBackupTask, ) } @@ -77,7 +77,7 @@ object ProtoBackupExport : ProtoBackupBase() { val task = { cleanupAutomatedBackups() createAutomatedBackup() - preferences.putLong(lastAutomatedBackupKey, System.currentTimeMillis()) + preferences.putLong(LAST_AUTOMATED_BACKUP_KEY, System.currentTimeMillis()) } val (hour, minute) = serverConfig.backupTime.value.split(":").map { it.toInt() } @@ -86,7 +86,7 @@ object ProtoBackupExport : ProtoBackupBase() { val backupInterval = serverConfig.backupInterval.value.days.coerceAtLeast(1.days) // trigger last backup in case the server wasn't running on the scheduled time - val lastAutomatedBackup = preferences.getLong(lastAutomatedBackupKey, System.currentTimeMillis()) + val lastAutomatedBackup = preferences.getLong(LAST_AUTOMATED_BACKUP_KEY, System.currentTimeMillis()) val wasPreviousBackupTriggered = (System.currentTimeMillis() - lastAutomatedBackup) < backupInterval.inWholeMilliseconds if (!wasPreviousBackupTriggered) { @@ -105,8 +105,8 @@ object ProtoBackupExport : ProtoBackupBase() { includeCategories = true, includeChapters = true, includeTracking = true, - includeHistory = true - ) + includeHistory = true, + ), ).use { input -> val automatedBackupDir = File(applicationDirs.automatedBackupRoot) automatedBackupDir.mkdirs() @@ -162,14 +162,15 @@ object ProtoBackupExport : ProtoBackupBase() { val databaseManga = transaction { MangaTable.select { MangaTable.inLibrary eq true } } - val backup: Backup = transaction { - Backup( - backupManga(databaseManga, flags), - backupCategories(), - emptyList(), - backupExtensionInfo(databaseManga) - ) - } + val backup: Backup = + transaction { + Backup( + backupManga(databaseManga, flags), + backupCategories(), + emptyList(), + backupExtensionInfo(databaseManga), + ) + } val byteArray = parser.encodeToByteArray(BackupSerializer, backup) @@ -179,48 +180,54 @@ object ProtoBackupExport : ProtoBackupBase() { return byteStream.toByteArray().inputStream() } - private fun backupManga(databaseManga: Query, flags: BackupFlags): List { + private fun backupManga( + databaseManga: Query, + flags: BackupFlags, + ): List { return databaseManga.map { mangaRow -> - val backupManga = BackupManga( - source = mangaRow[MangaTable.sourceReference], - url = mangaRow[MangaTable.url], - title = mangaRow[MangaTable.title], - artist = mangaRow[MangaTable.artist], - author = mangaRow[MangaTable.author], - description = mangaRow[MangaTable.description], - genre = mangaRow[MangaTable.genre]?.split(", ") ?: emptyList(), - status = MangaStatus.valueOf(mangaRow[MangaTable.status]).value, - thumbnailUrl = mangaRow[MangaTable.thumbnail_url], - dateAdded = TimeUnit.SECONDS.toMillis(mangaRow[MangaTable.inLibraryAt]), - viewer = 0, // not supported in Tachidesk - updateStrategy = UpdateStrategy.valueOf(mangaRow[MangaTable.updateStrategy]) - ) + val backupManga = + BackupManga( + source = mangaRow[MangaTable.sourceReference], + url = mangaRow[MangaTable.url], + title = mangaRow[MangaTable.title], + artist = mangaRow[MangaTable.artist], + author = mangaRow[MangaTable.author], + description = mangaRow[MangaTable.description], + genre = mangaRow[MangaTable.genre]?.split(", ") ?: emptyList(), + status = MangaStatus.valueOf(mangaRow[MangaTable.status]).value, + thumbnailUrl = mangaRow[MangaTable.thumbnail_url], + dateAdded = TimeUnit.SECONDS.toMillis(mangaRow[MangaTable.inLibraryAt]), + viewer = 0, // not supported in Tachidesk + updateStrategy = UpdateStrategy.valueOf(mangaRow[MangaTable.updateStrategy]), + ) val mangaId = mangaRow[MangaTable.id].value if (flags.includeChapters) { - val chapters = transaction { - ChapterTable.select { ChapterTable.manga eq mangaId } - .orderBy(ChapterTable.sourceOrder to SortOrder.DESC) - .map { - ChapterTable.toDataClass(it) - } - } - - backupManga.chapters = chapters.map { - BackupChapter( - it.url, - it.name, - it.scanlator, - it.read, - it.bookmarked, - it.lastPageRead, - TimeUnit.SECONDS.toMillis(it.fetchedAt), - it.uploadDate, - it.chapterNumber, - chapters.size - it.index - ) - } + val chapters = + transaction { + ChapterTable.select { ChapterTable.manga eq mangaId } + .orderBy(ChapterTable.sourceOrder to SortOrder.DESC) + .map { + ChapterTable.toDataClass(it) + } + } + + backupManga.chapters = + chapters.map { + BackupChapter( + it.url, + it.name, + it.scanlator, + it.read, + it.bookmarked, + it.lastPageRead, + TimeUnit.SECONDS.toMillis(it.fetchedAt), + it.uploadDate, + it.chapterNumber, + chapters.size - it.index, + ) + } } if (flags.includeCategories) { @@ -246,7 +253,7 @@ object ProtoBackupExport : ProtoBackupBase() { BackupCategory( it.name, it.order, - 0 // not supported in Tachidesk + 0, // not supported in Tachidesk ) } } @@ -260,7 +267,7 @@ object ProtoBackupExport : ProtoBackupBase() { val sourceRow = SourceTable.select { SourceTable.id eq it }.firstOrNull() BackupSource( sourceRow?.get(SourceTable.name) ?: "", - it + it, ) } .toList() diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/ProtoBackupImport.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/ProtoBackupImport.kt index 25f21bd55..6b7f257bf 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/ProtoBackupImport.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/ProtoBackupImport.kt @@ -47,9 +47,12 @@ object ProtoBackupImport : ProtoBackupBase() { private val errors = mutableListOf>() private val backupMutex = Mutex() + sealed class BackupRestoreState { data object Idle : BackupRestoreState() + data class RestoringCategories(val totalManga: Int) : BackupRestoreState() + data class RestoringManga(val current: Int, val totalManga: Int, val title: String) : BackupRestoreState() } @@ -70,26 +73,28 @@ object ProtoBackupImport : ProtoBackupBase() { restoreCategories(backup.backupCategories) } - val categoryMapping = transaction { - backup.backupCategories.associate { - it.order to CategoryTable.select { CategoryTable.name eq it.name }.first()[CategoryTable.id].value + val categoryMapping = + transaction { + backup.backupCategories.associate { + it.order to CategoryTable.select { CategoryTable.name eq it.name }.first()[CategoryTable.id].value + } } - } // Store source mapping for error messages sourceMapping = backup.getSourceMap() // Restore individual manga backup.backupManga.forEachIndexed { index, manga -> - backupRestoreState.value = BackupRestoreState.RestoringManga( - current = index + 1, - totalManga = backup.backupManga.size, - title = manga.title - ) + backupRestoreState.value = + BackupRestoreState.RestoringManga( + current = index + 1, + totalManga = backup.backupManga.size, + title = manga.title, + ) restoreManga( backupManga = manga, backupCategories = backup.backupCategories, - categoryMapping = categoryMapping + categoryMapping = categoryMapping, ) } @@ -126,7 +131,7 @@ object ProtoBackupImport : ProtoBackupBase() { private fun restoreManga( backupManga: BackupManga, backupCategories: List, - categoryMapping: Map + categoryMapping: Map, ) { val manga = backupManga.getMangaImpl() val chapters = backupManga.getChaptersImpl() @@ -150,36 +155,38 @@ object ProtoBackupImport : ProtoBackupBase() { history: List, tracks: List, backupCategories: List, - categoryMapping: Map + categoryMapping: Map, ) { - val dbManga = transaction { - MangaTable.select { (MangaTable.url eq manga.url) and (MangaTable.sourceReference eq manga.source) } - .firstOrNull() - } + val dbManga = + transaction { + MangaTable.select { (MangaTable.url eq manga.url) and (MangaTable.sourceReference eq manga.source) } + .firstOrNull() + } if (dbManga == null) { // Manga not in database transaction { // insert manga to database - val mangaId = MangaTable.insertAndGetId { - it[url] = manga.url - it[title] = manga.title - - it[artist] = manga.artist - it[author] = manga.author - it[description] = manga.description - it[genre] = manga.genre - it[status] = manga.status - it[thumbnail_url] = manga.thumbnail_url - it[updateStrategy] = manga.update_strategy.name + val mangaId = + MangaTable.insertAndGetId { + it[url] = manga.url + it[title] = manga.title - it[sourceReference] = manga.source + it[artist] = manga.artist + it[author] = manga.author + it[description] = manga.description + it[genre] = manga.genre + it[status] = manga.status + it[thumbnail_url] = manga.thumbnail_url + it[updateStrategy] = manga.update_strategy.name - it[initialized] = manga.description != null + it[sourceReference] = manga.source - it[inLibrary] = manga.favorite + it[initialized] = manga.description != null - it[inLibraryAt] = TimeUnit.MILLISECONDS.toSeconds(manga.date_added) - }.value + it[inLibrary] = manga.favorite + + it[inLibraryAt] = TimeUnit.MILLISECONDS.toSeconds(manga.date_added) + }.value // insert chapter data val chaptersLength = chapters.size diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/ProtoBackupValidator.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/ProtoBackupValidator.kt index 3604f5a0b..c56fb7bb0 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/ProtoBackupValidator.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/ProtoBackupValidator.kt @@ -24,7 +24,7 @@ object ProtoBackupValidator { val missingTrackers: List, val mangasMissingSources: List, @JsonIgnore - val missingSourceIds: List> + val missingSourceIds: List>, ) fun validate(backup: Backup): ValidationResult { @@ -34,9 +34,10 @@ object ProtoBackupValidator { val sources = backup.getSourceMap() - val missingSources = transaction { - sources.filter { SourceTable.select { SourceTable.id eq it.key }.firstOrNull() == null } - } + val missingSources = + transaction { + sources.filter { SourceTable.select { SourceTable.id eq it.key }.firstOrNull() == null } + } // val trackers = backup.backupManga // .flatMap { it.tracking } @@ -56,7 +57,7 @@ object ProtoBackupValidator { .sorted(), missingTrackers, emptyList(), - missingSources.toList() + missingSources.toList(), ) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/Backup.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/Backup.kt index 177b9a0e1..c32fb2035 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/Backup.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/Backup.kt @@ -9,7 +9,7 @@ data class Backup( @ProtoNumber(2) var backupCategories: List = emptyList(), // Bump by 100 to specify this is a 0.x value @ProtoNumber(100) var brokenBackupSources: List = emptyList(), - @ProtoNumber(101) var backupSources: List = emptyList() + @ProtoNumber(101) var backupSources: List = emptyList(), ) { fun getSourceMap(): Map { return (brokenBackupSources.map { BackupSource(it.name, it.sourceId) } + backupSources) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupCategory.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupCategory.kt index 5b7c95c65..0122b2bf0 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupCategory.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupCategory.kt @@ -2,8 +2,6 @@ package suwayomi.tachidesk.manga.impl.backup.proto.models import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber -import suwayomi.tachidesk.manga.impl.backup.models.Category -import suwayomi.tachidesk.manga.impl.backup.models.CategoryImpl @Serializable class BackupCategory( @@ -11,23 +9,5 @@ class BackupCategory( @ProtoNumber(2) var order: Int = 0, // @ProtoNumber(3) val updateInterval: Int = 0, 1.x value not used in 0.x // Bump by 100 to specify this is a 0.x value - @ProtoNumber(100) var flags: Int = 0 -) { - fun getCategoryImpl(): CategoryImpl { - return CategoryImpl().apply { - name = this@BackupCategory.name - flags = this@BackupCategory.flags - order = this@BackupCategory.order - } - } - - companion object { - fun copyFrom(category: Category): BackupCategory { - return BackupCategory( - name = category.name, - order = category.order, - flags = category.flags - ) - } - } -} + @ProtoNumber(100) var flags: Int = 0, +) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupChapter.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupChapter.kt index b493f1d56..bdc60b374 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupChapter.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupChapter.kt @@ -20,7 +20,7 @@ data class BackupChapter( @ProtoNumber(8) var dateUpload: Long = 0, // chapterNumber is called number is 1.x @ProtoNumber(9) var chapterNumber: Float = 0F, - @ProtoNumber(10) var sourceOrder: Int = 0 + @ProtoNumber(10) var sourceOrder: Int = 0, ) { fun toChapterImpl(): ChapterImpl { return ChapterImpl().apply { @@ -49,7 +49,7 @@ data class BackupChapter( lastPageRead = chapter.last_page_read, dateFetch = chapter.date_fetch, dateUpload = chapter.date_upload, - sourceOrder = chapter.source_order + sourceOrder = chapter.source_order, ) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupHistory.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupHistory.kt index c03d6f2bc..42c2e8d85 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupHistory.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupHistory.kt @@ -6,11 +6,11 @@ import kotlinx.serialization.protobuf.ProtoNumber @Serializable data class BrokenBackupHistory( @ProtoNumber(0) var url: String, - @ProtoNumber(1) var lastRead: Long + @ProtoNumber(1) var lastRead: Long, ) @Serializable data class BackupHistory( @ProtoNumber(1) var url: String, - @ProtoNumber(2) var lastRead: Long + @ProtoNumber(2) var lastRead: Long, ) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupManga.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupManga.kt index 31b2e849f..0fe37d168 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupManga.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupManga.kt @@ -37,7 +37,7 @@ data class BackupManga( @ProtoNumber(102) var brokenHistory: List = emptyList(), @ProtoNumber(103) var viewer_flags: Int? = null, @ProtoNumber(104) var history: List = emptyList(), - @ProtoNumber(105) var updateStrategy: UpdateStrategy = UpdateStrategy.ALWAYS_UPDATE + @ProtoNumber(105) var updateStrategy: UpdateStrategy = UpdateStrategy.ALWAYS_UPDATE, ) { fun getMangaImpl(): MangaImpl { return MangaImpl().apply { @@ -86,7 +86,7 @@ data class BackupManga( dateAdded = manga.date_added, viewer = manga.readingModeType, viewer_flags = manga.viewer_flags, - chapterFlags = manga.chapter_flags + chapterFlags = manga.chapter_flags, ) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupSource.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupSource.kt index 9ae7550b4..d5256ba54 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupSource.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupSource.kt @@ -7,19 +7,19 @@ import kotlinx.serialization.protobuf.ProtoNumber @Serializable data class BrokenBackupSource( @ProtoNumber(0) var name: String = "", - @ProtoNumber(1) var sourceId: Long + @ProtoNumber(1) var sourceId: Long, ) @Serializable data class BackupSource( @ProtoNumber(1) var name: String = "", - @ProtoNumber(2) var sourceId: Long + @ProtoNumber(2) var sourceId: Long, ) { companion object { fun copyFrom(source: Source): BackupSource { return BackupSource( name = source.name, - sourceId = source.id + sourceId = source.id, ) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupTracking.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupTracking.kt index 4135085e4..8898f3371 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupTracking.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupTracking.kt @@ -24,7 +24,7 @@ data class BackupTracking( // startedReadingDate is called startReadTime in 1.x @ProtoNumber(10) var startedReadingDate: Long = 0, // finishedReadingDate is called endReadTime in 1.x - @ProtoNumber(11) var finishedReadingDate: Long = 0 + @ProtoNumber(11) var finishedReadingDate: Long = 0, ) { fun getTrackingImpl(): TrackImpl { return TrackImpl().apply { @@ -58,7 +58,7 @@ data class BackupTracking( status = track.status, startedReadingDate = track.started_reading_date, finishedReadingDate = track.finished_reading_date, - trackingUrl = track.tracking_url + trackingUrl = track.tracking_url, ) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/chapter/ChapterForDownload.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/chapter/ChapterForDownload.kt index adfcfb9b7..a05771dbf 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/chapter/ChapterForDownload.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/chapter/ChapterForDownload.kt @@ -29,7 +29,11 @@ import suwayomi.tachidesk.manga.model.table.PageTable import suwayomi.tachidesk.manga.model.table.toDataClass import java.io.File -suspend fun getChapterDownloadReady(chapterId: Int? = null, chapterIndex: Int? = null, mangaId: Int? = null): ChapterDataClass { +suspend fun getChapterDownloadReady( + chapterId: Int? = null, + chapterIndex: Int? = null, + mangaId: Int? = null, +): ChapterDataClass { val chapter = ChapterForDownload(chapterId, chapterIndex, mangaId) return chapter.asDownloadReady() @@ -39,14 +43,17 @@ suspend fun getChapterDownloadReadyById(chapterId: Int): ChapterDataClass { return getChapterDownloadReady(chapterId = chapterId) } -suspend fun getChapterDownloadReadyByIndex(chapterIndex: Int, mangaId: Int): ChapterDataClass { +suspend fun getChapterDownloadReadyByIndex( + chapterIndex: Int, + mangaId: Int, +): ChapterDataClass { return getChapterDownloadReady(chapterIndex = chapterIndex, mangaId = mangaId) } private class ChapterForDownload( optChapterId: Int? = null, optChapterIndex: Int? = null, - optMangaId: Int? = null + optMangaId: Int? = null, ) { suspend fun asDownloadReady(): ChapterDataClass { if (isNotCompletelyDownloaded()) { @@ -74,7 +81,11 @@ private class ChapterForDownload( mangaId = chapterEntry[ChapterTable.manga].value } - private fun freshChapterEntry(optChapterId: Int? = null, optChapterIndex: Int? = null, optMangaId: Int? = null) = transaction { + private fun freshChapterEntry( + optChapterId: Int? = null, + optChapterIndex: Int? = null, + optMangaId: Int? = null, + ) = transaction { ChapterTable.select { if (optChapterId != null) { ChapterTable.id eq optChapterId @@ -94,7 +105,7 @@ private class ChapterForDownload( SChapter.create().apply { url = chapterEntry[ChapterTable.url] name = chapterEntry[ChapterTable.name] - } + }, ) } @@ -126,7 +137,7 @@ private class ChapterForDownload( private fun updatePageCount( pageList: List, - chapterId: Int + chapterId: Int, ) { transaction { ChapterTable.update({ ChapterTable.id eq chapterId }) { @@ -141,7 +152,7 @@ private class ChapterForDownload( return !( chapterEntry[ChapterTable.isDownloaded] && (firstPageExists() || File(getChapterCbzPath(mangaId, chapterEntry[ChapterTable.id].value)).exists()) - ) + ) } private fun firstPageExists(): Boolean { @@ -154,7 +165,7 @@ private class ChapterForDownload( return ImageResponse.findFileNameStartingWith( chapterDir, - getPageName(0) + getPageName(0), ) != null } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/DownloadManager.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/DownloadManager.kt index d59e782d4..205d0c297 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/DownloadManager.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/DownloadManager.kt @@ -57,16 +57,16 @@ object DownloadManager { private val downloadQueue = CopyOnWriteArrayList() private val downloaders = ConcurrentHashMap() - private const val downloadQueueKey = "downloadQueueKey" + private const val DOWNLOAD_QUEUE_KEY = "downloadQueueKey" private val sharedPreferences = Injekt.get().getSharedPreferences(DownloadManager::class.jvmName, Context.MODE_PRIVATE) private fun loadDownloadQueue(): List { - return sharedPreferences.getStringSet(downloadQueueKey, emptySet())?.mapNotNull { it.toInt() } ?: emptyList() + return sharedPreferences.getStringSet(DOWNLOAD_QUEUE_KEY, emptySet())?.mapNotNull { it.toInt() } ?: emptyList() } private fun saveDownloadQueue() { - sharedPreferences.edit().putStringSet(downloadQueueKey, downloadQueue.map { it.chapter.id.toString() }.toSet()) + sharedPreferences.edit().putStringSet(DOWNLOAD_QUEUE_KEY, downloadQueue.map { it.chapter.id.toString() }.toSet()) .apply() } @@ -95,30 +95,32 @@ object DownloadManager { fun notifyClient(ctx: WsContext) { ctx.send( - getStatus() + getStatus(), ) } fun handleRequest(ctx: WsMessageContext) { when (ctx.message()) { "STATUS" -> notifyClient(ctx) - else -> ctx.send( - """ + else -> + ctx.send( + """ |Invalid command. |Supported commands are: | - STATUS | sends the current download status | - """.trimMargin() - ) + """.trimMargin(), + ) } } private val notifyFlow = MutableSharedFlow(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) - val status = notifyFlow.sample(1.seconds) - .onStart { emit(Unit) } - .map { getStatus() } + val status = + notifyFlow.sample(1.seconds) + .onStart { emit(Unit) } + .map { getStatus() } init { scope.launch { @@ -129,6 +131,7 @@ object DownloadManager { } private val saveQueueFlow = MutableSharedFlow(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) + init { saveQueueFlow.onEach { saveDownloadQueue() }.launchIn(scope) } @@ -159,11 +162,12 @@ object DownloadManager { } else { Status.Started }, - downloadQueue.toList() + downloadQueue.toList(), ) } private val downloaderWatch = MutableSharedFlow(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) + init { serverConfig.subscribeTo(serverConfig.maxSourcesInParallel, { maxSourcesInParallel -> val runningDownloaders = downloaders.values.filter { it.isActive } @@ -186,14 +190,18 @@ object DownloadManager { val runningDownloaders = downloaders.values.filter { it.isActive } val availableDownloads = downloadQueue.filter { it.state != Error } - logger.info { "Running: ${runningDownloaders.size}, Queued: ${availableDownloads.size}, Failed: ${downloadQueue.size - availableDownloads.size}" } + logger.info { + "Running: ${runningDownloaders.size}, " + + "Queued: ${availableDownloads.size}, " + + "Failed: ${downloadQueue.size - availableDownloads.size}" + } if (runningDownloaders.size < serverConfig.maxSourcesInParallel.value) { availableDownloads.asSequence() .map { it.manga.sourceId } .distinct() .minus( - runningDownloaders.map { it.sourceId }.toSet() + runningDownloaders.map { it.sourceId }.toSet(), ) .take(serverConfig.maxSourcesInParallel.value - runningDownloaders.size) .map { getDownloader(it) } @@ -212,24 +220,29 @@ object DownloadManager { } } - private fun getDownloader(sourceId: String) = downloaders.getOrPut(sourceId) { - Downloader( - scope = scope, - sourceId = sourceId, - downloadQueue = downloadQueue, - notifier = ::notifyAllClients, - onComplete = ::refreshDownloaders, - onDownloadFinished = ::triggerSaveDownloadQueue - ) - } - - fun enqueueWithChapterIndex(mangaId: Int, chapterIndex: Int) { - val chapter = transaction { - ChapterTable - .slice(ChapterTable.id) - .select { ChapterTable.manga.eq(mangaId) and ChapterTable.sourceOrder.eq(chapterIndex) } - .first() + private fun getDownloader(sourceId: String) = + downloaders.getOrPut(sourceId) { + Downloader( + scope = scope, + sourceId = sourceId, + downloadQueue = downloadQueue, + notifier = ::notifyAllClients, + onComplete = ::refreshDownloaders, + onDownloadFinished = ::triggerSaveDownloadQueue, + ) } + + fun enqueueWithChapterIndex( + mangaId: Int, + chapterIndex: Int, + ) { + val chapter = + transaction { + ChapterTable + .slice(ChapterTable.id) + .select { ChapterTable.manga.eq(mangaId) and ChapterTable.sourceOrder.eq(chapterIndex) } + .first() + } enqueue(EnqueueInput(chapterIds = listOf(chapter[ChapterTable.id].value))) } @@ -237,35 +250,38 @@ object DownloadManager { // Input might have additional formats in the future, such as "All for mangaID" or "Unread for categoryID" // Having this input format is just future-proofing data class EnqueueInput( - val chapterIds: List? + val chapterIds: List?, ) fun enqueue(input: EnqueueInput) { if (input.chapterIds.isNullOrEmpty()) return - val chapters = transaction { - (ChapterTable innerJoin MangaTable) - .select { ChapterTable.id inList input.chapterIds } - .orderBy(ChapterTable.manga) - .orderBy(ChapterTable.sourceOrder) - .toList() - } + val chapters = + transaction { + (ChapterTable innerJoin MangaTable) + .select { ChapterTable.id inList input.chapterIds } + .orderBy(ChapterTable.manga) + .orderBy(ChapterTable.sourceOrder) + .toList() + } - val mangas = transaction { - chapters.distinctBy { chapter -> chapter[MangaTable.id] } - .map { MangaTable.toDataClass(it) } - .associateBy { it.id } - } + val mangas = + transaction { + chapters.distinctBy { chapter -> chapter[MangaTable.id] } + .map { MangaTable.toDataClass(it) } + .associateBy { it.id } + } - val inputPairs = transaction { - chapters.map { - Pair( - // this should be safe because mangas is created above from chapters - mangas[it[ChapterTable.manga].value]!!, - ChapterTable.toDataClass(it) - ) + val inputPairs = + transaction { + chapters.map { + Pair( + // this should be safe because mangas is created above from chapters + mangas[it[ChapterTable.manga].value]!!, + ChapterTable.toDataClass(it), + ) + } } - } addMultipleToQueue(inputPairs) } @@ -289,17 +305,21 @@ object DownloadManager { * Tries to add chapter to queue. * If chapter is added, returns the created DownloadChapter, otherwise returns null */ - private fun addToQueue(manga: MangaDataClass, chapter: ChapterDataClass): DownloadChapter? { + private fun addToQueue( + manga: MangaDataClass, + chapter: ChapterDataClass, + ): DownloadChapter? { val downloadChapter = downloadQueue.firstOrNull { it.mangaId == manga.id && it.chapterIndex == chapter.index } val addToQueue = downloadChapter == null if (addToQueue) { - val newDownloadChapter = DownloadChapter( - chapter.index, - manga.id, - chapter, - manga - ) + val newDownloadChapter = + DownloadChapter( + chapter.index, + manga.id, + chapter, + manga, + ) downloadQueue.add(newDownloadChapter) triggerSaveDownloadQueue() logger.debug { "Added chapter ${chapter.id} to download queue ($newDownloadChapter)" } @@ -325,11 +345,17 @@ object DownloadManager { dequeue(downloadQueue.filter { it.chapter.id in input.chapterIds }.toSet()) } - fun dequeue(chapterIndex: Int, mangaId: Int) { + fun dequeue( + chapterIndex: Int, + mangaId: Int, + ) { dequeue(downloadQueue.filter { it.mangaId == mangaId && it.chapterIndex == chapterIndex }.toSet()) } - fun dequeue(mangaIds: List, chaptersToIgnore: List = emptyList()) { + fun dequeue( + mangaIds: List, + chaptersToIgnore: List = emptyList(), + ) { dequeue(downloadQueue.filter { it.mangaId in mangaIds && it.chapter.id !in chaptersToIgnore }.toSet()) } @@ -342,21 +368,33 @@ object DownloadManager { notifyAllClients() } - fun reorder(chapterIndex: Int, mangaId: Int, to: Int) { - val download = downloadQueue.find { it.mangaId == mangaId && it.chapterIndex == chapterIndex } - ?: return + fun reorder( + chapterIndex: Int, + mangaId: Int, + to: Int, + ) { + val download = + downloadQueue.find { it.mangaId == mangaId && it.chapterIndex == chapterIndex } + ?: return reorder(download, to) } - fun reorder(chapterId: Int, to: Int) { - val download = downloadQueue.find { it.chapter.id == chapterId } - ?: return + fun reorder( + chapterId: Int, + to: Int, + ) { + val download = + downloadQueue.find { it.chapter.id == chapterId } + ?: return reorder(download, to) } - private fun reorder(download: DownloadChapter, to: Int) { + private fun reorder( + download: DownloadChapter, + to: Int, + ) { require(to >= 0) { "'to' must be over or equal to 0" } logger.debug { "reorder download $download from ${downloadQueue.indexOf(download)} to $to" } @@ -400,5 +438,5 @@ object DownloadManager { enum class DownloaderState(val state: Int) { Stopped(0), Running(1), - Paused(2) + Paused(2), } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/Downloader.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/Downloader.kt index 99f6af97a..b3abde53f 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/Downloader.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/Downloader.kt @@ -36,7 +36,7 @@ class Downloader( private val downloadQueue: CopyOnWriteArrayList, private val notifier: (immediate: Boolean) -> Unit, private val onComplete: () -> Unit, - private val onDownloadFinished: () -> Unit + private val onDownloadFinished: () -> Unit, ) { private val logger = KotlinLogging.logger("${Downloader::class.java.name} source($sourceId)") @@ -45,9 +45,13 @@ class Downloader( get() = downloadQueue.filter { it.manga.sourceId == sourceId } class StopDownloadException : Exception("Cancelled download") + class PauseDownloadException : Exception("Pause download") - private suspend fun step(download: DownloadChapter?, immediate: Boolean) { + private suspend fun step( + download: DownloadChapter?, + immediate: Boolean, + ) { notifier(immediate) currentCoroutineContext().ensureActive() if (download != null && download != availableSourceDownloads.firstOrNull { it.state != Error }) { @@ -64,16 +68,17 @@ class Downloader( fun start() { if (!isActive) { - job = scope.launch { - run() - }.also { job -> - job.invokeOnCompletion { - if (it !is CancellationException) { - logger.debug { "completed" } - onComplete() + job = + scope.launch { + run() + }.also { job -> + job.invokeOnCompletion { + if (it !is CancellationException) { + logger.debug { "completed" } + onComplete() + } } } - } logger.debug { "started" } notifier(false) } @@ -84,7 +89,10 @@ class Downloader( logger.debug { "stopped" } } - private suspend fun finishDownload(logger: KLogger, download: DownloadChapter) { + private suspend fun finishDownload( + logger: KLogger, + download: DownloadChapter, + ) { downloadQueue.removeIf { it.mangaId == download.mangaId && it.chapterIndex == download.chapterIndex } step(null, false) logger.debug { "finished" } @@ -93,9 +101,10 @@ class Downloader( private suspend fun run() { while (downloadQueue.isNotEmpty() && currentCoroutineContext().isActive) { - val download = availableSourceDownloads.firstOrNull { - (it.state == Queued || it.state == Finished || (it.state == Error && it.tries < 3)) // 3 re-tries per download - } ?: break + val download = + availableSourceDownloads.firstOrNull { + (it.state == Queued || it.state == Finished || (it.state == Error && it.tries < 3)) // 3 re-tries per download + } ?: break val logContext = "${logger.name} - downloadChapter($download))" val downloadLogger = KotlinLogging.logger(logContext) @@ -120,7 +129,9 @@ class Downloader( ChapterDownloadHelper.download(download.mangaId, download.chapter.id, download, scope, this::step) download.state = Finished transaction { - ChapterTable.update({ (ChapterTable.manga eq download.mangaId) and (ChapterTable.sourceOrder eq download.chapterIndex) }) { + ChapterTable.update( + { (ChapterTable.manga eq download.mangaId) and (ChapterTable.sourceOrder eq download.chapterIndex) }, + ) { it[isDownloaded] = true } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/fileProvider/ChaptersFilesProvider.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/fileProvider/ChaptersFilesProvider.kt index 01092db57..e5428bba9 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/fileProvider/ChaptersFilesProvider.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/fileProvider/ChaptersFilesProvider.kt @@ -27,7 +27,7 @@ abstract class ChaptersFilesProvider(val mangaId: Int, val chapterId: Int) : Dow open suspend fun downloadImpl( download: DownloadChapter, scope: CoroutineScope, - step: suspend (DownloadChapter?, Boolean) -> Unit + step: suspend (DownloadChapter?, Boolean) -> Unit, ): Boolean { val pageCount = download.chapter.pageCount val chapterDir = getChapterCachePath(mangaId, chapterId) @@ -42,16 +42,17 @@ abstract class ChaptersFilesProvider(val mangaId: Int, val chapterId: Int) : Dow Page.getPageImage( mangaId = download.mangaId, chapterIndex = download.chapterIndex, - index = pageNum + index = pageNum, ) { flow -> - pageProgressJob = flow - .sample(100) - .distinctUntilChanged() - .onEach { - download.progress = (pageNum.toFloat() + (it.toFloat() * 0.01f)) / pageCount - step(null, false) // don't throw on canceled download here since we can't do anything - } - .launchIn(scope) + pageProgressJob = + flow + .sample(100) + .distinctUntilChanged() + .onEach { + download.progress = (pageNum.toFloat() + (it.toFloat() * 0.01f)) / pageCount + step(null, false) // don't throw on canceled download here since we can't do anything + } + .launchIn(scope) } } finally { // always cancel the page progress job even if it throws an exception to avoid memory leaks diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/fileProvider/FileDownloader.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/fileProvider/FileDownloader.kt index bc9f86840..f57d85a0a 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/fileProvider/FileDownloader.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/fileProvider/FileDownloader.kt @@ -13,7 +13,11 @@ fun interface FileDownload0Args : FileDownload { } fun interface FileDownload3Args : FileDownload { - suspend fun execute(a: A, b: B, c: C): Boolean + suspend fun execute( + a: A, + b: B, + c: C, + ): Boolean override suspend fun executeDownload(vararg args: Any): Boolean { return execute(args[0] as A, args[1] as B, args[2] as C) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/fileProvider/impl/ArchiveProvider.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/fileProvider/impl/ArchiveProvider.kt index 0ffd4c0cc..fa88d6d49 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/fileProvider/impl/ArchiveProvider.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/fileProvider/impl/ArchiveProvider.kt @@ -28,7 +28,7 @@ class ArchiveProvider(mangaId: Int, chapterId: Int) : ChaptersFilesProvider(mang override suspend fun downloadImpl( download: DownloadChapter, scope: CoroutineScope, - step: suspend (DownloadChapter?, Boolean) -> Unit + step: suspend (DownloadChapter?, Boolean) -> Unit, ): Boolean { val mangaDownloadFolder = File(getMangaDownloadDir(mangaId)) val outputFile = File(getChapterCbzPath(mangaId, chapterId)) @@ -71,7 +71,10 @@ class ArchiveProvider(mangaId: Int, chapterId: Int) : ChaptersFilesProvider(mang return false } - private fun handleExistingCbzFile(cbzFile: File, chapterFolder: File) { + private fun handleExistingCbzFile( + cbzFile: File, + chapterFolder: File, + ) { if (!chapterFolder.exists()) chapterFolder.mkdirs() ZipArchiveInputStream(cbzFile.inputStream()).use { zipInputStream -> var zipEntry = zipInputStream.nextEntry diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/fileProvider/impl/FolderProvider.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/fileProvider/impl/FolderProvider.kt index 3975b1221..cf9f4bd2a 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/fileProvider/impl/FolderProvider.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/fileProvider/impl/FolderProvider.kt @@ -25,7 +25,7 @@ class FolderProvider(mangaId: Int, chapterId: Int) : ChaptersFilesProvider(manga override suspend fun downloadImpl( download: DownloadChapter, scope: CoroutineScope, - step: suspend (DownloadChapter?, Boolean) -> Unit + step: suspend (DownloadChapter?, Boolean) -> Unit, ): Boolean { val chapterDir = getChapterDownloadPath(mangaId, chapterId) val folder = File(chapterDir) @@ -51,10 +51,14 @@ class FolderProvider(mangaId: Int, chapterId: Int) : ChaptersFilesProvider(manga return File(chapterDir).deleteRecursively() } - private fun isExistingFile(folder: File, fileName: String): Boolean { - val existingFile = folder.listFiles { file -> - file.isFile && file.name.startsWith(fileName) - }?.firstOrNull() + private fun isExistingFile( + folder: File, + fileName: String, + ): Boolean { + val existingFile = + folder.listFiles { file -> + file.isFile && file.name.startsWith(fileName) + }?.firstOrNull() return existingFile?.exists() == true } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/model/DownloadChapter.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/model/DownloadChapter.kt index 2cd82654a..03a5966f4 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/model/DownloadChapter.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/model/DownloadChapter.kt @@ -18,7 +18,7 @@ class DownloadChapter( var manga: MangaDataClass, var state: DownloadState = Queued, var progress: Float = 0f, - var tries: Int = 0 + var tries: Int = 0, ) { override fun toString(): String { return "${manga.title} ($mangaId) - ${chapter.name} (${chapter.id}) | state= $state, tries= $tries, progress= $progress" diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/model/DownloadState.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/model/DownloadState.kt index 5be5d609e..61ef7d11b 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/model/DownloadState.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/model/DownloadState.kt @@ -11,5 +11,5 @@ enum class DownloadState(val state: Int) { Queued(0), Downloading(1), Finished(2), - Error(3) + Error(3), } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/model/DownloadStatus.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/model/DownloadStatus.kt index 782109bf4..d66132f3f 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/model/DownloadStatus.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/download/model/DownloadStatus.kt @@ -8,10 +8,11 @@ package suwayomi.tachidesk.manga.impl.download.model * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ enum class Status { - Stopped, Started; + Stopped, + Started, } data class DownloadStatus( val status: Status, - val queue: List + val queue: List, ) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/extension/Extension.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/extension/Extension.kt index e8f154ce6..a06667815 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/extension/Extension.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/extension/Extension.kt @@ -71,7 +71,10 @@ object Extension { } } - suspend fun installExternalExtension(inputStream: InputStream, apkName: String): Int { + suspend fun installExternalExtension( + inputStream: InputStream, + apkName: String, + ): Int { return installAPK(true) { val savePath = "${applicationDirs.extensionsRoot}/$apkName" logger.debug { "Saving apk at $apkName" } @@ -87,15 +90,19 @@ object Extension { } } - suspend fun installAPK(forceReinstall: Boolean = false, fetcher: suspend () -> String): Int { + suspend fun installAPK( + forceReinstall: Boolean = false, + fetcher: suspend () -> String, + ): Int { val apkFilePath = fetcher() val apkName = File(apkFilePath).name // check if we don't have the extension already installed // if it's installed and we want to update, it first has to be uninstalled - val isInstalled = transaction { - ExtensionTable.select { ExtensionTable.apkName eq apkName }.firstOrNull() - }?.get(ExtensionTable.isInstalled) ?: false + val isInstalled = + transaction { + ExtensionTable.select { ExtensionTable.apkName eq apkName }.firstOrNull() + }?.get(ExtensionTable.isInstalled) ?: false val fileNameWithoutType = apkName.substringBefore(".apk") @@ -119,7 +126,7 @@ object Extension { if (libVersion < LIB_VERSION_MIN || libVersion > LIB_VERSION_MAX) { throw Exception( "Lib version is $libVersion, while only versions " + - "$LIB_VERSION_MIN to $LIB_VERSION_MAX are allowed" + "$LIB_VERSION_MIN to $LIB_VERSION_MAX are allowed", ) } @@ -148,18 +155,20 @@ object Extension { // collect sources from the extension val extensionMainClassInstance = loadExtensionSources(jarFilePath, className) - val sources: List = when (extensionMainClassInstance) { - is Source -> listOf(extensionMainClassInstance) - is SourceFactory -> extensionMainClassInstance.createSources() - else -> throw RuntimeException("Unknown source class type! ${extensionMainClassInstance.javaClass}") - }.map { it as CatalogueSource } + val sources: List = + when (extensionMainClassInstance) { + is Source -> listOf(extensionMainClassInstance) + is SourceFactory -> extensionMainClassInstance.createSources() + else -> throw RuntimeException("Unknown source class type! ${extensionMainClassInstance.javaClass}") + }.map { it as CatalogueSource } val langs = sources.map { it.lang }.toSet() - val extensionLang = when (langs.size) { - 0 -> "" - 1 -> langs.first() - else -> "all" - } + val extensionLang = + when (langs.size) { + 0 -> "" + 1 -> langs.first() + else -> "all" + } val extensionName = packageInfo.applicationInfo.nonLocalizedLabel.toString().substringAfter("Tachiyomi: ") @@ -205,7 +214,10 @@ object Extension { } } - private fun extractAssetsFromApk(apkPath: String, jarPath: String) { + private fun extractAssetsFromApk( + apkPath: String, + jarPath: String, + ) { val apkFile = File(apkPath) val jarFile = File(jarPath) @@ -256,7 +268,10 @@ object Extension { private val network: NetworkHelper by injectLazy() - private suspend fun downloadAPKFile(url: String, savePath: String) { + private suspend fun downloadAPKFile( + url: String, + savePath: String, + ) { val request = Request.Builder().url(url).build() val response = network.client.newCall(request).await() @@ -275,23 +290,24 @@ object Extension { val extensionRecord = transaction { ExtensionTable.select { ExtensionTable.pkgName eq pkgName }.first() } val fileNameWithoutType = extensionRecord[ExtensionTable.apkName].substringBefore(".apk") val jarPath = "${applicationDirs.extensionsRoot}/$fileNameWithoutType.jar" - val sources = transaction { - val extensionId = extensionRecord[ExtensionTable.id].value + val sources = + transaction { + val extensionId = extensionRecord[ExtensionTable.id].value - val sources = SourceTable.select { SourceTable.extension eq extensionId }.map { it[SourceTable.id].value } + val sources = SourceTable.select { SourceTable.extension eq extensionId }.map { it[SourceTable.id].value } - SourceTable.deleteWhere { SourceTable.extension eq extensionId } + SourceTable.deleteWhere { SourceTable.extension eq extensionId } - if (extensionRecord[ExtensionTable.isObsolete]) { - ExtensionTable.deleteWhere { ExtensionTable.pkgName eq pkgName } - } else { - ExtensionTable.update({ ExtensionTable.pkgName eq pkgName }) { - it[isInstalled] = false + if (extensionRecord[ExtensionTable.isObsolete]) { + ExtensionTable.deleteWhere { ExtensionTable.pkgName eq pkgName } + } else { + ExtensionTable.update({ ExtensionTable.pkgName eq pkgName }) { + it[isInstalled] = false + } } - } - sources - } + sources + } if (File(jarPath).exists()) { // free up the file descriptor if exists @@ -323,17 +339,18 @@ object Extension { } suspend fun getExtensionIcon(apkName: String): Pair { - val iconUrl = if (apkName == "localSource") { - "" - } else { - transaction { ExtensionTable.select { ExtensionTable.apkName eq apkName }.first() }[ExtensionTable.iconUrl] - } + val iconUrl = + if (apkName == "localSource") { + "" + } else { + transaction { ExtensionTable.select { ExtensionTable.apkName eq apkName }.first() }[ExtensionTable.iconUrl] + } val cacheSaveDir = "${applicationDirs.extensionsRoot}/icon" return getImageResponse(cacheSaveDir, apkName) { network.client.newCall( - GET(iconUrl) + GET(iconUrl), ).await() } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/extension/ExtensionsList.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/extension/ExtensionsList.kt index 0cbf6e8f9..728871cc3 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/extension/ExtensionsList.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/extension/ExtensionsList.kt @@ -48,23 +48,24 @@ object ExtensionsList { return extensionTableAsDataClass() } - fun extensionTableAsDataClass() = transaction { - ExtensionTable.selectAll().filter { it[ExtensionTable.name] != LocalSource.EXTENSION_NAME }.map { - ExtensionDataClass( - it[ExtensionTable.apkName], - getExtensionIconUrl(it[ExtensionTable.apkName]), - it[ExtensionTable.name], - it[ExtensionTable.pkgName], - it[ExtensionTable.versionName], - it[ExtensionTable.versionCode], - it[ExtensionTable.lang], - it[ExtensionTable.isNsfw], - it[ExtensionTable.isInstalled], - it[ExtensionTable.hasUpdate], - it[ExtensionTable.isObsolete] - ) + fun extensionTableAsDataClass() = + transaction { + ExtensionTable.selectAll().filter { it[ExtensionTable.name] != LocalSource.EXTENSION_NAME }.map { + ExtensionDataClass( + it[ExtensionTable.apkName], + getExtensionIconUrl(it[ExtensionTable.apkName]), + it[ExtensionTable.name], + it[ExtensionTable.pkgName], + it[ExtensionTable.versionName], + it[ExtensionTable.versionCode], + it[ExtensionTable.lang], + it[ExtensionTable.isNsfw], + it[ExtensionTable.isInstalled], + it[ExtensionTable.hasUpdate], + it[ExtensionTable.isObsolete], + ) + } } - } private fun updateExtensionDatabase(foundExtensions: List) { transaction { diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/extension/github/ExtensionGithubApi.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/extension/github/ExtensionGithubApi.kt index c462c8b75..b19ce9aa5 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/extension/github/ExtensionGithubApi.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/extension/github/ExtensionGithubApi.kt @@ -36,7 +36,7 @@ object ExtensionGithubApi { val nsfw: Int, val hasReadme: Int = 0, val hasChangelog: Int = 0, - val sources: List? + val sources: List?, ) @Serializable @@ -44,27 +44,29 @@ object ExtensionGithubApi { val name: String, val lang: String, val id: Long, - val baseUrl: String + val baseUrl: String, ) private var requiresFallbackSource = false suspend fun findExtensions(): List { - val githubResponse = if (requiresFallbackSource) { - null - } else { - try { - client.newCall(GET("${REPO_URL_PREFIX}index.min.json")).awaitSuccess() - } catch (e: Throwable) { - logger.error(e) { "Failed to get extensions from GitHub" } - requiresFallbackSource = true + val githubResponse = + if (requiresFallbackSource) { null + } else { + try { + client.newCall(GET("${REPO_URL_PREFIX}index.min.json")).awaitSuccess() + } catch (e: Throwable) { + logger.error(e) { "Failed to get extensions from GitHub" } + requiresFallbackSource = true + null + } } - } - val response = githubResponse ?: run { - client.newCall(GET("${FALLBACK_REPO_URL_PREFIX}index.min.json")).awaitSuccess() - } + val response = + githubResponse ?: run { + client.newCall(GET("${FALLBACK_REPO_URL_PREFIX}index.min.json")).awaitSuccess() + } return with(json) { response @@ -107,7 +109,7 @@ object ExtensionGithubApi { hasChangelog = it.hasChangelog == 1, sources = it.sources?.toExtensionSources() ?: emptyList(), apkName = it.apk, - iconUrl = "${REPO_URL_PREFIX}icon/${it.apk.replace(".apk", ".png")}" + iconUrl = "${REPO_URL_PREFIX}icon/${it.apk.replace(".apk", ".png")}", ) } } @@ -118,7 +120,7 @@ object ExtensionGithubApi { name = it.name, lang = it.lang, id = it.id, - baseUrl = it.baseUrl + baseUrl = it.baseUrl, ) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/extension/github/OnlineExtension.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/extension/github/OnlineExtension.kt index 034cc3061..e2380beca 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/extension/github/OnlineExtension.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/extension/github/OnlineExtension.kt @@ -11,7 +11,7 @@ data class OnlineExtensionSource( val name: String, val lang: String, val id: Long, - val baseUrl: String + val baseUrl: String, ) data class OnlineExtension( @@ -25,5 +25,5 @@ data class OnlineExtension( val hasReadme: Boolean, val hasChangelog: Boolean, val sources: List, - val iconUrl: String + val iconUrl: String, ) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/update/IUpdater.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/update/IUpdater.kt index b804e7f47..0670bfd8d 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/update/IUpdater.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/update/IUpdater.kt @@ -5,7 +5,14 @@ import suwayomi.tachidesk.manga.model.dataclass.CategoryDataClass interface IUpdater { fun getLastUpdateTimestamp(): Long - fun addCategoriesToUpdateQueue(categories: List, clear: Boolean?, forceAll: Boolean) + + fun addCategoriesToUpdateQueue( + categories: List, + clear: Boolean?, + forceAll: Boolean, + ) + val status: StateFlow + fun reset() } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/update/UpdateJob.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/update/UpdateJob.kt index ba07836f4..2dfb6cece 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/update/UpdateJob.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/update/UpdateJob.kt @@ -7,10 +7,10 @@ enum class JobStatus { RUNNING, COMPLETE, FAILED, - SKIPPED + SKIPPED, } data class UpdateJob( val manga: MangaDataClass, - val status: JobStatus = JobStatus.PENDING + val status: JobStatus = JobStatus.PENDING, ) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/update/UpdateStatus.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/update/UpdateStatus.kt index 3ee1e3abb..323e27c18 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/update/UpdateStatus.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/update/UpdateStatus.kt @@ -5,7 +5,8 @@ import suwayomi.tachidesk.manga.model.dataclass.CategoryDataClass import suwayomi.tachidesk.manga.model.dataclass.MangaDataClass enum class CategoryUpdateStatus { - UPDATING, SKIPPED + UPDATING, + SKIPPED, } data class UpdateStatus( @@ -13,16 +14,21 @@ data class UpdateStatus( val mangaStatusMap: Map> = emptyMap(), val running: Boolean = false, @JsonIgnore - val numberOfJobs: Int = 0 + val numberOfJobs: Int = 0, ) { - - constructor(categories: Map>, jobs: List, skippedMangas: List, running: Boolean) : this( + constructor( + categories: Map>, + jobs: List, + skippedMangas: List, + running: Boolean, + ) : this( categories, - mangaStatusMap = jobs.groupBy { it.status } - .mapValues { entry -> - entry.value.map { it.manga } - }.plus(Pair(JobStatus.SKIPPED, skippedMangas)), + mangaStatusMap = + jobs.groupBy { it.status } + .mapValues { entry -> + entry.value.map { it.manga } + }.plus(Pair(JobStatus.SKIPPED, skippedMangas)), running = running, - numberOfJobs = jobs.size + numberOfJobs = jobs.size, ) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/update/Updater.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/update/Updater.kt index 4da90d971..7cd820fd8 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/update/Updater.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/update/Updater.kt @@ -70,7 +70,7 @@ class Updater : IUpdater { } } }, - ignoreInitialValue = false + ignoreInitialValue = false, ) } @@ -87,7 +87,11 @@ class Updater : IUpdater { return } - logger.info { "Trigger global update (interval= ${serverConfig.globalUpdateInterval.value}h, lastAutomatedUpdate= ${Date(lastAutomatedUpdate)})" } + logger.info { + "Trigger global update (interval= ${serverConfig.globalUpdateInterval.value}h, lastAutomatedUpdate= ${Date( + lastAutomatedUpdate, + )})" + } addCategoriesToUpdateQueue(Category.getCategoryList(), clear = true, forceAll = false) } @@ -103,7 +107,10 @@ class Updater : IUpdater { val lastAutomatedUpdate = preferences.getLong(lastAutomatedUpdateKey, 0) val timeToNextExecution = (updateInterval - (System.currentTimeMillis() - lastAutomatedUpdate)).mod(updateInterval) - val wasPreviousUpdateTriggered = System.currentTimeMillis() - (if (lastAutomatedUpdate > 0) lastAutomatedUpdate else System.currentTimeMillis()) < updateInterval + val wasPreviousUpdateTriggered = + System.currentTimeMillis() - ( + if (lastAutomatedUpdate > 0) lastAutomatedUpdate else System.currentTimeMillis() + ) < updateInterval if (!wasPreviousUpdateTriggered) { autoUpdateTask() } @@ -114,7 +121,12 @@ class Updater : IUpdater { /** * Updates the status and sustains the "skippedMangas" */ - private fun updateStatus(jobs: List, running: Boolean, categories: Map>? = null, skippedMangas: List? = null) { + private fun updateStatus( + jobs: List, + running: Boolean, + categories: Map>? = null, + skippedMangas: List? = null, + ) { val updateStatusCategories = categories ?: _status.value.categoryStatusMap val tmpSkippedMangas = skippedMangas ?: _status.value.mangaStatusMap[JobStatus.SKIPPED] ?: emptyList() _status.update { UpdateStatus(updateStatusCategories, jobs, tmpSkippedMangas, running) } @@ -136,7 +148,7 @@ class Updater : IUpdater { process(job), tracker.any { (_, job) -> job.status == JobStatus.PENDING || job.status == JobStatus.RUNNING - } + }, ) } } @@ -148,19 +160,24 @@ class Updater : IUpdater { private suspend fun process(job: UpdateJob): List { tracker[job.manga.id] = job.copy(status = JobStatus.RUNNING) updateStatus(tracker.values.toList(), true) - tracker[job.manga.id] = try { - logger.info { "Updating \"${job.manga.title}\" (source: ${job.manga.sourceId})" } - Chapter.getChapterList(job.manga.id, true) - job.copy(status = JobStatus.COMPLETE) - } catch (e: Exception) { - if (e is CancellationException) throw e - logger.error(e) { "Error while updating ${job.manga.title}" } - job.copy(status = JobStatus.FAILED) - } + tracker[job.manga.id] = + try { + logger.info { "Updating \"${job.manga.title}\" (source: ${job.manga.sourceId})" } + Chapter.getChapterList(job.manga.id, true) + job.copy(status = JobStatus.COMPLETE) + } catch (e: Exception) { + if (e is CancellationException) throw e + logger.error(e) { "Error while updating ${job.manga.title}" } + job.copy(status = JobStatus.FAILED) + } return tracker.values.toList() } - override fun addCategoriesToUpdateQueue(categories: List, clear: Boolean?, forceAll: Boolean) { + override fun addCategoriesToUpdateQueue( + categories: List, + clear: Boolean?, + forceAll: Boolean, + ) { preferences.putLong(lastUpdateKey, System.currentTimeMillis()) if (clear == true) { @@ -171,31 +188,53 @@ class Updater : IUpdater { val excludedCategories = includeInUpdateStatusToCategoryMap[IncludeInUpdate.EXCLUDE].orEmpty() val includedCategories = includeInUpdateStatusToCategoryMap[IncludeInUpdate.INCLUDE].orEmpty() val unsetCategories = includeInUpdateStatusToCategoryMap[IncludeInUpdate.UNSET].orEmpty() - val categoriesToUpdate = if (forceAll) { - categories - } else { - includedCategories.ifEmpty { unsetCategories } - } + val categoriesToUpdate = + if (forceAll) { + categories + } else { + includedCategories.ifEmpty { unsetCategories } + } val skippedCategories = categories.subtract(categoriesToUpdate.toSet()).toList() - val updateStatusCategories = mapOf( - Pair(CategoryUpdateStatus.UPDATING, categoriesToUpdate), - Pair(CategoryUpdateStatus.SKIPPED, skippedCategories) - ) + val updateStatusCategories = + mapOf( + Pair(CategoryUpdateStatus.UPDATING, categoriesToUpdate), + Pair(CategoryUpdateStatus.SKIPPED, skippedCategories), + ) logger.debug { "Updating categories: '${categoriesToUpdate.joinToString("', '") { it.name }}'" } - val categoriesToUpdateMangas = categoriesToUpdate - .flatMap { CategoryManga.getCategoryMangaList(it.id) } - .distinctBy { it.id } + val categoriesToUpdateMangas = + categoriesToUpdate + .flatMap { CategoryManga.getCategoryMangaList(it.id) } + .distinctBy { it.id } val mangasToCategoriesMap = CategoryManga.getMangasCategories(categoriesToUpdateMangas.map { it.id }) - val mangasToUpdate = categoriesToUpdateMangas - .asSequence() - .filter { it.updateStrategy == UpdateStrategy.ALWAYS_UPDATE } - .filter { if (serverConfig.excludeUnreadChapters.value) { (it.unreadCount ?: 0L) == 0L } else true } - .filter { if (serverConfig.excludeNotStarted.value) { it.lastReadAt != null } else true } - .filter { if (serverConfig.excludeCompleted.value) { it.status != MangaStatus.COMPLETED.name } else true } - .filter { forceAll || !excludedCategories.any { category -> mangasToCategoriesMap[it.id]?.contains(category) == true } } - .toList() + val mangasToUpdate = + categoriesToUpdateMangas + .asSequence() + .filter { it.updateStrategy == UpdateStrategy.ALWAYS_UPDATE } + .filter { + if (serverConfig.excludeUnreadChapters.value) { + (it.unreadCount ?: 0L) == 0L + } else { + true + } + } + .filter { + if (serverConfig.excludeNotStarted.value) { + it.lastReadAt != null + } else { + true + } + } + .filter { + if (serverConfig.excludeCompleted.value) { + it.status != MangaStatus.COMPLETED.name + } else { + true + } + } + .filter { forceAll || !excludedCategories.any { category -> mangasToCategoriesMap[it.id]?.contains(category) == true } } + .toList() val skippedMangas = categoriesToUpdateMangas.subtract(mangasToUpdate.toSet()).toList() // In case no manga gets updated and no update job was running before, the client would never receive an info about its update request @@ -207,7 +246,7 @@ class Updater : IUpdater { addMangasToQueue( mangasToUpdate - .sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER, MangaDataClass::title)) + .sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER, MangaDataClass::title)), ) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/update/UpdaterSocket.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/update/UpdaterSocket.kt index cafa504c0..e7cdf2fbf 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/update/UpdaterSocket.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/update/UpdaterSocket.kt @@ -19,22 +19,26 @@ object UpdaterSocket : Websocket() { private val updater by DI.global.instance() private var job: Job? = null - override fun notifyClient(ctx: WsContext, value: UpdateStatus?) { + override fun notifyClient( + ctx: WsContext, + value: UpdateStatus?, + ) { ctx.send(value ?: updater.status.value) } override fun handleRequest(ctx: WsMessageContext) { when (ctx.message()) { "STATUS" -> notifyClient(ctx, updater.status.value) - else -> ctx.send( - """ + else -> + ctx.send( + """ |Invalid command. |Supported commands are: | - STATUS | sends the current update status | - """.trimMargin() - ) + """.trimMargin(), + ) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/update/Websocket.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/update/Websocket.kt index 2687de462..940920a49 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/update/Websocket.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/update/Websocket.kt @@ -6,16 +6,24 @@ import java.util.concurrent.ConcurrentHashMap abstract class Websocket { protected val clients = ConcurrentHashMap() + open fun addClient(ctx: WsContext) { clients[ctx.sessionId] = ctx notifyClient(ctx, null) } + open fun removeClient(ctx: WsContext) { clients.remove(ctx.sessionId) } + open fun notifyAllClients(value: T) { clients.values.forEach { notifyClient(it, value) } } - abstract fun notifyClient(ctx: WsContext, value: T?) + + abstract fun notifyClient( + ctx: WsContext, + value: T?, + ) + abstract fun handleRequest(ctx: WsMessageContext) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/BytecodeEditor.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/BytecodeEditor.kt index c8f38857c..2ccabce07 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/BytecodeEditor.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/BytecodeEditor.kt @@ -55,13 +55,14 @@ object BytecodeEditor { // Invalid class size return null } - val cafebabe = String.format( - "%02X%02X%02X%02X", - bytes[0], - bytes[1], - bytes[2], - bytes[3] - ) + val cafebabe = + String.format( + "%02X%02X%02X%02X", + bytes[0], + bytes[1], + bytes[2], + bytes[3], + ) if (cafebabe.lowercase() != "cafebabe") { // Corrupted class return null @@ -80,14 +81,15 @@ object BytecodeEditor { /** * The path where replacement classes will reside */ - private const val replacementPath = "xyz/nulldev/androidcompat/replace" + private const val REPLACEMENT_PATH = "xyz/nulldev/androidcompat/replace" /** * List of classes that will be replaced */ - private val classesToReplace = listOf( - "java/text/SimpleDateFormat" - ) + private val classesToReplace = + listOf( + "java/text/SimpleDateFormat", + ) /** * Replace direct references to the class, used on places @@ -95,11 +97,12 @@ object BytecodeEditor { * * @return [String] of class or null if [String] was null */ - private fun String?.replaceDirectly() = when (this) { - null -> this - in classesToReplace -> "$replacementPath/$this" - else -> this - } + private fun String?.replaceDirectly() = + when (this) { + null -> this + in classesToReplace -> "$REPLACEMENT_PATH/$this" + else -> this + } /** * Replace references to the class, used in places that have @@ -112,7 +115,7 @@ object BytecodeEditor { var classReference = this if (classReference != null) { classesToReplace.forEach { - classReference = classReference?.replace(it, "$replacementPath/$it") + classReference = classReference?.replace(it, "$REPLACEMENT_PATH/$it") } } return classReference @@ -142,7 +145,7 @@ object BytecodeEditor { name: String?, desc: String?, signature: String?, - cst: Any? + cst: Any?, ): FieldVisitor? { logger.trace { "CLass Field" to "${desc.replaceIndirectly()}: ${cst?.let { it::class.java.simpleName }}: $cst" } return super.visitField(access, name, desc.replaceIndirectly(), signature, cst) @@ -154,7 +157,7 @@ object BytecodeEditor { name: String?, signature: String?, superName: String?, - interfaces: Array? + interfaces: Array?, ) { logger.trace { "Visiting $name: $signature: $superName" } super.visit(version, access, name, signature, superName, interfaces) @@ -171,16 +174,17 @@ object BytecodeEditor { name: String, desc: String, signature: String?, - exceptions: Array? + exceptions: Array?, ): MethodVisitor { logger.trace { "Processing method $name: ${desc.replaceIndirectly()}: $signature" } - val mv: MethodVisitor? = super.visitMethod( - access, - name, - desc.replaceIndirectly(), - signature, - exceptions - ) + val mv: MethodVisitor? = + super.visitMethod( + access, + name, + desc.replaceIndirectly(), + signature, + exceptions, + ) return object : MethodVisitor(Opcodes.ASM5, mv) { override fun visitLdcInsn(cst: Any?) { logger.trace { "Ldc" to "${cst?.let { "${it::class.java.simpleName}: $it" }}" } @@ -192,13 +196,16 @@ object BytecodeEditor { // fun fetchChapterList() { // if (format is SimpleDateFormat) // } - override fun visitTypeInsn(opcode: Int, type: String?) { + override fun visitTypeInsn( + opcode: Int, + type: String?, + ) { logger.trace { "Type" to "$opcode: ${type.replaceDirectly()}" } super.visitTypeInsn( opcode, - type.replaceDirectly() + type.replaceDirectly(), ) } @@ -211,7 +218,7 @@ object BytecodeEditor { owner: String?, name: String?, desc: String?, - itf: Boolean + itf: Boolean, ) { logger.trace { "Method" to "$opcode: ${owner.replaceDirectly()}: $name: ${desc.replaceIndirectly()}" @@ -221,7 +228,7 @@ object BytecodeEditor { owner.replaceDirectly(), name, desc.replaceIndirectly(), - itf + itf, ) } @@ -234,7 +241,7 @@ object BytecodeEditor { opcode: Int, owner: String?, name: String?, - desc: String? + desc: String?, ) { logger.trace { "Field" to "$opcode: $owner: $name: ${desc.replaceIndirectly()}" } super.visitFieldInsn(opcode, owner, name, desc.replaceIndirectly()) @@ -244,7 +251,7 @@ object BytecodeEditor { name: String?, desc: String?, bsm: Handle?, - vararg bsmArgs: Any? + vararg bsmArgs: Any?, ) { logger.trace { "InvokeDynamic" to "$name: $desc" } super.visitInvokeDynamicInsn(name, desc, bsm, *bsmArgs) @@ -252,7 +259,7 @@ object BytecodeEditor { } } }, - 0 + 0, ) return pair.first to cw.toByteArray() } @@ -262,7 +269,7 @@ object BytecodeEditor { pair.first, pair.second, StandardOpenOption.CREATE, - StandardOpenOption.TRUNCATE_EXISTING + StandardOpenOption.TRUNCATE_EXISTING, ) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/DirName.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/DirName.kt index 8f3844e83..ab8df1083 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/DirName.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/DirName.kt @@ -31,15 +31,19 @@ private fun getMangaDir(mangaId: Int): String { return "$sourceDir/$mangaDir" } -private fun getChapterDir(mangaId: Int, chapterId: Int): String { +private fun getChapterDir( + mangaId: Int, + chapterId: Int, +): String { val chapterEntry = transaction { ChapterTable.select { ChapterTable.id eq chapterId }.first() } - val chapterDir = SafePath.buildValidFilename( - when { - chapterEntry[ChapterTable.scanlator] != null -> "${chapterEntry[ChapterTable.scanlator]}_${chapterEntry[ChapterTable.name]}" - else -> chapterEntry[ChapterTable.name] - } - ) + val chapterDir = + SafePath.buildValidFilename( + when { + chapterEntry[ChapterTable.scanlator] != null -> "${chapterEntry[ChapterTable.scanlator]}_${chapterEntry[ChapterTable.name]}" + else -> chapterEntry[ChapterTable.name] + }, + ) return getMangaDir(mangaId) + "/$chapterDir" } @@ -52,20 +56,32 @@ fun getMangaDownloadDir(mangaId: Int): String { return applicationDirs.mangaDownloadsRoot + "/" + getMangaDir(mangaId) } -fun getChapterDownloadPath(mangaId: Int, chapterId: Int): String { +fun getChapterDownloadPath( + mangaId: Int, + chapterId: Int, +): String { return applicationDirs.mangaDownloadsRoot + "/" + getChapterDir(mangaId, chapterId) } -fun getChapterCbzPath(mangaId: Int, chapterId: Int): String { +fun getChapterCbzPath( + mangaId: Int, + chapterId: Int, +): String { return getChapterDownloadPath(mangaId, chapterId) + ".cbz" } -fun getChapterCachePath(mangaId: Int, chapterId: Int): String { +fun getChapterCachePath( + mangaId: Int, + chapterId: Int, +): String { return applicationDirs.tempMangaCacheRoot + "/" + getChapterDir(mangaId, chapterId) } /** return value says if rename/move was successful */ -fun updateMangaDownloadDir(mangaId: Int, newTitle: String): Boolean { +fun updateMangaDownloadDir( + mangaId: Int, + newTitle: String, +): Boolean { val mangaEntry = getMangaEntry(mangaId) val source = GetCatalogueSource.getCatalogueSourceOrStub(mangaEntry[MangaTable.sourceReference]) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/PackageTools.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/PackageTools.kt index 393aef226..e7c9d44ed 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/PackageTools.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/PackageTools.kt @@ -43,14 +43,18 @@ object PackageTools { const val LIB_VERSION_MIN = 1.3 const val LIB_VERSION_MAX = 1.5 - private const val officialSignature = "7ce04da7773d41b489f4693a366c36bcd0a11fc39b547168553c285bd7348e23" // inorichi's key - private const val unofficialSignature = "64feb21075ba97ebc9cc981243645b331595c111cef1b0d084236a0403b00581" // ArMor's key - val trustedSignatures = mutableSetOf() + officialSignature + unofficialSignature + private const val OFFICIAL_SIGNATURE = "7ce04da7773d41b489f4693a366c36bcd0a11fc39b547168553c285bd7348e23" // inorichi's key + private const val UNOFFICIAL_SIGNATURE = "64feb21075ba97ebc9cc981243645b331595c111cef1b0d084236a0403b00581" // ArMor's key + val trustedSignatures = setOf(OFFICIAL_SIGNATURE, UNOFFICIAL_SIGNATURE) /** * Convert dex to jar, a wrapper for the dex2jar library */ - fun dex2jar(dexFile: String, jarFile: String, fileNameWithoutType: String) { + fun dex2jar( + dexFile: String, + jarFile: String, + fileNameWithoutType: String, + ) { // adopted from com.googlecode.dex2jar.tools.Dex2jarCmd.doCommandLine // source at: https://github.com/DexPatcher/dex2jar/tree/v2.1-20190905-lanchon/dex-tools/src/main/java/com/googlecode/dex2jar/tools/Dex2jarCmd.java @@ -78,7 +82,7 @@ object PackageTools { https://bitbucket.org/pxb1988/dex2jar/issues https://github.com/pxb1988/dex2jar/issues dex2jar@googlegroups.com - """.trimIndent() + """.trimIndent(), ) handler.dump(errorFile, emptyArray()) } else { @@ -93,37 +97,40 @@ object PackageTools { val parsed = ApkFile(apk) val dbFactory = DocumentBuilderFactory.newInstance() val dBuilder = dbFactory.newDocumentBuilder() - val doc = parsed.manifestXml.byteInputStream().use { - dBuilder.parse(it) - } + val doc = + parsed.manifestXml.byteInputStream().use { + dBuilder.parse(it) + } logger.trace(parsed.manifestXml) - applicationInfo.metaData = Bundle().apply { - val appTag = doc.getElementsByTagName("application").item(0) - - appTag?.childNodes?.toList() - .orEmpty() - .asSequence() - .filter { - it.nodeType == Node.ELEMENT_NODE - }.map { - it as Element - }.filter { - it.tagName == "meta-data" - }.forEach { - putString( - it.attributes.getNamedItem("android:name").nodeValue, - it.attributes.getNamedItem("android:value").nodeValue - ) - } - } - - signatures = ( - parsed.apkSingers.flatMap { it.certificateMetas } - /*+ parsed.apkV2Singers.flatMap { it.certificateMetas }*/ + applicationInfo.metaData = + Bundle().apply { + val appTag = doc.getElementsByTagName("application").item(0) + + appTag?.childNodes?.toList() + .orEmpty() + .asSequence() + .filter { + it.nodeType == Node.ELEMENT_NODE + }.map { + it as Element + }.filter { + it.tagName == "meta-data" + }.forEach { + putString( + it.attributes.getNamedItem("android:name").nodeValue, + it.attributes.getNamedItem("android:value").nodeValue, + ) + } + } + + signatures = + ( + parsed.apkSingers.flatMap { it.certificateMetas } + // + parsed.apkV2Singers.flatMap { it.certificateMetas } ) // Blocked by: https://github.com/hsiafan/apk-parser/issues/72 - .map { Signature(it.data) }.toTypedArray() + .map { Signature(it.data) }.toTypedArray() } } @@ -142,7 +149,10 @@ object PackageTools { * loads the extension main class called [className] from the jar located at [jarPath] * It may return an instance of HttpSource or SourceFactory depending on the extension. */ - fun loadExtensionSources(jarPath: String, className: String): Any { + fun loadExtensionSources( + jarPath: String, + className: String, + ): Any { try { logger.debug { "loading jar with path: $jarPath" } val classLoader = jarLoaderMap[jarPath] ?: URLClassLoader(arrayOf(URL("file:$jarPath"))) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/lang/io.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/lang/FileExt.kt similarity index 100% rename from server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/lang/io.kt rename to server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/lang/FileExt.kt diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/lang/RxCoroutineBridge.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/lang/RxCoroutineBridge.kt index 5b07e817e..5f60ec988 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/lang/RxCoroutineBridge.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/lang/RxCoroutineBridge.kt @@ -17,50 +17,48 @@ import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException // source: https://github.com/jobobby04/TachiyomiSY/blob/9320221a4e8b118ef68deb60d8c4c32bcbb9e06f/app/src/main/java/eu/kanade/tachiyomi/util/lang/RxCoroutineBridge.kt -/* - * Util functions for bridging RxJava and coroutines. Taken from TachiyomiEH/SY. - */ +// Util functions for bridging RxJava and coroutines. Taken from TachiyomiEH/SY. suspend fun Observable.awaitSingle(): T = single().awaitOne() @OptIn(InternalCoroutinesApi::class) -private suspend fun Observable.awaitOne(): T = suspendCancellableCoroutine { cont -> - cont.unsubscribeOnCancellation( - subscribe( - object : Subscriber() { - override fun onStart() { - request(1) - } +private suspend fun Observable.awaitOne(): T = + suspendCancellableCoroutine { cont -> + cont.unsubscribeOnCancellation( + subscribe( + object : Subscriber() { + override fun onStart() { + request(1) + } - override fun onNext(t: T) { - cont.resume(t) - } + override fun onNext(t: T) { + cont.resume(t) + } - override fun onCompleted() { - if (cont.isActive) { - cont.resumeWithException( - IllegalStateException( - "Should have invoked onNext" + override fun onCompleted() { + if (cont.isActive) { + cont.resumeWithException( + IllegalStateException( + "Should have invoked onNext", + ), ) - ) + } } - } - override fun onError(e: Throwable) { + override fun onError(e: Throwable) { /* - * Rx1 observable throws NoSuchElementException if cancellation happened before - * element emission. To mitigate this we try to atomically resume continuation with exception: - * if resume failed, then we know that continuation successfully cancelled itself - */ - val token = cont.tryResumeWithException(e) - if (token != null) { - cont.completeResume(token) + * Rx1 observable throws NoSuchElementException if cancellation happened before + * element emission. To mitigate this we try to atomically resume continuation with exception: + * if resume failed, then we know that continuation successfully cancelled itself + */ + val token = cont.tryResumeWithException(e) + if (token != null) { + cont.completeResume(token) + } } - } - } + }, + ), ) - ) -} + } -private fun CancellableContinuation.unsubscribeOnCancellation(sub: Subscription) = - invokeOnCancellation { sub.unsubscribe() } +private fun CancellableContinuation.unsubscribeOnCancellation(sub: Subscription) = invokeOnCancellation { sub.unsubscribe() } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/network/OkHttp.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/network/OkHttp.kt index 35f3c19cf..5feb91209 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/network/OkHttp.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/network/OkHttp.kt @@ -20,7 +20,10 @@ suspend fun Call.await(): Response { return suspendCancellableCoroutine { continuation -> enqueue( object : Callback { - override fun onResponse(call: Call, response: Response) { + override fun onResponse( + call: Call, + response: Response, + ) { if (!response.isSuccessful) { continuation.resumeWithException(Exception("HTTP error ${response.code}")) return @@ -31,12 +34,15 @@ suspend fun Call.await(): Response { } } - override fun onFailure(call: Call, e: IOException) { + override fun onFailure( + call: Call, + e: IOException, + ) { // Don't bother with resuming the continuation if it is already cancelled. if (continuation.isCancelled) return continuation.resumeWithException(e) } - } + }, ) continuation.invokeOnCancellation { diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/source/GetCatalogueSource.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/source/GetCatalogueSource.kt index 7375282a2..520e71183 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/source/GetCatalogueSource.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/source/GetCatalogueSource.kt @@ -35,14 +35,16 @@ object GetCatalogueSource { return cachedResult } - val sourceRecord = transaction { - SourceTable.select { SourceTable.id eq sourceId }.firstOrNull() - } ?: return null + val sourceRecord = + transaction { + SourceTable.select { SourceTable.id eq sourceId }.firstOrNull() + } ?: return null val extensionId = sourceRecord[SourceTable.extension] - val extensionRecord = transaction { - ExtensionTable.select { ExtensionTable.id eq extensionId }.first() - } + val extensionRecord = + transaction { + ExtensionTable.select { ExtensionTable.id eq extensionId }.first() + } val apkName = extensionRecord[ExtensionTable.apkName] val className = extensionRecord[ExtensionTable.classFQName] diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/source/StubSource.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/source/StubSource.kt index 00220e3db..cf572e2b6 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/source/StubSource.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/source/StubSource.kt @@ -27,7 +27,11 @@ open class StubSource(override val id: Long) : CatalogueSource { } @Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getSearchManga")) - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { + override fun fetchSearchManga( + page: Int, + query: String, + filters: FilterList, + ): Observable { return Observable.error(getSourceNotInstalledException()) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/storage/ImageResponse.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/storage/ImageResponse.kt index 8a41d7a26..8776a4628 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/storage/ImageResponse.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/storage/ImageResponse.kt @@ -20,7 +20,10 @@ object ImageResponse { } /** find file with name when file extension is not known */ - fun findFileNameStartingWith(directoryPath: String, fileName: String): String? { + fun findFileNameStartingWith( + directoryPath: String, + fileName: String, + ): String? { val target = "$fileName." File(directoryPath).listFiles().orEmpty().forEach { file -> if (file.name.startsWith(target)) { @@ -30,11 +33,14 @@ object ImageResponse { return null } - fun getCachedImageResponse(cachedFile: String, filePath: String): Pair { + fun getCachedImageResponse( + cachedFile: String, + filePath: String, + ): Pair { val fileType = cachedFile.substringAfter("$filePath.") return Pair( pathToInputStream(cachedFile), - "image/$fileType" + "image/$fileType", ) } @@ -49,7 +55,7 @@ object ImageResponse { suspend fun getImageResponse( saveDir: String, fileName: String, - fetcher: suspend () -> Response + fetcher: suspend () -> Response, ): Pair { File(saveDir).mkdirs() @@ -80,14 +86,18 @@ object ImageResponse { } /** Save image safely */ - fun saveImage(filePath: String, image: InputStream): Pair { + fun saveImage( + filePath: String, + image: InputStream, + ): Pair { val tmpSavePath = "$filePath.tmp" val tmpSaveFile = File(tmpSavePath) image.use { input -> tmpSaveFile.outputStream().use { output -> input.copyTo(output) } } // find image type - val imageType = ImageUtil.findImageType { tmpSaveFile.inputStream() }?.mime - ?: "image/jpeg" + val imageType = + ImageUtil.findImageType { tmpSaveFile.inputStream() }?.mime + ?: "image/jpeg" val actualSavePath = "$filePath.${imageType.substringAfter("/")}" @@ -95,7 +105,10 @@ object ImageResponse { return Pair(actualSavePath, imageType) } - fun clearCachedImage(saveDir: String, fileName: String) { + fun clearCachedImage( + saveDir: String, + fileName: String, + ) { val cachedFile = findFileNameStartingWith(saveDir, fileName) cachedFile?.also { File(it).delete() diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/storage/ImageUtil.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/storage/ImageUtil.kt index ed65a253f..c7f2e4f04 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/storage/ImageUtil.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/storage/ImageUtil.kt @@ -19,12 +19,16 @@ import java.net.URLConnection // adopted from: eu.kanade.tachiyomi.util.system.ImageUtil object ImageUtil { - fun isImage(name: String, openStream: (() -> InputStream)? = null): Boolean { - val contentType = try { - URLConnection.guessContentTypeFromName(name) - } catch (e: Exception) { - null - } ?: openStream?.let { findImageType(it)?.mime } + fun isImage( + name: String, + openStream: (() -> InputStream)? = null, + ): Boolean { + val contentType = + try { + URLConnection.guessContentTypeFromName(name) + } catch (e: Exception) { + null + } ?: openStream?.let { findImageType(it)?.mime } return contentType?.startsWith("image/") ?: false } @@ -36,12 +40,13 @@ object ImageUtil { try { val bytes = ByteArray(12) - val length = if (stream.markSupported()) { - stream.mark(bytes.size) - stream.read(bytes, 0, bytes.size).also { stream.reset() } - } else { - stream.read(bytes, 0, bytes.size) - } + val length = + if (stream.markSupported()) { + stream.mark(bytes.size) + stream.read(bytes, 0, bytes.size).also { stream.reset() } + } else { + stream.read(bytes, 0, bytes.size) + } if (length == -1) { return null @@ -160,6 +165,6 @@ object ImageUtil { JPEG("image/jpeg", "jpg"), JXL("image/jxl", "jxl"), PNG("image/png", "png"), - WEBP("image/webp", "webp") + WEBP("image/webp", "webp"), } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/storage/Io.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/storage/Io.kt index 4ff4744da..a63ae8eb5 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/storage/Io.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/storage/Io.kt @@ -10,7 +10,10 @@ package suwayomi.tachidesk.manga.impl.util.storage import java.util.zip.ZipEntry import java.util.zip.ZipInputStream -fun ZipEntry.use(stream: ZipInputStream, block: (ZipEntry) -> Unit) { +fun ZipEntry.use( + stream: ZipInputStream, + block: (ZipEntry) -> Unit, +) { var exception: Throwable? = null try { return block(this) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/storage/OkioExtensions.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/storage/OkioExtensions.kt index e69c640c5..0c05c7342 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/storage/OkioExtensions.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/storage/OkioExtensions.kt @@ -14,6 +14,7 @@ import java.io.File import java.io.OutputStream // adopted from: https://github.com/tachiyomiorg/tachiyomi/blob/ff369010074b058bb734ce24c66508300e6e9ac6/app/src/main/java/eu/kanade/tachiyomi/util/storage/OkioExtensions.kt + /** * Saves the given source to a file and closes it. Directories will be created if needed. * diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/CategoryDataClass.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/CategoryDataClass.kt index 641aeea19..a4f3c7996 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/CategoryDataClass.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/CategoryDataClass.kt @@ -9,8 +9,13 @@ import com.fasterxml.jackson.annotation.JsonValue * 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/. */ -enum class IncludeInUpdate(@JsonValue val value: Int) { - EXCLUDE(0), INCLUDE(1), UNSET(-1); +enum class IncludeInUpdate( + @JsonValue val value: Int, +) { + EXCLUDE(0), + INCLUDE(1), + UNSET(-1), + ; companion object { fun fromValue(value: Int) = IncludeInUpdate.values().find { it.value == value } ?: UNSET @@ -24,5 +29,5 @@ data class CategoryDataClass( val default: Boolean, val size: Int, val includeInUpdate: IncludeInUpdate, - val meta: Map = emptyMap() + val meta: Map = emptyMap(), ) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/ChapterDataClass.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/ChapterDataClass.kt index ed3049a45..4d4e156a3 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/ChapterDataClass.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/ChapterDataClass.kt @@ -15,38 +15,27 @@ data class ChapterDataClass( val chapterNumber: Float, val scanlator: String?, val mangaId: Int, - /** chapter is read */ val read: Boolean, - /** chapter is bookmarked */ val bookmarked: Boolean, - /** last read page, zero means not read/no data */ val lastPageRead: Int, - /** last read page, zero means not read/no data */ val lastReadAt: Long, - // TODO(v0.6.0): rename to sourceOrder /** this chapter's index, starts with 1 */ val index: Int, - /** the date we fist saw this chapter*/ val fetchedAt: Long, - /** the website url of this chapter*/ val realUrl: String? = null, - /** is chapter downloaded */ val downloaded: Boolean, - /** used to construct pages in the front-end */ val pageCount: Int = -1, - /** total chapter count, used to calculate if there's a next and prev chapter */ val chapterCount: Int? = null, - /** used to store client specific values */ - val meta: Map = emptyMap() + val meta: Map = emptyMap(), ) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/ExtensionDataClass.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/ExtensionDataClass.kt index f3db7b72e..5ceb1f4d3 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/ExtensionDataClass.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/ExtensionDataClass.kt @@ -10,15 +10,13 @@ package suwayomi.tachidesk.manga.model.dataclass data class ExtensionDataClass( val apkName: String, val iconUrl: String, - val name: String, val pkgName: String, val versionName: String, val versionCode: Int, val lang: String, val isNsfw: Boolean, - val installed: Boolean, val hasUpdate: Boolean, - val obsolete: Boolean + val obsolete: Boolean, ) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/MangaChapterDataClass.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/MangaChapterDataClass.kt index 54d763e8e..c76298843 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/MangaChapterDataClass.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/MangaChapterDataClass.kt @@ -9,5 +9,5 @@ package suwayomi.tachidesk.manga.model.dataclass data class MangaChapterDataClass( val manga: MangaDataClass, - val chapter: ChapterDataClass + val chapter: ChapterDataClass, ) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/MangaDataClass.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/MangaDataClass.kt index 05ea72dd4..70baf21f1 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/MangaDataClass.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/MangaDataClass.kt @@ -15,14 +15,11 @@ import java.time.Instant data class MangaDataClass( val id: Int, val sourceId: String, - val url: String, val title: String, val thumbnailUrl: String? = null, val thumbnailUrlLastFetched: Long = 0, - val initialized: Boolean = false, - val artist: String? = null, val author: String? = null, val description: String? = null, @@ -31,30 +28,25 @@ data class MangaDataClass( val inLibrary: Boolean = false, val inLibraryAt: Long = 0, val source: SourceDataClass? = null, - /** meta data for clients */ val meta: Map = emptyMap(), - val realUrl: String? = null, var lastFetchedAt: Long? = 0, var chaptersLastFetchedAt: Long? = 0, - var updateStrategy: UpdateStrategy = UpdateStrategy.ALWAYS_UPDATE, - val freshData: Boolean = false, var unreadCount: Long? = null, var downloadCount: Long? = null, var chapterCount: Long? = null, var lastReadAt: Long? = null, var lastChapterRead: ChapterDataClass? = null, - val age: Long? = if (lastFetchedAt == null) 0 else Instant.now().epochSecond.minus(lastFetchedAt), - val chaptersAge: Long? = if (chaptersLastFetchedAt == null) null else Instant.now().epochSecond.minus(chaptersLastFetchedAt) + val chaptersAge: Long? = if (chaptersLastFetchedAt == null) null else Instant.now().epochSecond.minus(chaptersLastFetchedAt), ) data class PagedMangaListDataClass( val mangaList: List, - val hasNextPage: Boolean + val hasNextPage: Boolean, ) internal fun String?.toGenreList() = this?.split(",")?.trimAll().orEmpty() diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/PageDataClass.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/PageDataClass.kt index 0687e14bf..48cfbc65f 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/PageDataClass.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/PageDataClass.kt @@ -9,5 +9,5 @@ package suwayomi.tachidesk.manga.model.dataclass data class PageDataClass( val index: Int, - var imageUrl: String + var imageUrl: String, ) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/PaginatedList.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/PaginatedList.kt index 7deefd65c..5de0fc6f5 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/PaginatedList.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/PaginatedList.kt @@ -11,15 +11,15 @@ import kotlin.math.min open class PaginatedList( val page: List, - val hasNextPage: Boolean + val hasNextPage: Boolean, ) -const val PaginationFactor = 50 +const val PAGINATION_FACTOR = 50 fun paginatedFrom( pageNum: Int, - paginationFactor: Int = PaginationFactor, - lister: () -> List + paginationFactor: Int = PAGINATION_FACTOR, + lister: () -> List, ): PaginatedList { val list = lister() val lastIndex = list.size - 1 @@ -35,6 +35,6 @@ fun paginatedFrom( return PaginatedList( sliced, - higherIndex < lastIndex + higherIndex < lastIndex, ) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/SourceDataClass.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/SourceDataClass.kt index ca9e6a2f1..834319f27 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/SourceDataClass.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/SourceDataClass.kt @@ -14,16 +14,12 @@ data class SourceDataClass( val name: String, val lang: String, val iconUrl: String, - /** The Source provides a latest listing */ val supportsLatest: Boolean, - /** The Source implements [ConfigurableSource] */ val isConfigurable: Boolean, - /** The Source class has a @Nsfw annotation */ val isNsfw: Boolean, - /** A nicer version of [name] */ - val displayName: String + val displayName: String, ) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/table/CategoryTable.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/table/CategoryTable.kt index 47e86a39a..6ab21fe01 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/table/CategoryTable.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/table/CategoryTable.kt @@ -20,12 +20,13 @@ object CategoryTable : IntIdTable() { val includeInUpdate = integer("include_in_update").default(IncludeInUpdate.UNSET.value) } -fun CategoryTable.toDataClass(categoryEntry: ResultRow) = CategoryDataClass( - categoryEntry[id].value, - categoryEntry[order], - categoryEntry[name], - categoryEntry[isDefault], - Category.getCategorySize(categoryEntry[id].value), - IncludeInUpdate.fromValue(categoryEntry[includeInUpdate]), - Category.getCategoryMetaMap(categoryEntry[id].value) -) +fun CategoryTable.toDataClass(categoryEntry: ResultRow) = + CategoryDataClass( + categoryEntry[id].value, + categoryEntry[order], + categoryEntry[name], + categoryEntry[isDefault], + Category.getCategorySize(categoryEntry[id].value), + IncludeInUpdate.fromValue(categoryEntry[includeInUpdate]), + Category.getCategoryMetaMap(categoryEntry[id].value), + ) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/table/ChapterTable.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/table/ChapterTable.kt index 4de3b0824..f3abc528b 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/table/ChapterTable.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/table/ChapterTable.kt @@ -60,5 +60,5 @@ fun ChapterTable.toDataClass(chapterEntry: ResultRow) = downloaded = chapterEntry[isDownloaded], pageCount = chapterEntry[pageCount], chapterCount = transaction { ChapterTable.select { manga eq chapterEntry[manga].value }.count().toInt() }, - meta = getChapterMetaMap(chapterEntry[id]) + meta = getChapterMetaMap(chapterEntry[id]), ) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/table/ExtensionTable.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/table/ExtensionTable.kt index 60858ceee..0ff53079c 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/table/ExtensionTable.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/table/ExtensionTable.kt @@ -13,8 +13,12 @@ object ExtensionTable : IntIdTable() { val apkName = varchar("apk_name", 1024) // default is the local source icon from tachiyomi - val iconUrl = varchar("icon_url", 2048) - .default("https://raw.githubusercontent.com/tachiyomiorg/tachiyomi/64ba127e7d43b1d7e6d58a6f5c9b2bd5fe0543f7/app/src/main/res/mipmap-xxxhdpi/ic_local_source.webp") + @Suppress("ktlint:standard:max-line-length") + val iconUrl = + varchar("icon_url", 2048) + .default( + "https://raw.githubusercontent.com/tachiyomiorg/tachiyomi/64ba127e7d43b1d7e6d58a6f5c9b2bd5fe0543f7/app/src/main/res/mipmap-xxxhdpi/ic_local_source.webp", + ) val name = varchar("name", 128) val pkgName = varchar("pkg_name", 128) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/table/MangaTable.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/table/MangaTable.kt index a6349a5c2..17d89c889 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/table/MangaTable.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/table/MangaTable.kt @@ -50,14 +50,11 @@ fun MangaTable.toDataClass(mangaEntry: ResultRow) = MangaDataClass( id = mangaEntry[this.id].value, sourceId = mangaEntry[sourceReference].toString(), - url = mangaEntry[url], title = mangaEntry[title], thumbnailUrl = proxyThumbnailUrl(mangaEntry[this.id].value), thumbnailUrlLastFetched = mangaEntry[thumbnailUrlLastFetched], - initialized = mangaEntry[initialized], - artist = mangaEntry[artist], author = mangaEntry[author], description = mangaEntry[description], @@ -69,7 +66,7 @@ fun MangaTable.toDataClass(mangaEntry: ResultRow) = realUrl = mangaEntry[realUrl], lastFetchedAt = mangaEntry[lastFetchedAt], chaptersLastFetchedAt = mangaEntry[chaptersLastFetchedAt], - updateStrategy = UpdateStrategy.valueOf(mangaEntry[updateStrategy]) + updateStrategy = UpdateStrategy.valueOf(mangaEntry[updateStrategy]), ) enum class MangaStatus(val value: Int) { @@ -79,7 +76,8 @@ enum class MangaStatus(val value: Int) { LICENSED(3), PUBLISHING_FINISHED(4), CANCELLED(5), - ON_HIATUS(6); + ON_HIATUS(6), + ; companion object { fun valueOf(value: Int): MangaStatus = values().find { it.value == value } ?: UNKNOWN diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/JavalinSetup.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/JavalinSetup.kt index 9e52f95cf..0d0b8c0a4 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/JavalinSetup.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/JavalinSetup.kt @@ -50,10 +50,11 @@ object JavalinSetup { fun javalinSetup() { val server = Server() - val connector = ServerConnector(server).apply { - host = serverConfig.ip.value - port = serverConfig.port.value - } + val connector = + ServerConnector(server).apply { + host = serverConfig.ip.value + port = serverConfig.port.value + } server.addConnector(connector) serverConfig.subscribeTo(combine(serverConfig.ip, serverConfig.port) { ip, port -> Pair(ip, port) }, { (newIp, newPort) -> @@ -68,48 +69,49 @@ object JavalinSetup { logger.info { "Server ip and/or port changed from $oldIp:$oldPort to $newIp:$newPort " } }) - val app = Javalin.create { config -> - if (serverConfig.webUIEnabled.value) { - runBlocking { - WebInterfaceManager.setupWebUI() + val app = + Javalin.create { config -> + if (serverConfig.webUIEnabled.value) { + runBlocking { + WebInterfaceManager.setupWebUI() + } + + logger.info { "Serving web static files for ${serverConfig.webUIFlavor.value}" } + config.addStaticFiles(applicationDirs.webUIRoot, Location.EXTERNAL) + config.addSinglePageRoot("/", applicationDirs.webUIRoot + "/index.html", Location.EXTERNAL) + config.registerPlugin(OpenApiPlugin(getOpenApiOptions())) } - logger.info { "Serving web static files for ${serverConfig.webUIFlavor.value}" } - config.addStaticFiles(applicationDirs.webUIRoot, Location.EXTERNAL) - config.addSinglePageRoot("/", applicationDirs.webUIRoot + "/index.html", Location.EXTERNAL) - config.registerPlugin(OpenApiPlugin(getOpenApiOptions())) - } - - config.server { server } + config.server { server } - config.enableCorsForAllOrigins() + config.enableCorsForAllOrigins() - config.accessManager { handler, ctx, _ -> - fun credentialsValid(): Boolean { - val (username, password) = ctx.basicAuthCredentials() - return username == serverConfig.basicAuthUsername.value && password == serverConfig.basicAuthPassword.value - } + config.accessManager { handler, ctx, _ -> + fun credentialsValid(): Boolean { + val (username, password) = ctx.basicAuthCredentials() + return username == serverConfig.basicAuthUsername.value && password == serverConfig.basicAuthPassword.value + } - if (serverConfig.basicAuthEnabled.value && !(ctx.basicAuthCredentialsExist() && credentialsValid())) { - ctx.header("WWW-Authenticate", "Basic") - ctx.status(401).json("Unauthorized") - } else { - handler.handle(ctx) + if (serverConfig.basicAuthEnabled.value && !(ctx.basicAuthCredentialsExist() && credentialsValid())) { + ctx.header("WWW-Authenticate", "Basic") + ctx.status(401).json("Unauthorized") + } else { + handler.handle(ctx) + } } - } - }.events { event -> - event.serverStarted { - if (serverConfig.initialOpenInBrowserEnabled.value) { - Browser.openInBrowser() + }.events { event -> + event.serverStarted { + if (serverConfig.initialOpenInBrowserEnabled.value) { + Browser.openInBrowser() + } } - } - }.start() + }.start() // when JVM is prompted to shutdown, stop javalin gracefully Runtime.getRuntime().addShutdownHook( thread(start = false) { app.stop() - } + }, ) app.exception(NullPointerException::class.java) { e, ctx -> @@ -144,16 +146,17 @@ object JavalinSetup { } private fun getOpenApiOptions(): OpenApiOptions { - val applicationInfo = Info().apply { - version("1.0") - description("Tachidesk Api") - } + val applicationInfo = + Info().apply { + version("1.0") + description("Tachidesk Api") + } return OpenApiOptions(applicationInfo).apply { path("/api/openapi.json") swagger( SwaggerOptions("/api/swagger-ui").apply { title("Tachidesk Swagger Documentation") - } + }, ) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/ServerConfig.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/ServerConfig.kt index b6e79fd7f..407f7aae1 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/ServerConfig.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/ServerConfig.kt @@ -29,11 +29,18 @@ import kotlin.reflect.KProperty val mutableConfigValueScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) const val SERVER_CONFIG_MODULE_NAME = "server" -class ServerConfig(getConfig: () -> Config, val moduleName: String = SERVER_CONFIG_MODULE_NAME) : SystemPropertyOverridableConfigModule(getConfig, moduleName) { + +class ServerConfig(getConfig: () -> Config, val moduleName: String = SERVER_CONFIG_MODULE_NAME) : SystemPropertyOverridableConfigModule( + getConfig, + moduleName, +) { inner class OverrideConfigValue(private val configAdapter: ConfigAdapter) { private var flow: MutableStateFlow? = null - operator fun getValue(thisRef: ServerConfig, property: KProperty<*>): MutableStateFlow { + operator fun getValue( + thisRef: ServerConfig, + property: KProperty<*>, + ): MutableStateFlow { if (flow != null) { return flow!! } @@ -104,26 +111,35 @@ class ServerConfig(getConfig: () -> Config, val moduleName: String = SERVER_CONF // local source val localSourcePath: MutableStateFlow by OverrideConfigValue(StringConfigAdapter) - fun subscribeTo(flow: Flow, onChange: suspend (value: T) -> Unit, ignoreInitialValue: Boolean = true) { - val actualFlow = if (ignoreInitialValue) { - flow.drop(1) - } else { - flow - } + fun subscribeTo( + flow: Flow, + onChange: suspend (value: T) -> Unit, + ignoreInitialValue: Boolean = true, + ) { + val actualFlow = + if (ignoreInitialValue) { + flow.drop(1) + } else { + flow + } val sharedFlow = MutableSharedFlow(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) actualFlow.distinctUntilChanged().mapLatest { sharedFlow.emit(it) }.launchIn(mutableConfigValueScope) sharedFlow.onEach { onChange(it) }.launchIn(mutableConfigValueScope) } - fun subscribeTo(flow: Flow, onChange: suspend () -> Unit, ignoreInitialValue: Boolean = true) { + fun subscribeTo( + flow: Flow, + onChange: suspend () -> Unit, + ignoreInitialValue: Boolean = true, + ) { subscribeTo(flow, { _ -> onChange() }, ignoreInitialValue) } fun subscribeTo( mutableStateFlow: MutableStateFlow, onChange: suspend (value: T) -> Unit, - ignoreInitialValue: Boolean = true + ignoreInitialValue: Boolean = true, ) { subscribeTo(mutableStateFlow.asStateFlow(), onChange, ignoreInitialValue) } @@ -131,7 +147,7 @@ class ServerConfig(getConfig: () -> Config, val moduleName: String = SERVER_CONF fun subscribeTo( mutableStateFlow: MutableStateFlow, onChange: suspend () -> Unit, - ignoreInitialValue: Boolean = true + ignoreInitialValue: Boolean = true, ) { subscribeTo(mutableStateFlow.asStateFlow(), { _ -> onChange() }, ignoreInitialValue) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/ServerSetup.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/ServerSetup.kt index c0ea6863d..3ac0f0c00 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/ServerSetup.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/ServerSetup.kt @@ -28,6 +28,7 @@ import suwayomi.tachidesk.manga.impl.update.IUpdater import suwayomi.tachidesk.manga.impl.update.Updater import suwayomi.tachidesk.manga.impl.util.lang.renameTo import suwayomi.tachidesk.server.database.databaseUp +import suwayomi.tachidesk.server.generated.BuildConfig import suwayomi.tachidesk.server.util.AppMutex.handleAppMutex import suwayomi.tachidesk.server.util.SystemTray import xyz.nulldev.androidcompat.AndroidCompat @@ -46,7 +47,7 @@ private val logger = KotlinLogging.logger {} class ApplicationDirs( val dataRoot: String = ApplicationRootDir, - val tempRoot: String = "${System.getProperty("java.io.tmpdir")}/Tachidesk" + val tempRoot: String = "${System.getProperty("java.io.tmpdir")}/Tachidesk", ) { val extensionsRoot = "$dataRoot/extensions" val downloadsRoot get() = serverConfig.downloadsPath.value.ifBlank { "$dataRoot/downloads" } @@ -72,7 +73,7 @@ fun applicationSetup() { // register Tachidesk's config which is dubbed "ServerConfig" GlobalConfigManager.registerModule( - ServerConfig.register { GlobalConfigManager.config } + ServerConfig.register { GlobalConfigManager.config }, ) // Application dirs @@ -109,7 +110,7 @@ fun applicationSetup() { bind() with singleton { updater } bind() with singleton { JavalinJackson() } bind() with singleton { Json { ignoreUnknownKeys = true } } - } + }, ) logger.debug("Data Root directory is set to: ${applicationDirs.dataRoot}") @@ -143,7 +144,7 @@ fun applicationSetup() { applicationDirs.extensionsRoot + "/icon", applicationDirs.tempThumbnailCacheRoot, applicationDirs.downloadsRoot, - applicationDirs.localMangaRoot + applicationDirs.localMangaRoot, ).forEach { File(it).mkdirs() } @@ -204,7 +205,8 @@ fun applicationSetup() { } else { SystemTray.remove() } - } catch (e: Throwable) { // cover both java.lang.Exception and java.lang.Error + } catch (e: Throwable) { + // cover both java.lang.Exception and java.lang.Error e.printStackTrace() } }, ignoreInitialValue = false) @@ -219,7 +221,7 @@ fun applicationSetup() { combine( serverConfig.socksProxyEnabled, serverConfig.socksProxyHost, - serverConfig.socksProxyPort + serverConfig.socksProxyPort, ) { proxyEnabled, proxyHost, proxyPort -> Triple(proxyEnabled, proxyHost, proxyPort) }, @@ -233,7 +235,7 @@ fun applicationSetup() { System.getProperties()["socksProxyPort"] = "" } }, - ignoreInitialValue = false + ignoreInitialValue = false, ) // AES/CBC/PKCS7Padding Cypher provider for zh.copymanga diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/database/DBManager.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/database/DBManager.kt index 4c4e9282d..01df8d3fa 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/database/DBManager.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/database/DBManager.kt @@ -19,15 +19,15 @@ import suwayomi.tachidesk.server.ApplicationDirs import suwayomi.tachidesk.server.ServerConfig object DBManager { - val db by lazy { val applicationDirs by DI.global.instance() Database.connect( "jdbc:h2:${applicationDirs.dataRoot}/database", "org.h2.Driver", - databaseConfig = DatabaseConfig { - useNestedTransactions = true - } + databaseConfig = + DatabaseConfig { + useNestedTransactions = true + }, ) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0001_Initial.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0001_Initial.kt index ce0384720..3f1c96925 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0001_Initial.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0001_Initial.kt @@ -20,8 +20,11 @@ class M0001_Initial : AddTableMigration() { init { varchar("apk_name", 1024) // default is the local source icon from tachiyomi + @Suppress("ktlint:standard:max-line-length") varchar("icon_url", 2048) - .default("https://raw.githubusercontent.com/tachiyomiorg/tachiyomi/64ba127e7d43b1d7e6d58a6f5c9b2bd5fe0543f7/app/src/main/res/mipmap-xxxhdpi/ic_local_source.webp") + .default( + "https://raw.githubusercontent.com/tachiyomiorg/tachiyomi/64ba127e7d43b1d7e6d58a6f5c9b2bd5fe0543f7/app/src/main/res/mipmap-xxxhdpi/ic_local_source.webp", + ) varchar("name", 128) varchar("pkg_name", 128) varchar("version_name", 16) @@ -39,6 +42,7 @@ class M0001_Initial : AddTableMigration() { private class SourceTable(extensionTable: ExtensionTable) : IdTable() { override val id = long("id").entityId() + init { varchar("name", 128) varchar("lang", 10) @@ -128,7 +132,7 @@ class M0001_Initial : AddTableMigration() { chapterTable, pageTable, categoryTable, - categoryMangaTable + categoryMangaTable, ) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0002_ChapterTableIndexRename.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0002_ChapterTableIndexRename.kt index 028af7a28..937ba09e7 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0002_ChapterTableIndexRename.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0002_ChapterTableIndexRename.kt @@ -13,5 +13,5 @@ import de.neonew.exposed.migrations.helpers.RenameFieldMigration class M0002_ChapterTableIndexRename : RenameFieldMigration( "Chapter", "number_in_list", - "index" + "index", ) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0003_DefaultCategory.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0003_DefaultCategory.kt index 3f49612d3..049d4c254 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0003_DefaultCategory.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0003_DefaultCategory.kt @@ -13,5 +13,5 @@ import de.neonew.exposed.migrations.helpers.RenameFieldMigration class M0003_DefaultCategory : RenameFieldMigration( "Category", "is_landing", - "is_default" + "is_default", ) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0004_AnimeTablesBatch1.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0004_AnimeTablesBatch1.kt index 0dfb65906..7b3379c5d 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0004_AnimeTablesBatch1.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0004_AnimeTablesBatch1.kt @@ -18,8 +18,12 @@ class M0004_AnimeTablesBatch1 : AddTableMigration() { val apkName = varchar("apk_name", 1024) // default is the local source icon from tachiyomi - val iconUrl = varchar("icon_url", 2048) - .default("https://raw.githubusercontent.com/tachiyomiorg/tachiyomi/64ba127e7d43b1d7e6d58a6f5c9b2bd5fe0543f7/app/src/main/res/mipmap-xxxhdpi/ic_local_source.webp") + @Suppress("ktlint:standard:max-line-length") + val iconUrl = + varchar("icon_url", 2048) + .default( + "https://raw.githubusercontent.com/tachiyomiorg/tachiyomi/64ba127e7d43b1d7e6d58a6f5c9b2bd5fe0543f7/app/src/main/res/mipmap-xxxhdpi/ic_local_source.webp", + ) val name = varchar("name", 128) val pkgName = varchar("pkg_name", 128) @@ -44,8 +48,9 @@ class M0004_AnimeTablesBatch1 : AddTableMigration() { } override val tables: Array - get() = arrayOf( - AnimeExtensionTable(), - AnimeSourceTable() - ) + get() = + arrayOf( + AnimeExtensionTable(), + AnimeSourceTable(), + ) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0005_AnimeTablesBatch2.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0005_AnimeTablesBatch2.kt index a69fc2f7b..a1aba2845 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0005_AnimeTablesBatch2.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0005_AnimeTablesBatch2.kt @@ -1,3 +1,5 @@ +@file:Suppress("ktlint:standard:property-naming") + package suwayomi.tachidesk.server.database.migration /* @@ -35,7 +37,8 @@ class M0005_AnimeTablesBatch2 : AddTableMigration() { } override val tables: Array
- get() = arrayOf( - AnimeTable() - ) + get() = + arrayOf( + AnimeTable(), + ) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0006_AnimeTablesBatch3.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0006_AnimeTablesBatch3.kt index 139a6fd0d..4ab592b1a 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0006_AnimeTablesBatch3.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0006_AnimeTablesBatch3.kt @@ -1,3 +1,5 @@ +@file:Suppress("ktlint:standard:property-naming") + package suwayomi.tachidesk.server.database.migration /* @@ -34,7 +36,8 @@ class M0006_AnimeTablesBatch3 : AddTableMigration() { } override val tables: Array
- get() = arrayOf( - EpisodeTable() - ) + get() = + arrayOf( + EpisodeTable(), + ) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0007_ChapterIsDownloaded.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0007_ChapterIsDownloaded.kt index 51d2a320e..72bdba23f 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0007_ChapterIsDownloaded.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0007_ChapterIsDownloaded.kt @@ -14,5 +14,5 @@ class M0007_ChapterIsDownloaded : AddColumnMigration( "Chapter", "is_downloaded", "BOOLEAN", - "FALSE" + "FALSE", ) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0008_ChapterPageCount.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0008_ChapterPageCount.kt index 842fa85d2..cc2209985 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0008_ChapterPageCount.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0008_ChapterPageCount.kt @@ -14,5 +14,5 @@ class M0008_ChapterPageCount : AddColumnMigration( "Chapter", "page_count", "INT", - "-1" + "-1", ) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0009_ChapterLastReadAt.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0009_ChapterLastReadAt.kt index 2dfa51a1b..280aba9b8 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0009_ChapterLastReadAt.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0009_ChapterLastReadAt.kt @@ -14,5 +14,5 @@ class M0009_ChapterLastReadAt : AddColumnMigration( "Chapter", "last_read_at", "BIGINT", // BIGINT == Long - "0" + "0", ) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0010_MangaAndChapterMeta.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0010_MangaAndChapterMeta.kt index 64896ba6e..9808b8b75 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0010_MangaAndChapterMeta.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0010_MangaAndChapterMeta.kt @@ -29,8 +29,9 @@ class M0010_MangaAndChapterMeta : AddTableMigration() { } override val tables: Array
- get() = arrayOf( - ChapterMetaTable(), - MangaMetaTable() - ) + get() = + arrayOf( + ChapterMetaTable(), + MangaMetaTable(), + ) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0011_SourceDropPartOfFactorySource.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0011_SourceDropPartOfFactorySource.kt index 9be9429be..fbf57385a 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0011_SourceDropPartOfFactorySource.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0011_SourceDropPartOfFactorySource.kt @@ -12,5 +12,5 @@ import de.neonew.exposed.migrations.helpers.DropColumnMigration @Suppress("ClassName", "unused") class M0011_SourceDropPartOfFactorySource : DropColumnMigration( "Source", - "part_of_factory_source" + "part_of_factory_source", ) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0012_SourceIsNsfw.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0012_SourceIsNsfw.kt index e0a6f687b..fb64a1820 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0012_SourceIsNsfw.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0012_SourceIsNsfw.kt @@ -14,5 +14,5 @@ class M0012_SourceIsNsfw : AddColumnMigration( "Source", "is_nsfw", "BOOLEAN", - "FALSE" + "FALSE", ) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0013_MangaRealUrl.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0013_MangaRealUrl.kt index bf9d50bac..5215cff1d 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0013_MangaRealUrl.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0013_MangaRealUrl.kt @@ -14,5 +14,5 @@ class M0013_MangaRealUrl : AddColumnMigration( "Manga", "real_url", "VARCHAR(2048)", - "NULL" + "NULL", ) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0014_MangaRemoveLengthLimit.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0014_MangaRemoveLengthLimit.kt index f4ee82411..3a58fe3b1 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0014_MangaRemoveLengthLimit.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0014_MangaRemoveLengthLimit.kt @@ -11,10 +11,11 @@ import de.neonew.exposed.migrations.helpers.SQLMigration @Suppress("ClassName", "unused") class M0014_MangaRemoveLengthLimit : SQLMigration() { - override val sql = """ + override val sql = + """ ALTER TABLE MANGA ALTER COLUMN ARTIST VARCHAR(512); ALTER TABLE MANGA ALTER COLUMN AUTHOR VARCHAR(512); ALTER TABLE MANGA ALTER COLUMN DESCRIPTION VARCHAR; -- the default length is `Integer.MAX_VALUE` ALTER TABLE MANGA ALTER COLUMN GENRE VARCHAR; -- the default length is `Integer.MAX_VALUE` - """.trimIndent() + """.trimIndent() } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0015_SourceAndExtensionLangAddLengthLimit.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0015_SourceAndExtensionLangAddLengthLimit.kt index 46af5fe31..d8678505f 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0015_SourceAndExtensionLangAddLengthLimit.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0015_SourceAndExtensionLangAddLengthLimit.kt @@ -11,8 +11,9 @@ import de.neonew.exposed.migrations.helpers.SQLMigration @Suppress("ClassName", "unused") class M0015_SourceAndExtensionLangAddLengthLimit : SQLMigration() { - override val sql = """ + override val sql = + """ ALTER TABLE SOURCE ALTER COLUMN LANG VARCHAR(32); ALTER TABLE EXTENSION ALTER COLUMN LANG VARCHAR(32); - """.trimIndent() + """.trimIndent() } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0016_ChapterIndexRenameToSourceOrder.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0016_ChapterIndexRenameToSourceOrder.kt index 42f8faf79..037353f2c 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0016_ChapterIndexRenameToSourceOrder.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0016_ChapterIndexRenameToSourceOrder.kt @@ -11,7 +11,8 @@ import de.neonew.exposed.migrations.helpers.SQLMigration @Suppress("ClassName", "unused") class M0016_ChapterIndexRenameToSourceOrder : SQLMigration() { - override val sql = """ + override val sql = + """ ALTER TABLE CHAPTER ALTER COLUMN INDEX RENAME TO SOURCE_ORDER; - """.trimIndent() + """.trimIndent() } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0017_ChapterFetchedAt.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0017_ChapterFetchedAt.kt index bfea91769..5d8d40a63 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0017_ChapterFetchedAt.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0017_ChapterFetchedAt.kt @@ -14,5 +14,5 @@ class M0017_ChapterFetchedAt : AddColumnMigration( "Chapter", "fetched_at", "BIGINT", - "0" + "0", ) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0018_MangaInLibraryAt.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0018_MangaInLibraryAt.kt index 076c31667..c496dd789 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0018_MangaInLibraryAt.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0018_MangaInLibraryAt.kt @@ -14,5 +14,5 @@ class M0018_MangaInLibraryAt : AddColumnMigration( "Manga", "in_library_at", "BIGINT", - "0" + "0", ) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0019_RemoveAnime.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0019_RemoveAnime.kt index 84a393d4d..aad3a3ef4 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0019_RemoveAnime.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0019_RemoveAnime.kt @@ -1,3 +1,5 @@ +@file:Suppress("ktlint:standard:property-naming") + package suwayomi.tachidesk.server.database.migration /* @@ -16,10 +18,11 @@ class M0019_RemoveAnime : SQLMigration() { val AnimeSource = "ANIMESOURCE" val Episode = "EPISODE" - override val sql = """ + override val sql = + """ DROP TABLE $AnimeSource; DROP TABLE $AnimeExtension; DROP TABLE $Episode; DROP TABLE $Anime; - """.trimIndent() + """.trimIndent() } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0020_AddMangaLastFetchedAtColumns.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0020_AddMangaLastFetchedAtColumns.kt index 9422626b1..a8a19ba94 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0020_AddMangaLastFetchedAtColumns.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0020_AddMangaLastFetchedAtColumns.kt @@ -11,8 +11,9 @@ import de.neonew.exposed.migrations.helpers.SQLMigration @Suppress("ClassName", "unused") class M0020_AddMangaLastFetchedAtColumns : SQLMigration() { - override val sql = """ + override val sql = + """ ALTER TABLE Manga ADD COLUMN last_fetched_at BIGINT DEFAULT 0; ALTER TABLE Manga ADD COLUMN chapters_last_fetched_at BIGINT DEFAULT 0; - """.trimIndent() + """.trimIndent() } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0021_GlobalAndCategoryMeta.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0021_GlobalAndCategoryMeta.kt index a268f4cc4..2bd69e96d 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0021_GlobalAndCategoryMeta.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0021_GlobalAndCategoryMeta.kt @@ -27,8 +27,9 @@ class M0021_GlobalAndCategoryMeta : AddTableMigration() { } override val tables: Array
- get() = arrayOf( - GlobalMetaTable(), - CategoryMetaTable() - ) + get() = + arrayOf( + GlobalMetaTable(), + CategoryMetaTable(), + ) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0022_MangaThumbnailLastFetched.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0022_MangaThumbnailLastFetched.kt index f27a629c9..baeebf1f3 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0022_MangaThumbnailLastFetched.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0022_MangaThumbnailLastFetched.kt @@ -14,5 +14,5 @@ class M0022_MangaThumbnailLastFetched : AddColumnMigration( "Manga", "thumbnail_url_last_fetched", "BIGINT", - "0" + "0", ) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0023_CategoryMetaRefFix.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0023_CategoryMetaRefFix.kt index fcdc2b912..3fe87ab70 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0023_CategoryMetaRefFix.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0023_CategoryMetaRefFix.kt @@ -1,3 +1,5 @@ +@file:Suppress("ktlint:standard:property-naming") + package suwayomi.tachidesk.server.database.migration /* @@ -15,7 +17,7 @@ class M0023_CategoryMetaRefFix : SQLMigration() { fun String.toSqlName(): String = TransactionManager.defaultDatabase!!.identifierManager.let { it.quoteIfNecessary( - it.inProperCase(this) + it.inProperCase(this), ) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0024_MangaUpdateStrategy.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0024_MangaUpdateStrategy.kt index 3565b3992..a4906d28e 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0024_MangaUpdateStrategy.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0024_MangaUpdateStrategy.kt @@ -15,5 +15,5 @@ class M0024_MangaUpdateStrategy : AddColumnMigration( "Manga", "update_strategy", "VARCHAR(256)", - "'${UpdateStrategy.ALWAYS_UPDATE.name}'" + "'${UpdateStrategy.ALWAYS_UPDATE.name}'", ) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0025_ChapterRealUrl.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0025_ChapterRealUrl.kt index dd9d42343..5c43c6201 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0025_ChapterRealUrl.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0025_ChapterRealUrl.kt @@ -14,5 +14,5 @@ class M0025_ChapterRealUrl : AddColumnMigration( "Chapter", "real_url", "VARCHAR(2048)", - "NULL" + "NULL", ) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0026_CategoryIncludeInUpdate.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0026_CategoryIncludeInUpdate.kt index 15f92ef21..02e2c15bc 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0026_CategoryIncludeInUpdate.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0026_CategoryIncludeInUpdate.kt @@ -15,5 +15,5 @@ class M0026_CategoryIncludeInUpdate : AddColumnMigration( "Category", "include_in_update", "INT", - IncludeInUpdate.UNSET.value.toString() + IncludeInUpdate.UNSET.value.toString(), ) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0027_AddDefaultCategory.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0027_AddDefaultCategory.kt index 5954997c2..5cd1577b4 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0027_AddDefaultCategory.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0027_AddDefaultCategory.kt @@ -11,7 +11,8 @@ import de.neonew.exposed.migrations.helpers.SQLMigration @Suppress("ClassName", "unused") class M0027_AddDefaultCategory : SQLMigration() { - override val sql: String = """ + override val sql: String = + """ INSERT INTO CATEGORY (ID, NAME, IS_DEFAULT, "ORDER", INCLUDE_IN_UPDATE) VALUES (0, 'Default', TRUE, 0, -1) - """.trimIndent() + """.trimIndent() } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0028_AddCascade.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0028_AddCascade.kt index 50e86f709..4a85de54f 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0028_AddCascade.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0028_AddCascade.kt @@ -4,7 +4,8 @@ import de.neonew.exposed.migrations.helpers.SQLMigration @Suppress("ClassName", "unused") class M0028_AddCascade : SQLMigration() { - override val sql: String = """ + override val sql: String = + """ alter table CATEGORYMANGA drop constraint if exists FK_CATEGORYMANGA_CATEGORY__ID; alter table CATEGORYMANGA @@ -44,5 +45,5 @@ class M0028_AddCascade : SQLMigration() { add constraint FK_PAGE_CHAPTER__ID foreign key (CHAPTER) references CHAPTER on delete cascade; - """.trimIndent() + """.trimIndent() } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0029_DropMangaDefaultCategory.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0029_DropMangaDefaultCategory.kt index c7da22aac..3b90d79e9 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0029_DropMangaDefaultCategory.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0029_DropMangaDefaultCategory.kt @@ -12,5 +12,5 @@ import de.neonew.exposed.migrations.helpers.DropColumnMigration @Suppress("ClassName", "unused") class M0029_DropMangaDefaultCategory : DropColumnMigration( "Manga", - "default_category" + "default_category", ) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/util/AppExit.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/util/AppExit.kt index b6360456c..8d7fb3dd7 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/util/AppExit.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/util/AppExit.kt @@ -15,7 +15,7 @@ private val logger = KotlinLogging.logger {} enum class ExitCode(val code: Int) { Success(0), MutexCheckFailedTachideskRunning(1), - MutexCheckFailedAnotherAppRunning(2); + MutexCheckFailedAnotherAppRunning(2), } fun shutdownApp(exitCode: ExitCode) { diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/util/AppMutex.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/util/AppMutex.kt index 4b1120ae5..4478ac4db 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/util/AppMutex.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/util/AppMutex.kt @@ -28,7 +28,7 @@ object AppMutex { private enum class AppMutexState(val stat: Int) { Clear(0), TachideskInstanceRunning(1), - OtherApplicationRunning(2) + OtherApplicationRunning(2), } private val appIP = if (serverConfig.ip.value == "0.0.0.0") "127.0.0.1" else serverConfig.ip.value @@ -36,19 +36,22 @@ object AppMutex { private val jsonMapper by DI.global.instance() private fun checkAppMutex(): AppMutexState { - val client = OkHttpClient.Builder() - .connectTimeout(200, TimeUnit.MILLISECONDS) - .build() + val client = + OkHttpClient.Builder() + .connectTimeout(200, TimeUnit.MILLISECONDS) + .build() - val request = Builder() - .url("http://$appIP:${serverConfig.port.value}/api/v1/settings/about/") - .build() + val request = + Builder() + .url("http://$appIP:${serverConfig.port.value}/api/v1/settings/about/") + .build() - val response = try { - client.newCall(request).execute().body.string() - } catch (e: IOException) { - return AppMutexState.Clear - } + val response = + try { + client.newCall(request).execute().body.string() + } catch (e: IOException) { + return AppMutexState.Clear + } return try { jsonMapper.fromJsonString(response, AboutDataClass::class.java) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/util/Browser.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/util/Browser.kt index 99aa38de7..1760c0eb2 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/util/Browser.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/util/Browser.kt @@ -26,13 +26,15 @@ object Browser { try { val electronPath = serverConfig.electronPath.value electronInstances.add(ProcessBuilder(electronPath, appBaseUrl).start()) - } catch (e: Throwable) { // cover both java.lang.Exception and java.lang.Error + } catch (e: Throwable) { + // cover both java.lang.Exception and java.lang.Error e.printStackTrace() } } else { try { Desktop.browseURL(appBaseUrl) - } catch (e: Throwable) { // cover both java.lang.Exception and java.lang.Error + } catch (e: Throwable) { + // cover both java.lang.Exception and java.lang.Error e.printStackTrace() } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/util/Chromium.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/util/Chromium.kt index b39067f9f..f364780c4 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/util/Chromium.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/util/Chromium.kt @@ -25,16 +25,18 @@ object Chromium { fun preinstall(platformDir: String) { val loader = Thread.currentThread().contextClassLoader val resource = loader.getResource("driver/$platformDir/package/browsers.json") ?: return - val json = resource.openStream().use { - Json.decodeFromStream(it) - } - val revision = json["browsers"]?.jsonArray - ?.find { it.jsonObject["name"]?.jsonPrimitive?.contentOrNull == "chromium" } - ?.jsonObject - ?.get("revision") - ?.jsonPrimitive - ?.contentOrNull - ?: return + val json = + resource.openStream().use { + Json.decodeFromStream(it) + } + val revision = + json["browsers"]?.jsonArray + ?.find { it.jsonObject["name"]?.jsonPrimitive?.contentOrNull == "chromium" } + ?.jsonObject + ?.get("revision") + ?.jsonPrimitive + ?.contentOrNull + ?: return val playwrightDir = AppDirsFactory.getInstance().getUserDataDir("ms-playwright", null, null) val chromiumZip = Path(".").resolve("bin/chromium.zip") @@ -50,7 +52,7 @@ object Chromium { Files.copy( source, chromePath.resolve(source.absolutePathString().removePrefix("/")), - StandardCopyOption.REPLACE_EXISTING + StandardCopyOption.REPLACE_EXISTING, ) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/util/DocumentationDsl.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/util/DocumentationDsl.kt index bb20559e0..c15b8ef41 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/util/DocumentationDsl.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/util/DocumentationDsl.kt @@ -7,7 +7,10 @@ import io.javalin.plugin.openapi.dsl.OpenApiDocumentation import io.javalin.plugin.openapi.dsl.documented import io.swagger.v3.oas.models.Operation -fun getSimpleParamItem(ctx: Context, param: Param): String? { +fun getSimpleParamItem( + ctx: Context, + param: Param, +): String? { return when (param) { is Param.FormParam -> ctx.formParam(param.key) is Param.PathParam -> ctx.pathParam(param.key) @@ -17,52 +20,57 @@ fun getSimpleParamItem(ctx: Context, param: Param): String? { } @Suppress("UNCHECKED_CAST") -fun getParam(ctx: Context, param: Param): T { +fun getParam( + ctx: Context, + param: Param, +): T { if (param is Param.QueryParams<*, *>) { val item = ctx.queryParams(param.key).filter(String::isNotBlank) - val typedItem: List = when (param.clazz) { - String::class.java, java.lang.String::class.java -> item - Int::class.java, java.lang.Integer::class.java -> item.map { it.toIntOrNull() } - Long::class.java, java.lang.Long::class.java -> item.map { it.toLongOrNull() } - Boolean::class.java, java.lang.Boolean::class.java -> item.map { it.toBoolean() } - Float::class.java, java.lang.Float::class.java -> item.map { it.toFloatOrNull() } - Double::class.java, java.lang.Double::class.java -> item.map { it.toDoubleOrNull() } - else -> throw IllegalStateException("Unknown class ${param.clazz.simpleName}") - }.let { - if (param.nullable) { - it - } else { - it.filterNotNull() - } - }.ifEmpty { param.defaultValue } - return typedItem as T - } - val typedItem: Any? = when (val clazz = param.clazz as Class) { - String::class.java, java.lang.String::class.java -> getSimpleParamItem(ctx, param) ?: param.defaultValue - Int::class.java, java.lang.Integer::class.java -> getSimpleParamItem(ctx, param)?.toIntOrNull() ?: param.defaultValue - Long::class.java, java.lang.Long::class.java -> getSimpleParamItem(ctx, param)?.toLongOrNull() ?: param.defaultValue - Boolean::class.java, java.lang.Boolean::class.java -> getSimpleParamItem(ctx, param)?.toBoolean() ?: param.defaultValue - Float::class.java, java.lang.Float::class.java -> getSimpleParamItem(ctx, param)?.toFloatOrNull() ?: param.defaultValue - Double::class.java, java.lang.Double::class.java -> getSimpleParamItem(ctx, param)?.toDoubleOrNull() ?: param.defaultValue - else -> { - when (param) { - is Param.FormParam -> ctx.formParamAsClass(param.key, clazz) - is Param.PathParam -> ctx.pathParamAsClass(param.key, clazz) - is Param.QueryParam -> ctx.queryParamAsClass(param.key, clazz) - else -> throw IllegalStateException("Invalid param") + val typedItem: List = + when (param.clazz) { + String::class.java, java.lang.String::class.java -> item + Int::class.java, java.lang.Integer::class.java -> item.map { it.toIntOrNull() } + Long::class.java, java.lang.Long::class.java -> item.map { it.toLongOrNull() } + Boolean::class.java, java.lang.Boolean::class.java -> item.map { it.toBoolean() } + Float::class.java, java.lang.Float::class.java -> item.map { it.toFloatOrNull() } + Double::class.java, java.lang.Double::class.java -> item.map { it.toDoubleOrNull() } + else -> throw IllegalStateException("Unknown class ${param.clazz.simpleName}") }.let { if (param.nullable) { - it.allowNullable().get() ?: param.defaultValue + it } else { - if (param.defaultValue != null) { - it.getOrDefault(param.defaultValue!!) + it.filterNotNull() + } + }.ifEmpty { param.defaultValue } + return typedItem as T + } + val typedItem: Any? = + when (val clazz = param.clazz as Class) { + String::class.java, java.lang.String::class.java -> getSimpleParamItem(ctx, param) ?: param.defaultValue + Int::class.java, java.lang.Integer::class.java -> getSimpleParamItem(ctx, param)?.toIntOrNull() ?: param.defaultValue + Long::class.java, java.lang.Long::class.java -> getSimpleParamItem(ctx, param)?.toLongOrNull() ?: param.defaultValue + Boolean::class.java, java.lang.Boolean::class.java -> getSimpleParamItem(ctx, param)?.toBoolean() ?: param.defaultValue + Float::class.java, java.lang.Float::class.java -> getSimpleParamItem(ctx, param)?.toFloatOrNull() ?: param.defaultValue + Double::class.java, java.lang.Double::class.java -> getSimpleParamItem(ctx, param)?.toDoubleOrNull() ?: param.defaultValue + else -> { + when (param) { + is Param.FormParam -> ctx.formParamAsClass(param.key, clazz) + is Param.PathParam -> ctx.pathParamAsClass(param.key, clazz) + is Param.QueryParam -> ctx.queryParamAsClass(param.key, clazz) + else -> throw IllegalStateException("Invalid param") + }.let { + if (param.nullable) { + it.allowNullable().get() ?: param.defaultValue } else { - it.get() + if (param.defaultValue != null) { + it.getOrDefault(param.defaultValue!!) + } else { + it.get() + } } } } } - } return if (param.nullable) { typedItem as T @@ -74,7 +82,7 @@ fun getParam(ctx: Context, param: Param): T { inline fun getDocumentation( documentWith: OpenApiDocumentation.() -> Unit, noinline withResults: ResultsBuilder.() -> Unit, - vararg params: Param<*> + vararg params: Param<*>, ): OpenApiDocumentation { return OpenApiDocumentation().apply(documentWith).apply { applyResults(withResults) @@ -99,15 +107,27 @@ fun OpenApiDocumentation.withOperation(block: Operation.() -> Unit) { operation(block) } -inline fun formParam(key: String, defaultValue: T? = null): Param.FormParam { +inline fun formParam( + key: String, + defaultValue: T? = null, +): Param.FormParam { return Param.FormParam(key, T::class.java, defaultValue, null is T) } -inline fun queryParam(key: String, defaultValue: T? = null): Param.QueryParam { + +inline fun queryParam( + key: String, + defaultValue: T? = null, +): Param.QueryParam { return Param.QueryParam(key, T::class.java, defaultValue, null is T) } -inline fun queryParams(key: String, defaultValue: List = emptyList()): Param.QueryParams> { + +inline fun queryParams( + key: String, + defaultValue: List = emptyList(), +): Param.QueryParams> { return Param.QueryParams(key, T::class.java, defaultValue, null is T) } + inline fun pathParam(key: String): Param.PathParam { return Param.PathParam(key, T::class.java, null, false) } @@ -117,29 +137,33 @@ sealed class Param { abstract val clazz: Class<*> abstract val defaultValue: T? abstract val nullable: Boolean + data class FormParam( override val key: String, override val clazz: Class<*>, override val defaultValue: T?, - override val nullable: Boolean + override val nullable: Boolean, ) : Param() + data class QueryParam( override val key: String, override val clazz: Class<*>, override val defaultValue: T?, - override val nullable: Boolean + override val nullable: Boolean, ) : Param() + data class QueryParams>( override val key: String, override val clazz: Class, override val defaultValue: T, - override val nullable: Boolean + override val nullable: Boolean, ) : Param() + data class PathParam( override val key: String, override val clazz: Class<*>, override val defaultValue: T?, - override val nullable: Boolean + override val nullable: Boolean, ) : Param() } @@ -149,18 +173,26 @@ class ResultsBuilder { inline fun json(code: HttpCode) { results += ResultType.MimeType(code, "application/json", T::class.java) } + fun plainText(code: HttpCode) { results += ResultType.MimeType(code, "text/plain", String::class.java) } + fun image(code: HttpCode) { results += ResultType.MimeType(code, "image/*", ByteArray::class.java) } + fun stream(code: HttpCode) { results += ResultType.MimeType(code, "application/octet-stream", ByteArray::class.java) } - inline fun mime(code: HttpCode, mime: String) { + + inline fun mime( + code: HttpCode, + mime: String, + ) { results += ResultType.MimeType(code, mime, T::class.java) } + fun httpCode(code: HttpCode) { results += ResultType.StatusCode(code) } @@ -168,11 +200,13 @@ class ResultsBuilder { sealed class ResultType { abstract fun applyTo(documentation: OpenApiDocumentation) + data class MimeType(val code: HttpCode, val mime: String, private val clazz: Class<*>) : ResultType() { override fun applyTo(documentation: OpenApiDocumentation) { documentation.result(code.status.toString(), clazz, mime) } } + data class StatusCode(val code: HttpCode) : ResultType() { override fun applyTo(documentation: OpenApiDocumentation) { documentation.result(code.status.toString()) @@ -183,11 +217,11 @@ sealed class ResultType { inline fun handler( documentWith: OpenApiDocumentation.() -> Unit = {}, noinline behaviorOf: (ctx: Context) -> Unit, - noinline withResults: ResultsBuilder.() -> Unit + noinline withResults: ResultsBuilder.() -> Unit, ): DocumentedHandler { return documented( documentation = getDocumentation(documentWith, withResults), - handle = behaviorOf + handle = behaviorOf, ) } @@ -195,16 +229,16 @@ inline fun handler( param1: Param, documentWith: OpenApiDocumentation.() -> Unit, noinline behaviorOf: (ctx: Context, P1) -> Unit, - noinline withResults: ResultsBuilder.() -> Unit + noinline withResults: ResultsBuilder.() -> Unit, ): DocumentedHandler { return documented( documentation = getDocumentation(documentWith, withResults, param1), handle = { behaviorOf( it, - getParam(it, param1) + getParam(it, param1), ) - } + }, ) } @@ -213,7 +247,7 @@ inline fun handler( param2: Param, documentWith: OpenApiDocumentation.() -> Unit = {}, crossinline behaviorOf: (ctx: Context, P1, P2) -> Unit, - noinline withResults: ResultsBuilder.() -> Unit + noinline withResults: ResultsBuilder.() -> Unit, ): DocumentedHandler { return documented( documentation = getDocumentation(documentWith, withResults, param1, param2), @@ -221,9 +255,9 @@ inline fun handler( behaviorOf( it, getParam(it, param1), - getParam(it, param2) + getParam(it, param2), ) - } + }, ) } @@ -233,7 +267,7 @@ inline fun handler( param3: Param, documentWith: OpenApiDocumentation.() -> Unit = {}, crossinline behaviorOf: (ctx: Context, P1, P2, P3) -> Unit, - noinline withResults: ResultsBuilder.() -> Unit + noinline withResults: ResultsBuilder.() -> Unit, ): DocumentedHandler { return documented( documentation = getDocumentation(documentWith, withResults, param1, param2, param3), @@ -242,9 +276,9 @@ inline fun handler( it, getParam(it, param1), getParam(it, param2), - getParam(it, param3) + getParam(it, param3), ) - } + }, ) } @@ -255,7 +289,7 @@ inline fun handler( param4: Param, documentWith: OpenApiDocumentation.() -> Unit = {}, crossinline behaviorOf: (ctx: Context, P1, P2, P3, P4) -> Unit, - noinline withResults: ResultsBuilder.() -> Unit + noinline withResults: ResultsBuilder.() -> Unit, ): DocumentedHandler { return documented( documentation = getDocumentation(documentWith, withResults, param1, param2, param3, param4), @@ -265,9 +299,9 @@ inline fun handler( getParam(it, param1), getParam(it, param2), getParam(it, param3), - getParam(it, param4) + getParam(it, param4), ) - } + }, ) } @@ -279,10 +313,19 @@ inline fun handler( param5: Param, documentWith: OpenApiDocumentation.() -> Unit = {}, crossinline behaviorOf: (ctx: Context, P1, P2, P3, P4, P5) -> Unit, - noinline withResults: ResultsBuilder.() -> Unit + noinline withResults: ResultsBuilder.() -> Unit, ): DocumentedHandler { return documented( - documentation = getDocumentation(documentWith, withResults, param1, param2, param3, param4, param5), + documentation = + getDocumentation( + documentWith, + withResults, + param1, + param2, + param3, + param4, + param5, + ), handle = { behaviorOf( it, @@ -290,9 +333,9 @@ inline fun handler( getParam(it, param2), getParam(it, param3), getParam(it, param4), - getParam(it, param5) + getParam(it, param5), ) - } + }, ) } @@ -305,10 +348,20 @@ inline fun , documentWith: OpenApiDocumentation.() -> Unit = {}, crossinline behaviorOf: (ctx: Context, P1, P2, P3, P4, P5, P6) -> Unit, - noinline withResults: ResultsBuilder.() -> Unit + noinline withResults: ResultsBuilder.() -> Unit, ): DocumentedHandler { return documented( - documentation = getDocumentation(documentWith, withResults, param1, param2, param3, param4, param5, param6), + documentation = + getDocumentation( + documentWith, + withResults, + param1, + param2, + param3, + param4, + param5, + param6, + ), handle = { behaviorOf( it, @@ -317,13 +370,21 @@ inline fun handler( +inline fun < + reified P1, + reified P2, + reified P3, + reified P4, + reified P5, + reified P6, + reified P7, + > handler( param1: Param, param2: Param, param3: Param, @@ -333,10 +394,21 @@ inline fun , documentWith: OpenApiDocumentation.() -> Unit = {}, crossinline behaviorOf: (ctx: Context, P1, P2, P3, P4, P5, P6, P7) -> Unit, - noinline withResults: ResultsBuilder.() -> Unit + noinline withResults: ResultsBuilder.() -> Unit, ): DocumentedHandler { return documented( - documentation = getDocumentation(documentWith, withResults, param1, param2, param3, param4, param5, param6, param7), + documentation = + getDocumentation( + documentWith, + withResults, + param1, + param2, + param3, + param4, + param5, + param6, + param7, + ), handle = { behaviorOf( it, @@ -346,13 +418,22 @@ inline fun handler( +inline fun < + reified P1, + reified P2, + reified P3, + reified P4, + reified P5, + reified P6, + reified P7, + reified P8, + > handler( param1: Param, param2: Param, param3: Param, @@ -363,10 +444,22 @@ inline fun , documentWith: OpenApiDocumentation.() -> Unit = {}, crossinline behaviorOf: (ctx: Context, P1, P2, P3, P4, P5, P6, P7, P8) -> Unit, - noinline withResults: ResultsBuilder.() -> Unit + noinline withResults: ResultsBuilder.() -> Unit, ): DocumentedHandler { return documented( - documentation = getDocumentation(documentWith, withResults, param1, param2, param3, param4, param5, param6, param7, param8), + documentation = + getDocumentation( + documentWith, + withResults, + param1, + param2, + param3, + param4, + param5, + param6, + param7, + param8, + ), handle = { behaviorOf( it, @@ -377,13 +470,23 @@ inline fun handler( +inline fun < + reified P1, + reified P2, + reified P3, + reified P4, + reified P5, + reified P6, + reified P7, + reified P8, + reified P9, + > handler( param1: Param, param2: Param, param3: Param, @@ -395,10 +498,23 @@ inline fun , documentWith: OpenApiDocumentation.() -> Unit = {}, crossinline behaviorOf: (ctx: Context, P1, P2, P3, P4, P5, P6, P7, P8, P9) -> Unit, - noinline withResults: ResultsBuilder.() -> Unit + noinline withResults: ResultsBuilder.() -> Unit, ): DocumentedHandler { return documented( - documentation = getDocumentation(documentWith, withResults, param1, param2, param3, param4, param5, param6, param7, param8, param9), + documentation = + getDocumentation( + documentWith, + withResults, + param1, + param2, + param3, + param4, + param5, + param6, + param7, + param8, + param9, + ), handle = { behaviorOf( it, @@ -410,13 +526,24 @@ inline fun handler( +inline fun < + reified P1, + reified P2, + reified P3, + reified P4, + reified P5, + reified P6, + reified P7, + reified P8, + reified P9, + reified P10, + > handler( param1: Param, param2: Param, param3: Param, @@ -429,10 +556,24 @@ inline fun , documentWith: OpenApiDocumentation.() -> Unit = {}, crossinline behaviorOf: (ctx: Context, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10) -> Unit, - noinline withResults: ResultsBuilder.() -> Unit + noinline withResults: ResultsBuilder.() -> Unit, ): DocumentedHandler { return documented( - documentation = getDocumentation(documentWith, withResults, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10), + documentation = + getDocumentation( + documentWith, + withResults, + param1, + param2, + param3, + param4, + param5, + param6, + param7, + param8, + param9, + param10, + ), handle = { behaviorOf( it, @@ -445,8 +586,8 @@ inline fun SystemTray.DEBUG = debugLogsEnabled }, - ignoreInitialValue = false - ) + instance = + try { + // ref: https://github.com/dorkbox/SystemTray/blob/master/test/dorkbox/TestTray.java + serverConfig.subscribeTo( + serverConfig.debugLogsEnabled, + { debugLogsEnabled -> SystemTray.DEBUG = debugLogsEnabled }, + ignoreInitialValue = false, + ) - CacheUtil.clear(BuildConfig.NAME) + CacheUtil.clear(BuildConfig.NAME) - if (System.getProperty("os.name").startsWith("Mac")) { - SystemTray.FORCE_TRAY_TYPE = SystemTray.TrayType.Awt - } + if (System.getProperty("os.name").startsWith("Mac")) { + SystemTray.FORCE_TRAY_TYPE = SystemTray.TrayType.Awt + } - val systemTray = SystemTray.get(BuildConfig.NAME) - val mainMenu = systemTray.menu + val systemTray = SystemTray.get(BuildConfig.NAME) + val mainMenu = systemTray.menu - mainMenu.add( - MenuItem( - "Open Tachidesk" - ) { - openInBrowser() - } - ) + mainMenu.add( + MenuItem( + "Open Tachidesk", + ) { + openInBrowser() + }, + ) - val icon = ServerConfig::class.java.getResource("/icon/faviconlogo.png") + val icon = ServerConfig::class.java.getResource("/icon/faviconlogo.png") - // systemTray.setTooltip("Tachidesk") - systemTray.setImage(icon) - // systemTray.status = "No Mail" + // systemTray.setTooltip("Tachidesk") + systemTray.setImage(icon) + // systemTray.status = "No Mail" - mainMenu.add( - MenuItem("Quit") { - shutdownApp(Success) - } - ) + mainMenu.add( + MenuItem("Quit") { + shutdownApp(Success) + }, + ) - systemTray - } catch (e: Exception) { - e.printStackTrace() - null - } + systemTray + } catch (e: Exception) { + e.printStackTrace() + null + } } fun remove() { diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/util/WebInterfaceManager.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/util/WebInterfaceManager.kt index 7f268bdaf..480e6290e 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/util/WebInterfaceManager.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/util/WebInterfaceManager.kt @@ -41,7 +41,7 @@ import suwayomi.tachidesk.graphql.types.UpdateState.STOPPED import suwayomi.tachidesk.graphql.types.WebUIUpdateInfo import suwayomi.tachidesk.graphql.types.WebUIUpdateStatus import suwayomi.tachidesk.server.ApplicationDirs -import suwayomi.tachidesk.server.BuildConfig +import suwayomi.tachidesk.server.generated.BuildConfig import suwayomi.tachidesk.server.serverConfig import suwayomi.tachidesk.util.HAScheduler import uy.kohesive.injekt.injectLazy @@ -65,20 +65,22 @@ class BundledWebUIMissing : Exception("No bundled webUI version found") enum class WebUIInterface { BROWSER, - ELECTRON; + ELECTRON, + ; companion object { - fun from(value: String): WebUIInterface = WebUIInterface.values().find { it.name.lowercase() == value.lowercase() } ?: BROWSER + fun from(value: String): WebUIInterface = entries.find { it.name.lowercase() == value.lowercase() } ?: BROWSER } } enum class WebUIChannel { BUNDLED, // the default webUI version bundled with the server release STABLE, - PREVIEW; + PREVIEW, + ; companion object { - fun from(channel: String): WebUIChannel = WebUIChannel.values().find { it.name.lowercase() == channel.lowercase() } ?: STABLE + fun from(channel: String): WebUIChannel = entries.find { it.name.lowercase() == channel.lowercase() } ?: STABLE fun doesConfigChannelEqual(channel: WebUIChannel): Boolean { return serverConfig.webUIChannel.value.equals(channel.name, true) @@ -91,14 +93,14 @@ enum class WebUIFlavor( val repoUrl: String, val versionMappingUrl: String, val latestReleaseInfoUrl: String, - val baseFileName: String + val baseFileName: String, ) { WEBUI( "WebUI", "https://github.com/Suwayomi/Tachidesk-WebUI-preview", "https://raw.githubusercontent.com/Suwayomi/Tachidesk-WebUI/master/versionToServerVersionMapping.json", "https://api.github.com/repos/Suwayomi/Tachidesk-WebUI-preview/releases/latest", - "Tachidesk-WebUI" + "Tachidesk-WebUI", ), CUSTOM( @@ -106,11 +108,12 @@ enum class WebUIFlavor( "repoURL", "versionMappingUrl", "latestReleaseInfoURL", - "baseFileName" - ); + "baseFileName", + ), + ; companion object { - fun from(value: String): WebUIFlavor = WebUIFlavor.values().find { it.name == value } ?: WEBUI + fun from(value: String): WebUIFlavor = entries.find { it.name == value } ?: WEBUI } } @@ -118,8 +121,8 @@ object WebInterfaceManager { private val logger = KotlinLogging.logger {} private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default) - private const val webUIPreviewVersion = "PREVIEW" - private const val lastWebUIUpdateCheckKey = "lastWebUIUpdateCheckKey" + private const val WEBUI_PREVIEW_VERSION = "PREVIEW" + private const val LAST_WEBUI_UPDATE_CHECK_KEY = "lastWebUIUpdateCheckKey" private val preferences = Preferences.userNodeForPackage(WebInterfaceManager::class.java) private var currentUpdateTaskId: String = "" @@ -129,31 +132,33 @@ object WebInterfaceManager { private val notifyFlow = MutableSharedFlow(extraBufferCapacity = 1, onBufferOverflow = DROP_OLDEST) - val status = notifyFlow.sample(1.seconds) - .stateIn( - scope, - SharingStarted.Eagerly, - WebUIUpdateStatus( - info = WebUIUpdateInfo( - channel = serverConfig.webUIChannel.value, - tag = "", - updateAvailable = false + val status = + notifyFlow.sample(1.seconds) + .stateIn( + scope, + SharingStarted.Eagerly, + WebUIUpdateStatus( + info = + WebUIUpdateInfo( + channel = serverConfig.webUIChannel.value, + tag = "", + updateAvailable = false, + ), + state = STOPPED, + progress = 0, ), - state = STOPPED, - progress = 0 ) - ) init { serverConfig.subscribeTo( combine(serverConfig.webUIUpdateCheckInterval, serverConfig.webUIFlavor) { interval, flavor -> Pair( interval, - flavor + flavor, ) }, ::scheduleWebUIUpdateCheck, - ignoreInitialValue = false + ignoreInitialValue = false, ) } @@ -170,15 +175,18 @@ object WebInterfaceManager { } val updateInterval = serverConfig.webUIUpdateCheckInterval.value.hours.coerceAtLeast(1.hours).coerceAtMost(23.hours) - val lastAutomatedUpdate = preferences.getLong(lastWebUIUpdateCheckKey, System.currentTimeMillis()) + val lastAutomatedUpdate = preferences.getLong(LAST_WEBUI_UPDATE_CHECK_KEY, System.currentTimeMillis()) val task = { logger.debug { - "Checking for webUI update (channel= ${serverConfig.webUIChannel.value}, interval= ${serverConfig.webUIUpdateCheckInterval.value}h, lastAutomatedUpdate= ${ - Date( - lastAutomatedUpdate - ) - })" + "Checking for webUI update (" + + "channel= ${serverConfig.webUIChannel.value}, " + + "interval= ${serverConfig.webUIUpdateCheckInterval.value}h, " + + "lastAutomatedUpdate= ${ + Date( + lastAutomatedUpdate, + ) + })" } runBlocking { @@ -218,9 +226,10 @@ object WebInterfaceManager { // check if the bundled webUI version is a newer version than the current used version // this could be the case in case no compatible webUI version is available and a newer server version was installed val shouldUpdateToBundledVersion = - serverConfig.webUIFlavor.value == WebUIFlavor.WEBUI.uiName && extractVersion(getLocalVersion()) < extractVersion( - BuildConfig.WEBUI_TAG - ) + serverConfig.webUIFlavor.value == WebUIFlavor.WEBUI.uiName && extractVersion(getLocalVersion()) < + extractVersion( + BuildConfig.WEBUI_TAG, + ) if (shouldUpdateToBundledVersion) { logger.debug { "setupWebUI: update to bundled version \"${BuildConfig.WEBUI_TAG}\"" } @@ -259,7 +268,7 @@ object WebInterfaceManager { } // download the latest compatible version for the current selected webUI - val fallbackToDefaultWebUI = !doDownload() { getLatestCompatibleVersion() } + val fallbackToDefaultWebUI = !doDownload { getLatestCompatibleVersion() } if (!fallbackToDefaultWebUI) { return } @@ -269,7 +278,7 @@ object WebInterfaceManager { serverConfig.webUIFlavor.value = WebUIFlavor.WEBUI.uiName - val fallbackToBundledVersion = !doDownload() { getLatestCompatibleVersion() } + val fallbackToBundledVersion = !doDownload { getLatestCompatibleVersion() } if (!fallbackToBundledVersion) { return } @@ -315,7 +324,7 @@ object WebInterfaceManager { } private suspend fun checkForUpdate() { - preferences.putLong(lastWebUIUpdateCheckKey, System.currentTimeMillis()) + preferences.putLong(LAST_WEBUI_UPDATE_CHECK_KEY, System.currentTimeMillis()) val localVersion = getLocalVersion() if (!isUpdateAvailable(localVersion).second) { @@ -364,7 +373,11 @@ object WebInterfaceManager { val currentVersionMD5Sum = fetchMD5SumFor(currentVersion) val validationSucceeded = currentVersionMD5Sum == localMD5Sum - logger.info { "isLocalWebUIValid: Validation ${if (validationSucceeded) "succeeded" else "failed"} - md5: local= $localMD5Sum; expected= $currentVersionMD5Sum" } + logger.info { + "isLocalWebUIValid: Validation " + + "${if (validationSucceeded) "succeeded" else "failed"} - " + + "md5: local= $localMD5Sum; expected= $currentVersionMD5Sum" + } return validationSucceeded } @@ -390,7 +403,7 @@ object WebInterfaceManager { log: KLogger, execute: suspend () -> T, maxRetries: Int = 3, - retryCount: Int = 0 + retryCount: Int = 0, ): T { try { return execute() @@ -433,9 +446,9 @@ object WebInterfaceManager { KotlinLogging.logger("$logger fetchServerMappingFile"), { json.parseToJsonElement( - network.client.newCall(GET(WebUIFlavor.WEBUI.versionMappingUrl)).awaitSuccess().body.string() + network.client.newCall(GET(WebUIFlavor.WEBUI.versionMappingUrl)).awaitSuccess().body.string(), ).jsonArray - } + }, ) } @@ -448,16 +461,26 @@ object WebInterfaceManager { val currentServerVersionNumber = extractVersion(BuildConfig.REVISION) val webUIToServerVersionMappings = fetchServerMappingFile() - logger.debug { "getLatestCompatibleVersion: webUIChannel= ${serverConfig.webUIChannel.value}, currentServerVersion= ${BuildConfig.REVISION}, mappingFile= $webUIToServerVersionMappings" } + logger.debug { + "getLatestCompatibleVersion: " + + "webUIChannel= ${serverConfig.webUIChannel.value}, " + + "currentServerVersion= ${BuildConfig.REVISION}, " + + "mappingFile= $webUIToServerVersionMappings" + } for (i in 0 until webUIToServerVersionMappings.size) { val webUIToServerVersionEntry = webUIToServerVersionMappings[i].jsonObject - var webUIVersion = webUIToServerVersionEntry["uiVersion"]?.jsonPrimitive?.content ?: throw Exception("Invalid mappingFile") - val minServerVersionString = webUIToServerVersionEntry["serverVersion"]?.jsonPrimitive?.content ?: throw Exception("Invalid mappingFile") + var webUIVersion = + webUIToServerVersionEntry["uiVersion"]?.jsonPrimitive?.content + ?: throw Exception("Invalid mappingFile") + val minServerVersionString = + webUIToServerVersionEntry["serverVersion"] + ?.jsonPrimitive?.content + ?: throw Exception("Invalid mappingFile") val minServerVersionNumber = extractVersion(minServerVersionString) val ignorePreviewVersion = - !WebUIChannel.doesConfigChannelEqual(WebUIChannel.PREVIEW) && webUIVersion == webUIPreviewVersion + !WebUIChannel.doesConfigChannelEqual(WebUIChannel.PREVIEW) && webUIVersion == WEBUI_PREVIEW_VERSION if (ignorePreviewVersion) { continue } else { @@ -473,18 +496,23 @@ object WebInterfaceManager { throw Exception("No compatible webUI version found") } - private fun emitStatus(version: String, state: UpdateState, progress: Int) { + private fun emitStatus( + version: String, + state: UpdateState, + progress: Int, + ) { scope.launch { notifyFlow.emit( WebUIUpdateStatus( - info = WebUIUpdateInfo( - channel = serverConfig.webUIChannel.value, - tag = version, - updateAvailable = true - ), + info = + WebUIUpdateInfo( + channel = serverConfig.webUIChannel.value, + tag = version, + updateAvailable = true, + ), state, - progress - ) + progress, + ), ) } } @@ -512,7 +540,7 @@ object WebInterfaceManager { emitStatus( version, DOWNLOADING, - progress + progress, ) } }) @@ -533,7 +561,7 @@ object WebInterfaceManager { private suspend fun downloadVersionZipFile( url: String, filePath: String, - updateProgress: (progress: Int) -> Unit + updateProgress: (progress: Int) -> Unit, ) { val zipFile = File(filePath) zipFile.delete() @@ -576,7 +604,10 @@ object WebInterfaceManager { } } - private suspend fun isDownloadValid(zipFileName: String, zipFilePath: String): Boolean { + private suspend fun isDownloadValid( + zipFileName: String, + zipFilePath: String, + ): Boolean { val tempUnzippedWebUIFolderPath = zipFileName.replace(".zip", "") extractDownload(zipFilePath, tempUnzippedWebUIFolderPath) @@ -588,7 +619,10 @@ object WebInterfaceManager { return isDownloadValid } - private fun extractDownload(zipFilePath: String, targetPath: String) { + private fun extractDownload( + zipFilePath: String, + targetPath: String, + ) { File(targetPath).mkdirs() ZipFile(zipFilePath).use { it.extractAll(targetPath) } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/util/HAScheduler.kt b/server/src/main/kotlin/suwayomi/tachidesk/util/HAScheduler.kt index 966bae2a0..56d33e36c 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/util/HAScheduler.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/util/HAScheduler.kt @@ -21,7 +21,11 @@ import kotlin.time.Duration.Companion.seconds val cronParser = CronParser(CronDefinitionBuilder.instanceDefinitionFor(CRON4J)) -abstract class BaseHATask(val id: String, val execute: () -> Unit, val name: String?) : Comparable { +abstract class BaseHATask( + val id: String, + val execute: () -> Unit, + val name: String?, +) : Comparable { abstract fun getLastExecutionTime(): Long abstract fun getNextExecutionTime(): Long @@ -33,19 +37,34 @@ abstract class BaseHATask(val id: String, val execute: () -> Unit, val name: Str } override fun toString(): String { - return "Task \"$name\" ($id) lastExecution= ${Date(getLastExecutionTime())} nextExecution= ${Date(getNextExecutionTime())}" + return "Task \"$name\" ($id) " + + "lastExecution= ${Date(getLastExecutionTime())} " + + "nextExecution= ${Date(getNextExecutionTime())}" } } -class HACronTask(id: String, val cronExpr: String, execute: () -> Unit, name: String?) : BaseHATask(id, execute, name) { +class HACronTask( + id: String, + val cronExpr: String, + execute: () -> Unit, + name: String?, +) : BaseHATask(id, execute, name) { private val executionTime = ExecutionTime.forCron(cronParser.parse(cronExpr)) override fun getLastExecutionTime(): Long { - return executionTime.lastExecution(ZonedDateTime.now()).get().toEpochSecond().seconds.inWholeMilliseconds + return executionTime.lastExecution(ZonedDateTime.now()) + .get() + .toEpochSecond() + .seconds + .inWholeMilliseconds } override fun getNextExecutionTime(): Long { - return executionTime.nextExecution(ZonedDateTime.now()).get().toEpochSecond().seconds.inWholeMilliseconds + return executionTime.nextExecution(ZonedDateTime.now()) + .get() + .toEpochSecond() + .seconds + .inWholeMilliseconds } override fun getTimeToNextExecution(): Long { @@ -57,7 +76,18 @@ class HACronTask(id: String, val cronExpr: String, execute: () -> Unit, name: St } } -class HATask(id: String, val interval: Long, execute: () -> Unit, val timerTask: TimerTask, name: String?, val initialDelay: Long = interval) : BaseHATask(id, execute, name) { +class HATask( + id: String, + val interval: Long, + execute: () -> Unit, + val timerTask: TimerTask, + name: String?, + val initialDelay: Long = interval, +) : BaseHATask( + id, + execute, + name, + ) { private val firstExecutionTime = System.currentTimeMillis() + initialDelay private fun getElapsedTimeOfCurrentInterval(): Long { @@ -119,7 +149,10 @@ object HAScheduler { val systemWasInHibernation = elapsedTime > interval.inWholeMilliseconds + HIBERNATION_THRESHOLD if (systemWasInHibernation) { - logger.debug { "System hibernation detected, task was delayed by ${elapsedTime - interval.inWholeMilliseconds}ms" } + logger.debug { + "System hibernation detected, task was delayed by " + + "${elapsedTime - interval.inWholeMilliseconds}ms" + } scheduledTasks.toList().forEach { val wasLastExecutionMissed = currentTime - it.getLastExecutionTime() - elapsedTime < 0 if (wasLastExecutionMissed) { @@ -142,11 +175,14 @@ object HAScheduler { } }, interval.inWholeMilliseconds, - interval.inWholeMilliseconds + interval.inWholeMilliseconds, ) } - private fun createTimerTask(interval: Long, execute: () -> Unit): TimerTask { + private fun createTimerTask( + interval: Long, + execute: () -> Unit, + ): TimerTask { return object : TimerTask() { var lastExecutionTime: Long = 0 @@ -170,7 +206,12 @@ object HAScheduler { } } - fun schedule(execute: () -> Unit, interval: Long, delay: Long, name: String?): String { + fun schedule( + execute: () -> Unit, + interval: Long, + delay: Long, + name: String?, + ): String { val taskId = UUID.randomUUID().toString() val timerTask = createTimerTask(interval, execute) @@ -193,7 +234,10 @@ object HAScheduler { return task } - fun reschedule(taskId: String, interval: Long) { + fun reschedule( + taskId: String, + interval: Long, + ) { val task = deschedule(taskId) ?: return val timerTask = createTimerTask(interval, task.execute) @@ -209,19 +253,24 @@ object HAScheduler { logger.debug { "reschedule: new= $updatedTask, old= $task" } } - fun scheduleCron(execute: () -> Unit, cronExpr: String, name: String?): String { + fun scheduleCron( + execute: () -> Unit, + cronExpr: String, + name: String?, + ): String { if (!scheduler.isStarted) { scheduler.start() } - val taskId = scheduler.schedule( - cronExpr, - object : Task() { - override fun execute(context: TaskExecutionContext?) { - execute() - } - } - ) + val taskId = + scheduler.schedule( + cronExpr, + object : Task() { + override fun execute(context: TaskExecutionContext?) { + execute() + } + }, + ) val task = HACronTask(taskId, cronExpr, execute, name) scheduledTasks.add(task) @@ -239,7 +288,10 @@ object HAScheduler { logger.debug { "descheduleCron: $task" } } - fun rescheduleCron(taskId: String, cronExpr: String) { + fun rescheduleCron( + taskId: String, + cronExpr: String, + ) { val task = scheduledTasks.find { it.id == taskId } ?: return val updatedTask = HACronTask(taskId, cronExpr, task.execute, task.name) diff --git a/server/src/test/kotlin/masstest/CloudFlareTest.kt b/server/src/test/kotlin/masstest/CloudFlareTest.kt index 5305eb792..62bac77c7 100644 --- a/server/src/test/kotlin/masstest/CloudFlareTest.kt +++ b/server/src/test/kotlin/masstest/CloudFlareTest.kt @@ -39,9 +39,10 @@ class CloudFlareTest { Unit } - nhentai = Source.getSourceList() - .firstNotNullOf { it.id.toLong().takeIf { it == 3122156392225024195L } } - .let(GetCatalogueSource::getCatalogueSourceOrNull) as HttpSource + nhentai = + Source.getSourceList() + .firstNotNullOf { it.id.toLong().takeIf { it == 3122156392225024195L } } + .let(GetCatalogueSource::getCatalogueSourceOrNull) as HttpSource } setLoggingEnabled(true) } @@ -49,9 +50,10 @@ class CloudFlareTest { private val logger = KotlinLogging.logger {} @Test - fun `test nhentai browse`() = runTest { - assert(nhentai.getPopularManga(1).mangas.isNotEmpty()) { - "NHentai results were empty" + fun `test nhentai browse`() = + runTest { + assert(nhentai.getPopularManga(1).mangas.isNotEmpty()) { + "NHentai results were empty" + } } - } } diff --git a/server/src/test/kotlin/masstest/TestExtensionCompatibility.kt b/server/src/test/kotlin/masstest/TestExtensionCompatibility.kt index 3c9437469..0f614a429 100644 --- a/server/src/test/kotlin/masstest/TestExtensionCompatibility.kt +++ b/server/src/test/kotlin/masstest/TestExtensionCompatibility.kt @@ -90,7 +90,7 @@ class TestExtensionCompatibility { repeat { source.getPopularManga(1) } .mangas.firstOrNull() ?: throw Exception("Source returned no manga") - ) + ) } catch (e: Exception) { logger.warn { "Failed to fetch popular manga from $source: ${e.message}" } failedToFetch += source to e @@ -102,7 +102,7 @@ class TestExtensionCompatibility { failedToFetch.joinToString("\n") { (source, exception) -> "${source.name} (${source.lang.uppercase()}, ${source.id}):" + " ${exception.message}" - } + }, ) logger.info { "Now fetching manga info from ${mangaToFetch.size} sources" } @@ -116,7 +116,9 @@ class TestExtensionCompatibility { manga.initialized = true } catch (e: Exception) { logger.warn { - "Failed to fetch manga info from $source for ${manga.title} (${source.mangaDetailsRequest(manga).url}): ${e.message}" + "Failed to fetch manga info from $source for ${manga.title} (${source.mangaDetailsRequest( + manga, + ).url}): ${e.message}" } mangaFailedToFetch += Triple(source, manga, e) } @@ -128,7 +130,7 @@ class TestExtensionCompatibility { "${source.name} (${source.lang}, ${source.id}):" + " ${manga.title} (${source.mangaDetailsRequest(manga).url}):" + " ${exception.message}" - } + }, ) logger.info { "Now fetching manga chapters from ${mangaToFetch.size} sources" } @@ -138,19 +140,24 @@ class TestExtensionCompatibility { semaphore.withPermit { logger.info { "${chapterCount.getAndIncrement()} - Now fetching manga chapters from $source" } try { - chaptersToFetch += Triple( - source, - manga, - repeat { source.getChapterList(manga) }.firstOrNull() ?: throw Exception("Source returned no chapters") - ) + chaptersToFetch += + Triple( + source, + manga, + repeat { source.getChapterList(manga) }.firstOrNull() ?: throw Exception("Source returned no chapters"), + ) } catch (e: Exception) { logger.warn { - "Failed to fetch manga chapters from $source for ${manga.title} (${source.mangaDetailsRequest(manga).url}): ${e.message}" + "Failed to fetch manga chapters from $source for ${manga.title} (${source.mangaDetailsRequest( + manga, + ).url}): ${e.message}" } chaptersFailedToFetch += Triple(source, manga, e) } catch (e: NoClassDefFoundError) { logger.warn { - "Failed to fetch manga chapters from $source for ${manga.title} (${source.mangaDetailsRequest(manga).url}): ${e.message}" + "Failed to fetch manga chapters from $source for ${manga.title} (${source.mangaDetailsRequest( + manga, + ).url}): ${e.message}" } chaptersFailedToFetch += Triple(source, manga, e) } @@ -163,7 +170,7 @@ class TestExtensionCompatibility { "${source.name} (${source.lang}, ${source.id}):" + " ${manga.title} (${source.mangaDetailsRequest(manga).url}):" + " ${exception.message}" - } + }, ) val pageListCount = AtomicInteger(1) @@ -175,7 +182,9 @@ class TestExtensionCompatibility { repeat { source.getPageList(chapter) } } catch (e: Exception) { logger.warn { - "Failed to fetch manga info from $source for ${manga.title} (${source.mangaDetailsRequest(manga).url}): ${e.message}" + "Failed to fetch manga info from $source for ${manga.title} (${source.mangaDetailsRequest( + manga, + ).url}): ${e.message}" } chaptersPageListFailedToFetch += Triple(source, manga to chapter, e) } @@ -188,7 +197,7 @@ class TestExtensionCompatibility { "${source.name} (${source.lang}, ${source.id}):" + " ${manga.first.title} (${source.mangaDetailsRequest(manga.first).url}):" + " ${manga.second.name} (${manga.second.url}): ${exception.message}" - } + }, ) } } @@ -197,7 +206,8 @@ class TestExtensionCompatibility { for (i in 1..2) { try { return block() - } catch (e: Exception) {} + } catch (e: Exception) { + } } return block() } diff --git a/server/src/test/kotlin/suwayomi/tachidesk/graphql/RequestParserTest.kt b/server/src/test/kotlin/suwayomi/tachidesk/graphql/RequestParserTest.kt index ece905fc1..aa6daf389 100644 --- a/server/src/test/kotlin/suwayomi/tachidesk/graphql/RequestParserTest.kt +++ b/server/src/test/kotlin/suwayomi/tachidesk/graphql/RequestParserTest.kt @@ -20,41 +20,80 @@ class RequestParserTest { private val requestParser = JavalinGraphQLRequestParser() @Test - fun testZero() = runTest { - every { ctx.appAttribute(JSON_MAPPER_KEY) } returns (JavalinJackson(JavalinJackson.defaultMapper())) - every { ctx.formParam("operations") } returns """{ "query": "mutation (${'$'}file: Upload!) { singleUpload(file: ${'$'}file) { id } }", "variables": { "file": null } }""" - every { ctx.formParam("map") } returns """{ "0": ["variables.file"] }""" - every { ctx.uploadedFile("0") } returns UploadedFile(ByteArrayInputStream(byteArrayOf()), "", "", "", 0) - val test = requestParser.parseRequest(ctx) - assertIs(test) - assertNotNull(test.variables?.get("file")) - println("File: " + test.variables?.get("file")) - } + fun testZero() = + runTest { + every { ctx.appAttribute(JSON_MAPPER_KEY) } returns + (JavalinJackson(JavalinJackson.defaultMapper())) + every { + ctx.formParam("operations") + } returns + """{ "query": "mutation (${'$'}file: Upload!) { + |singleUpload(file: ${'$'}file) { id } }", "variables": { "file": null } + |} + """.trimMargin() + every { ctx.formParam("map") } returns """{ "0": ["variables.file"] }""" + every { ctx.uploadedFile("0") } returns + UploadedFile( + ByteArrayInputStream(byteArrayOf()), "", "", "", 0, + ) + val test = requestParser.parseRequest(ctx) + assertIs(test) + assertNotNull(test.variables?.get("file")) + println("File: " + test.variables?.get("file")) + } @Test - fun testTest() = runTest { - every { ctx.appAttribute(JSON_MAPPER_KEY) } returns (JavalinJackson(JavalinJackson.defaultMapper())) - every { ctx.formParam("operations") } returns """{ "query": "mutation (${'$'}file: Upload!) { singleUpload(file: ${'$'}file) { id } }", "variables": { "file": null } }""" - every { ctx.formParam("map") } returns """{ "test": ["variables.file"] }""" - every { ctx.uploadedFile("test") } returns UploadedFile(ByteArrayInputStream(byteArrayOf()), "", "", "", 0) - val test = requestParser.parseRequest(ctx) - assertIs(test) - assertNotNull(test.variables?.get("file")) - println("File: " + test.variables?.get("file")) - } + fun testTest() = + runTest { + every { ctx.appAttribute(JSON_MAPPER_KEY) } returns + (JavalinJackson(JavalinJackson.defaultMapper())) + every { + ctx.formParam("operations") + } returns + """{ "query": "mutation (${'$'}file: Upload!) { + |singleUpload(file: ${'$'}file) { id } }", "variables": { "file": null } + |} + """.trimMargin() + every { ctx.formParam("map") } returns """{ "test": ["variables.file"] }""" + every { ctx.uploadedFile("test") } returns + UploadedFile( + ByteArrayInputStream(byteArrayOf()), "", "", "", 0, + ) + val test = requestParser.parseRequest(ctx) + assertIs(test) + assertNotNull(test.variables?.get("file")) + println("File: " + test.variables?.get("file")) + } @Test - fun testList() = runTest { - every { ctx.appAttribute(JSON_MAPPER_KEY) } returns (JavalinJackson(JavalinJackson.defaultMapper())) - every { ctx.formParam("operations") } returns """{ "query": "mutation (${'$'}files: [Upload!]!) { singleUpload(files: ${'$'}files) { id } }", "variables": { "files": [null, null] } }""" - every { ctx.formParam("map") } returns """{ "test": ["variables.files.0"], "test2": ["variables.files.1"] }""" - every { ctx.uploadedFile("test") } returns UploadedFile(ByteArrayInputStream(byteArrayOf()), "", "", "", 0) - every { ctx.uploadedFile("test2") } returns UploadedFile(ByteArrayInputStream(byteArrayOf()), "", "", "", 0) - val test = requestParser.parseRequest(ctx) - assertIs(test) - val files = test.variables?.get("files") - assertIs>(files) - assert(files.all { it is UploadedFile }) - println("Files: $files") - } + fun testList() = + runTest { + every { ctx.appAttribute(JSON_MAPPER_KEY) } returns + (JavalinJackson(JavalinJackson.defaultMapper())) + every { + ctx.formParam("operations") + } returns + """{ "query": "mutation (${'$'}files: [Upload!]!) { + |singleUpload(files: ${'$'}files) { id } }", "variables": { "files": [null, null] } + |} + """.trimMargin() + every { ctx.formParam("map") } returns + """ + { "test": ["variables.files.0"], "test2": ["variables.files.1"] } + """.trimIndent() + every { ctx.uploadedFile("test") } returns + UploadedFile( + ByteArrayInputStream(byteArrayOf()), "", "", "", 0, + ) + every { ctx.uploadedFile("test2") } returns + UploadedFile( + ByteArrayInputStream(byteArrayOf()), "", "", "", 0, + ) + val test = requestParser.parseRequest(ctx) + assertIs(test) + val files = test.variables?.get("files") + assertIs>(files) + assert(files.all { it is UploadedFile }) + println("Files: $files") + } } diff --git a/server/src/test/kotlin/suwayomi/tachidesk/manga/controller/CategoryControllerTest.kt b/server/src/test/kotlin/suwayomi/tachidesk/manga/controller/CategoryControllerTest.kt index 76852ab2b..589e52d96 100644 --- a/server/src/test/kotlin/suwayomi/tachidesk/manga/controller/CategoryControllerTest.kt +++ b/server/src/test/kotlin/suwayomi/tachidesk/manga/controller/CategoryControllerTest.kt @@ -36,7 +36,7 @@ class CategoryControllerTest : ApplicationTest() { @AfterEach internal fun tearDown() { clearTables( - CategoryTable + CategoryTable, ) } } diff --git a/server/src/test/kotlin/suwayomi/tachidesk/manga/controller/UpdateControllerTest.kt b/server/src/test/kotlin/suwayomi/tachidesk/manga/controller/UpdateControllerTest.kt index 9c1a3ad32..cd150b6a6 100644 --- a/server/src/test/kotlin/suwayomi/tachidesk/manga/controller/UpdateControllerTest.kt +++ b/server/src/test/kotlin/suwayomi/tachidesk/manga/controller/UpdateControllerTest.kt @@ -63,9 +63,7 @@ internal class UpdateControllerTest : ApplicationTest() { assertEquals(3, updater.status.value.numberOfJobs) } - private fun createLibraryManga( - _title: String - ): Int { + private fun createLibraryManga(_title: String): Int { return transaction { MangaTable.insertAndGetId { it[title] = _title @@ -81,7 +79,7 @@ internal class UpdateControllerTest : ApplicationTest() { clearTables( CategoryMangaTable, MangaTable, - CategoryTable + CategoryTable, ) val updater by DI.global.instance() runBlocking { updater.reset() } diff --git a/server/src/test/kotlin/suwayomi/tachidesk/manga/impl/CategoryMangaTest.kt b/server/src/test/kotlin/suwayomi/tachidesk/manga/impl/CategoryMangaTest.kt index 770fcf93e..731356d99 100644 --- a/server/src/test/kotlin/suwayomi/tachidesk/manga/impl/CategoryMangaTest.kt +++ b/server/src/test/kotlin/suwayomi/tachidesk/manga/impl/CategoryMangaTest.kt @@ -33,36 +33,36 @@ class CategoryMangaTest : ApplicationTest() { assertEquals( 0, CategoryManga.getCategoryMangaList(DEFAULT_CATEGORY_ID)[0].unreadCount, - "Manga should not have any unread chapters" + "Manga should not have any unread chapters", ) createChapters(mangaId, 10, false) assertEquals( 10, CategoryManga.getCategoryMangaList(DEFAULT_CATEGORY_ID)[0].unreadCount, - "Manga should have unread chapters" + "Manga should have unread chapters", ) val categoryId = Category.createCategory("Old") assertEquals( 0, CategoryManga.getCategoryMangaList(categoryId).size, - "Newly created category shouldn't have any Mangas" + "Newly created category shouldn't have any Mangas", ) CategoryManga.addMangaToCategory(mangaId, categoryId) assertEquals( 1, CategoryManga.getCategoryMangaList(categoryId).size, - "Manga should been moved" + "Manga should been moved", ) assertEquals( 10, CategoryManga.getCategoryMangaList(categoryId)[0].unreadCount, - "Manga should keep it's unread count in moved category" + "Manga should keep it's unread count in moved category", ) assertEquals( 0, CategoryManga.getCategoryMangaList(DEFAULT_CATEGORY_ID).size, - "Manga shouldn't be member of default category after moving" + "Manga shouldn't be member of default category after moving", ) } @@ -72,7 +72,7 @@ class CategoryMangaTest : ApplicationTest() { ChapterTable, CategoryMangaTable, MangaTable, - CategoryTable + CategoryTable, ) } } diff --git a/server/src/test/kotlin/suwayomi/tachidesk/manga/impl/MangaTest.kt b/server/src/test/kotlin/suwayomi/tachidesk/manga/impl/MangaTest.kt index 470ea522f..a7a7fb453 100644 --- a/server/src/test/kotlin/suwayomi/tachidesk/manga/impl/MangaTest.kt +++ b/server/src/test/kotlin/suwayomi/tachidesk/manga/impl/MangaTest.kt @@ -33,24 +33,24 @@ class MangaTest : ApplicationTest() { assertEquals( 1, Manga.getMangaMetaMap(metaManga).size, - "Manga meta should still only have one pair" + "Manga meta should still only have one pair", ) assertEquals( "newValue", Manga.getMangaMetaMap(metaManga)["test"], - "Manga meta with key 'test' should use the value `newValue`" + "Manga meta with key 'test' should use the value `newValue`", ) Manga.modifyMangaMeta(metaManga, "test2", "value2") assertEquals( 2, Manga.getMangaMetaMap(metaManga).size, - "Manga Meta should have an additional pair" + "Manga Meta should have an additional pair", ) assertEquals( "value2", Manga.getMangaMetaMap(metaManga)["test2"], - "Manga Meta for key 'test2' should be 'value2'" + "Manga Meta for key 'test2' should be 'value2'", ) } @@ -58,7 +58,7 @@ class MangaTest : ApplicationTest() { internal fun tearDown() { clearTables( MangaMetaTable, - MangaTable + MangaTable, ) } } diff --git a/server/src/test/kotlin/suwayomi/tachidesk/manga/impl/PageTest.kt b/server/src/test/kotlin/suwayomi/tachidesk/manga/impl/PageTest.kt index 14ed3a5a6..18a07ca74 100644 --- a/server/src/test/kotlin/suwayomi/tachidesk/manga/impl/PageTest.kt +++ b/server/src/test/kotlin/suwayomi/tachidesk/manga/impl/PageTest.kt @@ -13,14 +13,14 @@ import suwayomi.tachidesk.test.ApplicationTest import kotlin.test.assertEquals class PageTest : ApplicationTest() { - @Test fun testGetPageName() { val tests = listOf(0, 1, 2, 100) - val testResults = tests.map { - getPageName(it) - } + val testResults = + tests.map { + getPageName(it) + } assertEquals(testResults[0], "001") assertEquals(testResults[1], "002") diff --git a/server/src/test/kotlin/suwayomi/tachidesk/manga/impl/SearchTest.kt b/server/src/test/kotlin/suwayomi/tachidesk/manga/impl/SearchTest.kt index 5b9793c9d..b9ee64558 100644 --- a/server/src/test/kotlin/suwayomi/tachidesk/manga/impl/SearchTest.kt +++ b/server/src/test/kotlin/suwayomi/tachidesk/manga/impl/SearchTest.kt @@ -39,7 +39,11 @@ class SearchTest : ApplicationTest() { var mangas: List = emptyList() @Deprecated("Use the non-RxJava API instead", replaceWith = ReplaceWith("getSearchManga")) - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { + override fun fetchSearchManga( + page: Int, + query: String, + filters: FilterList, + ): Observable { return Observable.just(MangasPage(mangas, false)) } } @@ -57,9 +61,10 @@ class SearchTest : ApplicationTest() { @Test fun searchWorks() { - val searchResults = runBlocking { - sourceSearch(sourceId, "all the mangas", 1) - } + val searchResults = + runBlocking { + sourceSearch(sourceId, "all the mangas", 1) + } assertEquals(mangasCount, searchResults.mangaList.size, "should return all the mangas") } @@ -89,39 +94,45 @@ class FilterListTest : ApplicationTest() { assertEquals( 0, - filterList.size + filterList.size, ) } class FilterListSource(id: Long) : EmptyFilterListSource(id) { class SelectFilter(name: String, values: Array) : Filter.Select(name, values) + class TextFilter(name: String) : Filter.Text(name) + class TestCheckBox(name: String) : Filter.CheckBox(name, false) + class TriState(name: String, state: Int) : Filter.TriState(name, state) + class Group(name: String, state: List) : Filter.Group(name, state) + class Sort(name: String, values: Array, state: Selection) : Filter.Sort(name, values, state) - override var mFilterList = FilterList( - Filter.Header("This is a header"), - Filter.Separator(), - SelectFilter("Select one of these:", arrayOf("this", "that", "none of them")), - TextFilter("text filter"), - TestCheckBox("check this or else!"), - TriState("wanna hook up?", Filter.TriState.STATE_IGNORE), - Group( - "my Todo", - listOf( - TestCheckBox("Write Tests"), - TestCheckBox("Write More Tests"), - TestCheckBox("Write Even More Tests") - ) - ), - Sort( - "Sort", - arrayOf("Alphabetic", "Date published", "Rating"), - Filter.Sort.Selection(2, false) + override var mFilterList = + FilterList( + Filter.Header("This is a header"), + Filter.Separator(), + SelectFilter("Select one of these:", arrayOf("this", "that", "none of them")), + TextFilter("text filter"), + TestCheckBox("check this or else!"), + TriState("wanna hook up?", Filter.TriState.STATE_IGNORE), + Group( + "my Todo", + listOf( + TestCheckBox("Write Tests"), + TestCheckBox("Write More Tests"), + TestCheckBox("Write Even More Tests"), + ), + ), + Sort( + "Sort", + arrayOf("Alphabetic", "Date published", "Rating"), + Filter.Sort.Selection(2, false), + ), ) - ) } @Test @@ -131,27 +142,27 @@ class FilterListTest : ApplicationTest() { assertEquals( FilterObject("Header", source.mFilterList[0]), - filterList[0] + filterList[0], ) assertEquals( FilterObject("Separator", source.mFilterList[1]), - filterList[1] + filterList[1], ) assertEquals( FilterObject("Select", source.mFilterList[2]), - filterList[2] + filterList[2], ) assertEquals( FilterObject("Text", source.mFilterList[3]), - filterList[3] + filterList[3], ) assertEquals( FilterObject("CheckBox", source.mFilterList[4]), - filterList[4] + filterList[4], ) assertEquals( FilterObject("TriState", source.mFilterList[5]), - filterList[5] + filterList[5], ) assertEquals( filterList[6], @@ -162,14 +173,14 @@ class FilterListTest : ApplicationTest() { listOf( FilterObject("CheckBox", (source.mFilterList[6].state as List>)[0]), FilterObject("CheckBox", (source.mFilterList[6].state as List>)[1]), - FilterObject("CheckBox", (source.mFilterList[6].state as List>)[2]) - ) - ) - ) + FilterObject("CheckBox", (source.mFilterList[6].state as List>)[2]), + ), + ), + ), ) assertEquals( FilterObject("Sort", source.mFilterList[7]), - filterList[7] + filterList[7], ) // make sure that we can convert this to json @@ -182,24 +193,24 @@ class FilterListTest : ApplicationTest() { setFilter( source.id, - FilterChange(0, "change!") + FilterChange(0, "change!"), ) setFilter( source.id, - FilterChange(1, "change!") + FilterChange(1, "change!"), ) val filterList = getFilterList(source.id, false) assertEquals( filterList[0].filter.state, - 0 + 0, ) assertEquals( filterList[1].filter.state, - 0 + 0, ) } @@ -209,14 +220,14 @@ class FilterListTest : ApplicationTest() { setFilter( source.id, - FilterChange(2, "1") + FilterChange(2, "1"), ) val filterList = getFilterList(source.id, false) assertEquals( filterList[2].filter.state, - 1 + 1, ) } @@ -226,14 +237,14 @@ class FilterListTest : ApplicationTest() { setFilter( source.id, - FilterChange(3, "I'm a changed man!") + FilterChange(3, "I'm a changed man!"), ) val filterList = getFilterList(source.id, false) assertEquals( filterList[3].filter.state, - "I'm a changed man!" + "I'm a changed man!", ) } @@ -243,14 +254,14 @@ class FilterListTest : ApplicationTest() { setFilter( source.id, - FilterChange(4, "true") + FilterChange(4, "true"), ) val filterList = getFilterList(source.id, false) assertEquals( filterList[4].filter.state, - true + true, ) } @@ -260,14 +271,14 @@ class FilterListTest : ApplicationTest() { setFilter( source.id, - FilterChange(5, "1") + FilterChange(5, "1"), ) val filterList = getFilterList(source.id, false) assertEquals( filterList[5].filter.state, - Filter.TriState.STATE_INCLUDE + Filter.TriState.STATE_INCLUDE, ) } @@ -277,14 +288,14 @@ class FilterListTest : ApplicationTest() { setFilter( source.id, - FilterChange(6, """{"position":0,"state":"true"}""") + FilterChange(6, """{"position":0,"state":"true"}"""), ) val filterList = getFilterList(source.id, false) assertEquals( (filterList[6].filter.state as List)[0].filter.state, - true + true, ) } @@ -294,14 +305,14 @@ class FilterListTest : ApplicationTest() { setFilter( source.id, - FilterChange(7, """{"index":1,"ascending":"true"}""") + FilterChange(7, """{"index":1,"ascending":"true"}"""), ) val filterList = getFilterList(source.id, false) assertEquals( filterList[7].filter.state, - Filter.Sort.Selection(1, true) + Filter.Sort.Selection(1, true), ) } diff --git a/server/src/test/kotlin/suwayomi/tachidesk/manga/model/PaginatedListTest.kt b/server/src/test/kotlin/suwayomi/tachidesk/manga/model/PaginatedListTest.kt index c6b6dd6f6..120fb24e7 100644 --- a/server/src/test/kotlin/suwayomi/tachidesk/manga/model/PaginatedListTest.kt +++ b/server/src/test/kotlin/suwayomi/tachidesk/manga/model/PaginatedListTest.kt @@ -9,8 +9,8 @@ package suwayomi.tachidesk.manga.model import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test +import suwayomi.tachidesk.manga.model.dataclass.PAGINATION_FACTOR import suwayomi.tachidesk.manga.model.dataclass.PaginatedList -import suwayomi.tachidesk.manga.model.dataclass.PaginationFactor import suwayomi.tachidesk.manga.model.dataclass.paginatedFrom import suwayomi.tachidesk.test.ApplicationTest @@ -21,85 +21,88 @@ class PaginatedListTest : ApplicationTest() { assertEquals( PaginatedList(emptyList(), false), - paginated + paginated, ) } @Test fun `size smaller than PaginationFactor`() { - val paginated = paginatedFrom(0) { listIndicesOf(0, PaginationFactor - 1) } + val paginated = paginatedFrom(0) { listIndicesOf(0, PAGINATION_FACTOR - 1) } assertEquals( - PaginatedList(listIndicesOf(0, PaginationFactor - 1), false), - paginated + PaginatedList(listIndicesOf(0, PAGINATION_FACTOR - 1), false), + paginated, ) } @Test fun `one less than two times PaginationFactor`() { - val masterLister = { listIndicesOf(0, PaginationFactor * 2 - 1) } + val masterLister = { listIndicesOf(0, PAGINATION_FACTOR * 2 - 1) } val firstPage = paginatedFrom(0, lister = masterLister) assertEquals( - PaginatedList(listIndicesOf(0, PaginationFactor), true), - firstPage + PaginatedList(listIndicesOf(0, PAGINATION_FACTOR), true), + firstPage, ) val secondPage = paginatedFrom(1, lister = masterLister) assertEquals( - PaginatedList(listIndicesOf(PaginationFactor, PaginationFactor * 2 - 1), false), - secondPage + PaginatedList(listIndicesOf(PAGINATION_FACTOR, PAGINATION_FACTOR * 2 - 1), false), + secondPage, ) } @Test fun `two times PaginationFactor`() { - val masterLister = { listIndicesOf(0, PaginationFactor * 2) } + val masterLister = { listIndicesOf(0, PAGINATION_FACTOR * 2) } val firstPage = paginatedFrom(0, lister = masterLister) assertEquals( - PaginatedList(listIndicesOf(0, PaginationFactor), true), - firstPage + PaginatedList(listIndicesOf(0, PAGINATION_FACTOR), true), + firstPage, ) val secondPage = paginatedFrom(1, lister = masterLister) assertEquals( - PaginatedList(listIndicesOf(PaginationFactor, PaginationFactor * 2), false), - secondPage + PaginatedList(listIndicesOf(PAGINATION_FACTOR, PAGINATION_FACTOR * 2), false), + secondPage, ) } @Test fun `one more than two times PaginationFactor`() { - val masterLister = { listIndicesOf(0, PaginationFactor * 2 + 1) } + val masterLister = { listIndicesOf(0, PAGINATION_FACTOR * 2 + 1) } val firstPage = paginatedFrom(0, lister = masterLister) assertEquals( - PaginatedList(listIndicesOf(0, PaginationFactor), true), - firstPage + PaginatedList(listIndicesOf(0, PAGINATION_FACTOR), true), + firstPage, ) val secondPage = paginatedFrom(1, lister = masterLister) assertEquals( - PaginatedList(listIndicesOf(PaginationFactor, PaginationFactor * 2), true), - secondPage + PaginatedList(listIndicesOf(PAGINATION_FACTOR, PAGINATION_FACTOR * 2), true), + secondPage, ) val thirdPage = paginatedFrom(2, lister = masterLister) assertEquals( - PaginatedList(listIndicesOf(PaginationFactor * 2, PaginationFactor * 2 + 1), false), - thirdPage + PaginatedList(listIndicesOf(PAGINATION_FACTOR * 2, PAGINATION_FACTOR * 2 + 1), false), + thirdPage, ) } - private fun listIndicesOf(first: Int, last: Int): List { + private fun listIndicesOf( + first: Int, + last: Int, + ): List { return (first until last).toList() } } diff --git a/server/src/test/kotlin/suwayomi/tachidesk/test/ApplicationTest.kt b/server/src/test/kotlin/suwayomi/tachidesk/test/ApplicationTest.kt index cde63aa2a..dc474c688 100644 --- a/server/src/test/kotlin/suwayomi/tachidesk/test/ApplicationTest.kt +++ b/server/src/test/kotlin/suwayomi/tachidesk/test/ApplicationTest.kt @@ -64,7 +64,7 @@ open class ApplicationTest { bind() with singleton { applicationDirs } bind() with singleton { JavalinJackson() } bind() with singleton { TestUpdater() } - } + }, ) logger.debug("Data Root directory is set to: ${applicationDirs.dataRoot}") @@ -76,14 +76,14 @@ open class ApplicationTest { applicationDirs.extensionsRoot + "/icon", applicationDirs.tempThumbnailCacheRoot, applicationDirs.downloadsRoot, - applicationDirs.localMangaRoot + applicationDirs.localMangaRoot, ).forEach { File(it).mkdirs() } // register Tachidesk's config which is dubbed "ServerConfig" GlobalConfigManager.registerModule( - ServerConfig.register { GlobalConfigManager.config } + ServerConfig.register { GlobalConfigManager.config }, ) // Make sure only one instance of the app is running @@ -128,7 +128,8 @@ open class ApplicationTest { if (serverConfig.systemTrayEnabled.value) { try { SystemTray.create() - } catch (e: Throwable) { // cover both java.lang.Exception and java.lang.Error + } catch (e: Throwable) { + // cover both java.lang.Exception and java.lang.Error e.printStackTrace() } } diff --git a/server/src/test/kotlin/suwayomi/tachidesk/test/TestUtils.kt b/server/src/test/kotlin/suwayomi/tachidesk/test/TestUtils.kt index ec912bfab..bc4554a44 100644 --- a/server/src/test/kotlin/suwayomi/tachidesk/test/TestUtils.kt +++ b/server/src/test/kotlin/suwayomi/tachidesk/test/TestUtils.kt @@ -21,18 +21,17 @@ import suwayomi.tachidesk.manga.model.table.MangaTable fun setLoggingEnabled(enabled: Boolean = true) { val logger = (KotlinLogging.logger(Logger.ROOT_LOGGER_NAME).underlyingLogger as ch.qos.logback.classic.Logger) - logger.level = if (enabled) { - Level.DEBUG - } else { - Level.ERROR - } + logger.level = + if (enabled) { + Level.DEBUG + } else { + Level.ERROR + } } const val BASE_PATH = "build/tmp/TestDesk" -fun createLibraryManga( - _title: String -): Int { +fun createLibraryManga(_title: String): Int { return transaction { MangaTable.insertAndGetId { it[title] = _title @@ -43,9 +42,7 @@ fun createLibraryManga( } } -fun createSMangas( - count: Int -): List { +fun createSMangas(count: Int): List { return (0 until count).map { SManga.create().apply { title = "Manga $it" @@ -57,7 +54,7 @@ fun createSMangas( fun createChapters( mangaId: Int, amount: Int, - read: Boolean + read: Boolean, ) { val list = listOf((0 until amount)).flatten().map { 1 } transaction { diff --git a/settings.gradle.kts b/settings.gradle.kts index d839d3b71..f33b86e72 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -5,4 +5,4 @@ include("server") include("AndroidCompat") include("AndroidCompat:Config") -enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") \ No newline at end of file +enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")