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