From 297709baa485c384412dde0a0a4b52abb08cdd57 Mon Sep 17 00:00:00 2001 From: BoulderBadgeDad Date: Sat, 20 Jun 2026 14:34:14 -0700 Subject: [PATCH] video scan automation: incremental + schedule-only (no startup-proximate full scan) The scheduled 'Scan Video Library' defaulted to a FULL scan (re-reads everything + prunes) that fired ~5 min after every app start. A recurring scan should be light: switched the seed to INCREMENTAL (newest-only, no prune) and pushed the first run a full interval out so it runs on its 6h cadence rather than right after startup. One-time migration (_fix_video_scan_default, flag-guarded) corrects existing rows that still carry the old full+startup default, without overriding a deliberate user choice. --- core/automation_engine.py | 32 +++++++++++++++++-- .../test_engine_schedule_integration.py | 2 +- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/core/automation_engine.py b/core/automation_engine.py index a752207d..0e1aa477 100644 --- a/core/automation_engine.py +++ b/core/automation_engine.py @@ -155,9 +155,11 @@ SYSTEM_AUTOMATIONS = [ 'trigger_type': 'schedule', 'trigger_config': {'interval': 6, 'unit': 'hours'}, 'action_type': 'video_scan_library', - 'action_config': {'mode': 'full'}, + 'action_config': {'mode': 'incremental'}, # recurring = light (newest-only, no prune) 'owned_by': 'video', - 'initial_delay': 300, # 5 min after startup + # First run a full interval out — a scheduled scan should NOT fire right after + # app startup; post-download freshness is handled by the event chain instead. + 'initial_delay': 6 * 60 * 60, }, ] @@ -302,6 +304,32 @@ class AutomationEngine: logger.info(f"System automation '{spec['name']}' next_run set to {initial_delay}s from now") else: logger.info(f"System automation '{spec['name']}' ready (event-based)") + self._fix_video_scan_default() + + def _fix_video_scan_default(self): + """One-time correction: the first 'Scan Video Library' seed defaulted to a + FULL scan that fired ~5 min after startup. Switch existing rows that still + carry that old default to an INCREMENTAL scan and push the next run a full + interval out (no startup-proximate scan). Guarded by a flag + only touches + the unchanged default, so a user's deliberate choice is never overridden.""" + try: + from config.settings import config_manager + if config_manager.get('video_scan_default_v2', False): + return + auto = self.db.get_system_automation_by_action('video_scan_library') + if auto: + try: + cfg = json.loads(auto.get('action_config') or '{}') + except (ValueError, TypeError): + cfg = {} + if cfg.get('mode', 'full') == 'full': # still the old default → correct it + self.db.update_automation( + auto['id'], action_config=json.dumps({'mode': 'incremental'}), + next_run=_utc_after(6 * 60 * 60)) + logger.info("Migrated 'Scan Video Library' to incremental + schedule-only") + config_manager.set('video_scan_default_v2', True) + except Exception: + logger.exception("video scan default migration failed") def get_system_automation_next_run_seconds(self, action_type): """Get seconds until next run for a system automation. Returns 0 if not found or disabled.""" diff --git a/tests/automation/test_engine_schedule_integration.py b/tests/automation/test_engine_schedule_integration.py index 22583767..6d4b1856 100644 --- a/tests/automation/test_engine_schedule_integration.py +++ b/tests/automation/test_engine_schedule_integration.py @@ -432,7 +432,7 @@ def test_ensure_system_automations_seeds_video_with_owned_by_and_mode(): assert 'video_scan_library' in created, 'video twin not seeded' video = created['video_scan_library'] assert video['owned_by'] == 'video' - assert json.loads(video['action_config']) == {'mode': 'full'} + assert json.loads(video['action_config']) == {'mode': 'incremental'} # Music automations stay owned_by=None (shown on the music page). assert created['scan_library']['owned_by'] is None