diff --git a/app/src/main/java/com/github/damontecres/wholphin/data/SeerrServerDao.kt b/app/src/main/java/com/github/damontecres/wholphin/data/SeerrServerDao.kt index 59f5d9ea..3a2c325c 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/data/SeerrServerDao.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/data/SeerrServerDao.kt @@ -47,7 +47,7 @@ interface SeerrServerDao { suspend fun deleteUser( serverId: Int, jellyfinUserRowId: Int, - ) + ): Int suspend fun deleteUser(user: SeerrUser) = deleteUser(user.serverId, user.jellyfinUserRowId) diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/SeerrServerRepository.kt b/app/src/main/java/com/github/damontecres/wholphin/services/SeerrServerRepository.kt index 78ff7d3b..970e80c3 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/SeerrServerRepository.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/SeerrServerRepository.kt @@ -11,6 +11,7 @@ import com.github.damontecres.wholphin.api.seerr.model.PublicSettings import com.github.damontecres.wholphin.api.seerr.model.User import com.github.damontecres.wholphin.data.SeerrServerDao import com.github.damontecres.wholphin.data.ServerRepository +import com.github.damontecres.wholphin.data.model.JellyfinUser import com.github.damontecres.wholphin.data.model.SeerrAuthMethod import com.github.damontecres.wholphin.data.model.SeerrPermission import com.github.damontecres.wholphin.data.model.SeerrServer @@ -24,8 +25,10 @@ import dagger.hilt.android.scopes.ActivityScoped import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.update +import kotlinx.coroutines.supervisorScope import okhttp3.OkHttpClient import timber.log.Timber import javax.inject.Inject @@ -162,7 +165,7 @@ class SeerrServerRepository apiKey, okHttpClient .newBuilder() - .connectTimeout(5.seconds) + .connectTimeout(2.seconds) .readTimeout(6.seconds) .build(), ) @@ -170,10 +173,11 @@ class SeerrServerRepository return LoadingState.Success } - suspend fun removeServer() { - val current = (_connection.value as? SeerrConnectionStatus.Success)?.current ?: return - seerrServerDao.deleteUser(current.server.id, current.user.jellyfinUserRowId) + suspend fun removeServerForCurrentUser(): Boolean { + val current = current.firstOrNull() ?: return false + val rows = seerrServerDao.deleteUser(current.server.id, current.user.jellyfinUserRowId) clear() + return rows > 0 } } @@ -266,47 +270,56 @@ class UserSwitchListener seerrServerRepository.clear() homeSettingsService.currentSettings.update { HomePageResolvedSettings.EMPTY } if (user != null) { - // Check for home settings - launchIO { - homeSettingsService.loadCurrentSettings(user.id) - } - // Check for seerr server - launchIO { - seerrServerDao - .getUsersByJellyfinUser(user.rowId) - .lastOrNull() - ?.let { seerrUser -> - val server = - seerrServerDao.getServer(seerrUser.serverId)?.server - if (server != null) { - Timber.i("Found a seerr user & server") - try { - seerrApi.update(server.url, seerrUser.credential) - val userConfig = - if (seerrUser.authMethod != SeerrAuthMethod.API_KEY) { - login( - seerrApi.api, - seerrUser.authMethod, - seerrUser.username, - seerrUser.password, - ) - } else { - seerrApi.api.usersApi.authMeGet() - } - seerrServerRepository.set(server, seerrUser, userConfig) - } catch (ex: Exception) { - Timber.w( - ex, - "Error logging into %s", - server.url, - ) - seerrServerRepository.error(server.url, ex) - } - } - } - } + switchUser(user) } } } } + + private suspend fun switchUser(user: JellyfinUser) = + supervisorScope { + // Check for home settings + launchIO { + homeSettingsService.loadCurrentSettings(user.id) + } + // Check for seerr server + launchIO { + seerrServerDao + .getUsersByJellyfinUser(user.rowId) + .lastOrNull() + ?.let { seerrUser -> + val server = + seerrServerDao.getServer(seerrUser.serverId)?.server + if (server != null) { + Timber.i("Found a seerr user & server") + try { + seerrApi.update(server.url, seerrUser.credential) + val userConfig = + if (seerrUser.authMethod != SeerrAuthMethod.API_KEY) { + login( + seerrApi.api, + seerrUser.authMethod, + seerrUser.username, + seerrUser.password, + ) + } else { + seerrApi.api.usersApi.authMeGet() + } + seerrServerRepository.set( + server, + seerrUser, + userConfig, + ) + } catch (ex: Exception) { + Timber.w( + ex, + "Error logging into %s", + server.url, + ) + seerrServerRepository.error(server.url, ex) + } + } + } + } + } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferencesContent.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferencesContent.kt index d9f1fb4d..729e7085 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferencesContent.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferencesContent.kt @@ -554,6 +554,7 @@ fun PreferencesContent( currentUsername = currentUser?.name, status = status, onSubmit = seerrVm::submitServer, + onResetStatus = seerrVm::resetStatus, onDismissRequest = { seerrDialogMode = SeerrDialogMode.None }, ) } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/seerr/AddSeerrServer.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/seerr/AddSeerrServer.kt index 81e06fb8..5fa8c667 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/seerr/AddSeerrServer.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/seerr/AddSeerrServer.kt @@ -4,7 +4,6 @@ import androidx.compose.foundation.focusGroup import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width @@ -66,15 +65,19 @@ fun AddSeerrServerApiKey( style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface, textAlign = TextAlign.Center, - modifier = Modifier.fillMaxWidth(), + modifier = Modifier.align(Alignment.CenterHorizontally), ) + val labelWidth = 90.dp Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.align(Alignment.CenterHorizontally), ) { Text( text = stringResource(R.string.url), - modifier = Modifier.padding(end = 8.dp), + modifier = + Modifier + .width(labelWidth) + .padding(end = 8.dp), ) EditTextBox( value = url, @@ -105,7 +108,10 @@ fun AddSeerrServerApiKey( ) { Text( text = stringResource(R.string.api_key), - modifier = Modifier.padding(end = 8.dp), + modifier = + Modifier + .width(labelWidth) + .padding(end = 8.dp), ) EditTextBox( value = apiKey, @@ -173,7 +179,7 @@ fun AddSeerrServerUsername( style = MaterialTheme.typography.titleMedium, color = MaterialTheme.colorScheme.onSurface, textAlign = TextAlign.Center, - modifier = Modifier.fillMaxWidth(), + modifier = Modifier.align(Alignment.CenterHorizontally), ) val labelWidth = 90.dp Row( diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/seerr/AddSeerrServerDialog.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/seerr/AddSeerrServerDialog.kt index 9566b88b..d84b98db 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/seerr/AddSeerrServerDialog.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/seerr/AddSeerrServerDialog.kt @@ -1,12 +1,16 @@ package com.github.damontecres.wholphin.ui.setup.seerr +import androidx.compose.foundation.layout.widthIn import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.DialogProperties import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.data.model.SeerrAuthMethod import com.github.damontecres.wholphin.ui.components.BasicDialog @@ -20,6 +24,7 @@ fun AddSeerServerDialog( currentUsername: String?, status: LoadingState, onSubmit: (url: String, username: String, passwordOrApiKey: String, method: SeerrAuthMethod) -> Unit, + onResetStatus: () -> Unit, onDismissRequest: () -> Unit, ) { var authMethod by remember { mutableStateOf(null) } @@ -34,6 +39,7 @@ fun AddSeerServerDialog( -> { BasicDialog( onDismissRequest = { authMethod = null }, + properties = DialogProperties(usePlatformDefaultWidth = false), ) { AddSeerrServerUsername( onSubmit = { url, username, password -> @@ -41,6 +47,7 @@ fun AddSeerServerDialog( }, username = currentUsername ?: "", status = status, + modifier = Modifier.widthIn(min = 320.dp), ) } } @@ -54,6 +61,7 @@ fun AddSeerServerDialog( onSubmit.invoke(url, "", apiKey, SeerrAuthMethod.API_KEY) }, status = status, + modifier = Modifier.widthIn(min = 320.dp), ) } } @@ -61,7 +69,10 @@ fun AddSeerServerDialog( null -> { ChooseSeerrLoginType( onDismissRequest = onDismissRequest, - onChoose = { authMethod = it }, + onChoose = { + onResetStatus.invoke() + authMethod = it + }, ) } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/seerr/SwitchSeerrViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/seerr/SwitchSeerrViewModel.kt index 4f93217a..0f62f12f 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/seerr/SwitchSeerrViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/seerr/SwitchSeerrViewModel.kt @@ -50,63 +50,73 @@ class SwitchSeerrViewModel serverConnectionStatus.update { LoadingState.Error("Invalid URL", ex) } return@launchIO } - var result: LoadingState = LoadingState.Error("No url") + Timber.v("Urls to try: %s", urls) + val results = mutableMapOf() for (url in urls) { Timber.d("Trying %s", url) - result = - try { - seerrServerRepository.testConnection( - authMethod = authMethod, - url = url.toString(), - username = username.takeIf { authMethod != SeerrAuthMethod.API_KEY }, - passwordOrApiKey = passwordOrApiKey, - ) - } catch (ex: ClientException) { - Timber.w(ex, "ClientException logging in") - if (ex.statusCode == 401 || ex.statusCode == 403) { - showToast(context, "Invalid credentials") - result = LoadingState.Error("Invalid credentials", ex) - break - } else { - LoadingState.Error("Could not connect with URL") - } - } catch (ex: Exception) { - Timber.w(ex, "Exception logging in") - LoadingState.Error(ex) - } - if (result is LoadingState.Success) { - when (authMethod) { - SeerrAuthMethod.LOCAL, - SeerrAuthMethod.JELLYFIN, - -> { - seerrServerRepository.addAndChangeServer( - url.toString(), - authMethod, - username, - passwordOrApiKey, - ) - } - - SeerrAuthMethod.API_KEY -> { - seerrServerRepository.addAndChangeServer( - url.toString(), - passwordOrApiKey, - ) - } - } + try { + seerrServerRepository.testConnection( + authMethod = authMethod, + url = url.toString(), + username = username.takeIf { authMethod != SeerrAuthMethod.API_KEY }, + passwordOrApiKey = passwordOrApiKey, + ) + results[url] = LoadingState.Success break + } catch (ex: ClientException) { + Timber.w(ex, "ClientException logging in %s", url) + if (ex.statusCode == 401 || ex.statusCode == 403) { + showToast(context, "Invalid credentials") + results[url] = LoadingState.Error("Invalid credentials", ex) + } else { + results[url] = LoadingState.Error("Could not connect with URL") + } + } catch (ex: Exception) { + Timber.w(ex, "ClientException logging in %s", url) + results[url] = LoadingState.Error(ex) } } - if (result is LoadingState.Error) { - showToast(context, "Error: ${result.message}") + val result = results.filter { (url, state) -> state is LoadingState.Success } + if (result.isNotEmpty()) { + val url = result.keys.first() + when (authMethod) { + SeerrAuthMethod.LOCAL, + SeerrAuthMethod.JELLYFIN, + -> { + seerrServerRepository.addAndChangeServer( + url.toString(), + authMethod, + username, + passwordOrApiKey, + ) + } + + SeerrAuthMethod.API_KEY -> { + seerrServerRepository.addAndChangeServer( + url.toString(), + passwordOrApiKey, + ) + } + } + } else { + val message = + results + .map { (url, state) -> + val s = state as? LoadingState.Error + "$url - ${s?.localizedMessage}" + }.joinToString("\n") + showToast(context, "Could not connect") + serverConnectionStatus.update { LoadingState.Error(message) } } - serverConnectionStatus.update { result } } } fun removeServer() { viewModelScope.launchIO { - seerrServerRepository.removeServer() + val result = seerrServerRepository.removeServerForCurrentUser() + if (!result) { + showToast(context, "Could not remove server") + } } } diff --git a/app/src/test/java/com/github/damontecres/wholphin/test/TestSeerr.kt b/app/src/test/java/com/github/damontecres/wholphin/test/TestSeerr.kt index 76893f8c..a4acf93f 100644 --- a/app/src/test/java/com/github/damontecres/wholphin/test/TestSeerr.kt +++ b/app/src/test/java/com/github/damontecres/wholphin/test/TestSeerr.kt @@ -62,4 +62,32 @@ class TestSeerr { ) Assert.assertEquals(expected, urls) } + + @Test + fun testCreateUrls5() { + val urls = + createUrls("10.0.0.2:443") + .map { it.toString() } + + val expected = + listOf( + "http://10.0.0.2:443/api/v1", + "https://10.0.0.2/api/v1", + ) + Assert.assertEquals(expected, urls) + } + + @Test + fun testCreateUrls6() { + val urls = + createUrls("10.0.0.2:8080") + .map { it.toString() } + + val expected = + listOf( + "http://10.0.0.2:8080/api/v1", + "https://10.0.0.2:8080/api/v1", + ) + Assert.assertEquals(expected, urls) + } }