Option to load from server plugin

This commit is contained in:
Damontecres 2026-05-13 09:44:15 -04:00
parent f02f66c506
commit a27bb2a962
No known key found for this signature in database
8 changed files with 104 additions and 5 deletions

View file

@ -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<JellyfinServer?> get() = _current.map { it?.server }
val currentUser: LiveData<JellyfinUser?> get() = _current.map { it?.user }
val serverPluginInstalled = MutableStateFlow<Boolean>(false)
/**
* Adds a server to the app database and updated the [ApiClient] to the server's URL
*

View file

@ -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()

View file

@ -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) {

View file

@ -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<HomePageSettings>(res.body.byteStream())
json.decodeFromStream<HomePageSettings>(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())
}
}
}

View file

@ -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,

View file

@ -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) {

View file

@ -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<AppPreferences>,
@param:IoCoroutineScope private val ioScope: CoroutineScope,
) : ViewModel() {
private val _state = MutableStateFlow(HomePageSettingsState.EMPTY)
val state: StateFlow<HomePageSettingsState> = _state
val serverPluginActive get() = serverRepository.serverPluginInstalled
private var idCounter by Delegates.notNull<Int>()
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(

View file

@ -774,6 +774,7 @@
<string name="skip_behavior_summary">For intros, credits, etc</string>
<string name="play_with">Play with</string>
<string name="transcoding">Transcoding</string>
<string name="load_from_server_plugin">Load default from server</string>
<array name="ass_subtitle_modes">
<item>@string/ass_subtitle_mode_libass</item>
<item>@string/ass_subtitle_mode_exoplayer</item>