fix(sync): queue a watch position the server could not be told about (DR-154)
sync_queue and its drain (DR-131) were built, tested and running, but the
stop-report path never fed them, so closing a video while the server was
unreachable lost the resume point outright.
HybridRepository::report_playback_stopped is a bare pass-through to the
online repository ("Playback reporting goes directly to server"), and on
failure the error surfaced to a frontend catch whose own comment read
"Server error - could queue, but for now just log". Both producers that
would have queued it -- PlaybackReporter::queue_for_sync in Rust and
syncService.queuePlaybackProgress on the frontend -- have no callers on
the playback path. user_data.pending_sync was dutifully set to 1, but
nothing drains that flag for positions the way favourites do (DR-120).
The command layer now enqueues a report_playback_stopped row whenever the
push fails; the existing drain already parses and replays that operation.
The pending row for an item is superseded in place rather than appended
to: progress is reported every 10s, so a server that stays down would
otherwise add a row per tick, all obsoleted by the newest -- the
unbounded queue DR-131 exists to prevent. Only pending/failed rows are
superseded, since reviving an abandoned row restores that same growing
counter. Queueing is best-effort and never fails the command: the local
position is already saved, so a failed queue write must not be reported
as a lost position.
Verified red->green in the jellytau-builder image: the four new tests
failed to compile (enqueue_playback_stopped not found) before the fix.
Full Rust suite passes (627 tests), cargo fmt clean, clippy adds no new
warnings; frontend suite (933) and svelte-check also clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -712,9 +712,19 @@ pub async fn repository_report_playback_progress(
|
||||
}
|
||||
|
||||
/// Report playback stopped
|
||||
///
|
||||
/// A stop-report that cannot reach the server is queued rather than dropped:
|
||||
/// this is the position the resume point is built from, and losing it is
|
||||
/// exactly the "it forgot where I was" the sync queue exists to prevent. The
|
||||
/// drain (DR-131) pushes it on the next reconnect. Queueing is best-effort —
|
||||
/// failing the command because the *queue* write failed would tell the caller
|
||||
/// the report was lost when the local position was already saved.
|
||||
///
|
||||
/// TRACES: UR-025 | DR-154 | UT-151
|
||||
#[tauri::command]
|
||||
#[specta::specta]
|
||||
pub async fn repository_report_playback_stopped(
|
||||
db: State<'_, crate::commands::storage::DatabaseWrapper>,
|
||||
manager: State<'_, RepositoryManagerWrapper>,
|
||||
handle: String,
|
||||
item_id: String,
|
||||
@@ -723,10 +733,39 @@ pub async fn repository_report_playback_stopped(
|
||||
// Milliseconds across the boundary; the Jellyfin API wants ticks.
|
||||
let position_ticks = position_ms * 10_000;
|
||||
let repo = manager.0.get(&handle).ok_or("Repository not found")?;
|
||||
repo.as_ref()
|
||||
|
||||
let result = repo
|
||||
.as_ref()
|
||||
.report_playback_stopped(&item_id, position_ticks)
|
||||
.await;
|
||||
|
||||
if let Err(e) = &result {
|
||||
let db_service = {
|
||||
let database = db.0.lock().map_err(|err| err.to_string())?;
|
||||
Arc::new(database.service())
|
||||
};
|
||||
let user_id = repo.user_id().to_string();
|
||||
if let Err(queue_err) = crate::commands::sync_drain::enqueue_playback_stopped(
|
||||
&db_service,
|
||||
&user_id,
|
||||
&item_id,
|
||||
position_ticks,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| format!("{:?}", e))
|
||||
{
|
||||
warn!(
|
||||
"[Repository] Stop-report for {} failed ({:?}) and could not be queued: {}",
|
||||
item_id, e, queue_err
|
||||
);
|
||||
} else {
|
||||
debug!(
|
||||
"[Repository] Stop-report for {} failed ({:?}); queued for the next reconnect",
|
||||
item_id, e
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
result.map_err(|e| format!("{:?}", e))
|
||||
}
|
||||
|
||||
/// Get image URL for an item
|
||||
|
||||
Reference in New Issue
Block a user