Auto-Sync tile: light for the WHOLE pipeline, including scheduled auto-sync

The tile's liveness was wired to sync:progress / discovery:progress — both
ROOM-scoped (only clients watching a specific playlist receive them), so the
dashboard tile would basically never light. And the scheduled auto-sync runs
as an automation, reporting on automation:progress — the wrong tile.

The 1s sync emitter now also sends an UNSCOPED sync:active heartbeat while any
playlist work is running anywhere: manual per-playlist syncs (sync_states),
the UI-triggered mirrored pipeline (playlist_pipeline_progress_states), and
scheduled auto-sync pipelines (running automations whose action_type is
playlist_pipeline / sync_playlist / refresh_mirrored). Emitted only while
active; the tile's 6s freshness decay handles the off. The dashboard listens
for the heartbeat alongside the (kept) room-scoped signals.
pull/812/head
BoulderBadgeDad 5 days ago
parent ace4b15d2e
commit d35b09fc3c

@ -35269,6 +35269,27 @@ def handle_discovery_unsubscribe(data):
for pid in data.get('ids', []):
leave_room(f'discovery:{pid}')
_SYNC_ACTIVE_STATUSES = ('starting', 'syncing', 'running', 'in_progress', 'discovering', 'analyzing')
_SYNC_AUTOMATION_TYPES = ('playlist_pipeline', 'sync_playlist', 'refresh_mirrored')
def _any_playlist_sync_running() -> bool:
"""True while ANY playlist sync work is running anywhere: a manual
per-playlist sync, the UI-triggered mirrored pipeline, or a scheduled
auto-sync pipeline (which runs as a playlist-flavored automation)."""
with sync_lock:
if any((s or {}).get('status') in _SYNC_ACTIVE_STATUSES for s in sync_states.values()):
return True
with playlist_pipeline_progress_lock:
if any((s or {}).get('status') == 'running' for s in playlist_pipeline_progress_states.values()):
return True
with _auto_progress.progress_lock:
return any(
s.get('status') == 'running' and s.get('action_type') in _SYNC_AUTOMATION_TYPES
for s in _auto_progress.progress_states.values()
)
def _emit_sync_progress_loop():
"""Push sync progress to subscribed rooms every 1 second."""
while not globals().get('IS_SHUTTING_DOWN', False):
@ -35282,6 +35303,14 @@ def _emit_sync_progress_loop():
}, room=f'sync:{pid}')
except Exception as e:
logger.debug("sync progress emit failed: %s", e)
# Quick Actions gauge heartbeat — UNSCOPED, unlike sync:progress
# which only reaches clients subscribed to a playlist room. The
# dashboard's Auto-Sync tile needs to light for ALL pipeline
# work, including the scheduled auto-sync (an automation).
# Emitted only while active; the frontend decays on silence.
if _any_playlist_sync_running():
socketio.emit('sync:active', {'active': True})
except Exception as e:
logger.debug(f"Error in sync progress loop: {e}")

@ -496,6 +496,11 @@ function initializeWebSocket() {
// Phase 5 event listeners (sync/discovery progress + scans)
socket.on('sync:progress', (data) => { qaSignal('sync'); updateSyncProgressFromData(data); });
socket.on('discovery:progress', (data) => { qaSignal('sync'); updateDiscoveryProgressFromData(data); });
// Unscoped heartbeat for the Auto-Sync tile: sync:progress above is
// room-scoped (only playlist watchers receive it), so the dashboard
// relies on this 1s pulse that fires while ANY pipeline work runs —
// manual syncs, UI pipelines, and the scheduled auto-sync automation.
socket.on('sync:active', () => qaSignal('sync'));
socket.on('scan:watchlist', (data) => {
updateWatchlistScanFromData(data);
const watchlistBtn = document.querySelector('.nav-button[data-page="watchlist"]');

Loading…
Cancel
Save