From a27bb2a962a691043ec5936765b6d29ba7e2de13 Mon Sep 17 00:00:00 2001 From: Damontecres Date: Wed, 13 May 2026 09:44:15 -0400 Subject: [PATCH] Option to load from server plugin --- .../wholphin/data/ServerRepository.kt | 3 ++ .../wholphin/services/HomeSettingsService.kt | 3 +- .../services/SeerrServerRepository.kt | 14 ++++++- .../wholphin/services/ServerPluginApi.kt | 24 ++++++++++-- .../ui/main/settings/HomeSettingsGlobal.kt | 18 +++++++++ .../ui/main/settings/HomeSettingsPage.kt | 8 ++++ .../ui/main/settings/HomeSettingsViewModel.kt | 38 +++++++++++++++++++ app/src/main/res/values/strings.xml | 1 + 8 files changed, 104 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/github/damontecres/wholphin/data/ServerRepository.kt b/app/src/main/java/com/github/damontecres/wholphin/data/ServerRepository.kt index 7e32f2e5..dd5e5383 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/data/ServerRepository.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/data/ServerRepository.kt @@ -16,6 +16,7 @@ import com.github.damontecres.wholphin.util.EqualityMutableLiveData import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.withContext import kotlinx.serialization.Serializable import org.jellyfin.sdk.Jellyfin @@ -54,6 +55,8 @@ class ServerRepository val currentServer: LiveData get() = _current.map { it?.server } val currentUser: LiveData get() = _current.map { it?.user } + val serverPluginInstalled = MutableStateFlow(false) + /** * Adds a server to the app database and updated the [ApiClient] to the server's URL * diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/HomeSettingsService.kt b/app/src/main/java/com/github/damontecres/wholphin/services/HomeSettingsService.kt index 3af9ddf7..d503d855 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/HomeSettingsService.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/HomeSettingsService.kt @@ -199,7 +199,7 @@ class HomeSettingsService try { block.invoke() } catch (ex: Exception) { - Timber.w(ex, "Error loading local settings") + Timber.w(ex, "Error loading settings") null } @@ -213,6 +213,7 @@ class HomeSettingsService suspend fun loadCurrentSettings(userId: UUID) { Timber.v("Getting setting for %s", userId) // User local then server/remote otherwise create a default + // TODO figure out priority order val settings = tryLoad { serverPluginApi.fetchHomePageSettings() 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 7096b833..c91e03bf 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 @@ -272,6 +272,7 @@ class UserSwitchListener private val seerrServerDao: SeerrServerDao, private val seerrApi: SeerrApi, private val homeSettingsService: HomeSettingsService, + private val serverPluginApi: ServerPluginApi, ) { init { context as AppCompatActivity @@ -289,8 +290,19 @@ class UserSwitchListener private suspend fun switchUser(user: JellyfinUser) = supervisorScope { - // Check for home settings + // Check if plugin is installed, then for home settings launchIO { + val serverPluginInstalled = + try { + serverPluginApi.public() + } catch (ex: Exception) { + Timber.e(ex, "Error checking for server plugin") + false + } + Timber.i("Server plugin installed: %s", serverPluginInstalled) + serverRepository.serverPluginInstalled.value = serverPluginInstalled + + // Check for home settings homeSettingsService.loadCurrentSettings(user.id) } if (BuildConfig.DISCOVER_ENABLED) { diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/ServerPluginApi.kt b/app/src/main/java/com/github/damontecres/wholphin/services/ServerPluginApi.kt index 9fe1def9..794e6981 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/ServerPluginApi.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/ServerPluginApi.kt @@ -9,6 +9,7 @@ import okhttp3.OkHttpClient import okhttp3.Request import org.jellyfin.sdk.api.client.ApiClient import org.jellyfin.sdk.api.client.exception.ApiClientException +import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton @@ -22,8 +23,24 @@ class ServerPluginApi private fun createUrl(path: String): String? = api.baseUrl?.let { if (it.endsWith("/")) "${it}wholphin/$path" else "$it/wholphin/$path" } + private val json = + Json { + ignoreUnknownKeys = false + } + companion object { - private const val HOME_CONFIG_PATH = "home" + private const val HOME_CONFIG_PATH = "homesettings" + } + + suspend fun public(): Boolean { + val url = createUrl("public") ?: return false + val request = + Request + .Builder() + .url(url) + .get() + .build() + return okHttpClient.newCall(request).execute().isSuccessful } @OptIn(ExperimentalSerializationApi::class) @@ -37,11 +54,12 @@ class ServerPluginApi .build() return okHttpClient.newCall(request).execute().use { res -> if (res.isSuccessful) { - Json.decodeFromStream(res.body.byteStream()) + json.decodeFromStream(res.body.byteStream()) } else if (res.code == 404) { + Timber.w("fetchHomePageSettings returned 404") null } else { - throw ApiClientException(res.body.string()) + throw ApiClientException(res.code.toString() + " " + res.body.string()) } } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsGlobal.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsGlobal.kt index 35c17a7e..461a7dfe 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsGlobal.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsGlobal.kt @@ -37,6 +37,8 @@ fun HomeSettingsGlobal( onClickSave: () -> Unit, onClickLoad: () -> Unit, onClickLoadWeb: () -> Unit, + serverPluginActive: Boolean, + onClickLoadPlugin: () -> Unit, onClickReset: () -> Unit, onClickViewNextUp: () -> Unit, modifier: Modifier = Modifier, @@ -166,6 +168,22 @@ fun HomeSettingsGlobal( modifier = Modifier, ) } + if (serverPluginActive) { + item { + HomeSettingsListItem( + selected = false, + headlineText = stringResource(R.string.load_from_server_plugin), + leadingContent = { + Text( + text = stringResource(R.string.fa_download), + fontFamily = FontAwesome, + ) + }, + onClick = onClickLoadPlugin, + modifier = Modifier, + ) + } + } item { HomeSettingsListItem( selected = false, diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsPage.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsPage.kt index 3ec26f92..15661472 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsPage.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsPage.kt @@ -65,6 +65,7 @@ fun HomeSettingsPage( var showRemovedNextUpDialog by remember { mutableStateOf(false) } val state by viewModel.state.collectAsState() + val serverPluginActive by viewModel.serverPluginActive.collectAsState() var position by rememberPosition(0, 0) // TODO discover rows val discoverEnabled = false // by viewModel.discoverEnabled.collectAsState(false) @@ -274,6 +275,13 @@ fun HomeSettingsPage( viewModel.loadFromRemote() } }, + serverPluginActive = serverPluginActive, + onClickLoadPlugin = { + showConfirmDialog = + ShowConfirm(R.string.overwrite_local_settings) { + viewModel.loadFromRemotePlugin() + } + }, onClickLoadWeb = { showConfirmDialog = ShowConfirm(R.string.overwrite_local_settings) { diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsViewModel.kt index c4874aa0..01a4a0c0 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsViewModel.kt @@ -29,6 +29,7 @@ import com.github.damontecres.wholphin.services.HomeRowConfigDisplay import com.github.damontecres.wholphin.services.HomeSettingsService import com.github.damontecres.wholphin.services.NavDrawerService import com.github.damontecres.wholphin.services.SeerrServerRepository +import com.github.damontecres.wholphin.services.ServerPluginApi import com.github.damontecres.wholphin.services.UnsupportedHomeSettingsVersionException import com.github.damontecres.wholphin.services.UserPreferencesService import com.github.damontecres.wholphin.services.hilt.IoCoroutineScope @@ -74,12 +75,15 @@ class HomeSettingsViewModel private val navDrawerService: NavDrawerService, private val backdropService: BackdropService, private val seerrServerRepository: SeerrServerRepository, + private val serverPluginApi: ServerPluginApi, val preferencesDataStore: DataStore, @param:IoCoroutineScope private val ioScope: CoroutineScope, ) : ViewModel() { private val _state = MutableStateFlow(HomePageSettingsState.EMPTY) val state: StateFlow = _state + val serverPluginActive get() = serverRepository.serverPluginInstalled + private var idCounter by Delegates.notNull() val discoverEnabled = seerrServerRepository.active @@ -767,6 +771,40 @@ class HomeSettingsViewModel } } } + + fun loadFromRemotePlugin() { + viewModelScope.launchIO { + Timber.d("Loading home settings from server plugin") + try { + // TODO should this remove local settings? + // TODO how to set up priorities + _state.update { it.copy(loading = LoadingState.Loading) } + val result = serverPluginApi.fetchHomePageSettings() + if (result != null) { + Timber.v("Got remote settings") + val newRows = + result.rows.mapIndexed { index, config -> + homeSettingsService.resolve(index, config) + } + idCounter = newRows.maxOfOrNull { it.id }?.plus(1) ?: 0 + _state.update { + it.copy(rows = newRows) + } + } else { + Timber.v("No server plugin settings") + showToast(context, "No server plugin settings found") + } + fetchRowData() + } catch (ex: UnsupportedHomeSettingsVersionException) { + // TODO + Timber.w(ex) + showToast(context, "Error: ${ex.localizedMessage}") + } catch (ex: Exception) { + Timber.e(ex) + showToast(context, "Error: ${ex.localizedMessage}") + } + } + } } data class HomePageSettingsState( diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b9b56865..d3801ce3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -774,6 +774,7 @@ For intros, credits, etc Play with Transcoding + Load default from server @string/ass_subtitle_mode_libass @string/ass_subtitle_mode_exoplayer