mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
Fixes for Jellyseerr login issues (#1031)
## Description Fixes some issues with Jellyseerr login. Also makes error message more clear by showing the URLs that were tested and the error that occurred. Also fixes an issue where you could not remove a Jellyseerr server if it was previously configured but can no longer connect. ### Related issues Fixes #1028 Should fix #1022 Related to #747 ### Testing Emulator, tested adding and removing valid & invalid IPs ## Screenshots N/A ## AI or LLM usage None
This commit is contained in:
parent
6867ba7657
commit
ce6461d23b
7 changed files with 165 additions and 96 deletions
|
|
@ -47,7 +47,7 @@ interface SeerrServerDao {
|
|||
suspend fun deleteUser(
|
||||
serverId: Int,
|
||||
jellyfinUserRowId: Int,
|
||||
)
|
||||
): Int
|
||||
|
||||
suspend fun deleteUser(user: SeerrUser) = deleteUser(user.serverId, user.jellyfinUserRowId)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -554,6 +554,7 @@ fun PreferencesContent(
|
|||
currentUsername = currentUser?.name,
|
||||
status = status,
|
||||
onSubmit = seerrVm::submitServer,
|
||||
onResetStatus = seerrVm::resetStatus,
|
||||
onDismissRequest = { seerrDialogMode = SeerrDialogMode.None },
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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<SeerrAuthMethod?>(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
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<HttpUrl, LoadingState>()
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue