From 230427e75851148b75ba9eca91019eb24242640b Mon Sep 17 00:00:00 2001 From: Mitchell Syer Date: Fri, 5 Jan 2024 19:14:09 -0500 Subject: [PATCH] Support Custom Repos (#803) * Support custom repos * Fix migration * Make extension after update optional --- .../xyz/nulldev/ts/config/ConfigModule.kt | 12 +++++-- .../graphql/mutations/ExtensionMutation.kt | 5 +-- .../graphql/mutations/SettingsMutation.kt | 3 ++ .../graphql/queries/ExtensionQuery.kt | 4 +++ .../tachidesk/graphql/types/ExtensionType.kt | 2 ++ .../tachidesk/graphql/types/MangaType.kt | 4 +++ .../tachidesk/graphql/types/SettingsType.kt | 9 +++++ .../suwayomi/tachidesk/manga/impl/Chapter.kt | 2 +- .../manga/impl/extension/ExtensionsList.kt | 18 ++++++++-- .../extension/github/ExtensionGithubApi.kt | 35 ++++++++++++++----- .../impl/extension/github/OnlineExtension.kt | 1 + .../tachidesk/manga/impl/util/PackageTools.kt | 3 +- .../model/dataclass/ExtensionDataClass.kt | 1 + .../manga/model/table/ExtensionTable.kt | 1 + .../tachidesk/manga/model/table/MangaTable.kt | 2 +- .../suwayomi/tachidesk/server/ServerConfig.kt | 26 +++++++++++--- .../migration/M0031_AddExtensionRepo.kt | 18 ++++++++++ .../migration/M0032_FixExtensionRepos.kt | 20 +++++++++++ .../src/main/resources/server-reference.conf | 5 +++ 19 files changed, 149 insertions(+), 22 deletions(-) create mode 100644 server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0031_AddExtensionRepo.kt create mode 100644 server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0032_FixExtensionRepos.kt 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 5b5507131..61d82277f 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 @@ -8,6 +8,8 @@ package xyz.nulldev.ts.config * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import com.typesafe.config.Config +import com.typesafe.config.ConfigFactory +import com.typesafe.config.ConfigValueFactory import io.github.config4k.getValue import kotlin.reflect.KProperty @@ -30,18 +32,24 @@ class SystemPropertyOverrideDelegate(val getConfig: () -> Config, val moduleName thisRef: R, property: KProperty<*>, ): T { - val configValue: T = getConfig().getValue(thisRef, property) + val config = getConfig() + val configValue: T = config.getValue(thisRef, property) val combined = System.getProperty( "$CONFIG_PREFIX.$moduleName.${property.name}", - configValue.toString(), + if (T::class.simpleName == "List") { + ConfigValueFactory.fromAnyRef(configValue).render() + } else { + configValue.toString() + }, ) return when (T::class.simpleName) { "Int" -> combined.toInt() "Boolean" -> combined.toBoolean() "Double" -> combined.toDouble() + "List" -> ConfigFactory.parseString("internal=" + combined).getStringList("internal").orEmpty() // add more types as needed else -> combined // covers String } as T 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 15c874712..23520a3a9 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/ExtensionMutation.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/ExtensionMutation.kt @@ -20,7 +20,7 @@ class ExtensionMutation { data class UpdateExtensionPayload( val clientMutationId: String?, - val extension: ExtensionType, + val extension: ExtensionType?, ) data class UpdateExtensionInput( @@ -77,7 +77,8 @@ class ExtensionMutation { }.thenApply { val extension = transaction { - ExtensionType(ExtensionTable.select { ExtensionTable.pkgName eq id }.first()) + ExtensionTable.select { ExtensionTable.pkgName eq id }.firstOrNull() + ?.let { ExtensionType(it) } } UpdateExtensionPayload( 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 e56d45b63..9ee358e4a 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/SettingsMutation.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/mutations/SettingsMutation.kt @@ -55,6 +55,9 @@ class SettingsMutation { updateSetting(settings.excludeEntryWithUnreadChapters, serverConfig.excludeEntryWithUnreadChapters) updateSetting(settings.autoDownloadAheadLimit, serverConfig.autoDownloadAheadLimit) + // extension + updateSetting(settings.extensionRepos, serverConfig.extensionRepos) + // requests updateSetting(settings.maxSourcesInParallel, serverConfig.maxSourcesInParallel) 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 9380ca800..92e7ffc31 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/ExtensionQuery.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/queries/ExtensionQuery.kt @@ -82,6 +82,7 @@ class ExtensionQuery { } data class ExtensionCondition( + val repo: String? = null, val apkName: String? = null, val iconUrl: String? = null, val name: String? = null, @@ -96,6 +97,7 @@ class ExtensionQuery { ) : HasGetOp { override fun getOp(): Op? { val opAnd = OpAnd() + opAnd.eq(repo, ExtensionTable.repo) opAnd.eq(apkName, ExtensionTable.apkName) opAnd.eq(iconUrl, ExtensionTable.iconUrl) opAnd.eq(name, ExtensionTable.name) @@ -112,6 +114,7 @@ class ExtensionQuery { } data class ExtensionFilter( + val repo: StringFilter? = null, val apkName: StringFilter? = null, val iconUrl: StringFilter? = null, val name: StringFilter? = null, @@ -129,6 +132,7 @@ class ExtensionQuery { ) : Filter { override fun getOpList(): List> { return listOfNotNull( + andFilterWithCompareString(ExtensionTable.repo, repo), andFilterWithCompareString(ExtensionTable.apkName, apkName), andFilterWithCompareString(ExtensionTable.iconUrl, iconUrl), andFilterWithCompareString(ExtensionTable.name, name), 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 4b0d350ca..1962eb459 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/ExtensionType.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/ExtensionType.kt @@ -20,6 +20,7 @@ import suwayomi.tachidesk.manga.model.table.ExtensionTable import java.util.concurrent.CompletableFuture class ExtensionType( + val repo: String?, val apkName: String, val iconUrl: String, val name: String, @@ -33,6 +34,7 @@ class ExtensionType( val isObsolete: Boolean, ) : Node { constructor(row: ResultRow) : this( + repo = row[ExtensionTable.repo], apkName = row[ExtensionTable.apkName], iconUrl = Extension.getExtensionIconUrl(row[ExtensionTable.apkName]), name = row[ExtensionTable.name], 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 130c96f1c..dccd1ba76 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/MangaType.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/MangaType.kt @@ -8,6 +8,7 @@ package suwayomi.tachidesk.graphql.types import com.expediagroup.graphql.server.extensions.getValueFromDataLoader +import eu.kanade.tachiyomi.source.model.UpdateStrategy import graphql.schema.DataFetchingEnvironment import org.jetbrains.exposed.sql.ResultRow import suwayomi.tachidesk.graphql.server.primitives.Cursor @@ -37,6 +38,7 @@ class MangaType( val status: MangaStatus, val inLibrary: Boolean, val inLibraryAt: Long, + val updateStrategy: UpdateStrategy, val realUrl: String?, var lastFetchedAt: Long?, // todo var chaptersLastFetchedAt: Long?, // todo @@ -73,6 +75,7 @@ class MangaType( MangaStatus.valueOf(row[MangaTable.status]), row[MangaTable.inLibrary], row[MangaTable.inLibraryAt], + UpdateStrategy.valueOf(row[MangaTable.updateStrategy]), row[MangaTable.realUrl], row[MangaTable.lastFetchedAt], row[MangaTable.chaptersLastFetchedAt], @@ -92,6 +95,7 @@ class MangaType( MangaStatus.valueOf(dataClass.status), dataClass.inLibrary, dataClass.inLibraryAt, + dataClass.updateStrategy, dataClass.realUrl, dataClass.lastFetchedAt, dataClass.chaptersLastFetchedAt, 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 c848a1c09..32c526bc0 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/SettingsType.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/SettingsType.kt @@ -40,6 +40,9 @@ interface Settings : Node { val excludeEntryWithUnreadChapters: Boolean? val autoDownloadAheadLimit: Int? + // extension + val extensionRepos: List? + // requests val maxSourcesInParallel: Int? @@ -90,6 +93,8 @@ data class PartialSettingsType( override val autoDownloadNewChapters: Boolean?, override val excludeEntryWithUnreadChapters: Boolean?, override val autoDownloadAheadLimit: Int?, + // extension + override val extensionRepos: List?, // requests override val maxSourcesInParallel: Int?, // updater @@ -135,6 +140,8 @@ class SettingsType( override val autoDownloadNewChapters: Boolean, override val excludeEntryWithUnreadChapters: Boolean, override val autoDownloadAheadLimit: Int, + // extension + override val extensionRepos: List, // requests override val maxSourcesInParallel: Int, // updater @@ -179,6 +186,8 @@ class SettingsType( config.autoDownloadNewChapters.value, config.excludeEntryWithUnreadChapters.value, config.autoDownloadAheadLimit.value, + // extension + config.extensionRepos.value, // requests config.maxSourcesInParallel.value, // updater 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 3ee74d8b5..8c5343446 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Chapter.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Chapter.kt @@ -143,7 +143,7 @@ object Chapter { val chapterNumber = ChapterRecognition.parseChapterNumber(manga.title, chapter.name, chapter.chapter_number.toDouble()) chapter.chapter_number = chapterNumber.toFloat() chapter.name = chapter.name.sanitize(manga.title) - chapter.scanlator = chapter.scanlator?.ifBlank { null } + chapter.scanlator = chapter.scanlator?.ifBlank { null }?.trim() } val now = Instant.now().epochSecond 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 d392075f8..14fe11158 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 @@ -23,6 +23,7 @@ import suwayomi.tachidesk.manga.impl.extension.github.ExtensionGithubApi import suwayomi.tachidesk.manga.impl.extension.github.OnlineExtension import suwayomi.tachidesk.manga.model.dataclass.ExtensionDataClass import suwayomi.tachidesk.manga.model.table.ExtensionTable +import suwayomi.tachidesk.server.serverConfig import java.util.concurrent.ConcurrentHashMap import kotlin.time.Duration.Companion.seconds @@ -38,7 +39,17 @@ object ExtensionsList { logger.debug("Getting extensions list from the internet") lastUpdateCheck = System.currentTimeMillis() - val foundExtensions = ExtensionGithubApi.findExtensions() + val extensions = + (listOf(ExtensionGithubApi.REPO_URL_PREFIX) + serverConfig.extensionRepos.value).map { repo -> + kotlin.runCatching { + ExtensionGithubApi.findExtensions(repo) + }.onFailure { + logger.warn(it) { + "Failed to fetch extensions for repo: $repo" + } + } + } + val foundExtensions = extensions.mapNotNull { it.getOrNull() }.flatten() updateExtensionDatabase(foundExtensions) } else { logger.debug("used cached extension list") @@ -54,6 +65,7 @@ object ExtensionsList { transaction { ExtensionTable.selectAll().filter { it[ExtensionTable.name] != LocalSource.EXTENSION_NAME }.map { ExtensionDataClass( + it[ExtensionTable.repo], it[ExtensionTable.apkName], getExtensionIconUrl(it[ExtensionTable.apkName]), it[ExtensionTable.name], @@ -77,7 +89,7 @@ object ExtensionsList { val extensionsToUpdate = mutableListOf>() val extensionsToInsert = mutableListOf() val extensionsToDelete = - installedExtensions.mapNotNull { (pkgName, extension) -> + installedExtensions.filter { it.value[ExtensionTable.repo] != null }.mapNotNull { (pkgName, extension) -> extension.takeUnless { foundExtensions.any { it.pkgName == pkgName } } } foundExtensions.forEach { @@ -124,6 +136,7 @@ object ExtensionsList { extensionsToFullyUpdate.forEach { (foundExtension, extensionRecord) -> addBatch(EntityID(extensionRecord[ExtensionTable.id].value, ExtensionTable)) // extension is not installed, so we can overwrite the data without a care + this[ExtensionTable.repo] = foundExtension.repo this[ExtensionTable.name] = foundExtension.name this[ExtensionTable.versionName] = foundExtension.versionName this[ExtensionTable.versionCode] = foundExtension.versionCode @@ -138,6 +151,7 @@ object ExtensionsList { } if (extensionsToInsert.isNotEmpty()) { ExtensionTable.batchInsert(extensionsToInsert) { foundExtension -> + this[ExtensionTable.repo] = foundExtension.repo this[ExtensionTable.name] = foundExtension.name this[ExtensionTable.pkgName] = foundExtension.pkgName this[ExtensionTable.versionName] = foundExtension.versionName 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 e0ac99f8b..bf23b11b9 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 @@ -20,7 +20,7 @@ import suwayomi.tachidesk.manga.model.dataclass.ExtensionDataClass import uy.kohesive.injekt.injectLazy object ExtensionGithubApi { - private const val REPO_URL_PREFIX = "https://raw.githubusercontent.com/tachiyomiorg/tachiyomi-extensions/repo/" + const val REPO_URL_PREFIX = "https://raw.githubusercontent.com/tachiyomiorg/tachiyomi-extensions/repo/" private const val FALLBACK_REPO_URL_PREFIX = "https://gcore.jsdelivr.net/gh/tachiyomiorg/tachiyomi-extensions@repo/" private val logger = KotlinLogging.logger {} private val json: Json by injectLazy() @@ -49,13 +49,13 @@ object ExtensionGithubApi { private var requiresFallbackSource = false - suspend fun findExtensions(): List { + suspend fun findExtensions(repo: String): List { val githubResponse = if (requiresFallbackSource) { null } else { try { - client.newCall(GET("${REPO_URL_PREFIX}index.min.json")).awaitSuccess() + client.newCall(GET("${repo.repoUrlReplace()}index.min.json")).awaitSuccess() } catch (e: Throwable) { logger.error(e) { "Failed to get extensions from GitHub" } requiresFallbackSource = true @@ -65,18 +65,18 @@ object ExtensionGithubApi { val response = githubResponse ?: run { - client.newCall(GET("${FALLBACK_REPO_URL_PREFIX}index.min.json")).awaitSuccess() + client.newCall(GET("${repo.fallbackRepoUrlReplace()}index.min.json")).awaitSuccess() } return with(json) { response .parseAs>() - .toExtensions() + .toExtensions(repo.repoUrlReplace()) } } fun getApkUrl(extension: ExtensionDataClass): String { - return "$REPO_URL_PREFIX/apk/${extension.apkName}" + return "${extension.repo!!.repoUrlReplace()}/apk/${extension.apkName}" } private val client by lazy { @@ -91,7 +91,7 @@ object ExtensionGithubApi { .build() } - private fun List.toExtensions(): List { + private fun List.toExtensions(repo: String): List { return this .filter { val libVersion = it.version.substringBeforeLast('.').toDouble() @@ -99,6 +99,7 @@ object ExtensionGithubApi { } .map { OnlineExtension( + repo = repo, name = it.name.substringAfter("Tachiyomi: "), pkgName = it.pkg, versionName = it.version, @@ -109,7 +110,7 @@ object ExtensionGithubApi { hasChangelog = it.hasChangelog == 1, sources = it.sources?.toExtensionSources() ?: emptyList(), apkName = it.apk, - iconUrl = "${REPO_URL_PREFIX}icon/${it.pkg}.png", + iconUrl = "${repo}icon/${it.pkg}.png", ) } } @@ -124,4 +125,22 @@ object ExtensionGithubApi { ) } } + + private fun String.repoUrlReplace() = + replace(repoMatchRegex) { + "https://raw.githubusercontent.com/${it.groupValues[1]}/${it.groupValues[2]}/" + + "${it.groupValues.getOrNull(3)?.ifBlank { null } ?: "repo"}/" + } + + private fun String.fallbackRepoUrlReplace() = + replace(repoMatchRegex) { + "https://gcore.jsdelivr.net/gh/${it.groupValues[1]}/${it.groupValues[2]}@" + + "${it.groupValues.getOrNull(3)?.ifBlank { null } ?: "repo"}/" + } + + private val repoMatchRegex = + ( + "https:\\/\\/(?:www|raw)?(?:github|githubusercontent)\\.com" + + "\\/([^\\/]+)\\/([^\\/]+)(?:\\/(?:tree|blob)\\/(.*))?\\/?" + ).toRegex() } 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 e2380beca..8a399b647 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 @@ -15,6 +15,7 @@ data class OnlineExtensionSource( ) data class OnlineExtension( + val repo: String, val name: String, val pkgName: String, val apkName: String, 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 e7c9d44ed..4b446650d 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 @@ -44,8 +44,7 @@ object PackageTools { const val LIB_VERSION_MAX = 1.5 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) + val trustedSignatures = setOf(OFFICIAL_SIGNATURE) /** * Convert dex to jar, a wrapper for the dex2jar library 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 5ceb1f4d3..cb6218fef 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 @@ -8,6 +8,7 @@ package suwayomi.tachidesk.manga.model.dataclass * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ data class ExtensionDataClass( + val repo: String?, val apkName: String, val iconUrl: String, val name: String, 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 0ff53079c..9aa02d1e0 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 @@ -11,6 +11,7 @@ import org.jetbrains.exposed.dao.id.IntIdTable object ExtensionTable : IntIdTable() { val apkName = varchar("apk_name", 1024) + val repo = varchar("repo", 1024).nullable() // default is the local source icon from tachiyomi @Suppress("ktlint:standard:max-line-length") 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 17d89c889..9f7472280 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 @@ -80,6 +80,6 @@ enum class MangaStatus(val value: Int) { ; companion object { - fun valueOf(value: Int): MangaStatus = values().find { it.value == value } ?: UNKNOWN + fun valueOf(value: Int): MangaStatus = entries.find { it.value == value } ?: UNKNOWN } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/ServerConfig.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/ServerConfig.kt index f1ae3d0ac..227721fad 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/server/ServerConfig.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/ServerConfig.kt @@ -35,9 +35,14 @@ class ServerConfig(getConfig: () -> Config, val moduleName: String = SERVER_CONF getConfig, moduleName, ) { - inner class OverrideConfigValue(private val configAdapter: ConfigAdapter) { + open inner class OverrideConfigValue(private val configAdapter: ConfigAdapter) { private var flow: MutableStateFlow? = null + open fun getValueFromConfig( + thisRef: ServerConfig, + property: KProperty<*>, + ): Any = configAdapter.toType(overridableConfig.getValue(thisRef, property)) + operator fun getValue( thisRef: ServerConfig, property: KProperty<*>, @@ -46,13 +51,13 @@ class ServerConfig(getConfig: () -> Config, val moduleName: String = SERVER_CONF return flow!! } - val getValueFromConfig = { configAdapter.toType(overridableConfig.getValue(thisRef, property)) } - val value = getValueFromConfig() + @Suppress("UNCHECKED_CAST") + val value = getValueFromConfig(thisRef, property) as T val stateFlow = MutableStateFlow(value) flow = stateFlow - stateFlow.drop(1).distinctUntilChanged().filter { it != getValueFromConfig() } + stateFlow.drop(1).distinctUntilChanged().filter { it != getValueFromConfig(thisRef, property) } .onEach { GlobalConfigManager.updateValue("$moduleName.${property.name}", it as Any) } .launchIn(mutableConfigValueScope) @@ -60,6 +65,16 @@ class ServerConfig(getConfig: () -> Config, val moduleName: String = SERVER_CONF } } + inner class OverrideConfigValues(private val configAdapter: ConfigAdapter) : OverrideConfigValue(configAdapter) { + override fun getValueFromConfig( + thisRef: ServerConfig, + property: KProperty<*>, + ): Any { + return overridableConfig.getValue>(thisRef, property) + .map { configAdapter.toType(it) } + } + } + val ip: MutableStateFlow by OverrideConfigValue(StringConfigAdapter) val port: MutableStateFlow by OverrideConfigValue(IntConfigAdapter) @@ -84,6 +99,9 @@ class ServerConfig(getConfig: () -> Config, val moduleName: String = SERVER_CONF val excludeEntryWithUnreadChapters: MutableStateFlow by OverrideConfigValue(BooleanConfigAdapter) val autoDownloadAheadLimit: MutableStateFlow by OverrideConfigValue(IntConfigAdapter) + // extensions + val extensionRepos: MutableStateFlow> by OverrideConfigValues(StringConfigAdapter) + // requests val maxSourcesInParallel: MutableStateFlow by OverrideConfigValue(IntConfigAdapter) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0031_AddExtensionRepo.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0031_AddExtensionRepo.kt new file mode 100644 index 000000000..494e9f88e --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0031_AddExtensionRepo.kt @@ -0,0 +1,18 @@ +package suwayomi.tachidesk.server.database.migration + +/* + * Copyright (C) Contributors to the Suwayomi project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +import de.neonew.exposed.migrations.helpers.AddColumnMigration + +@Suppress("ClassName", "unused") +class M0031_AddExtensionRepo : AddColumnMigration( + "Extension", + "repo", + "VARCHAR(1024)", + "NULL", +) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0032_FixExtensionRepos.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0032_FixExtensionRepos.kt new file mode 100644 index 000000000..cb3f46236 --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0032_FixExtensionRepos.kt @@ -0,0 +1,20 @@ +package suwayomi.tachidesk.server.database.migration + +/* + * Copyright (C) Contributors to the Suwayomi project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +import de.neonew.exposed.migrations.helpers.SQLMigration + +@Suppress("ClassName", "unused") +class M0032_FixExtensionRepos : SQLMigration() { + // language=h2 + override val sql = + """ + UPDATE EXTENSION + SET REPO = 'https://raw.githubusercontent.com/tachiyomiorg/tachiyomi-extensions/repo/'; + """.trimIndent() +} diff --git a/server/src/main/resources/server-reference.conf b/server/src/main/resources/server-reference.conf index d13b0b6a2..70f38a3db 100644 --- a/server/src/main/resources/server-reference.conf +++ b/server/src/main/resources/server-reference.conf @@ -23,6 +23,11 @@ server.autoDownloadNewChapters = false # if new chapters that have been retrieve server.excludeEntryWithUnreadChapters = true # ignore automatic chapter downloads of entries with unread chapters server.autoDownloadAheadLimit = 0 # 0 to disable it - how many unread downloaded chapters should be available - if the limit is reached, new chapters won't be downloaded automatically. this limit will also be applied to the auto download of new chapters on an update +# extension repos +server.extensionRepos = [ + # an example: https://github.com/MY_ACCOUNT/MY_REPO/tree/repo +] + # requests server.maxSourcesInParallel = 6 # range: 1 <= n <= 20 - default: 6 - sets how many sources can do requests (updates, downloads) in parallel. updates/downloads are grouped by source and all mangas of a source are updated/downloaded synchronously