Feature/streamline settings (#1614)

* Cleanup graphql setting mutation

* Validate values read from config

* Generate server-reference.conf files from ServerConfig

* Remove unnecessary enum value handling in config value update

Commit df0078b725 introduced the usage of config4k, which handles enums automatically. Thus, this handling is outdated and not needed anymore

* Generate gql SettingsType from ServerConfig

* Extract settings backup logic

* Generate settings backup files

* Move "group" arg to second position

To make it easier to detect and have it at the same position consistently for all settings.

* Remove setting generation from compilation

* Extract setting generation code into new module

* Extract pure setting generation code into new module

* Remove generated settings files from src tree

* Force each setting to set a default value
This commit is contained in:
schroda
2025-09-01 23:02:58 +02:00
committed by GitHub
parent 11b2a6b616
commit 8ef2877040
48 changed files with 2443 additions and 1330 deletions

View File

@@ -31,13 +31,12 @@ import suwayomi.tachidesk.manga.impl.Chapter
import suwayomi.tachidesk.manga.impl.Manga
import suwayomi.tachidesk.manga.impl.Source
import suwayomi.tachidesk.manga.impl.backup.BackupFlags
import suwayomi.tachidesk.manga.impl.backup.proto.handlers.BackupSettingsHandler
import suwayomi.tachidesk.manga.impl.backup.proto.models.Backup
import suwayomi.tachidesk.manga.impl.backup.proto.models.BackupCategory
import suwayomi.tachidesk.manga.impl.backup.proto.models.BackupChapter
import suwayomi.tachidesk.manga.impl.backup.proto.models.BackupHistory
import suwayomi.tachidesk.manga.impl.backup.proto.models.BackupManga
import suwayomi.tachidesk.manga.impl.backup.proto.models.BackupServerSettings
import suwayomi.tachidesk.manga.impl.backup.proto.models.BackupServerSettings.BackupSettingsDownloadConversionType
import suwayomi.tachidesk.manga.impl.backup.proto.models.BackupSource
import suwayomi.tachidesk.manga.impl.backup.proto.models.BackupTracking
import suwayomi.tachidesk.manga.impl.track.Track
@@ -97,15 +96,11 @@ object ProtoBackupExport : ProtoBackupBase() {
}
}
val (hour, minute) =
val (backupHour, backupMinute) =
serverConfig.backupTime.value
.split(":")
.map { it.toInt() }
val backupHour = hour.coerceAtLeast(0).coerceAtMost(23)
val backupMinute = minute.coerceAtLeast(0).coerceAtMost(59)
val backupInterval =
serverConfig.backupInterval.value.days
.coerceAtLeast(1.days)
val backupInterval = serverConfig.backupInterval.value.days
// trigger last backup in case the server wasn't running on the scheduled time
val lastAutomatedBackup = preferences.getLong(LAST_AUTOMATED_BACKUP_KEY, 0)
@@ -193,7 +188,7 @@ object ProtoBackupExport : ProtoBackupBase() {
backupCategories(flags),
backupExtensionInfo(databaseManga, flags),
backupGlobalMeta(flags),
backupServerSettings(flags),
BackupSettingsHandler.backup(flags),
)
}
@@ -378,102 +373,4 @@ object ProtoBackupExport : ProtoBackupBase() {
return GlobalMeta.getMetaMap()
}
private fun backupServerSettings(flags: BackupFlags): BackupServerSettings? {
if (!flags.includeServerSettings) {
return null
}
return BackupServerSettings(
ip = serverConfig.ip.value,
port = serverConfig.port.value,
// socks
socksProxyEnabled = serverConfig.socksProxyEnabled.value,
socksProxyVersion = serverConfig.socksProxyVersion.value,
socksProxyHost = serverConfig.socksProxyHost.value,
socksProxyPort = serverConfig.socksProxyPort.value,
socksProxyUsername = serverConfig.socksProxyUsername.value,
socksProxyPassword = serverConfig.socksProxyPassword.value,
// webUI
webUIFlavor = serverConfig.webUIFlavor.value,
initialOpenInBrowserEnabled = serverConfig.initialOpenInBrowserEnabled.value,
webUIInterface = serverConfig.webUIInterface.value,
electronPath = serverConfig.electronPath.value,
webUIChannel = serverConfig.webUIChannel.value,
webUIUpdateCheckInterval = serverConfig.webUIUpdateCheckInterval.value,
// downloader
downloadAsCbz = serverConfig.downloadAsCbz.value,
downloadsPath = serverConfig.downloadsPath.value,
autoDownloadNewChapters = serverConfig.autoDownloadNewChapters.value,
excludeEntryWithUnreadChapters = serverConfig.excludeEntryWithUnreadChapters.value,
autoDownloadAheadLimit = 0, // deprecated
autoDownloadNewChaptersLimit = serverConfig.autoDownloadNewChaptersLimit.value,
autoDownloadIgnoreReUploads = serverConfig.autoDownloadIgnoreReUploads.value,
downloadConversions =
serverConfig.downloadConversions.value.map {
BackupSettingsDownloadConversionType(
it.key,
it.value.target,
it.value.compressionLevel,
)
},
// extension
extensionRepos = serverConfig.extensionRepos.value,
// requests
maxSourcesInParallel = serverConfig.maxSourcesInParallel.value,
// updater
excludeUnreadChapters = serverConfig.excludeUnreadChapters.value,
excludeNotStarted = serverConfig.excludeNotStarted.value,
excludeCompleted = serverConfig.excludeCompleted.value,
globalUpdateInterval = serverConfig.globalUpdateInterval.value,
updateMangas = serverConfig.updateMangas.value,
// Authentication
authMode = serverConfig.authMode.value,
jwtAudience = serverConfig.jwtAudience.value,
jwtTokenExpiry = serverConfig.jwtTokenExpiry.value,
jwtRefreshExpiry = serverConfig.jwtRefreshExpiry.value,
authUsername = serverConfig.authUsername.value,
authPassword = serverConfig.authPassword.value,
basicAuthEnabled = false,
basicAuthUsername = null,
basicAuthPassword = null,
// misc
debugLogsEnabled = serverConfig.debugLogsEnabled.value,
gqlDebugLogsEnabled = false, // deprecated
systemTrayEnabled = serverConfig.systemTrayEnabled.value,
maxLogFiles = serverConfig.maxLogFiles.value,
maxLogFileSize = serverConfig.maxLogFileSize.value,
maxLogFolderSize = serverConfig.maxLogFolderSize.value,
// backup
backupPath = serverConfig.backupPath.value,
backupTime = serverConfig.backupTime.value,
backupInterval = serverConfig.backupInterval.value,
backupTTL = serverConfig.backupTTL.value,
// local source
localSourcePath = serverConfig.localSourcePath.value,
// cloudflare bypass
flareSolverrEnabled = serverConfig.flareSolverrEnabled.value,
flareSolverrUrl = serverConfig.flareSolverrUrl.value,
flareSolverrTimeout = serverConfig.flareSolverrTimeout.value,
flareSolverrSessionName = serverConfig.flareSolverrSessionName.value,
flareSolverrSessionTtl = serverConfig.flareSolverrSessionTtl.value,
flareSolverrAsResponseFallback = serverConfig.flareSolverrAsResponseFallback.value,
// opds
opdsUseBinaryFileSizes = serverConfig.opdsUseBinaryFileSizes.value,
opdsItemsPerPage = serverConfig.opdsItemsPerPage.value,
opdsEnablePageReadProgress = serverConfig.opdsEnablePageReadProgress.value,
opdsMarkAsReadOnDownload = serverConfig.opdsMarkAsReadOnDownload.value,
opdsShowOnlyUnreadChapters = serverConfig.opdsShowOnlyUnreadChapters.value,
opdsShowOnlyDownloadedChapters = serverConfig.opdsShowOnlyDownloadedChapters.value,
opdsChapterSortOrder = serverConfig.opdsChapterSortOrder.value,
// koreader sync
koreaderSyncServerUrl = serverConfig.koreaderSyncServerUrl.value,
koreaderSyncUsername = serverConfig.koreaderSyncUsername.value,
koreaderSyncUserkey = serverConfig.koreaderSyncUserkey.value,
koreaderSyncDeviceId = serverConfig.koreaderSyncDeviceId.value,
koreaderSyncChecksumMethod = serverConfig.koreaderSyncChecksumMethod.value,
koreaderSyncStrategy = serverConfig.koreaderSyncStrategy.value,
koreaderSyncPercentageTolerance = serverConfig.koreaderSyncPercentageTolerance.value,
)
}
}

View File

@@ -31,8 +31,6 @@ import org.jetbrains.exposed.sql.statements.BatchUpdateStatement
import org.jetbrains.exposed.sql.transactions.transaction
import org.jetbrains.exposed.sql.update
import suwayomi.tachidesk.global.impl.GlobalMeta
import suwayomi.tachidesk.graphql.mutations.SettingsMutation
import suwayomi.tachidesk.graphql.types.AuthMode
import suwayomi.tachidesk.graphql.types.toStatus
import suwayomi.tachidesk.manga.impl.Category
import suwayomi.tachidesk.manga.impl.Category.modifyCategoriesMetas
@@ -43,12 +41,12 @@ import suwayomi.tachidesk.manga.impl.Manga.modifyMangasMetas
import suwayomi.tachidesk.manga.impl.Source.modifySourceMetas
import suwayomi.tachidesk.manga.impl.backup.proto.ProtoBackupValidator.ValidationResult
import suwayomi.tachidesk.manga.impl.backup.proto.ProtoBackupValidator.validate
import suwayomi.tachidesk.manga.impl.backup.proto.handlers.BackupSettingsHandler
import suwayomi.tachidesk.manga.impl.backup.proto.models.Backup
import suwayomi.tachidesk.manga.impl.backup.proto.models.BackupCategory
import suwayomi.tachidesk.manga.impl.backup.proto.models.BackupChapter
import suwayomi.tachidesk.manga.impl.backup.proto.models.BackupHistory
import suwayomi.tachidesk.manga.impl.backup.proto.models.BackupManga
import suwayomi.tachidesk.manga.impl.backup.proto.models.BackupServerSettings
import suwayomi.tachidesk.manga.impl.backup.proto.models.BackupSource
import suwayomi.tachidesk.manga.impl.backup.proto.models.BackupTracking
import suwayomi.tachidesk.manga.impl.track.tracker.TrackerManager
@@ -58,7 +56,6 @@ import suwayomi.tachidesk.manga.model.dataclass.TrackRecordDataClass
import suwayomi.tachidesk.manga.model.table.ChapterTable
import suwayomi.tachidesk.manga.model.table.MangaTable
import suwayomi.tachidesk.server.database.dbTransaction
import suwayomi.tachidesk.server.serverConfig
import java.io.InputStream
import java.util.Date
import java.util.Timer
@@ -215,7 +212,7 @@ object ProtoBackupImport : ProtoBackupBase() {
BackupRestoreState.RestoringSettings(restoreCategories + restoreMeta + restoreSettings, restoreAmount),
)
restoreServerSettings(backup.serverSettings)
BackupSettingsHandler.restore(backup.serverSettings)
// Store source mapping for error messages
val sourceMapping = backup.getSourceMap()
@@ -522,21 +519,5 @@ object ProtoBackupImport : ProtoBackupBase() {
modifySourceMetas(backupSources.associateBy { it.sourceId }.mapValues { it.value.meta })
}
private fun restoreServerSettings(backupServerSettings: BackupServerSettings?) {
if (backupServerSettings == null) {
return
}
SettingsMutation().updateSettings(
backupServerSettings.copy(
// legacy settings cannot overwrite new settings
basicAuthEnabled =
backupServerSettings.basicAuthEnabled.takeIf {
serverConfig.authMode.value == AuthMode.NONE
},
),
)
}
private fun TrackRecordDataClass.forComparison() = this.copy(id = 0, mangaId = 0)
}

View File

@@ -1,95 +0,0 @@
package suwayomi.tachidesk.manga.impl.backup.proto.models
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
import org.jetbrains.exposed.sql.SortOrder
import suwayomi.tachidesk.graphql.types.AuthMode
import suwayomi.tachidesk.graphql.types.KoreaderSyncChecksumMethod
import suwayomi.tachidesk.graphql.types.KoreaderSyncStrategy
import suwayomi.tachidesk.graphql.types.Settings
import suwayomi.tachidesk.graphql.types.SettingsDownloadConversion
import suwayomi.tachidesk.graphql.types.WebUIChannel
import suwayomi.tachidesk.graphql.types.WebUIFlavor
import suwayomi.tachidesk.graphql.types.WebUIInterface
import kotlin.time.Duration
@Serializable
data class BackupServerSettings(
@ProtoNumber(1) override var ip: String,
@ProtoNumber(2) override var port: Int,
@ProtoNumber(3) override var socksProxyEnabled: Boolean,
@ProtoNumber(4) override var socksProxyVersion: Int,
@ProtoNumber(5) override var socksProxyHost: String,
@ProtoNumber(6) override var socksProxyPort: String,
@ProtoNumber(7) override var socksProxyUsername: String,
@ProtoNumber(8) override var socksProxyPassword: String,
@ProtoNumber(9) override var webUIFlavor: WebUIFlavor,
@ProtoNumber(10) override var initialOpenInBrowserEnabled: Boolean,
@ProtoNumber(11) override var webUIInterface: WebUIInterface,
@ProtoNumber(12) override var electronPath: String,
@ProtoNumber(13) override var webUIChannel: WebUIChannel,
@ProtoNumber(14) override var webUIUpdateCheckInterval: Double,
@ProtoNumber(15) override var downloadAsCbz: Boolean,
@ProtoNumber(16) override var downloadsPath: String,
@ProtoNumber(17) override var autoDownloadNewChapters: Boolean,
@ProtoNumber(18) override var excludeEntryWithUnreadChapters: Boolean,
@ProtoNumber(19) override var autoDownloadAheadLimit: Int,
@ProtoNumber(20) override var autoDownloadNewChaptersLimit: Int,
@ProtoNumber(21) override var autoDownloadIgnoreReUploads: Boolean,
@ProtoNumber(22) override var extensionRepos: List<String>,
@ProtoNumber(23) override var maxSourcesInParallel: Int,
@ProtoNumber(24) override var excludeUnreadChapters: Boolean,
@ProtoNumber(25) override var excludeNotStarted: Boolean,
@ProtoNumber(26) override var excludeCompleted: Boolean,
@ProtoNumber(27) override var globalUpdateInterval: Double,
@ProtoNumber(28) override var updateMangas: Boolean,
@ProtoNumber(29) override var basicAuthEnabled: Boolean?,
@ProtoNumber(30) override var authUsername: String,
@ProtoNumber(31) override var authPassword: String,
@ProtoNumber(32) override var debugLogsEnabled: Boolean,
@ProtoNumber(33) override var gqlDebugLogsEnabled: Boolean,
@ProtoNumber(34) override var systemTrayEnabled: Boolean,
@ProtoNumber(35) override var maxLogFiles: Int,
@ProtoNumber(36) override var maxLogFileSize: String,
@ProtoNumber(37) override var maxLogFolderSize: String,
@ProtoNumber(38) override var backupPath: String,
@ProtoNumber(39) override var backupTime: String,
@ProtoNumber(40) override var backupInterval: Int,
@ProtoNumber(41) override var backupTTL: Int,
@ProtoNumber(42) override var localSourcePath: String,
@ProtoNumber(43) override var flareSolverrEnabled: Boolean,
@ProtoNumber(44) override var flareSolverrUrl: String,
@ProtoNumber(45) override var flareSolverrTimeout: Int,
@ProtoNumber(46) override var flareSolverrSessionName: String,
@ProtoNumber(47) override var flareSolverrSessionTtl: Int,
@ProtoNumber(48) override var flareSolverrAsResponseFallback: Boolean,
@ProtoNumber(49) override var opdsUseBinaryFileSizes: Boolean,
@ProtoNumber(50) override var opdsItemsPerPage: Int,
@ProtoNumber(51) override var opdsEnablePageReadProgress: Boolean,
@ProtoNumber(52) override var opdsMarkAsReadOnDownload: Boolean,
@ProtoNumber(53) override var opdsShowOnlyUnreadChapters: Boolean,
@ProtoNumber(54) override var opdsShowOnlyDownloadedChapters: Boolean,
@ProtoNumber(55) override var opdsChapterSortOrder: SortOrder,
@ProtoNumber(56) override var authMode: AuthMode,
@ProtoNumber(57) override val downloadConversions: List<BackupSettingsDownloadConversionType>?,
@ProtoNumber(58) override var jwtAudience: String?,
@ProtoNumber(59) override var koreaderSyncServerUrl: String,
@ProtoNumber(60) override var koreaderSyncUsername: String,
@ProtoNumber(61) override var koreaderSyncUserkey: String,
@ProtoNumber(62) override var koreaderSyncDeviceId: String,
@ProtoNumber(63) override var koreaderSyncChecksumMethod: KoreaderSyncChecksumMethod,
@ProtoNumber(64) override var koreaderSyncStrategy: KoreaderSyncStrategy,
@ProtoNumber(65) override var koreaderSyncPercentageTolerance: Double,
@ProtoNumber(66) override var jwtTokenExpiry: Duration?,
@ProtoNumber(67) override var jwtRefreshExpiry: Duration?,
// Deprecated settings
@ProtoNumber(99991) override var basicAuthUsername: String?,
@ProtoNumber(99992) override var basicAuthPassword: String?,
) : Settings {
@Serializable
class BackupSettingsDownloadConversionType(
@ProtoNumber(1) override val mimeType: String,
@ProtoNumber(2) override val target: String,
@ProtoNumber(3) override val compressionLevel: Double?,
) : SettingsDownloadConversion
}

View File

@@ -0,0 +1,12 @@
package suwayomi.tachidesk.manga.impl.backup.proto.models
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
import suwayomi.tachidesk.graphql.types.SettingsDownloadConversion
@Serializable
class BackupSettingsDownloadConversionType(
@ProtoNumber(1) override val mimeType: String,
@ProtoNumber(2) override val target: String,
@ProtoNumber(3) override val compressionLevel: Double?,
) : SettingsDownloadConversion

View File

@@ -261,7 +261,7 @@ object DownloadManager {
"Failed: ${downloadQueue.size - availableDownloads.size}"
}
if (runningDownloaders.size < serverConfig.maxSourcesInParallel.value.coerceAtLeast(1)) {
if (runningDownloaders.size < serverConfig.maxSourcesInParallel.value) {
availableDownloads
.asSequence()
.map { it.manga.sourceId }

View File

@@ -14,6 +14,7 @@ import org.apache.commons.compress.archivers.zip.ZipArchiveEntry
import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.transactions.transaction
import org.jetbrains.exposed.sql.update
import suwayomi.tachidesk.graphql.types.DownloadConversion
import suwayomi.tachidesk.manga.impl.Page
import suwayomi.tachidesk.manga.impl.chapter.getChapterDownloadReady
import suwayomi.tachidesk.manga.impl.download.model.DownloadChapter
@@ -25,7 +26,6 @@ import suwayomi.tachidesk.manga.impl.util.getChapterDownloadPath
import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse
import suwayomi.tachidesk.manga.model.table.ChapterTable
import suwayomi.tachidesk.manga.model.table.MangaTable
import suwayomi.tachidesk.server.ServerConfig
import suwayomi.tachidesk.server.serverConfig
import suwayomi.tachidesk.util.ConversionUtil
import java.io.File
@@ -261,7 +261,7 @@ abstract class ChaptersFilesProvider<Type : FileType>(
private fun convertPage(
page: File,
conversion: ServerConfig.DownloadConversion,
conversion: DownloadConversion,
) {
val (targetMime, compressionLevel) = conversion

View File

@@ -96,8 +96,7 @@ class Updater : IUpdater {
serverConfig.subscribeTo(serverConfig.globalUpdateInterval, ::scheduleUpdateTask)
serverConfig.subscribeTo(
serverConfig.maxSourcesInParallel,
{ value ->
val newMaxPermits = value.coerceAtLeast(1).coerceAtMost(20)
{ newMaxPermits ->
val permitDifference = maxSourcesInParallel - newMaxPermits
maxSourcesInParallel = newMaxPermits
@@ -160,10 +159,7 @@ class Updater : IUpdater {
return
}
val updateInterval =
serverConfig.globalUpdateInterval.value.hours
.coerceAtLeast(6.hours)
.inWholeMilliseconds
val updateInterval = serverConfig.globalUpdateInterval.value.hours.inWholeMilliseconds
val lastAutomatedUpdate = getLastAutomatedUpdateTimestamp()
val isInitialScheduling = lastAutomatedUpdate == 0L