Fix mobile home catalog cache invalidation

This commit is contained in:
tapframe 2026-06-27 16:19:17 +05:30
parent f2eddbaf5b
commit 4aa29087df
3 changed files with 94 additions and 24 deletions

View file

@ -1,6 +1,8 @@
package com.nuvio.app.features.home
import com.nuvio.app.core.i18n.localizedMediaTypeLabel
import com.nuvio.app.features.addons.AddonCatalog
import com.nuvio.app.features.addons.AddonManifest
import com.nuvio.app.features.addons.ManagedAddon
import com.nuvio.app.features.addons.enabledAddons
import com.nuvio.app.features.catalog.supportsPagination
@ -17,7 +19,21 @@ data class HomeCatalogDefinition(
val type: String,
val catalogId: String,
val supportsPagination: Boolean,
)
val descriptorSignature: String,
) {
val cacheKey: String
get() = "$key|$descriptorSignature"
}
fun buildHomeCatalogRefreshSignature(addons: List<ManagedAddon>): List<String> =
addons.enabledAddons().mapNotNull { addon ->
val manifest = addon.manifest ?: return@mapNotNull null
addon to manifest
}.flatMap { (addon, manifest) ->
manifest.catalogs.map { catalog ->
buildHomeCatalogDescriptorSignature(addon, manifest, catalog)
}
}.sorted()
fun buildHomeCatalogDefinitions(addons: List<ManagedAddon>): List<HomeCatalogDefinition> =
addons.enabledAddons().mapNotNull { addon ->
@ -41,8 +57,68 @@ fun buildHomeCatalogDefinitions(addons: List<ManagedAddon>): List<HomeCatalogDef
type = catalog.type,
catalogId = catalog.id,
supportsPagination = catalog.supportsPagination(),
descriptorSignature = buildHomeCatalogDescriptorSignature(addon, manifest, catalog),
)
}
}.distinctBy(HomeCatalogDefinition::key)
private fun buildHomeCatalogDescriptorSignature(
addon: ManagedAddon,
manifest: AddonManifest,
catalog: AddonCatalog,
): String =
buildString {
append(addon.displayTitle)
append('|')
append(manifest.id)
append('|')
append(manifest.name)
append('|')
append(manifest.version)
append('|')
append(manifest.description)
append('|')
append(manifest.logoUrl.orEmpty())
append('|')
append(manifest.transportUrl)
append('|')
append(manifest.types.joinToString(","))
append('|')
append(manifest.idPrefixes.joinToString(","))
append('|')
append(manifest.resources.joinToString(",") { resource ->
listOf(
resource.name,
resource.types.joinToString("/"),
resource.idPrefixes.joinToString("/"),
).joinToString(":")
})
append('|')
append(
listOf(
manifest.behaviorHints.configurable,
manifest.behaviorHints.configurationRequired,
manifest.behaviorHints.adult,
manifest.behaviorHints.p2p,
).joinToString(":"),
)
append('|')
append(catalog.type)
append('|')
append(catalog.id)
append('|')
append(catalog.name)
append('|')
append(catalog.supportsPagination())
append('|')
append(catalog.extra.joinToString(",") { extra ->
listOf(
extra.name,
extra.isRequired.toString(),
extra.options.joinToString("/"),
extra.optionsLimit?.toString().orEmpty(),
).joinToString(":")
})
}
internal fun String.displayLabel(): String = localizedMediaTypeLabel(this)

View file

@ -47,15 +47,15 @@ object HomeRepository {
val activeAddons = addons.enabledAddons()
val requests = buildHomeCatalogDefinitions(activeAddons)
currentDefinitions = requests
val requestKeys = requests.mapTo(mutableSetOf(), HomeCatalogDefinition::key)
cachedSections = cachedSections.filterKeys(requestKeys::contains)
val requestCacheKeys = requests.mapTo(mutableSetOf(), HomeCatalogDefinition::cacheKey)
cachedSections = cachedSections.filterKeys(requestCacheKeys::contains)
val requestKey = requests.joinToString(separator = "|") { request ->
"${request.manifestUrl}:${request.type}:${request.catalogId}"
request.cacheKey
}
if (!force && activeRequestKey == requestKey && _uiState.value.isLoading) return
if (!force && requestKey == lastRequestKey && requestKeys.all(cachedSections::containsKey)) {
if (!force && requestKey == lastRequestKey && requestCacheKeys.all(cachedSections::containsKey)) {
if (_uiState.value.sections.isEmpty() || _uiState.value.heroItems.isEmpty()) {
applyCurrentSettings()
}
@ -90,7 +90,7 @@ object HomeRepository {
snapshot = HomeCatalogSettingsRepository.snapshot(),
)
val pendingRequests = prioritizedRequests.filter { definition ->
force || cachedSections[definition.key] == null
force || cachedSections[definition.cacheKey] == null
}
if (pendingRequests.isEmpty()) {
publishCurrentState(
@ -108,16 +108,20 @@ object HomeRepository {
pendingRequests.chunked(HOME_CATALOG_FETCH_BATCH_SIZE).forEach { batch ->
if (activeRequestKey != requestKey) return@launch
val results = batch.map { request ->
async { runCatching { request.toSection() } }
async { request to runCatching { request.toSection() } }
}.awaitAll()
if (activeRequestKey != requestKey) return@launch
results.mapNotNull { it.getOrNull() }.forEach { section ->
loadedSections[section.key] = section
results.mapNotNull { (request, result) ->
result.getOrNull()?.let { section -> request.cacheKey to section }
}.forEach { (cacheKey, section) ->
loadedSections[cacheKey] = section
}
if (firstErrorMessage == null) {
firstErrorMessage = results.firstNotNullOfOrNull { it.exceptionOrNull()?.message }
firstErrorMessage = results.firstNotNullOfOrNull { (_, result) ->
result.exceptionOrNull()?.message
}
}
cachedSections = loadedSections.toMap()
lastErrorMessage = firstErrorMessage
@ -190,7 +194,7 @@ object HomeRepository {
val preference = preferences[definition.key]
if (preference?.enabled == false) return@mapNotNull null
val section = cachedSections[definition.key]?.withReleaseFilter() ?: return@mapNotNull null
val section = cachedSections[definition.cacheKey]?.withReleaseFilter() ?: return@mapNotNull null
if (section.items.isEmpty()) return@mapNotNull null
val customTitle = preference?.customTitle.orEmpty()
section.copy(
@ -202,7 +206,7 @@ object HomeRepository {
val heroRandom = Random((requestKey?.hashCode() ?: 0).absoluteValue + 1)
currentDefinitions
.filter { definition -> preferences[definition.key]?.heroSourceEnabled != false }
.mapNotNull { definition -> cachedSections[definition.key] }
.mapNotNull { definition -> cachedSections[definition.cacheKey] }
.map { section -> section.withReleaseFilter() }
.flatMap { section -> section.items }
.distinctBy { item -> "${item.type}:${item.id}" }

View file

@ -433,18 +433,8 @@ fun HomeScreen(
.sorted()
}
val catalogRefreshKey = remember(availableManifests) {
availableManifests
.map { manifest ->
buildString {
append(manifest.transportUrl)
append(':')
append(manifest.catalogs.joinToString(separator = ",") { catalog ->
"${catalog.type}:${catalog.id}:${catalog.extra.count { it.isRequired }}"
})
}
}
.sorted()
val catalogRefreshKey = remember(enabledAddons) {
buildHomeCatalogRefreshSignature(enabledAddons)
}
LaunchedEffect(catalogRefreshKey) {