mirror of
https://github.com/FluxaMedia/fluxa.git
synced 2026-07-27 04:12:10 +00:00
Pull Stremio's library as a timestamped watchlist and reconcile it
LibraryItem never declared removed/_mtime so those fields were silently dropped by Gson before Rust ever saw them, even though library_state.rs already worked on raw JSON and could have used them. Adds the fields, StremioRepository.getLibraryWatchlistWithTimestamps, and wires it into the same mergeWatchlist reconciliation the other providers use -- but pull-only (reconcileWatchlistPullOnly), not the full push-capable path. Deliberately not pushing local watchlist changes to Stremio: that would need a "toggle membership" write that preserves whatever progress/state fields already exist on the remote item (a read-modify-write), which isn't built and isn't safe to guess at without being able to verify Stremio's actual datastore PUT semantics (full-replace vs partial-patch) against a live account.
This commit is contained in:
parent
fea466cf27
commit
1039bfa5d3
4 changed files with 55 additions and 1 deletions
|
|
@ -75,6 +75,10 @@ internal class HomeLibraryCoordinator(
|
|||
.getOrDefault(emptyList())
|
||||
} ?: emptyList()
|
||||
}
|
||||
val stremioAuthKey = profile?.authKey?.takeIf { !profile.isGuest }
|
||||
val stremioWatchlistWithTimestamps = stremioAuthKey?.let { authKey ->
|
||||
async(Dispatchers.IO) { repository.getLibraryWatchlistWithTimestamps(authKey) }
|
||||
}
|
||||
|
||||
val traktToken = profile?.traktAccessToken
|
||||
val traktPlannedWithListedAt = if (!traktToken.isNullOrBlank()) async(Dispatchers.IO) { traktRepository.getTraktWatchlistWithListedAt(traktToken) } else null
|
||||
|
|
@ -142,6 +146,13 @@ internal class HomeLibraryCoordinator(
|
|||
.onFailure { Log.w("HomeLibrary", "AniList watchlist reconcile failed", it) }
|
||||
}
|
||||
}
|
||||
if (stremioAuthKey != null) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
val remoteWatchlist = stremioWatchlistWithTimestamps?.await().orEmpty()
|
||||
runCatching { reconcileWatchlistPullOnly(remoteWatchlist) }
|
||||
.onFailure { Log.w("HomeLibrary", "Stremio watchlist reconcile failed", it) }
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w("HomeLibrary", "Failed to load library data", e)
|
||||
setLibraryState(_state.value.copy(
|
||||
|
|
@ -174,6 +185,18 @@ internal class HomeLibraryCoordinator(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun reconcileWatchlistPullOnly(remoteEntries: List<Pair<Meta, Long>>) {
|
||||
val localSnapshot = watchlistManager.getWatchlistMembershipSnapshot()
|
||||
val remoteMembership = remoteEntries.map { (meta, updatedAtMs) ->
|
||||
ExternalSyncMergeBridge.RemoteMembershipItem(meta.id, updatedAtMs)
|
||||
}
|
||||
val plan = ExternalSyncMergeBridge.mergeWatchlist(localSnapshot, remoteMembership)
|
||||
val remoteById = remoteEntries.associateBy { it.first.id }
|
||||
plan.applyLocalAdd.forEach { id ->
|
||||
remoteById[id]?.first?.let { watchlistManager.applyRemoteWatchlistAdd(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun reconcileWatchedEpisodes(profile: UserProfile, remoteWatched: Map<String, Long>) {
|
||||
val localSnapshot = watchlistManager.getWatchedEpisodeMembershipSnapshot()
|
||||
val remoteMembership = remoteWatched.map { (videoId, watchedAtMs) ->
|
||||
|
|
|
|||
|
|
@ -1781,6 +1781,10 @@ object FluxaCoreNative {
|
|||
return value.takeUnless { it.isJsonNull }?.let { gson.fromJson<List<Meta>>(it, metaListType) } ?: emptyList()
|
||||
}
|
||||
|
||||
fun libraryWatchlistItems(items: List<LibraryItem>): com.google.gson.JsonElement {
|
||||
return FluxaCoreUniFfi.coreInvokeValue("libraryWatchlistItems", gson.toJson(items))
|
||||
}
|
||||
|
||||
fun filterHomeContinueWatching(items: List<Meta>, traktWatchedState: TraktWatchedState): List<Meta> {
|
||||
val args = JsonObject().apply {
|
||||
addProperty("itemsJson", gson.toJson(items))
|
||||
|
|
|
|||
|
|
@ -211,6 +211,31 @@ class StremioRepository @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun getLibraryWatchlistWithTimestamps(authKey: String): List<Pair<Meta, Long>> = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val response = authService.getDatastore(DatastoreRequest(authKey, "library"))
|
||||
val value = FluxaCoreNative.libraryWatchlistItems(response.body()?.result.orEmpty())
|
||||
if (value.isJsonNull) return@withContext emptyList()
|
||||
value.asJsonArray.mapNotNull { entry ->
|
||||
val item = entry.asJsonObject
|
||||
val id = item.get("id")?.takeUnless { it.isJsonNull }?.asString ?: return@mapNotNull null
|
||||
val updatedAtMs = item.get("updatedAtMs")?.takeUnless { it.isJsonNull }?.asLong ?: return@mapNotNull null
|
||||
val meta = Meta(
|
||||
id = id,
|
||||
name = item.get("name")?.takeUnless { it.isJsonNull }?.asString ?: id,
|
||||
type = item.get("type")?.takeUnless { it.isJsonNull }?.asString ?: "",
|
||||
poster = item.get("poster")?.takeUnless { it.isJsonNull }?.asString,
|
||||
background = item.get("background")?.takeUnless { it.isJsonNull }?.asString,
|
||||
reason = "Stremio"
|
||||
)
|
||||
meta to updatedAtMs
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
failureReporter.report("stremio.library.getWatchlist", e)
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getWatchedVideoIds(authKey: String, imdbId: String): List<String> = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val response = authService.getDatastore(DatastoreRequest(authKey, "library"))
|
||||
|
|
|
|||
|
|
@ -20,7 +20,9 @@ data class LibraryItem(
|
|||
val background: String? = null,
|
||||
val logo: String? = null,
|
||||
val state: LibraryItemState? = null,
|
||||
val lastWatched: String? = null
|
||||
val lastWatched: String? = null,
|
||||
val removed: Boolean? = null,
|
||||
val _mtime: String? = null
|
||||
) {
|
||||
val id: String get() = _id
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue