mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-08-16 12:24:40 +00:00
feat: catalog order to be cross-platform
This commit is contained in:
parent
850988bf87
commit
5249614b50
2 changed files with 134 additions and 20 deletions
|
|
@ -1,3 +1,5 @@
|
|||
package com.nuvio.app.core.sync
|
||||
|
||||
internal const val MOBILE_SYNC_PLATFORM = "mobile"
|
||||
internal const val HOME_CATALOG_SHARED_SYNC_PLATFORM = "home_catalog_shared"
|
||||
internal val HOME_CATALOG_LEGACY_SYNC_PLATFORMS = listOf(MOBILE_SYNC_PLATFORM, "tv")
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ package com.nuvio.app.features.home
|
|||
import co.touchlab.kermit.Logger
|
||||
import com.nuvio.app.core.auth.AuthRepository
|
||||
import com.nuvio.app.core.auth.AuthState
|
||||
import com.nuvio.app.core.sync.MOBILE_SYNC_PLATFORM
|
||||
import com.nuvio.app.core.sync.HOME_CATALOG_LEGACY_SYNC_PLATFORMS
|
||||
import com.nuvio.app.core.sync.HOME_CATALOG_SHARED_SYNC_PLATFORM
|
||||
import com.nuvio.app.core.network.SupabaseProvider
|
||||
import com.nuvio.app.features.profiles.ProfileRepository
|
||||
import io.github.jan.supabase.postgrest.postgrest
|
||||
|
|
@ -25,6 +26,7 @@ import kotlinx.serialization.Serializable
|
|||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.buildJsonObject
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.put
|
||||
|
||||
@Serializable
|
||||
|
|
@ -53,6 +55,14 @@ private data class SupabaseHomeCatalogSettingsBlob(
|
|||
@SerialName("updated_at") val updatedAt: String? = null,
|
||||
)
|
||||
|
||||
private data class RemoteHomeCatalogSettings(
|
||||
val platform: String,
|
||||
val payload: SyncHomeCatalogPayload,
|
||||
val updatedAt: String?,
|
||||
val hasHideUnreleasedContent: Boolean,
|
||||
val hasHideCatalogUnderline: Boolean,
|
||||
)
|
||||
|
||||
object HomeCatalogSettingsSyncService {
|
||||
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
|
||||
private val log = Logger.withTag("HomeCatalogSettingsSyncService")
|
||||
|
|
@ -62,6 +72,8 @@ object HomeCatalogSettingsSyncService {
|
|||
}
|
||||
|
||||
private const val PUSH_DEBOUNCE_MS = 1500L
|
||||
private const val HIDE_UNRELEASED_CONTENT_KEY = "hide_unreleased_content"
|
||||
private const val HIDE_CATALOG_UNDERLINE_KEY = "hide_catalog_underline"
|
||||
|
||||
@Volatile
|
||||
var isSyncingFromRemote: Boolean = false
|
||||
|
|
@ -76,31 +88,18 @@ object HomeCatalogSettingsSyncService {
|
|||
|
||||
suspend fun pullFromServer(profileId: Int) {
|
||||
runCatching {
|
||||
val params = buildJsonObject {
|
||||
put("p_profile_id", profileId)
|
||||
put("p_platform", MOBILE_SYNC_PLATFORM)
|
||||
}
|
||||
val result = SupabaseProvider.client.postgrest.rpc("sync_pull_home_catalog_settings", params)
|
||||
val blobs = result.decodeList<SupabaseHomeCatalogSettingsBlob>()
|
||||
val blob = blobs.firstOrNull()
|
||||
val localPayload = HomeCatalogSettingsRepository.exportToSyncPayload()
|
||||
val remote = fetchBestRemotePayload(profileId, localPayload)
|
||||
|
||||
if (blob == null) {
|
||||
if (remote == null) {
|
||||
log.i { "pullFromServer — no remote home catalog settings found" }
|
||||
val localPayload = HomeCatalogSettingsRepository.exportToSyncPayload()
|
||||
if (localPayload.items.isNotEmpty()) {
|
||||
pushToRemote(profileId)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
val remotePayload = runCatching {
|
||||
json.decodeFromJsonElement(SyncHomeCatalogPayload.serializer(), blob.settingsJson)
|
||||
}.getOrNull()
|
||||
|
||||
if (remotePayload == null) {
|
||||
log.w { "pullFromServer — failed to parse remote home catalog settings" }
|
||||
return
|
||||
}
|
||||
val remotePayload = remote.payload
|
||||
|
||||
if (remotePayload.items.isEmpty()) {
|
||||
log.i { "pullFromServer — remote has empty items, preserving local catalog order" }
|
||||
|
|
@ -118,6 +117,9 @@ object HomeCatalogSettingsSyncService {
|
|||
HomeCatalogSettingsRepository.applyFromRemote(remotePayload)
|
||||
isSyncingFromRemote = false
|
||||
log.i { "pullFromServer — applied ${remotePayload.items.size} items from remote" }
|
||||
if (remote.platform != HOME_CATALOG_SHARED_SYNC_PLATFORM) {
|
||||
pushToRemote(profileId)
|
||||
}
|
||||
}.onFailure { e ->
|
||||
isSyncingFromRemote = false
|
||||
log.e(e) { "pullFromServer — FAILED" }
|
||||
|
|
@ -142,11 +144,11 @@ object HomeCatalogSettingsSyncService {
|
|||
private suspend fun pushToRemote(profileId: Int) {
|
||||
runCatching {
|
||||
val payload = HomeCatalogSettingsRepository.exportToSyncPayload()
|
||||
val jsonElement = json.encodeToJsonElement(SyncHomeCatalogPayload.serializer(), payload)
|
||||
val jsonElement = mergedSharedPayloadJson(profileId, payload)
|
||||
|
||||
val params = buildJsonObject {
|
||||
put("p_profile_id", profileId)
|
||||
put("p_platform", MOBILE_SYNC_PLATFORM)
|
||||
put("p_platform", HOME_CATALOG_SHARED_SYNC_PLATFORM)
|
||||
put("p_settings_json", jsonElement)
|
||||
}
|
||||
SupabaseProvider.client.postgrest.rpc("sync_push_home_catalog_settings", params)
|
||||
|
|
@ -172,4 +174,114 @@ object HomeCatalogSettingsSyncService {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun fetchBestRemotePayload(
|
||||
profileId: Int,
|
||||
localPayload: SyncHomeCatalogPayload,
|
||||
): RemoteHomeCatalogSettings? {
|
||||
val shared = fetchRemotePayload(
|
||||
profileId = profileId,
|
||||
platform = HOME_CATALOG_SHARED_SYNC_PLATFORM,
|
||||
localPayload = localPayload,
|
||||
)
|
||||
val legacyRows = HOME_CATALOG_LEGACY_SYNC_PLATFORMS
|
||||
.mapNotNull { platform ->
|
||||
fetchRemotePayload(
|
||||
profileId = profileId,
|
||||
platform = platform,
|
||||
localPayload = localPayload,
|
||||
)
|
||||
}
|
||||
val rows = listOfNotNull(shared) + legacyRows
|
||||
val selected = rows
|
||||
.filter { it.payload.items.isNotEmpty() }
|
||||
.maxByOrNull { it.updatedAt.orEmpty() }
|
||||
?: shared
|
||||
?: legacyRows.maxByOrNull { it.updatedAt.orEmpty() }
|
||||
|
||||
return selected?.withNewestStandaloneSettings(rows)
|
||||
}
|
||||
|
||||
private suspend fun fetchRemotePayload(
|
||||
profileId: Int,
|
||||
platform: String,
|
||||
localPayload: SyncHomeCatalogPayload,
|
||||
): RemoteHomeCatalogSettings? {
|
||||
val blob = fetchRemoteBlob(profileId, platform) ?: return null
|
||||
val payload = decodePayloadPreservingLocalDefaults(blob.settingsJson, localPayload)
|
||||
if (payload == null) {
|
||||
log.w { "pullFromServer — failed to parse remote home catalog settings for platform=$platform" }
|
||||
return null
|
||||
}
|
||||
return RemoteHomeCatalogSettings(
|
||||
platform = platform,
|
||||
payload = payload,
|
||||
updatedAt = blob.updatedAt,
|
||||
hasHideUnreleasedContent = blob.settingsJson.containsKey(HIDE_UNRELEASED_CONTENT_KEY),
|
||||
hasHideCatalogUnderline = blob.settingsJson.containsKey(HIDE_CATALOG_UNDERLINE_KEY),
|
||||
)
|
||||
}
|
||||
|
||||
private fun RemoteHomeCatalogSettings.withNewestStandaloneSettings(
|
||||
rows: List<RemoteHomeCatalogSettings>,
|
||||
): RemoteHomeCatalogSettings {
|
||||
val hideUnreleasedSource = rows
|
||||
.filter { it.hasHideUnreleasedContent }
|
||||
.maxByOrNull { it.updatedAt.orEmpty() }
|
||||
val hideUnderlineSource = rows
|
||||
.filter { it.hasHideCatalogUnderline }
|
||||
.maxByOrNull { it.updatedAt.orEmpty() }
|
||||
|
||||
return copy(
|
||||
payload = payload.copy(
|
||||
hideUnreleasedContent = hideUnreleasedSource?.payload?.hideUnreleasedContent
|
||||
?: payload.hideUnreleasedContent,
|
||||
hideCatalogUnderline = hideUnderlineSource?.payload?.hideCatalogUnderline
|
||||
?: payload.hideCatalogUnderline,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun fetchRemoteBlob(
|
||||
profileId: Int,
|
||||
platform: String,
|
||||
): SupabaseHomeCatalogSettingsBlob? {
|
||||
val params = buildJsonObject {
|
||||
put("p_profile_id", profileId)
|
||||
put("p_platform", platform)
|
||||
}
|
||||
val result = SupabaseProvider.client.postgrest.rpc("sync_pull_home_catalog_settings", params)
|
||||
return result.decodeList<SupabaseHomeCatalogSettingsBlob>().firstOrNull()
|
||||
}
|
||||
|
||||
private fun decodePayloadPreservingLocalDefaults(
|
||||
settingsJson: JsonObject,
|
||||
localPayload: SyncHomeCatalogPayload,
|
||||
): SyncHomeCatalogPayload? = runCatching {
|
||||
val decoded = json.decodeFromJsonElement(SyncHomeCatalogPayload.serializer(), settingsJson)
|
||||
decoded.copy(
|
||||
hideUnreleasedContent = if (settingsJson.containsKey(HIDE_UNRELEASED_CONTENT_KEY)) {
|
||||
decoded.hideUnreleasedContent
|
||||
} else {
|
||||
localPayload.hideUnreleasedContent
|
||||
},
|
||||
hideCatalogUnderline = if (settingsJson.containsKey(HIDE_CATALOG_UNDERLINE_KEY)) {
|
||||
decoded.hideCatalogUnderline
|
||||
} else {
|
||||
localPayload.hideCatalogUnderline
|
||||
},
|
||||
)
|
||||
}.getOrNull()
|
||||
|
||||
private suspend fun mergedSharedPayloadJson(
|
||||
profileId: Int,
|
||||
payload: SyncHomeCatalogPayload,
|
||||
): JsonObject {
|
||||
val localJson = json.encodeToJsonElement(SyncHomeCatalogPayload.serializer(), payload).jsonObject
|
||||
val remoteJson = fetchRemoteBlob(profileId, HOME_CATALOG_SHARED_SYNC_PLATFORM)?.settingsJson
|
||||
return buildJsonObject {
|
||||
remoteJson?.forEach { (key, value) -> put(key, value) }
|
||||
localJson.forEach { (key, value) -> put(key, value) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue