From de68ce2c309c2c5f43cd2b2e9bd491a85eff50c7 Mon Sep 17 00:00:00 2001 From: tapframe <85391825+tapframe@users.noreply.github.com> Date: Wed, 20 May 2026 16:45:35 +0530 Subject: [PATCH] ref(sync): pull method to support optional parameters for last watched and limit --- .../watching/sync/ProgressSyncAdapter.kt | 6 +- .../sync/SupabaseProgressSyncAdapter.kt | 16 ++++- .../watchprogress/WatchProgressRepository.kt | 70 +++++++++++-------- 3 files changed, 60 insertions(+), 32 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watching/sync/ProgressSyncAdapter.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watching/sync/ProgressSyncAdapter.kt index d8449cce..3109c4fc 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watching/sync/ProgressSyncAdapter.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watching/sync/ProgressSyncAdapter.kt @@ -14,7 +14,11 @@ data class ProgressSyncRecord( ) interface ProgressSyncAdapter { - suspend fun pull(profileId: Int): List + suspend fun pull( + profileId: Int, + sinceLastWatched: Long? = null, + limit: Int? = null, + ): List suspend fun push( profileId: Int, diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watching/sync/SupabaseProgressSyncAdapter.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watching/sync/SupabaseProgressSyncAdapter.kt index 083a3b93..6743de34 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watching/sync/SupabaseProgressSyncAdapter.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watching/sync/SupabaseProgressSyncAdapter.kt @@ -17,8 +17,20 @@ object SupabaseProgressSyncAdapter : ProgressSyncAdapter { encodeDefaults = true } - override suspend fun pull(profileId: Int): List { - val params = buildJsonObject { put("p_profile_id", profileId) } + override suspend fun pull( + profileId: Int, + sinceLastWatched: Long?, + limit: Int?, + ): List { + val params = buildJsonObject { + put("p_profile_id", profileId) + if (sinceLastWatched != null) { + put("p_since_last_watched", sinceLastWatched) + } + if (limit != null) { + put("p_limit", limit) + } + } val result = SupabaseProvider.client.postgrest.rpc("sync_pull_watch_progress", params) val serverEntries = result.decodeList() return serverEntries.map { entry -> diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watchprogress/WatchProgressRepository.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watchprogress/WatchProgressRepository.kt index d452d3a8..8f4569b3 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watchprogress/WatchProgressRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watchprogress/WatchProgressRepository.kt @@ -12,6 +12,7 @@ import com.nuvio.app.features.trakt.TraktProgressRepository import com.nuvio.app.features.trakt.TraktSettingsRepository import com.nuvio.app.features.trakt.shouldUseTraktProgress as shouldUseTraktProgressSource import com.nuvio.app.features.watching.application.WatchingActions +import com.nuvio.app.features.watching.sync.ProgressSyncRecord import com.nuvio.app.features.watching.sync.ProgressSyncAdapter import com.nuvio.app.features.watching.sync.SupabaseProgressSyncAdapter import kotlinx.coroutines.CancellationException @@ -180,38 +181,23 @@ object WatchProgressRepository { } runCatching { - val serverEntries = syncAdapter.pull(profileId = profileId) - + val sinceLastWatched = entriesByVideoId.values + .maxOfOrNull { entry -> entry.lastUpdatedEpochMs } + ?.takeIf { hasCompletedInitialNuvioSyncPull } + val serverEntries = syncAdapter.pull( + profileId = profileId, + sinceLastWatched = sinceLastWatched, + ) + val isIncrementalPull = sinceLastWatched != null val oldLocal = entriesByVideoId.toMap() - val newMap = mutableMapOf() + val newMap = if (isIncrementalPull) { + entriesByVideoId.toMutableMap() + } else { + mutableMapOf() + } serverEntries.forEach { entry -> - val videoId = entry.videoId - val cached = oldLocal[videoId] - newMap[videoId] = WatchProgressEntry( - contentType = entry.contentType, - parentMetaId = entry.contentId, - parentMetaType = cached?.parentMetaType ?: entry.contentType, - videoId = videoId, - title = cached?.title?.takeIf { it.isNotBlank() } ?: entry.contentId, - logo = cached?.logo, - poster = cached?.poster, - background = cached?.background, - seasonNumber = entry.season, - episodeNumber = entry.episode, - episodeTitle = cached?.episodeTitle, - episodeThumbnail = cached?.episodeThumbnail, - lastPositionMs = entry.position, - durationMs = entry.duration, - lastUpdatedEpochMs = entry.lastWatched, - providerName = cached?.providerName, - providerAddonId = cached?.providerAddonId, - lastStreamTitle = cached?.lastStreamTitle, - lastStreamSubtitle = cached?.lastStreamSubtitle, - pauseDescription = cached?.pauseDescription, - lastSourceUrl = cached?.lastSourceUrl, - isCompleted = isWatchProgressComplete(entry.position, entry.duration, false), - ) + newMap[entry.videoId] = entry.toWatchProgressEntry(cached = oldLocal[entry.videoId]) } entriesByVideoId = newMap @@ -232,6 +218,32 @@ object WatchProgressRepository { } } + private fun ProgressSyncRecord.toWatchProgressEntry(cached: WatchProgressEntry?): WatchProgressEntry = + WatchProgressEntry( + contentType = contentType, + parentMetaId = contentId, + parentMetaType = cached?.parentMetaType ?: contentType, + videoId = videoId, + title = cached?.title?.takeIf { it.isNotBlank() } ?: contentId, + logo = cached?.logo, + poster = cached?.poster, + background = cached?.background, + seasonNumber = season, + episodeNumber = episode, + episodeTitle = cached?.episodeTitle, + episodeThumbnail = cached?.episodeThumbnail, + lastPositionMs = position, + durationMs = duration, + lastUpdatedEpochMs = lastWatched, + providerName = cached?.providerName, + providerAddonId = cached?.providerAddonId, + lastStreamTitle = cached?.lastStreamTitle, + lastStreamSubtitle = cached?.lastStreamSubtitle, + pauseDescription = cached?.pauseDescription, + lastSourceUrl = cached?.lastSourceUrl, + isCompleted = isWatchProgressComplete(position, duration, false), + ) + private fun resolveRemoteMetadata() { val needsResolution = entriesByVideoId.values .filter { it.poster.isNullOrBlank() || it.background.isNullOrBlank() }