diff --git a/android/src/main/java/ca/gosyer/jui/android/data/download/AndroidDownloadService.kt b/android/src/main/java/ca/gosyer/jui/android/data/download/AndroidDownloadService.kt index f8c9c0d8..f451827b 100644 --- a/android/src/main/java/ca/gosyer/jui/android/data/download/AndroidDownloadService.kt +++ b/android/src/main/java/ca/gosyer/jui/android/data/download/AndroidDownloadService.kt @@ -145,8 +145,9 @@ class AndroidDownloadService : Service() { } runCatching { client.ws( - host = serverUrl.substringAfter("://"), - path = downloadsQuery() + host = serverUrl.host, + port = serverUrl.port, + path = serverUrl.encodedPath + downloadsQuery() ) { errorConnectionCount = 0 status.value = Status.RUNNING diff --git a/data/src/jvmMain/kotlin/ca/gosyer/data/base/WebsocketService.kt b/data/src/jvmMain/kotlin/ca/gosyer/data/base/WebsocketService.kt index 0b131551..ddcd0f1a 100644 --- a/data/src/jvmMain/kotlin/ca/gosyer/data/base/WebsocketService.kt +++ b/data/src/jvmMain/kotlin/ca/gosyer/data/base/WebsocketService.kt @@ -55,8 +55,9 @@ abstract class WebsocketService( } runCatching { client.ws( - host = serverUrl.substringAfter("://"), - path = query + host = serverUrl.host, + port = serverUrl.port, + path = serverUrl.encodedPath + query ) { errorConnectionCount = 0 _status.value = Status.RUNNING diff --git a/data/src/jvmMain/kotlin/ca/gosyer/data/server/ServerPreferences.kt b/data/src/jvmMain/kotlin/ca/gosyer/data/server/ServerPreferences.kt index 9a2b12af..de5bd2f7 100644 --- a/data/src/jvmMain/kotlin/ca/gosyer/data/server/ServerPreferences.kt +++ b/data/src/jvmMain/kotlin/ca/gosyer/data/server/ServerPreferences.kt @@ -10,6 +10,7 @@ import ca.gosyer.core.prefs.Preference import ca.gosyer.core.prefs.PreferenceStore import ca.gosyer.data.server.model.Auth import ca.gosyer.data.server.model.Proxy +import io.ktor.http.Url class ServerPreferences(private val preferenceStore: PreferenceStore) { @@ -21,8 +22,12 @@ class ServerPreferences(private val preferenceStore: PreferenceStore) { return preferenceStore.getInt("server_port", 4567) } - fun serverUrl(): Preference { - return ServerUrlPreference("", server(), port()) + fun pathPrefix(): Preference { + return preferenceStore.getString("server_path_prefix", "") + } + + fun serverUrl(): Preference { + return ServerUrlPreference("", server(), port(), pathPrefix()) } fun proxy(): Preference { diff --git a/data/src/jvmMain/kotlin/ca/gosyer/data/server/ServerUrlPreference.kt b/data/src/jvmMain/kotlin/ca/gosyer/data/server/ServerUrlPreference.kt index aa92a909..2c5da7de 100644 --- a/data/src/jvmMain/kotlin/ca/gosyer/data/server/ServerUrlPreference.kt +++ b/data/src/jvmMain/kotlin/ca/gosyer/data/server/ServerUrlPreference.kt @@ -7,6 +7,8 @@ package ca.gosyer.data.server import ca.gosyer.core.prefs.Preference +import io.ktor.http.URLBuilder +import io.ktor.http.Url import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted @@ -17,42 +19,56 @@ import kotlinx.coroutines.flow.stateIn class ServerUrlPreference( private val key: String, private val server: Preference, - private val port: Preference -) : Preference { + private val port: Preference, + private val pathPrefix: Preference +) : Preference { override fun key(): String { return key } - override fun get(): String { - return server.get() + ":" + port.get() + override fun get(): Url { + return URLBuilder(server.get()).apply { + port = this@ServerUrlPreference.port.get() + if (pathPrefix.isSet() && pathPrefix.get().isNotBlank()) { + path(pathPrefix.get()) + } + }.build() } - override fun set(value: String) { - val (server, port) = value.split(':') - this.server.set(server) - this.port.set(port.toInt()) + override fun set(value: Url) { + server.set(value.protocol.name + "://" + value.host) + port.set(value.port) + pathPrefix.set(value.encodedPath) } override fun isSet(): Boolean { - return server.isSet() || port.isSet() + return server.isSet() || port.isSet() || pathPrefix.isSet() } override fun delete() { server.delete() port.delete() + pathPrefix.delete() } - override fun defaultValue(): String { - return server.defaultValue() + ":" + port.defaultValue() + override fun defaultValue(): Url { + return URLBuilder(server.defaultValue()).apply { + port = this@ServerUrlPreference.port.defaultValue() + }.build() } - override fun changes(): Flow { - return combine(server.changes(), port.changes()) { server, port -> - "$server:$port" + override fun changes(): Flow { + return combine(server.changes(), port.changes(), pathPrefix.changes()) { server, port, pathPrefix -> + URLBuilder(server).apply { + this.port = port + if (pathPrefix.isNotBlank()) { + path(pathPrefix) + } + }.build() } } - override fun stateIn(scope: CoroutineScope): StateFlow { + override fun stateIn(scope: CoroutineScope): StateFlow { return changes().stateIn(scope, SharingStarted.Eagerly, get()) } } diff --git a/data/src/jvmMain/kotlin/ca/gosyer/data/server/interactions/BaseInteractionHandler.kt b/data/src/jvmMain/kotlin/ca/gosyer/data/server/interactions/BaseInteractionHandler.kt index 85e369be..b8ea2629 100644 --- a/data/src/jvmMain/kotlin/ca/gosyer/data/server/interactions/BaseInteractionHandler.kt +++ b/data/src/jvmMain/kotlin/ca/gosyer/data/server/interactions/BaseInteractionHandler.kt @@ -14,5 +14,5 @@ open class BaseInteractionHandler( serverPreferences: ServerPreferences ) { private val _serverUrl = serverPreferences.serverUrl() - val serverUrl get() = _serverUrl.get() + val serverUrl get() = _serverUrl.get().toString() } diff --git a/i18n/src/commonMain/resources/MR/values/base/strings.xml b/i18n/src/commonMain/resources/MR/values/base/strings.xml index d92bbba4..1e287000 100644 --- a/i18n/src/commonMain/resources/MR/values/base/strings.xml +++ b/i18n/src/commonMain/resources/MR/values/base/strings.xml @@ -237,6 +237,8 @@ Basic auth password Server URL Server PORT + Server Path Prefix + Path prefix to be used before endpoint paths, leave blank for unused. Warning The below settings require a restart to take affect No Proxy diff --git a/presentation/src/jvmMain/kotlin/ca/gosyer/ui/base/image/KamelConfigProvider.kt b/presentation/src/jvmMain/kotlin/ca/gosyer/ui/base/image/KamelConfigProvider.kt index f7d0fd59..a865c7e6 100644 --- a/presentation/src/jvmMain/kotlin/ca/gosyer/ui/base/image/KamelConfigProvider.kt +++ b/presentation/src/jvmMain/kotlin/ca/gosyer/ui/base/image/KamelConfigProvider.kt @@ -56,21 +56,21 @@ class KamelConfigProvider @Inject constructor( } } - class MangaCoverMapper(private val serverUrlStateFlow: StateFlow) : Mapper { + class MangaCoverMapper(private val serverUrlStateFlow: StateFlow) : Mapper { override fun map(input: Manga): Url { - return Url(serverUrlStateFlow.value + input.thumbnailUrl) + return Url(serverUrlStateFlow.value.toString() + input.thumbnailUrl) } } - class ExtensionIconMapper(private val serverUrlStateFlow: StateFlow) : Mapper { + class ExtensionIconMapper(private val serverUrlStateFlow: StateFlow) : Mapper { override fun map(input: Extension): Url { - return Url(serverUrlStateFlow.value + input.iconUrl) + return Url(serverUrlStateFlow.value.toString() + input.iconUrl) } } - class SourceIconMapper(private val serverUrlStateFlow: StateFlow) : Mapper { + class SourceIconMapper(private val serverUrlStateFlow: StateFlow) : Mapper { override fun map(input: Source): Url { - return Url(serverUrlStateFlow.value + input.iconUrl) + return Url(serverUrlStateFlow.value.toString() + input.iconUrl) } } } diff --git a/presentation/src/jvmMain/kotlin/ca/gosyer/ui/settings/SettingsServerScreen.kt b/presentation/src/jvmMain/kotlin/ca/gosyer/ui/settings/SettingsServerScreen.kt index 5a6689ef..855586bf 100644 --- a/presentation/src/jvmMain/kotlin/ca/gosyer/ui/settings/SettingsServerScreen.kt +++ b/presentation/src/jvmMain/kotlin/ca/gosyer/ui/settings/SettingsServerScreen.kt @@ -59,6 +59,7 @@ class SettingsServerScreen : Screen { authValue = connectionVM.auth.collectAsState().value, serverUrl = connectionVM.serverUrl, serverPort = connectionVM.serverPort, + serverPathPrefix = connectionVM.serverPathPrefix, proxy = connectionVM.proxy, proxyChoices = connectionVM.getProxyChoices(), httpHost = connectionVM.httpHost, @@ -83,6 +84,7 @@ class SettingsServerViewModel @Inject constructor( ) : ViewModel(contextWrapper) { val serverUrl = serverPreferences.server().asStateIn(scope) val serverPort = serverPreferences.port().asStringStateIn(scope) + val serverPathPrefix = serverPreferences.pathPrefix().asStateIn(scope) val proxy = serverPreferences.proxy().asStateIn(scope) @@ -125,6 +127,7 @@ fun SettingsServerScreenContent( authValue: Auth, serverUrl: PreferenceMutableStateFlow, serverPort: PreferenceMutableStateFlow, + serverPathPrefix: PreferenceMutableStateFlow, proxy: PreferenceMutableStateFlow, proxyChoices: Map, httpHost: PreferenceMutableStateFlow, @@ -159,6 +162,13 @@ fun SettingsServerScreenContent( subtitle = serverPort.collectAsState().value ) } + item { + EditTextPreference( + serverPathPrefix, + stringResource(MR.strings.server_path_prefix), + subtitle = stringResource(MR.strings.server_path_prefix_sub) + ) + } item { PreferenceRow(