From df78b42bac5bb3749955441d974ec511d112c813 Mon Sep 17 00:00:00 2001
From: damontecres <154766448+damontecres@users.noreply.github.com>
Date: Sat, 22 Nov 2025 12:48:39 -0500
Subject: [PATCH] Easier initial setup for server URLs (#300)
Use the server scoring API when adding a new server. This allows just
entering a hostname or IP address without the scheme (`http`/`https`) or
port in most cases.
Note: non-standard ports would still need to be provided
Closes #5
Closes #296
---
.../ui/setup/SwitchServerViewModel.kt | 106 ++++++++++++------
app/src/main/res/values/strings.xml | 2 +-
2 files changed, 72 insertions(+), 36 deletions(-)
diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchServerViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchServerViewModel.kt
index 23201235..be93e772 100644
--- a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchServerViewModel.kt
+++ b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchServerViewModel.kt
@@ -12,6 +12,7 @@ import com.github.damontecres.wholphin.data.model.JellyfinServer
import com.github.damontecres.wholphin.services.NavigationManager
import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.ui.nav.Destination
+import com.github.damontecres.wholphin.ui.setValueOnMain
import com.github.damontecres.wholphin.ui.showToast
import com.github.damontecres.wholphin.util.LoadingState
import dagger.hilt.android.lifecycle.HiltViewModel
@@ -23,6 +24,8 @@ import org.jellyfin.sdk.Jellyfin
import org.jellyfin.sdk.api.client.HttpClientOptions
import org.jellyfin.sdk.api.client.extensions.quickConnectApi
import org.jellyfin.sdk.api.client.extensions.systemApi
+import org.jellyfin.sdk.discovery.RecommendedServerInfoScore
+import org.jellyfin.sdk.discovery.RecommendedServerIssue
import org.jellyfin.sdk.model.serializer.toUUID
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
import timber.log.Timber
@@ -130,48 +133,81 @@ class SwitchServerViewModel
status
}
- fun addServer(serverUrl: String) {
+ fun addServer(inputUrl: String) {
addServerState.value = LoadingState.Loading
viewModelScope.launchIO {
try {
- val serverInfo by jellyfin
- .createApi(serverUrl)
- .systemApi
- .getPublicSystemInfo()
- val id = serverInfo.id?.toUUIDOrNull()
- if (id != null && serverInfo.startupWizardCompleted == true) {
- val server =
- JellyfinServer(
- id = id,
- name = serverInfo.serverName,
- url = serverUrl,
- version = serverInfo.version,
- )
- serverRepository.addAndChangeServer(server)
- val quickConnect =
- jellyfin
- .createApi(serverUrl)
- .quickConnectApi
- .getQuickConnectEnabled()
- .content
- withContext(Dispatchers.Main) {
- serverQuickConnect.value =
- serverQuickConnect.value!!.toMutableMap().apply {
- put(id, quickConnect)
- }
- }
- withContext(Dispatchers.Main) {
- addServerState.value = LoadingState.Success
- navigationManager.navigateTo(Destination.UserList(server))
+ val scores =
+ jellyfin.discovery.getRecommendedServers(inputUrl).sortedBy { it.score }
+ val bestServer =
+ scores.firstOrNull { it.score != RecommendedServerInfoScore.BAD }
+ val serverInfo = bestServer?.systemInfo?.getOrNull()
+ if (bestServer != null && serverInfo != null) {
+ val serverUrl = bestServer.address
+
+ val id = serverInfo.id?.toUUIDOrNull()
+
+ if (id != null && serverInfo.startupWizardCompleted == true) {
+ val server =
+ JellyfinServer(
+ id = id,
+ name = serverInfo.serverName,
+ url = serverUrl,
+ version = serverInfo.version,
+ )
+ serverRepository.addAndChangeServer(server)
+ val quickConnect =
+ jellyfin
+ .createApi(serverUrl)
+ .quickConnectApi
+ .getQuickConnectEnabled()
+ .content
+ withContext(Dispatchers.Main) {
+ serverQuickConnect.value =
+ serverQuickConnect.value!!.toMutableMap().apply {
+ put(id, quickConnect)
+ }
+ }
+ withContext(Dispatchers.Main) {
+ addServerState.value = LoadingState.Success
+ navigationManager.navigateTo(Destination.UserList(server))
+ }
+ } else {
+ withContext(Dispatchers.Main) {
+ addServerState.value =
+ LoadingState.Error("Server returned invalid response")
+ }
}
} else {
- withContext(Dispatchers.Main) {
- addServerState.value =
- LoadingState.Error("Server returned invalid response")
- }
+ Timber.w("Error connecting with %s: %s", inputUrl, scores)
+ // No good server candidate
+ val errors =
+ scores.joinToString("\n") {
+ val issues =
+ it.issues.firstOrNull()?.let {
+ when (it) {
+ is RecommendedServerIssue.InvalidProductName,
+ is RecommendedServerIssue.MissingSystemInfo,
+ -> "Invalid server info"
+
+ is RecommendedServerIssue.SecureConnectionFailed,
+ is RecommendedServerIssue.ServerUnreachable,
+ is RecommendedServerIssue.SlowResponse,
+ -> "Unable to connect"
+
+ RecommendedServerIssue.MissingVersion,
+ is RecommendedServerIssue.OutdatedServerVersion,
+ is RecommendedServerIssue.UnsupportedServerVersion,
+ -> "Unsupported server version"
+ }
+ }
+ "${it.address} - $issues"
+ }
+ val message = "Error, tried addresses:\n$errors"
+ addServerState.setValueOnMain(LoadingState.Error(message))
}
} catch (ex: Exception) {
- Timber.w(ex, "Error creating API for $serverUrl")
+ Timber.w(ex, "Error creating API for $inputUrl")
withContext(Dispatchers.Main) {
addServerState.value =
LoadingState.Error(exception = ex)
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 72f5668e..31bbd291 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -36,7 +36,7 @@
Downloading…
Enabled
- Enter Server IP or URL including port
+ Enter Server IP or URL
Episodes
Error loading collection %1$s
External