Implement configuration of server settings during runtime

This commit is contained in:
Syer10
2024-03-30 16:22:50 -04:00
parent a04762842b
commit 52ea0f1c37
34 changed files with 1544 additions and 289 deletions

View File

@@ -0,0 +1,29 @@
/*
* 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/.
*/
package ca.gosyer.jui.domain.server.service
import ca.gosyer.jui.core.prefs.Preference
import ca.gosyer.jui.core.prefs.PreferenceStore
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
actual class ServerHostPreferences actual constructor(
@Suppress("unused") private val preferenceStore: PreferenceStore,
) {
actual fun host(): Preference<Boolean> = object : Preference<Boolean> {
override fun key(): String = "host"
override fun get(): Boolean = false
override fun isSet(): Boolean = false
override fun delete() {}
override fun defaultValue(): Boolean = false
override fun changes(): Flow<Boolean> = MutableStateFlow(false)
override fun stateIn(scope: CoroutineScope): StateFlow<Boolean> = MutableStateFlow(false)
override fun set(value: Boolean) {}
}
}

View File

@@ -17,6 +17,7 @@ import ca.gosyer.jui.domain.migration.service.MigrationPreferences
import ca.gosyer.jui.domain.reader.service.ReaderPreferences
import ca.gosyer.jui.domain.server.Http
import ca.gosyer.jui.domain.server.httpClient
import ca.gosyer.jui.domain.server.service.ServerHostPreferences
import ca.gosyer.jui.domain.server.service.ServerPreferences
import ca.gosyer.jui.domain.source.service.CatalogPreferences
import ca.gosyer.jui.domain.ui.service.UiPreferences
@@ -107,6 +108,11 @@ interface SharedDomainComponent : CoreComponent {
val updatePreferencesFactory: UpdatePreferences
get() = UpdatePreferences(preferenceFactory.create("update"))
@get:AppScope
@get:Provides
val serverHostPreferencesFactory: ServerHostPreferences
get() = ServerHostPreferences(preferenceFactory.create("host"))
@get:AppScope
@get:Provides
val libraryUpdateServiceFactory: LibraryUpdateService

View File

@@ -0,0 +1,16 @@
/*
* 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/.
*/
package ca.gosyer.jui.domain.server.service
import ca.gosyer.jui.core.prefs.Preference
import ca.gosyer.jui.core.prefs.PreferenceStore
expect class ServerHostPreferences(
preferenceStore: PreferenceStore,
) {
fun host(): Preference<Boolean>
}

View File

@@ -6,7 +6,7 @@
package ca.gosyer.jui.domain.settings.interactor
import ca.gosyer.jui.domain.settings.service.SettingsRepository
import ca.gosyer.jui.domain.settings.service.SettingsRepositoryOld
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.singleOrNull
import me.tatarka.inject.annotations.Inject
@@ -15,7 +15,7 @@ import org.lighthousegames.logging.logging
class AboutServer
@Inject
constructor(
private val settingsRepository: SettingsRepository,
private val settingsRepositoryOld: SettingsRepositoryOld,
) {
suspend fun await(onError: suspend (Throwable) -> Unit = {}) =
asFlow()
@@ -25,7 +25,7 @@ class AboutServer
}
.singleOrNull()
fun asFlow() = settingsRepository.aboutServer()
fun asFlow() = settingsRepositoryOld.aboutServer()
companion object {
private val log = logging()

View File

@@ -6,7 +6,7 @@
package ca.gosyer.jui.domain.settings.interactor
import ca.gosyer.jui.domain.settings.service.SettingsRepository
import ca.gosyer.jui.domain.settings.service.SettingsRepositoryOld
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.singleOrNull
import me.tatarka.inject.annotations.Inject
@@ -15,7 +15,7 @@ import org.lighthousegames.logging.logging
class CheckUpdate
@Inject
constructor(
private val settingsRepository: SettingsRepository,
private val settingsRepositoryOld: SettingsRepositoryOld,
) {
suspend fun await(onError: suspend (Throwable) -> Unit = {}) =
asFlow()
@@ -25,7 +25,7 @@ class CheckUpdate
}
.singleOrNull()
fun asFlow() = settingsRepository.checkUpdate()
fun asFlow() = settingsRepositoryOld.checkUpdate()
companion object {
private val log = logging()

View File

@@ -0,0 +1,33 @@
/*
* 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/.
*/
package ca.gosyer.jui.domain.settings.interactor
import ca.gosyer.jui.domain.settings.service.SettingsRepository
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.singleOrNull
import me.tatarka.inject.annotations.Inject
import org.lighthousegames.logging.logging
class GetSettings
@Inject
constructor(
private val settingsRepository: SettingsRepository,
) {
suspend fun await(onError: suspend (Throwable) -> Unit = {}) =
asFlow()
.catch {
onError(it)
log.warn(it) { "Failed to check for server updates" }
}
.singleOrNull()
fun asFlow() = settingsRepository.getSettings()
companion object {
private val log = logging()
}
}

View File

@@ -0,0 +1,34 @@
/*
* 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/.
*/
package ca.gosyer.jui.domain.settings.interactor
import ca.gosyer.jui.domain.settings.model.SetSettingsInput
import ca.gosyer.jui.domain.settings.service.SettingsRepository
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.singleOrNull
import me.tatarka.inject.annotations.Inject
import org.lighthousegames.logging.logging
class SetSettings
@Inject
constructor(
private val settingsRepository: SettingsRepository,
) {
suspend fun await(input: SetSettingsInput, onError: suspend (Throwable) -> Unit = {}) =
asFlow(input)
.catch {
onError(it)
log.warn(it) { "Failed to check for server updates" }
}
.singleOrNull()
fun asFlow(input: SetSettingsInput) = settingsRepository.setSettings(input)
companion object {
private val log = logging()
}
}

View File

@@ -0,0 +1,52 @@
/*
* 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/.
*/
package ca.gosyer.jui.domain.settings.model
class SetSettingsInput(
val autoDownloadNewChapters: Boolean? = null,
val autoDownloadNewChaptersLimit: Int? = null,
val backupInterval: Int? = null,
val backupPath: String? = null,
val backupTTL: Int? = null,
val backupTime: String? = null,
val basicAuthEnabled: Boolean? = null,
val basicAuthPassword: String? = null,
val basicAuthUsername: String? = null,
val debugLogsEnabled: Boolean? = null,
val downloadAsCbz: Boolean? = null,
val downloadsPath: String? = null,
val electronPath: String? = null,
val excludeCompleted: Boolean? = null,
val excludeEntryWithUnreadChapters: Boolean? = null,
val excludeNotStarted: Boolean? = null,
val excludeUnreadChapters: Boolean? = null,
val extensionRepos: List<String>? = null,
val flareSolverrEnabled: Boolean? = null,
val flareSolverrSessionName: String? = null,
val flareSolverrSessionTtl: Int? = null,
val flareSolverrTimeout: Int? = null,
val flareSolverrUrl: String? = null,
val globalUpdateInterval: Double? = null,
val gqlDebugLogsEnabled: Boolean? = null,
val initialOpenInBrowserEnabled: Boolean? = null,
val ip: String? = null,
val localSourcePath: String? = null,
val maxSourcesInParallel: Int? = null,
val port: Int? = null,
val socksProxyEnabled: Boolean? = null,
val socksProxyHost: String? = null,
val socksProxyPassword: String? = null,
val socksProxyPort: String? = null,
val socksProxyUsername: String? = null,
val socksProxyVersion: Int? = null,
val systemTrayEnabled: Boolean? = null,
val updateMangas: Boolean? = null,
val webUIChannel: WebUIChannel? = null,
val webUIFlavor: WebUIFlavor? = null,
val webUIInterface: WebUIInterface? = null,
val webUIUpdateCheckInterval: Double? = null,
)

View File

@@ -0,0 +1,55 @@
/*
* 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/.
*/
package ca.gosyer.jui.domain.settings.model
import androidx.compose.runtime.Stable
@Stable
class Settings(
val autoDownloadNewChapters: Boolean,
val autoDownloadNewChaptersLimit: Int,
val backupInterval: Int,
val backupPath: String,
val backupTTL: Int,
val backupTime: String,
val basicAuthEnabled: Boolean,
val basicAuthPassword: String,
val basicAuthUsername: String,
val debugLogsEnabled: Boolean,
val downloadAsCbz: Boolean,
val downloadsPath: String,
val electronPath: String,
val excludeCompleted: Boolean,
val excludeEntryWithUnreadChapters: Boolean,
val excludeNotStarted: Boolean,
val excludeUnreadChapters: Boolean,
val extensionRepos: List<String>,
val flareSolverrEnabled: Boolean,
val flareSolverrSessionName: String,
val flareSolverrSessionTtl: Int,
val flareSolverrTimeout: Int,
val flareSolverrUrl: String,
val globalUpdateInterval: Double,
val gqlDebugLogsEnabled: Boolean,
val initialOpenInBrowserEnabled: Boolean,
val ip: String,
val localSourcePath: String,
val maxSourcesInParallel: Int,
val port: Int,
val socksProxyEnabled: Boolean,
val socksProxyHost: String,
val socksProxyPassword: String,
val socksProxyPort: String,
val socksProxyUsername: String,
val socksProxyVersion: Int,
val systemTrayEnabled: Boolean,
val updateMangas: Boolean,
val webUIChannel: WebUIChannel,
val webUIFlavor: WebUIFlavor,
val webUIInterface: WebUIInterface,
val webUIUpdateCheckInterval: Double,
)

View File

@@ -0,0 +1,14 @@
/*
* 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/.
*/
package ca.gosyer.jui.domain.settings.model
enum class WebUIChannel {
BUNDLED,
STABLE,
PREVIEW,
UNKNOWN__;
}

View File

@@ -0,0 +1,14 @@
/*
* 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/.
*/
package ca.gosyer.jui.domain.settings.model
enum class WebUIFlavor {
WEBUI,
VUI,
CUSTOM,
UNKNOWN__;
}

View File

@@ -0,0 +1,13 @@
/*
* 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/.
*/
package ca.gosyer.jui.domain.settings.model
enum class WebUIInterface {
BROWSER,
ELECTRON,
UNKNOWN__;
}

View File

@@ -6,16 +6,13 @@
package ca.gosyer.jui.domain.settings.service
import ca.gosyer.jui.domain.settings.model.About
import de.jensklingenberg.ktorfit.http.GET
import de.jensklingenberg.ktorfit.http.POST
import io.ktor.client.statement.HttpResponse
import ca.gosyer.jui.domain.settings.model.SetSettingsInput
import ca.gosyer.jui.domain.settings.model.Settings
import kotlinx.coroutines.flow.Flow
interface SettingsRepository {
@GET("api/v1/settings/about")
fun aboutServer(): Flow<About>
@POST("api/v1/settings/check-update")
fun checkUpdate(): Flow<HttpResponse>
fun getSettings(): Flow<Settings>
fun setSettings(input: SetSettingsInput): Flow<Unit>
}

View File

@@ -0,0 +1,21 @@
/*
* 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/.
*/
package ca.gosyer.jui.domain.settings.service
import ca.gosyer.jui.domain.settings.model.About
import de.jensklingenberg.ktorfit.http.GET
import de.jensklingenberg.ktorfit.http.POST
import io.ktor.client.statement.HttpResponse
import kotlinx.coroutines.flow.Flow
interface SettingsRepositoryOld {
@GET("api/v1/settings/about")
fun aboutServer(): Flow<About>
@POST("api/v1/settings/check-update")
fun checkUpdate(): Flow<HttpResponse>
}

View File

@@ -18,11 +18,6 @@ actual interface DomainComponent : SharedDomainComponent {
val serverHostPreferences: ServerHostPreferences
@get:AppScope
@get:Provides
val serverHostPreferencesFactory: ServerHostPreferences
get() = ServerHostPreferences(preferenceFactory.create("host"))
@get:AppScope
@get:Provides
val serverServiceFactory: ServerService

View File

@@ -10,10 +10,10 @@ import ca.gosyer.jui.core.prefs.Preference
import ca.gosyer.jui.core.prefs.PreferenceStore
import ca.gosyer.jui.domain.server.service.host.ServerHostPreference
class ServerHostPreferences(
actual class ServerHostPreferences actual constructor(
private val preferenceStore: PreferenceStore,
) {
fun host(): Preference<Boolean> = preferenceStore.getBoolean("host", true)
actual fun host(): Preference<Boolean> = preferenceStore.getBoolean("host", true)
private val ip = ServerHostPreference.IP(preferenceStore)
@@ -23,45 +23,20 @@ class ServerHostPreferences(
fun port(): Preference<Int> = port.preference()
// Proxy
private val socksProxyEnabled = ServerHostPreference.SocksProxyEnabled(preferenceStore)
fun socksProxyEnabled(): Preference<Boolean> = socksProxyEnabled.preference()
private val socksProxyHost = ServerHostPreference.SocksProxyHost(preferenceStore)
fun socksProxyHost(): Preference<String> = socksProxyHost.preference()
private val socksProxyPort = ServerHostPreference.SocksProxyPort(preferenceStore)
fun socksProxyPort(): Preference<Int> = socksProxyPort.preference()
// Misc
private val debugLogsEnabled = ServerHostPreference.DebugLogsEnabled(preferenceStore)
fun debugLogsEnabled(): Preference<Boolean> = debugLogsEnabled.preference()
private val systemTrayEnabled = ServerHostPreference.SystemTrayEnabled(preferenceStore)
fun systemTrayEnabled(): Preference<Boolean> = systemTrayEnabled.preference()
// Downloader
private val downloadPath = ServerHostPreference.DownloadPath(preferenceStore)
fun downloadPath(): Preference<String> = downloadPath.preference()
private val downloadAsCbz = ServerHostPreference.DownloadAsCbz(preferenceStore)
// Backup
private val backupPath = ServerHostPreference.BackupPath(preferenceStore)
fun downloadAsCbz(): Preference<Boolean> = downloadAsCbz.preference()
fun backupPath(): Preference<String> = backupPath.preference()
// WebUI
private val webUIEnabled = ServerHostPreference.WebUIEnabled(preferenceStore)
// LocalSource
private val localSourcePath = ServerHostPreference.LocalSourcePath(preferenceStore)
fun webUIEnabled(): Preference<Boolean> = webUIEnabled.preference()
private val openInBrowserEnabled = ServerHostPreference.OpenInBrowserEnabled(preferenceStore)
fun openInBrowserEnabled(): Preference<Boolean> = openInBrowserEnabled.preference()
fun localSourcePath(): Preference<String> = localSourcePath.preference()
// Authentication
private val basicAuthEnabled = ServerHostPreference.BasicAuthEnabled(preferenceStore)
@@ -80,19 +55,15 @@ class ServerHostPreferences(
listOf(
ip,
port,
socksProxyEnabled,
socksProxyHost,
socksProxyPort,
debugLogsEnabled,
systemTrayEnabled,
downloadPath,
downloadAsCbz,
webUIEnabled,
openInBrowserEnabled,
backupPath,
localSourcePath,
basicAuthEnabled,
basicAuthUsername,
basicAuthPassword,
).mapNotNull {
it.getProperty()
}.toTypedArray()
}.plus(
"-Dsuwayomi.tachidesk.config.server.initialOpenInBrowserEnabled=false"
).toTypedArray()
}

View File

@@ -79,49 +79,6 @@ sealed class ServerHostPreference<T : Any> {
4567,
)
// Proxy
class SocksProxyEnabled(
preferenceStore: PreferenceStore,
) : BooleanServerHostPreference(
preferenceStore,
"socksProxyEnabled",
false,
)
class SocksProxyHost(
preferenceStore: PreferenceStore,
) : StringServerHostPreference(
preferenceStore,
"socksProxyHost",
"",
)
class SocksProxyPort(
override val preferenceStore: PreferenceStore,
) : IntServerHostPreference(
preferenceStore,
"socksProxyPort",
0,
)
// Misc
class DebugLogsEnabled(
preferenceStore: PreferenceStore,
) : BooleanServerHostPreference(
preferenceStore,
"debugLogsEnabled",
false,
)
class SystemTrayEnabled(
preferenceStore: PreferenceStore,
) : BooleanServerHostPreference(
preferenceStore,
"systemTrayEnabled",
false,
true,
)
// Downloader
class DownloadPath(
preferenceStore: PreferenceStore,
@@ -131,32 +88,23 @@ sealed class ServerHostPreference<T : Any> {
"",
)
class DownloadAsCbz(
// Backup
class BackupPath(
preferenceStore: PreferenceStore,
) : BooleanServerHostPreference(
preferenceStore,
"downloadAsCbz",
false,
)
) : StringServerHostPreference(
preferenceStore,
"backupPath",
"",
)
// WebUI
class WebUIEnabled(
// LocalSource
class LocalSourcePath(
preferenceStore: PreferenceStore,
) : BooleanServerHostPreference(
preferenceStore,
"webUIEnabled",
false,
true,
)
class OpenInBrowserEnabled(
preferenceStore: PreferenceStore,
) : BooleanServerHostPreference(
preferenceStore,
"initialOpenInBrowserEnabled",
false,
true,
)
) : StringServerHostPreference(
preferenceStore,
"localSourcePath",
"",
)
// Authentication
class BasicAuthEnabled(

View File

@@ -0,0 +1,29 @@
/*
* 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/.
*/
package ca.gosyer.jui.domain.server.service
import ca.gosyer.jui.core.prefs.Preference
import ca.gosyer.jui.core.prefs.PreferenceStore
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
actual class ServerHostPreferences actual constructor(
@Suppress("unused") private val preferenceStore: PreferenceStore,
) {
actual fun host(): Preference<Boolean> = object : Preference<Boolean> {
override fun key(): String = "host"
override fun get(): Boolean = false
override fun isSet(): Boolean = false
override fun delete() {}
override fun defaultValue(): Boolean = false
override fun changes(): Flow<Boolean> = MutableStateFlow(false)
override fun stateIn(scope: CoroutineScope): StateFlow<Boolean> = MutableStateFlow(false)
override fun set(value: Boolean) {}
}
}