mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-20 17:02:02 +00:00
ref: cache and log cleanup
This commit is contained in:
parent
8464f4db48
commit
5aee64e25e
5 changed files with 103 additions and 608 deletions
|
|
@ -14,7 +14,6 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import co.touchlab.kermit.Logger
|
||||
import com.nuvio.app.core.network.NetworkCondition
|
||||
import com.nuvio.app.core.network.NetworkStatusRepository
|
||||
import com.nuvio.app.core.ui.LocalNuvioBottomNavigationOverlayPadding
|
||||
|
|
@ -356,43 +355,6 @@ fun HomeScreen(
|
|||
todayIsoDate = CurrentDateProvider.todayIsoDate(),
|
||||
)
|
||||
}
|
||||
LaunchedEffect(
|
||||
isTraktProgressActive,
|
||||
traktSettingsUiState.continueWatchingDaysCap,
|
||||
watchProgressUiState.hasLoadedRemoteProgress,
|
||||
continueWatchingPreferences.upNextFromFurthestEpisode,
|
||||
watchProgressUiState.entries,
|
||||
effectiveWatchProgressEntries,
|
||||
allNextUpSeedEntries,
|
||||
recentNextUpSeedEntries,
|
||||
nextUpSuppressedSeriesIds,
|
||||
visibleContinueWatchingEntries,
|
||||
completedSeriesCandidates,
|
||||
cachedInProgressItems,
|
||||
cachedNextUpItems,
|
||||
nextUpItemsBySeries,
|
||||
processedNextUpContentIds,
|
||||
effectivNextUpItems,
|
||||
continueWatchingItems,
|
||||
) {
|
||||
homeCwLog.d {
|
||||
"build summary source=${if (isTraktProgressActive) "trakt" else "nuvio_sync"} " +
|
||||
"remoteLoaded=${watchProgressUiState.hasLoadedRemoteProgress} " +
|
||||
"daysCap=${traktSettingsUiState.continueWatchingDaysCap} " +
|
||||
"raw=${watchProgressUiState.entries.size} rawSources=${watchProgressUiState.entries.debugSourceCounts()} " +
|
||||
"effective=${effectiveWatchProgressEntries.size} seedAll=${allNextUpSeedEntries.size} " +
|
||||
"seedRecent=${recentNextUpSeedEntries.size} seedSuppressed=${nextUpSuppressedSeriesIds.size} " +
|
||||
"useFurthest=${continueWatchingPreferences.upNextFromFurthestEpisode} " +
|
||||
"visibleInProgress=${visibleContinueWatchingEntries.size} " +
|
||||
"completedCandidates=${completedSeriesCandidates.size} cachedInProgress=${cachedInProgressItems.size} " +
|
||||
"cachedNextUp=${cachedNextUpItems.size} liveNextUp=${nextUpItemsBySeries.size} " +
|
||||
"processedNextUp=${processedNextUpContentIds.size} " +
|
||||
"effectiveNextUp=${effectivNextUpItems.size} final=${continueWatchingItems.size} " +
|
||||
"rawItems=${watchProgressUiState.entries.debugWatchProgressSummary()} " +
|
||||
"completed=${completedSeriesCandidates.debugCompletedSeriesSummary()} " +
|
||||
"finalItems=${continueWatchingItems.debugContinueWatchingSummary()}"
|
||||
}
|
||||
}
|
||||
val availableManifests = remember(addonsUiState.addons) {
|
||||
addonsUiState.addons.mapNotNull { addon -> addon.manifest }
|
||||
}
|
||||
|
|
@ -430,35 +392,51 @@ fun HomeScreen(
|
|||
|
||||
LaunchedEffect(
|
||||
completedSeriesCandidates,
|
||||
cachedNextUpItems,
|
||||
visibleContinueWatchingEntries,
|
||||
metaProviderKey,
|
||||
continueWatchingPreferences.showUnairedNextUp,
|
||||
) {
|
||||
if (completedSeriesCandidates.isEmpty()) {
|
||||
homeCwLog.d {
|
||||
"next-up resolve skipped: no completed series candidates " +
|
||||
"entries=${effectiveWatchProgressEntries.size} sources=${effectiveWatchProgressEntries.debugSourceCounts()}"
|
||||
}
|
||||
nextUpItemsBySeries = emptyMap()
|
||||
processedNextUpContentIds = emptySet()
|
||||
return@LaunchedEffect
|
||||
}
|
||||
|
||||
if (metaProviderKey.isEmpty()) {
|
||||
homeCwLog.d {
|
||||
"next-up resolve deferred: no meta providers candidates=${completedSeriesCandidates.size} " +
|
||||
"candidates=${completedSeriesCandidates.debugCompletedSeriesSummary()}"
|
||||
val cachedResolvedNextUpItems = completedSeriesCandidates.mapNotNull { candidate ->
|
||||
val cached = cachedNextUpItems[candidate.content.id] ?: return@mapNotNull null
|
||||
val item = cached.second
|
||||
if (
|
||||
item.nextUpSeedSeasonNumber != candidate.seasonNumber ||
|
||||
item.nextUpSeedEpisodeNumber != candidate.episodeNumber
|
||||
) {
|
||||
return@mapNotNull null
|
||||
}
|
||||
candidate.content.id to cached
|
||||
}.toMap()
|
||||
val candidatesToResolve = completedSeriesCandidates.filter { candidate ->
|
||||
candidate.content.id !in cachedResolvedNextUpItems
|
||||
}
|
||||
if (candidatesToResolve.isEmpty()) {
|
||||
nextUpItemsBySeries = cachedResolvedNextUpItems
|
||||
processedNextUpContentIds = completedSeriesCandidates.mapTo(mutableSetOf()) { candidate ->
|
||||
candidate.content.id
|
||||
}
|
||||
saveContinueWatchingSnapshots(
|
||||
nextUpItemsBySeries = cachedResolvedNextUpItems,
|
||||
visibleContinueWatchingEntries = visibleContinueWatchingEntries,
|
||||
todayIsoDate = CurrentDateProvider.todayIsoDate(),
|
||||
)
|
||||
return@LaunchedEffect
|
||||
}
|
||||
|
||||
if (metaProviderKey.isEmpty()) {
|
||||
return@LaunchedEffect
|
||||
}
|
||||
|
||||
val todayIsoDate = CurrentDateProvider.todayIsoDate()
|
||||
val semaphore = Semaphore(4)
|
||||
homeCwLog.d {
|
||||
"next-up resolve start candidates=${completedSeriesCandidates.size} " +
|
||||
"showUnaired=${continueWatchingPreferences.showUnairedNextUp} " +
|
||||
"metaProviders=${metaProviderKey.size} candidates=${completedSeriesCandidates.debugCompletedSeriesSummary()}"
|
||||
}
|
||||
val results = completedSeriesCandidates.map { completedEntry ->
|
||||
val freshResults = candidatesToResolve.map { completedEntry ->
|
||||
async {
|
||||
semaphore.withPermit {
|
||||
val meta = MetaDetailsRepository.fetch(
|
||||
|
|
@ -466,10 +444,6 @@ fun HomeScreen(
|
|||
id = completedEntry.content.id,
|
||||
)
|
||||
if (meta == null) {
|
||||
homeCwLog.d {
|
||||
"next-up meta miss content=${completedEntry.debugSummary()} " +
|
||||
"type=${completedEntry.content.type} id=${completedEntry.content.id}"
|
||||
}
|
||||
return@withPermit null
|
||||
}
|
||||
val nextEpisode = meta.nextReleasedEpisodeAfter(
|
||||
|
|
@ -479,84 +453,28 @@ fun HomeScreen(
|
|||
showUnairedNextUp = continueWatchingPreferences.showUnairedNextUp,
|
||||
)
|
||||
if (nextEpisode == null) {
|
||||
homeCwLog.d {
|
||||
"next-up no next episode content=${completedEntry.debugSummary()} " +
|
||||
"videos=${meta.videos.size}"
|
||||
}
|
||||
return@withPermit null
|
||||
}
|
||||
val item = completedEntry.toContinueWatchingSeed(meta)
|
||||
.toUpNextContinueWatchingItem(nextEpisode)
|
||||
if (nextUpDismissKey(item.parentMetaId, item.nextUpSeedSeasonNumber, item.nextUpSeedEpisodeNumber) in continueWatchingPreferences.dismissedNextUpKeys) {
|
||||
homeCwLog.d { "next-up dismissed item=${item.debugSummary()}" }
|
||||
return@withPermit null
|
||||
}
|
||||
homeCwLog.d {
|
||||
"next-up built seed=${completedEntry.debugSummary()} item=${item.debugSummary()} " +
|
||||
"released=${nextEpisode.released}"
|
||||
}
|
||||
completedEntry.content.id to (completedEntry.markedAtEpochMs to item)
|
||||
}
|
||||
}
|
||||
}.awaitAll().filterNotNull().toMap()
|
||||
val results = cachedResolvedNextUpItems + freshResults
|
||||
nextUpItemsBySeries = results
|
||||
processedNextUpContentIds = completedSeriesCandidates.mapTo(mutableSetOf()) { candidate ->
|
||||
candidate.content.id
|
||||
}
|
||||
|
||||
val nextUpCache = results.mapNotNull { (contentId, pair) ->
|
||||
val item = pair.second
|
||||
CachedNextUpItem(
|
||||
contentId = contentId,
|
||||
contentType = item.parentMetaType,
|
||||
name = item.title,
|
||||
poster = item.poster,
|
||||
backdrop = item.background,
|
||||
logo = item.logo,
|
||||
videoId = item.videoId,
|
||||
season = item.seasonNumber,
|
||||
episode = item.episodeNumber,
|
||||
episodeTitle = item.episodeTitle,
|
||||
episodeThumbnail = item.episodeThumbnail,
|
||||
pauseDescription = item.pauseDescription,
|
||||
released = item.released,
|
||||
hasAired = item.released?.let { released ->
|
||||
isReleasedBy(todayIsoDate = todayIsoDate, releasedDate = released)
|
||||
} ?: true,
|
||||
lastWatched = pair.first,
|
||||
sortTimestamp = pair.first,
|
||||
seedSeason = item.nextUpSeedSeasonNumber,
|
||||
seedEpisode = item.nextUpSeedEpisodeNumber,
|
||||
)
|
||||
}
|
||||
val inProgressCache = visibleContinueWatchingEntries.map { entry ->
|
||||
CachedInProgressItem(
|
||||
contentId = entry.parentMetaId,
|
||||
contentType = entry.contentType,
|
||||
name = entry.title,
|
||||
poster = entry.poster,
|
||||
backdrop = entry.background,
|
||||
logo = entry.logo,
|
||||
videoId = entry.videoId,
|
||||
season = entry.seasonNumber,
|
||||
episode = entry.episodeNumber,
|
||||
episodeTitle = entry.episodeTitle,
|
||||
episodeThumbnail = entry.episodeThumbnail,
|
||||
pauseDescription = entry.pauseDescription,
|
||||
position = entry.lastPositionMs,
|
||||
duration = entry.durationMs,
|
||||
lastWatched = entry.lastUpdatedEpochMs,
|
||||
progressPercent = entry.progressPercent,
|
||||
)
|
||||
}
|
||||
ContinueWatchingEnrichmentCache.saveSnapshots(
|
||||
nextUp = nextUpCache,
|
||||
inProgress = inProgressCache,
|
||||
saveContinueWatchingSnapshots(
|
||||
nextUpItemsBySeries = results,
|
||||
visibleContinueWatchingEntries = visibleContinueWatchingEntries,
|
||||
todayIsoDate = todayIsoDate,
|
||||
)
|
||||
homeCwLog.d {
|
||||
"next-up resolve complete results=${results.size} nextUpCache=${nextUpCache.size} " +
|
||||
"inProgressCache=${inProgressCache.size} items=${results.values.map { it.second }.debugContinueWatchingSummary()}"
|
||||
}
|
||||
}
|
||||
|
||||
val hasActiveAddons = addonsUiState.addons.any { it.manifest != null }
|
||||
|
|
@ -784,7 +702,6 @@ fun HomeScreen(
|
|||
private const val HOME_CATALOG_PREVIEW_LIMIT = 18
|
||||
private const val MILLIS_PER_DAY = 24L * 60L * 60L * 1000L
|
||||
private const val OPTIMISTIC_NEXT_UP_SEED_WINDOW_MS = 3L * 60L * 1000L
|
||||
private val homeCwLog = Logger.withTag("HomeCW")
|
||||
|
||||
internal fun filterEntriesForTraktContinueWatchingWindow(
|
||||
entries: List<WatchProgressEntry>,
|
||||
|
|
@ -1090,6 +1007,62 @@ private data class HomeContinueWatchingCandidate(
|
|||
val isProgressEntry: Boolean,
|
||||
)
|
||||
|
||||
private fun saveContinueWatchingSnapshots(
|
||||
nextUpItemsBySeries: Map<String, Pair<Long, ContinueWatchingItem>>,
|
||||
visibleContinueWatchingEntries: List<WatchProgressEntry>,
|
||||
todayIsoDate: String,
|
||||
) {
|
||||
val nextUpCache = nextUpItemsBySeries.mapNotNull { (contentId, pair) ->
|
||||
val item = pair.second
|
||||
CachedNextUpItem(
|
||||
contentId = contentId,
|
||||
contentType = item.parentMetaType,
|
||||
name = item.title,
|
||||
poster = item.poster,
|
||||
backdrop = item.background,
|
||||
logo = item.logo,
|
||||
videoId = item.videoId,
|
||||
season = item.seasonNumber,
|
||||
episode = item.episodeNumber,
|
||||
episodeTitle = item.episodeTitle,
|
||||
episodeThumbnail = item.episodeThumbnail,
|
||||
pauseDescription = item.pauseDescription,
|
||||
released = item.released,
|
||||
hasAired = item.released?.let { released ->
|
||||
isReleasedBy(todayIsoDate = todayIsoDate, releasedDate = released)
|
||||
} ?: true,
|
||||
lastWatched = pair.first,
|
||||
sortTimestamp = pair.first,
|
||||
seedSeason = item.nextUpSeedSeasonNumber,
|
||||
seedEpisode = item.nextUpSeedEpisodeNumber,
|
||||
)
|
||||
}
|
||||
val inProgressCache = visibleContinueWatchingEntries.map { entry ->
|
||||
CachedInProgressItem(
|
||||
contentId = entry.parentMetaId,
|
||||
contentType = entry.contentType,
|
||||
name = entry.title,
|
||||
poster = entry.poster,
|
||||
backdrop = entry.background,
|
||||
logo = entry.logo,
|
||||
videoId = entry.videoId,
|
||||
season = entry.seasonNumber,
|
||||
episode = entry.episodeNumber,
|
||||
episodeTitle = entry.episodeTitle,
|
||||
episodeThumbnail = entry.episodeThumbnail,
|
||||
pauseDescription = entry.pauseDescription,
|
||||
position = entry.lastPositionMs,
|
||||
duration = entry.durationMs,
|
||||
lastWatched = entry.lastUpdatedEpochMs,
|
||||
progressPercent = entry.progressPercent,
|
||||
)
|
||||
}
|
||||
ContinueWatchingEnrichmentCache.saveSnapshots(
|
||||
nextUp = nextUpCache,
|
||||
inProgress = inProgressCache,
|
||||
)
|
||||
}
|
||||
|
||||
private fun CompletedSeriesCandidate.toContinueWatchingSeed(meta: com.nuvio.app.features.details.MetaDetails) =
|
||||
WatchProgressEntry(
|
||||
contentType = content.type,
|
||||
|
|
@ -1201,83 +1174,3 @@ private fun ContinueWatchingItem.withFallbackMetadata(
|
|||
released = released ?: fallback.released,
|
||||
)
|
||||
}
|
||||
|
||||
private fun WatchProgressEntry.debugSummary(): String =
|
||||
buildString {
|
||||
append(parentMetaType)
|
||||
append(":")
|
||||
append(parentMetaId)
|
||||
if (seasonNumber != null || episodeNumber != null) {
|
||||
append(" s=")
|
||||
append(seasonNumber)
|
||||
append(" e=")
|
||||
append(episodeNumber)
|
||||
}
|
||||
append(" video=")
|
||||
append(videoId)
|
||||
append(" pct=")
|
||||
append(progressPercent)
|
||||
append(" completed=")
|
||||
append(isCompleted)
|
||||
append(" effectiveCompleted=")
|
||||
append(isEffectivelyCompleted)
|
||||
append(" src=")
|
||||
append(source)
|
||||
append(" last=")
|
||||
append(lastUpdatedEpochMs)
|
||||
}
|
||||
|
||||
private fun Collection<WatchProgressEntry>.debugWatchProgressSummary(limit: Int = 10): String =
|
||||
take(limit).joinToString(separator = " | ") { it.debugSummary() }.ifBlank { "none" }
|
||||
|
||||
private fun Collection<WatchProgressEntry>.debugSourceCounts(): String =
|
||||
groupingBy { it.source }
|
||||
.eachCount()
|
||||
.entries
|
||||
.sortedBy { it.key }
|
||||
.joinToString(separator = ",") { "${it.key}=${it.value}" }
|
||||
.ifBlank { "none" }
|
||||
|
||||
private fun CompletedSeriesCandidate.debugSummary(): String =
|
||||
buildString {
|
||||
append(content.type)
|
||||
append(":")
|
||||
append(content.id)
|
||||
append(" s=")
|
||||
append(seasonNumber)
|
||||
append(" e=")
|
||||
append(episodeNumber)
|
||||
append(" marked=")
|
||||
append(markedAtEpochMs)
|
||||
}
|
||||
|
||||
private fun Collection<CompletedSeriesCandidate>.debugCompletedSeriesSummary(limit: Int = 10): String =
|
||||
take(limit).joinToString(separator = " | ") { it.debugSummary() }.ifBlank { "none" }
|
||||
|
||||
private fun ContinueWatchingItem.debugSummary(): String =
|
||||
buildString {
|
||||
append(if (isNextUp) "next_up" else "in_progress")
|
||||
append(":")
|
||||
append(parentMetaType)
|
||||
append(":")
|
||||
append(parentMetaId)
|
||||
if (seasonNumber != null || episodeNumber != null) {
|
||||
append(" s=")
|
||||
append(seasonNumber)
|
||||
append(" e=")
|
||||
append(episodeNumber)
|
||||
}
|
||||
append(" video=")
|
||||
append(videoId)
|
||||
append(" seed=")
|
||||
append(nextUpSeedSeasonNumber)
|
||||
append("x")
|
||||
append(nextUpSeedEpisodeNumber)
|
||||
append(" progress=")
|
||||
append(progressFraction)
|
||||
append(" resume=")
|
||||
append(resumePositionMs)
|
||||
}
|
||||
|
||||
private fun Collection<ContinueWatchingItem>.debugContinueWatchingSummary(limit: Int = 10): String =
|
||||
take(limit).joinToString(separator = " | ") { it.debugSummary() }.ifBlank { "none" }
|
||||
|
|
|
|||
|
|
@ -111,12 +111,10 @@ object TraktProgressRepository {
|
|||
val requestId = nextRefreshRequestId()
|
||||
val headers = TraktAuthRepository.authorizedHeaders()
|
||||
if (headers == null) {
|
||||
log.d { "refreshNow request=$requestId skipped: missing authorized headers" }
|
||||
_uiState.value = TraktProgressUiState()
|
||||
return
|
||||
}
|
||||
|
||||
log.d { "refreshNow request=$requestId start currentEntries=${_uiState.value.entries.size}" }
|
||||
_uiState.value = _uiState.value.copy(isLoading = true, errorMessage = null)
|
||||
|
||||
val playbackEntries = runCatching {
|
||||
|
|
@ -140,10 +138,6 @@ object TraktProgressRepository {
|
|||
errorMessage = null,
|
||||
hasLoadedRemoteProgress = false,
|
||||
)
|
||||
log.d {
|
||||
"refreshNow request=$requestId playback applied entries=${playbackEntries.size} " +
|
||||
"sources=${playbackEntries.debugSourceCounts()} items=${playbackEntries.debugWatchProgressSummary()}"
|
||||
}
|
||||
|
||||
if (playbackEntries.isNotEmpty()) {
|
||||
launchHydration(requestId = requestId, entries = playbackEntries)
|
||||
|
|
@ -178,11 +172,6 @@ object TraktProgressRepository {
|
|||
errorMessage = null,
|
||||
hasLoadedRemoteProgress = true,
|
||||
)
|
||||
log.d {
|
||||
"refreshNow request=$requestId completed snapshot applied " +
|
||||
"completedEntries=${completedEntries.size} merged=${merged.size} " +
|
||||
"sources=${merged.debugSourceCounts()} items=${merged.debugWatchProgressSummary()}"
|
||||
}
|
||||
|
||||
if (merged.isNotEmpty()) {
|
||||
launchHydration(requestId = requestId, entries = merged)
|
||||
|
|
@ -212,10 +201,6 @@ object TraktProgressRepository {
|
|||
isLoading = false,
|
||||
errorMessage = null,
|
||||
)
|
||||
log.d {
|
||||
"hydrate request=$requestId applied hydrated=${hydrated.size} merged=${merged.size} " +
|
||||
"items=${merged.debugWatchProgressSummary()}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -228,10 +213,6 @@ object TraktProgressRepository {
|
|||
current[normalizedEntry.videoId] = normalizedEntry
|
||||
}
|
||||
_uiState.value = _uiState.value.copy(entries = current.values.sortedByDescending { it.lastUpdatedEpochMs })
|
||||
log.d {
|
||||
"optimistic progress applied entry=${normalizedEntry.debugSummary()} " +
|
||||
"entries=${_uiState.value.entries.size}"
|
||||
}
|
||||
}
|
||||
|
||||
fun applyOptimisticRemoval(videoId: String) {
|
||||
|
|
@ -239,7 +220,6 @@ object TraktProgressRepository {
|
|||
if (videoId.isBlank()) return
|
||||
val filtered = _uiState.value.entries.filterNot { it.videoId == videoId }
|
||||
_uiState.value = _uiState.value.copy(entries = filtered)
|
||||
log.d { "optimistic removal videoId=$videoId entries=${filtered.size}" }
|
||||
}
|
||||
|
||||
fun applyOptimisticRemoval(
|
||||
|
|
@ -260,10 +240,6 @@ object TraktProgressRepository {
|
|||
}
|
||||
}
|
||||
_uiState.value = _uiState.value.copy(entries = filtered)
|
||||
log.d {
|
||||
"optimistic removal contentId=$normalizedContentId s=$seasonNumber e=$episodeNumber " +
|
||||
"entries=${filtered.size}"
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun removeProgress(
|
||||
|
|
@ -275,7 +251,6 @@ object TraktProgressRepository {
|
|||
if (normalizedContentId.isBlank()) return
|
||||
val headers = TraktAuthRepository.authorizedHeaders() ?: return
|
||||
|
||||
log.d { "removeProgress start contentId=$normalizedContentId s=$seasonNumber e=$episodeNumber" }
|
||||
applyOptimisticRemoval(
|
||||
contentId = normalizedContentId,
|
||||
seasonNumber = seasonNumber,
|
||||
|
|
@ -343,12 +318,10 @@ object TraktProgressRepository {
|
|||
}
|
||||
}
|
||||
|
||||
log.d { "removeProgress complete contentId=$normalizedContentId refreshing" }
|
||||
refreshNow()
|
||||
}
|
||||
|
||||
private suspend fun fetchPlaybackEntries(headers: Map<String, String>): List<WatchProgressEntry> = withContext(Dispatchers.Default) {
|
||||
log.d { "fetchPlaybackEntries start" }
|
||||
val payloads = coroutineScope {
|
||||
val moviesPayload = async {
|
||||
httpGetTextWithHeaders(
|
||||
|
|
@ -371,10 +344,6 @@ object TraktProgressRepository {
|
|||
|
||||
val moviePlayback = json.decodeFromString<List<TraktPlaybackItem>>(moviesPayload)
|
||||
val episodePlayback = json.decodeFromString<List<TraktPlaybackItem>>(episodesPayload)
|
||||
log.d {
|
||||
"fetchPlaybackEntries raw movies=${moviePlayback.size} episodes=${episodePlayback.size} " +
|
||||
"movieItems=${moviePlayback.debugPlaybackSummary()} episodeItems=${episodePlayback.debugPlaybackSummary()}"
|
||||
}
|
||||
|
||||
val inProgressMovies = moviePlayback.mapIndexedNotNull { index, item ->
|
||||
mapPlaybackMovie(item = item, fallbackIndex = index)
|
||||
|
|
@ -384,15 +353,10 @@ object TraktProgressRepository {
|
|||
}
|
||||
|
||||
val merged = mergeNewestByVideoId(inProgressMovies + inProgressEpisodes)
|
||||
log.d {
|
||||
"fetchPlaybackEntries mapped movies=${inProgressMovies.size} episodes=${inProgressEpisodes.size} " +
|
||||
"merged=${merged.size} items=${merged.debugWatchProgressSummary()}"
|
||||
}
|
||||
merged
|
||||
}
|
||||
|
||||
private suspend fun fetchHistoryEntries(headers: Map<String, String>): List<WatchProgressEntry> = withContext(Dispatchers.Default) {
|
||||
log.d { "fetchHistoryEntries start limit=$HISTORY_LIMIT" }
|
||||
val payloads = coroutineScope {
|
||||
val historyPayload = async {
|
||||
httpGetTextWithHeaders(
|
||||
|
|
@ -414,10 +378,6 @@ object TraktProgressRepository {
|
|||
val movieHistoryPayload = payloads[1]
|
||||
val episodeHistory = json.decodeFromString<List<TraktHistoryEpisodeItem>>(historyPayload)
|
||||
val movieHistory = json.decodeFromString<List<TraktHistoryMovieItem>>(movieHistoryPayload)
|
||||
log.d {
|
||||
"fetchHistoryEntries raw episodes=${episodeHistory.size} movies=${movieHistory.size} " +
|
||||
"episodeItems=${episodeHistory.debugHistoryEpisodeSummary()} movieItems=${movieHistory.debugHistoryMovieSummary()}"
|
||||
}
|
||||
|
||||
val completedEpisodes = episodeHistory
|
||||
.mapIndexedNotNull { index, item -> mapHistoryEpisode(item = item, fallbackIndex = index) }
|
||||
|
|
@ -427,10 +387,6 @@ object TraktProgressRepository {
|
|||
.distinctBy { entry -> entry.videoId }
|
||||
|
||||
val merged = mergeNewestByVideoId(completedEpisodes + completedMovies)
|
||||
log.d {
|
||||
"fetchHistoryEntries mapped episodes=${completedEpisodes.size} movies=${completedMovies.size} " +
|
||||
"merged=${merged.size} items=${merged.debugWatchProgressSummary()}"
|
||||
}
|
||||
merged
|
||||
}
|
||||
|
||||
|
|
@ -444,10 +400,6 @@ object TraktProgressRepository {
|
|||
headers = headers,
|
||||
)
|
||||
val watchedShows = json.decodeFromString<List<TraktWatchedShowItem>>(payload)
|
||||
log.d {
|
||||
"fetchWatchedShowSeedEntries raw shows=${watchedShows.size} " +
|
||||
"items=${watchedShows.debugWatchedShowSummary()}"
|
||||
}
|
||||
val mapped = watchedShows
|
||||
.mapNotNull { item ->
|
||||
mapWatchedShowSeed(
|
||||
|
|
@ -456,10 +408,6 @@ object TraktProgressRepository {
|
|||
)
|
||||
}
|
||||
.sortedByDescending { entry -> entry.lastUpdatedEpochMs }
|
||||
log.d {
|
||||
"fetchWatchedShowSeedEntries mapped=${mapped.size} useFurthest=$useFurthestEpisode " +
|
||||
"items=${mapped.debugWatchProgressSummary()}"
|
||||
}
|
||||
mapped
|
||||
}
|
||||
|
||||
|
|
@ -914,165 +862,3 @@ private data class TraktEpisode(
|
|||
@SerialName("number") val number: Int? = null,
|
||||
@SerialName("ids") val ids: TraktExternalIds? = null,
|
||||
)
|
||||
|
||||
private fun WatchProgressEntry.debugSummary(): String =
|
||||
buildString {
|
||||
append(parentMetaType)
|
||||
append(":")
|
||||
append(parentMetaId)
|
||||
if (seasonNumber != null || episodeNumber != null) {
|
||||
append(" s=")
|
||||
append(seasonNumber)
|
||||
append(" e=")
|
||||
append(episodeNumber)
|
||||
}
|
||||
append(" video=")
|
||||
append(videoId)
|
||||
append(" pct=")
|
||||
append(progressPercent)
|
||||
append(" completed=")
|
||||
append(isCompleted)
|
||||
append(" effectiveCompleted=")
|
||||
append(isEffectivelyCompleted)
|
||||
append(" src=")
|
||||
append(source)
|
||||
append(" last=")
|
||||
append(lastUpdatedEpochMs)
|
||||
}
|
||||
|
||||
private fun Collection<WatchProgressEntry>.debugWatchProgressSummary(limit: Int = 10): String =
|
||||
take(limit).joinToString(separator = " | ") { it.debugSummary() }.ifBlank { "none" }
|
||||
|
||||
private fun Collection<WatchProgressEntry>.debugSourceCounts(): String =
|
||||
groupingBy { it.source }
|
||||
.eachCount()
|
||||
.entries
|
||||
.sortedBy { it.key }
|
||||
.joinToString(separator = ",") { "${it.key}=${it.value}" }
|
||||
.ifBlank { "none" }
|
||||
|
||||
private fun Collection<TraktPlaybackItem>.debugPlaybackSummary(limit: Int = 8): String =
|
||||
take(limit).joinToString(separator = " | ") { item ->
|
||||
val media = item.movie ?: item.show
|
||||
val episode = item.episode
|
||||
buildString {
|
||||
append(media?.title ?: "unknown")
|
||||
append(" ids=")
|
||||
append(media?.ids.debugIds())
|
||||
if (episode != null) {
|
||||
append(" ep=")
|
||||
append(episode.season)
|
||||
append("x")
|
||||
append(episode.number)
|
||||
append(" epIds=")
|
||||
append(episode.ids.debugIds())
|
||||
}
|
||||
append(" progress=")
|
||||
append(item.progress)
|
||||
append(" pausedAt=")
|
||||
append(item.pausedAt)
|
||||
append(" playbackId=")
|
||||
append(item.id)
|
||||
}
|
||||
}.ifBlank { "none" }
|
||||
|
||||
private fun Collection<TraktHistoryEpisodeItem>.debugHistoryEpisodeSummary(limit: Int = 8): String =
|
||||
take(limit).joinToString(separator = " | ") { item ->
|
||||
buildString {
|
||||
append(item.show?.title ?: "unknown")
|
||||
append(" ids=")
|
||||
append(item.show?.ids.debugIds())
|
||||
append(" ep=")
|
||||
append(item.episode?.season)
|
||||
append("x")
|
||||
append(item.episode?.number)
|
||||
append(" epIds=")
|
||||
append(item.episode?.ids.debugIds())
|
||||
append(" watchedAt=")
|
||||
append(item.watchedAt)
|
||||
}
|
||||
}.ifBlank { "none" }
|
||||
|
||||
private fun Collection<TraktHistoryMovieItem>.debugHistoryMovieSummary(limit: Int = 8): String =
|
||||
take(limit).joinToString(separator = " | ") { item ->
|
||||
buildString {
|
||||
append(item.movie?.title ?: "unknown")
|
||||
append(" ids=")
|
||||
append(item.movie?.ids.debugIds())
|
||||
append(" watchedAt=")
|
||||
append(item.watchedAt)
|
||||
}
|
||||
}.ifBlank { "none" }
|
||||
|
||||
private fun Collection<TraktWatchedShowItem>.debugWatchedShowSummary(limit: Int = 8): String =
|
||||
take(limit).joinToString(separator = " | ") { item ->
|
||||
val episodeCount = item.seasons.orEmpty().sumOf { season ->
|
||||
season.episodes.orEmpty().count { episode ->
|
||||
(episode.number ?: 0) > 0 && (episode.plays ?: 1) > 0
|
||||
}
|
||||
}
|
||||
val latest = item.seasons.orEmpty()
|
||||
.flatMap { season ->
|
||||
val seasonNumber = season.number
|
||||
season.episodes.orEmpty().mapNotNull { episode ->
|
||||
val episodeNumber = episode.number ?: return@mapNotNull null
|
||||
val watchedAt = episode.lastWatchedAt ?: item.lastWatchedAt
|
||||
TraktWatchedShowEpisodeSeed(
|
||||
season = seasonNumber ?: 0,
|
||||
episode = episodeNumber,
|
||||
watchedAt = watchedAt
|
||||
?.let { value ->
|
||||
runCatching { TraktPlatformClock.parseIsoDateTimeToEpochMs(value) }.getOrNull()
|
||||
}
|
||||
?: 0L,
|
||||
)
|
||||
}
|
||||
}
|
||||
.maxWithOrNull(
|
||||
compareBy<TraktWatchedShowEpisodeSeed>(
|
||||
{ it.watchedAt },
|
||||
{ it.season },
|
||||
{ it.episode },
|
||||
),
|
||||
)
|
||||
buildString {
|
||||
append(item.show?.title ?: "unknown")
|
||||
append(" ids=")
|
||||
append(item.show?.ids.debugIds())
|
||||
append(" episodes=")
|
||||
append(episodeCount)
|
||||
append(" latest=")
|
||||
append(latest?.season)
|
||||
append("x")
|
||||
append(latest?.episode)
|
||||
append(" lastWatchedAt=")
|
||||
append(item.lastWatchedAt)
|
||||
}
|
||||
}.ifBlank { "none" }
|
||||
|
||||
private fun TraktExternalIds?.debugIds(): String =
|
||||
if (this == null) {
|
||||
"none"
|
||||
} else {
|
||||
buildString {
|
||||
imdb?.takeIf { it.isNotBlank() }?.let {
|
||||
append("imdb:")
|
||||
append(it)
|
||||
}
|
||||
tmdb?.let {
|
||||
if (isNotEmpty()) append(",")
|
||||
append("tmdb:")
|
||||
append(it)
|
||||
}
|
||||
trakt?.let {
|
||||
if (isNotEmpty()) append(",")
|
||||
append("trakt:")
|
||||
append(it)
|
||||
}
|
||||
slug?.takeIf { it.isNotBlank() }?.let {
|
||||
if (isNotEmpty()) append(",")
|
||||
append("slug:")
|
||||
append(it)
|
||||
}
|
||||
}.ifBlank { "none" }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package com.nuvio.app.features.watching.sync
|
||||
|
||||
import co.touchlab.kermit.Logger
|
||||
import com.nuvio.app.core.network.SupabaseProvider
|
||||
import com.nuvio.app.features.watchprogress.WatchProgressEntry
|
||||
import io.github.jan.supabase.postgrest.postgrest
|
||||
|
|
@ -13,18 +12,16 @@ import kotlinx.serialization.json.encodeToJsonElement
|
|||
import kotlinx.serialization.json.put
|
||||
|
||||
object SupabaseProgressSyncAdapter : ProgressSyncAdapter {
|
||||
private val log = Logger.withTag("NuvioSyncProgress")
|
||||
private val json = Json {
|
||||
ignoreUnknownKeys = true
|
||||
encodeDefaults = true
|
||||
}
|
||||
|
||||
override suspend fun pull(profileId: Int): List<ProgressSyncRecord> {
|
||||
log.d { "pull start profileId=$profileId" }
|
||||
val params = buildJsonObject { put("p_profile_id", profileId) }
|
||||
val result = SupabaseProvider.client.postgrest.rpc("sync_pull_watch_progress", params)
|
||||
val serverEntries = result.decodeList<WatchProgressSyncEntry>()
|
||||
val records = serverEntries.map { entry ->
|
||||
return serverEntries.map { entry ->
|
||||
ProgressSyncRecord(
|
||||
contentId = entry.contentId,
|
||||
contentType = entry.contentType,
|
||||
|
|
@ -36,21 +33,12 @@ object SupabaseProgressSyncAdapter : ProgressSyncAdapter {
|
|||
lastWatched = entry.lastWatched,
|
||||
)
|
||||
}
|
||||
log.d {
|
||||
"pull returned raw=${serverEntries.size} records=${records.size} " +
|
||||
"items=${records.debugProgressRecordSummary()}"
|
||||
}
|
||||
return records
|
||||
}
|
||||
|
||||
override suspend fun push(
|
||||
profileId: Int,
|
||||
entries: Collection<WatchProgressEntry>,
|
||||
) {
|
||||
log.d {
|
||||
"push start profileId=$profileId entries=${entries.size} " +
|
||||
"items=${entries.debugWatchProgressEntrySummary()}"
|
||||
}
|
||||
val syncEntries = entries.map { entry ->
|
||||
WatchProgressSyncEntry(
|
||||
contentId = entry.parentMetaId,
|
||||
|
|
@ -69,17 +57,12 @@ object SupabaseProgressSyncAdapter : ProgressSyncAdapter {
|
|||
put("p_entries", json.encodeToJsonElement(syncEntries))
|
||||
}
|
||||
SupabaseProvider.client.postgrest.rpc("sync_push_watch_progress", params)
|
||||
log.d { "push complete profileId=$profileId entries=${syncEntries.size}" }
|
||||
}
|
||||
|
||||
override suspend fun delete(
|
||||
profileId: Int,
|
||||
entries: Collection<WatchProgressEntry>,
|
||||
) {
|
||||
log.d {
|
||||
"delete start profileId=$profileId entries=${entries.size} " +
|
||||
"items=${entries.debugWatchProgressEntrySummary()}"
|
||||
}
|
||||
val progressKeys = entries.map { entry ->
|
||||
if (entry.seasonNumber != null && entry.episodeNumber != null) {
|
||||
"${entry.parentMetaId}_s${entry.seasonNumber}e${entry.episodeNumber}"
|
||||
|
|
@ -92,7 +75,6 @@ object SupabaseProgressSyncAdapter : ProgressSyncAdapter {
|
|||
put("p_keys", json.encodeToJsonElement(progressKeys))
|
||||
}
|
||||
SupabaseProvider.client.postgrest.rpc("sync_delete_watch_progress", params)
|
||||
log.d { "delete complete profileId=$profileId keys=${progressKeys.joinToString(limit = 12)}" }
|
||||
}
|
||||
|
||||
private fun progressKeyForEntry(entry: WatchProgressEntry): String =
|
||||
|
|
@ -115,53 +97,3 @@ private data class WatchProgressSyncEntry(
|
|||
@SerialName("last_watched") val lastWatched: Long = 0,
|
||||
@SerialName("progress_key") val progressKey: String = "",
|
||||
)
|
||||
|
||||
private fun Collection<ProgressSyncRecord>.debugProgressRecordSummary(limit: Int = 10): String =
|
||||
take(limit).joinToString(separator = " | ") { record ->
|
||||
buildString {
|
||||
append(record.contentType)
|
||||
append(":")
|
||||
append(record.contentId)
|
||||
if (record.season != null || record.episode != null) {
|
||||
append(" s=")
|
||||
append(record.season)
|
||||
append(" e=")
|
||||
append(record.episode)
|
||||
}
|
||||
append(" video=")
|
||||
append(record.videoId)
|
||||
append(" pos=")
|
||||
append(record.position)
|
||||
append(" dur=")
|
||||
append(record.duration)
|
||||
append(" last=")
|
||||
append(record.lastWatched)
|
||||
}
|
||||
}.ifBlank { "none" }
|
||||
|
||||
private fun Collection<WatchProgressEntry>.debugWatchProgressEntrySummary(limit: Int = 10): String =
|
||||
take(limit).joinToString(separator = " | ") { entry ->
|
||||
buildString {
|
||||
append(entry.parentMetaType)
|
||||
append(":")
|
||||
append(entry.parentMetaId)
|
||||
if (entry.seasonNumber != null || entry.episodeNumber != null) {
|
||||
append(" s=")
|
||||
append(entry.seasonNumber)
|
||||
append(" e=")
|
||||
append(entry.episodeNumber)
|
||||
}
|
||||
append(" video=")
|
||||
append(entry.videoId)
|
||||
append(" pos=")
|
||||
append(entry.lastPositionMs)
|
||||
append(" dur=")
|
||||
append(entry.durationMs)
|
||||
append(" pct=")
|
||||
append(entry.progressPercent)
|
||||
append(" completed=")
|
||||
append(entry.isCompleted)
|
||||
append(" last=")
|
||||
append(entry.lastUpdatedEpochMs)
|
||||
}
|
||||
}.ifBlank { "none" }
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ internal object ContinueWatchingEnrichmentCache {
|
|||
}
|
||||
|
||||
private const val storageKey = "cw_enrichment_cache"
|
||||
private var lastPayloadHash: Int? = null
|
||||
|
||||
fun getNextUpSnapshot(): List<CachedNextUpItem> =
|
||||
loadPayload()?.nextUp ?: emptyList()
|
||||
|
|
@ -75,11 +76,17 @@ internal object ContinueWatchingEnrichmentCache {
|
|||
fun saveSnapshots(
|
||||
nextUp: List<CachedNextUpItem>,
|
||||
inProgress: List<CachedInProgressItem>,
|
||||
force: Boolean = false,
|
||||
) {
|
||||
val payload = CachedEnrichmentPayload(nextUp = nextUp, inProgress = inProgress)
|
||||
val payloadHash = payload.hashCode()
|
||||
if (!force && lastPayloadHash == payloadHash) return
|
||||
|
||||
val encoded = runCatching {
|
||||
json.encodeToString(CachedEnrichmentPayload(nextUp = nextUp, inProgress = inProgress))
|
||||
json.encodeToString(payload)
|
||||
}.getOrNull() ?: return
|
||||
ContinueWatchingEnrichmentStorage.savePayload(ProfileScopedKey.of(storageKey), encoded)
|
||||
lastPayloadHash = payloadHash
|
||||
}
|
||||
|
||||
private fun loadPayload(): CachedEnrichmentPayload? {
|
||||
|
|
@ -87,6 +94,8 @@ internal object ContinueWatchingEnrichmentCache {
|
|||
?: return null
|
||||
return runCatching {
|
||||
json.decodeFromString<CachedEnrichmentPayload>(raw)
|
||||
}.getOrNull()
|
||||
}.getOrNull()?.also { payload ->
|
||||
lastPayloadHash = payload.hashCode()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ 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.ProgressSyncAdapter
|
||||
import com.nuvio.app.features.watching.sync.ProgressSyncRecord
|
||||
import com.nuvio.app.features.watching.sync.SupabaseProgressSyncAdapter
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
|
@ -99,17 +98,7 @@ object WatchProgressRepository {
|
|||
if (authState !is AuthState.Authenticated || authState.isAnonymous) continue
|
||||
if (!hasCompletedInitialNuvioSyncPull || isPullingNuvioSyncFromServer) continue
|
||||
|
||||
log.d {
|
||||
"periodic NuvioSync pull start profileId=${ProfileRepository.activeProfileId} " +
|
||||
"entries=${entriesByVideoId.size}"
|
||||
}
|
||||
runCatching { pullFromServer(ProfileRepository.activeProfileId) }
|
||||
.onSuccess {
|
||||
log.d {
|
||||
"periodic NuvioSync pull complete profileId=${ProfileRepository.activeProfileId} " +
|
||||
"entries=${entriesByVideoId.size}"
|
||||
}
|
||||
}
|
||||
.onFailure { error ->
|
||||
if (error is CancellationException) throw error
|
||||
log.w { "Periodic NuvioSync pull failed: ${error.message}" }
|
||||
|
|
@ -171,13 +160,8 @@ object WatchProgressRepository {
|
|||
currentProfileId = profileId
|
||||
|
||||
val useTraktProgress = shouldUseTraktProgress()
|
||||
log.d {
|
||||
"pullFromServer start profileId=$profileId source=${if (useTraktProgress) "trakt" else "nuvio_sync"} " +
|
||||
"localEntries=${entriesByVideoId.size}"
|
||||
}
|
||||
|
||||
if (!useTraktProgress && isPullingNuvioSyncFromServer) {
|
||||
log.d { "pullFromServer NuvioSync skipped: pull already in flight profileId=$profileId" }
|
||||
return
|
||||
}
|
||||
if (!useTraktProgress) {
|
||||
|
|
@ -192,20 +176,11 @@ object WatchProgressRepository {
|
|||
log.e(e) { "Failed to pull Trakt progress" }
|
||||
}
|
||||
publish()
|
||||
log.d {
|
||||
"pullFromServer trakt complete entries=${TraktProgressRepository.uiState.value.entries.size} " +
|
||||
"sources=${TraktProgressRepository.uiState.value.entries.debugSourceCounts()} " +
|
||||
"items=${TraktProgressRepository.uiState.value.entries.debugWatchProgressEntrySummary()}"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
runCatching {
|
||||
val serverEntries = syncAdapter.pull(profileId = profileId)
|
||||
log.d {
|
||||
"pullFromServer NuvioSync returned ${serverEntries.size} records " +
|
||||
"items=${serverEntries.debugProgressRecordSummary()}"
|
||||
}
|
||||
|
||||
val oldLocal = entriesByVideoId.toMap()
|
||||
val newMap = mutableMapOf<String, WatchProgressEntry>()
|
||||
|
|
@ -244,10 +219,6 @@ object WatchProgressRepository {
|
|||
hasCompletedInitialNuvioSyncPull = true
|
||||
publish()
|
||||
persist()
|
||||
log.d {
|
||||
"pullFromServer NuvioSync applied entries=${entriesByVideoId.size} " +
|
||||
"items=${entriesByVideoId.values.debugWatchProgressEntrySummary()}"
|
||||
}
|
||||
|
||||
resolveRemoteMetadata()
|
||||
}.onFailure { e ->
|
||||
|
|
@ -267,14 +238,8 @@ object WatchProgressRepository {
|
|||
.groupBy { it.parentMetaId to it.contentType }
|
||||
|
||||
if (needsResolution.isEmpty()) {
|
||||
log.d { "resolveRemoteMetadata skipped: all entries have artwork" }
|
||||
return
|
||||
}
|
||||
log.d {
|
||||
"resolveRemoteMetadata start groups=${needsResolution.size} " +
|
||||
"entries=${needsResolution.values.sumOf { it.size }} " +
|
||||
"keys=${needsResolution.keys.joinToString(limit = 12) { (metaId, type) -> "$type:$metaId" }}"
|
||||
}
|
||||
|
||||
metadataResolutionJob?.cancel()
|
||||
metadataResolutionJob = syncScope.launch {
|
||||
|
|
@ -291,7 +256,6 @@ object WatchProgressRepository {
|
|||
MetaDetailsRepository.fetch(metaType, metaId)
|
||||
}.getOrNull()
|
||||
if (meta == null) {
|
||||
log.d { "resolveRemoteMetadata miss type=$metaType id=$metaId entries=${entries.size}" }
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -316,13 +280,8 @@ object WatchProgressRepository {
|
|||
}
|
||||
|
||||
publish()
|
||||
log.d {
|
||||
"resolveRemoteMetadata applied type=$metaType id=$metaId entries=${entries.size} " +
|
||||
"metaVideos=${meta.videos.size}"
|
||||
}
|
||||
}
|
||||
persist()
|
||||
log.d { "resolveRemoteMetadata complete entries=${entriesByVideoId.size}" }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -447,10 +406,6 @@ object WatchProgressRepository {
|
|||
isEnded = snapshot.isEnded,
|
||||
)
|
||||
if (!isCompleted && !shouldStoreWatchProgress(positionMs = positionMs, durationMs = durationMs)) {
|
||||
log.d {
|
||||
"upsert skipped below threshold video=${session.videoId} content=${session.parentMetaId} " +
|
||||
"s=${session.seasonNumber} e=${session.episodeNumber} pos=$positionMs dur=$durationMs ended=${snapshot.isEnded}"
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -484,10 +439,6 @@ object WatchProgressRepository {
|
|||
}
|
||||
|
||||
val useTraktProgress = shouldUseTraktProgress()
|
||||
log.d {
|
||||
"upsert progress source=${if (useTraktProgress) "trakt" else "nuvio_sync"} " +
|
||||
"entry=${entry.debugSummary()} snapshotEnded=${snapshot.isEnded}"
|
||||
}
|
||||
|
||||
entriesByVideoId[session.videoId] = entry
|
||||
if (useTraktProgress) {
|
||||
|
|
@ -508,9 +459,7 @@ object WatchProgressRepository {
|
|||
syncScope.launch {
|
||||
runCatching {
|
||||
val profileId = ProfileRepository.activeProfileId
|
||||
log.d { "pushScrobbleToServer profileId=$profileId entry=${entry.debugSummary()}" }
|
||||
syncAdapter.push(profileId = profileId, entries = listOf(entry))
|
||||
log.d { "pushScrobbleToServer complete profileId=$profileId video=${entry.videoId}" }
|
||||
}.onFailure { e ->
|
||||
log.e(e) { "Failed to push watch progress scrobble" }
|
||||
}
|
||||
|
|
@ -523,12 +472,7 @@ object WatchProgressRepository {
|
|||
runCatching {
|
||||
if (entries.isEmpty()) return@runCatching
|
||||
val profileId = ProfileRepository.activeProfileId
|
||||
log.d {
|
||||
"pushDeleteToServer profileId=$profileId entries=${entries.size} " +
|
||||
"items=${entries.debugWatchProgressEntrySummary()}"
|
||||
}
|
||||
syncAdapter.delete(profileId = profileId, entries = entries)
|
||||
log.d { "pushDeleteToServer complete profileId=$profileId entries=${entries.size}" }
|
||||
}.onFailure { e ->
|
||||
log.e(e) { "Failed to push watch progress delete" }
|
||||
}
|
||||
|
|
@ -538,11 +482,6 @@ object WatchProgressRepository {
|
|||
private fun publish() {
|
||||
val entries = currentEntries()
|
||||
val sortedEntries = entries.sortedByDescending { it.lastUpdatedEpochMs }
|
||||
log.d {
|
||||
"publish source=${if (shouldUseTraktProgress()) "trakt" else "nuvio_sync"} " +
|
||||
"entries=${sortedEntries.size} cw=${sortedEntries.continueWatchingEntries().size} " +
|
||||
"sources=${sortedEntries.debugSourceCounts()} items=${sortedEntries.debugWatchProgressEntrySummary()}"
|
||||
}
|
||||
_uiState.value = WatchProgressUiState(
|
||||
entries = sortedEntries,
|
||||
hasLoadedRemoteProgress = if (shouldUseTraktProgress()) {
|
||||
|
|
@ -575,67 +514,3 @@ object WatchProgressRepository {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
private fun ProgressSyncRecord.debugSummary(): String =
|
||||
buildString {
|
||||
append(contentType)
|
||||
append(":")
|
||||
append(contentId)
|
||||
if (season != null || episode != null) {
|
||||
append(" s=")
|
||||
append(season)
|
||||
append(" e=")
|
||||
append(episode)
|
||||
}
|
||||
append(" video=")
|
||||
append(videoId)
|
||||
append(" pos=")
|
||||
append(position)
|
||||
append(" dur=")
|
||||
append(duration)
|
||||
append(" last=")
|
||||
append(lastWatched)
|
||||
}
|
||||
|
||||
private fun Collection<ProgressSyncRecord>.debugProgressRecordSummary(limit: Int = 10): String =
|
||||
take(limit).joinToString(separator = " | ") { it.debugSummary() }.ifBlank { "none" }
|
||||
|
||||
private fun WatchProgressEntry.debugSummary(): String =
|
||||
buildString {
|
||||
append(parentMetaType)
|
||||
append(":")
|
||||
append(parentMetaId)
|
||||
if (seasonNumber != null || episodeNumber != null) {
|
||||
append(" s=")
|
||||
append(seasonNumber)
|
||||
append(" e=")
|
||||
append(episodeNumber)
|
||||
}
|
||||
append(" video=")
|
||||
append(videoId)
|
||||
append(" pos=")
|
||||
append(lastPositionMs)
|
||||
append(" dur=")
|
||||
append(durationMs)
|
||||
append(" pct=")
|
||||
append(progressPercent)
|
||||
append(" completed=")
|
||||
append(isCompleted)
|
||||
append(" effectiveCompleted=")
|
||||
append(isEffectivelyCompleted)
|
||||
append(" src=")
|
||||
append(source)
|
||||
append(" last=")
|
||||
append(lastUpdatedEpochMs)
|
||||
}
|
||||
|
||||
private fun Collection<WatchProgressEntry>.debugWatchProgressEntrySummary(limit: Int = 10): String =
|
||||
take(limit).joinToString(separator = " | ") { it.debugSummary() }.ifBlank { "none" }
|
||||
|
||||
private fun Collection<WatchProgressEntry>.debugSourceCounts(): String =
|
||||
groupingBy { it.source }
|
||||
.eachCount()
|
||||
.entries
|
||||
.sortedBy { it.key }
|
||||
.joinToString(separator = ",") { "${it.key}=${it.value}" }
|
||||
.ifBlank { "none" }
|
||||
|
|
|
|||
Loading…
Reference in a new issue