diff --git a/static/js/modes/wefax.js b/static/js/modes/wefax.js index 8bfdc7b..d9ae178 100644 --- a/static/js/modes/wefax.js +++ b/static/js/modes/wefax.js @@ -17,6 +17,8 @@ var WeFax = (function () { selectedStation: null, pollTimer: null, countdownInterval: null, + schedulerPollTimer: null, + schedulerEnabled: false, }; // ---- Scope state ---- @@ -59,6 +61,7 @@ var WeFax = (function () { disconnectSSE(); stopScope(); stopCountdownTimer(); + stopSchedulerPoll(); if (state.pollTimer) { clearInterval(state.pollTimer); state.pollTimer = null; @@ -210,7 +213,9 @@ var WeFax = (function () { state.running = false; updateButtons(false); setStatus('Stopping...'); - disconnectSSE(); + if (!state.schedulerEnabled) { + disconnectSSE(); + } fetch('/wefax/stop', { method: 'POST' }) .then(function (r) { return r.json(); }) @@ -983,6 +988,11 @@ var WeFax = (function () { var sidebar = document.getElementById('wefaxSidebarAutoSchedule'); if (strip) strip.checked = !!data.enabled; if (sidebar) sidebar.checked = !!data.enabled; + state.schedulerEnabled = !!data.enabled; + if (data.enabled) { + connectSSE(); + startSchedulerPoll(); + } }) .catch(function () { /* ignore */ }); } @@ -1024,6 +1034,9 @@ var WeFax = (function () { if (data.status === 'ok') { setStatus('Auto-capture enabled — ' + (data.scheduled_count || 0) + ' broadcasts scheduled'); syncSchedulerCheckboxes(true); + state.schedulerEnabled = true; + connectSSE(); + startSchedulerPoll(); } else { setStatus('Scheduler error: ' + (data.message || 'unknown')); syncSchedulerCheckboxes(false); @@ -1041,6 +1054,11 @@ var WeFax = (function () { .then(function () { setStatus('Auto-capture disabled'); syncSchedulerCheckboxes(false); + state.schedulerEnabled = false; + stopSchedulerPoll(); + if (!state.running) { + disconnectSSE(); + } }) .catch(function (err) { console.error('WeFax scheduler disable error:', err); @@ -1055,6 +1073,34 @@ var WeFax = (function () { } } + function startSchedulerPoll() { + stopSchedulerPoll(); + state.schedulerPollTimer = setInterval(function () { + fetch('/wefax/status') + .then(function (r) { return r.json(); }) + .then(function (data) { + if (data.running && !state.running) { + state.running = true; + updateButtons(true); + setStatus('Auto-capture in progress...'); + connectSSE(); + } else if (!data.running && state.running) { + state.running = false; + updateButtons(false); + loadImages(); + } + }) + .catch(function () { /* ignore poll errors */ }); + }, 10000); + } + + function stopSchedulerPoll() { + if (state.schedulerPollTimer) { + clearInterval(state.schedulerPollTimer); + state.schedulerPollTimer = null; + } + } + function syncSchedulerCheckboxes(enabled) { var strip = document.getElementById('wefaxStripAutoSchedule'); var sidebar = document.getElementById('wefaxSidebarAutoSchedule'); diff --git a/utils/wefax_scheduler.py b/utils/wefax_scheduler.py index 6877717..3f8d9e5 100644 --- a/utils/wefax_scheduler.py +++ b/utils/wefax_scheduler.py @@ -296,6 +296,11 @@ class WeFaxScheduler: sb._timer.daemon = True sb._timer.start() + logger.info( + "Scheduled capture: %s at %s UTC (fires in %.0fs)", + content, utc_time, delay, + ) + self._broadcasts.append(sb) logger.info( @@ -314,6 +319,24 @@ class WeFaxScheduler: self._refresh_timer.start() def _execute_capture(self, sb: ScheduledBroadcast) -> None: + """Execute capture for a scheduled broadcast (with error guard).""" + logger.info("Timer fired for broadcast: %s at %s", sb.content, sb.utc_time) + try: + self._execute_capture_inner(sb) + except Exception: + logger.exception( + "Unhandled exception in scheduled capture: %s at %s", + sb.content, sb.utc_time, + ) + sb.status = 'skipped' + self._emit_event({ + 'type': 'schedule_capture_skipped', + 'broadcast': sb.to_dict(), + 'reason': 'error', + 'detail': 'internal error — see server logs', + }) + + def _execute_capture_inner(self, sb: ScheduledBroadcast) -> None: """Execute capture for a scheduled broadcast.""" if not self._enabled or sb.status != 'scheduled': return