From bdcd929558a0992b29cf0eb629018ac11a49bfa6 Mon Sep 17 00:00:00 2001 From: BoulderBadgeDad Date: Sat, 20 Jun 2026 14:55:34 -0700 Subject: [PATCH] video automations: remove the standalone 'Scan Video Library' system automation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Superseded by the post-download chain (Auto-Scan Video After Downloads → Auto-Update Video Database After Scan), which keeps the library fresh without a separate scheduled scan. Two small changes: drop it from SYSTEM_AUTOMATIONS (no longer seeded), and a one-time flag-guarded cleanup (_fix_video_scan_default, v3) that DELETES the existing system row — so it actually stops running, not just hidden. The video_scan_library action/handler/block stay, so a custom scan automation can still be built later. Engine seed test updated. 66 automation tests pass. --- core/automation_engine.py | 42 ++++++------------- .../test_engine_schedule_integration.py | 37 ++++++++++++---- 2 files changed, 41 insertions(+), 38 deletions(-) diff --git a/core/automation_engine.py b/core/automation_engine.py index 4d83defa..a599c8d0 100644 --- a/core/automation_engine.py +++ b/core/automation_engine.py @@ -150,17 +150,8 @@ SYSTEM_AUTOMATIONS = [ # them out) and ON the video Automations page (it shows only these). # Schedule-based for now; a video-download-complete event trigger can # replace the schedule once that event is wired into the engine. - { - 'name': 'Scan Video Library', - 'trigger_type': 'schedule', - 'trigger_config': {'interval': 6, 'unit': 'hours'}, - 'action_type': 'video_scan_library', - 'action_config': {'mode': 'incremental'}, # recurring = light (newest-only, no prune) - 'owned_by': 'video', - # 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, - }, + # (No standalone scheduled scan — the post-download chain below keeps the library + # fresh. The 'video_scan_library' action/block still exist for a custom automation.) # Post-download chain (video twin of music's batch_complete → scan_library → # library_scan_completed → start_database_update). Event-based, so a finished # video download refreshes the server then pulls the new media into video.db. @@ -325,29 +316,22 @@ class AutomationEngine: 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.""" + """One-time cleanup: remove the standalone 'Scan Video Library' system + automation — it's superseded by the post-download chain (Auto-Scan Video + After Downloads → Auto-Update Video Database After Scan). Only deletes the + SYSTEM row; a user can still build their own scan automation from the action. + Flag-guarded so it runs once.""" try: from config.settings import config_manager - if config_manager.get('video_scan_default_v2', False): + if config_manager.get('video_scan_cleanup_v3', 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) + if auto and auto.get('is_system'): + self.db.delete_automation(auto['id']) + logger.info("Removed superseded 'Scan Video Library' system automation") + config_manager.set('video_scan_cleanup_v3', True) except Exception: - logger.exception("video scan default migration failed") + logger.exception("video scan cleanup 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 6d4b1856..d742155e 100644 --- a/tests/automation/test_engine_schedule_integration.py +++ b/tests/automation/test_engine_schedule_integration.py @@ -411,10 +411,9 @@ def test_end_to_end_monthly_schedule_produces_valid_db_string(engine_with_db): def test_ensure_system_automations_seeds_video_with_owned_by_and_mode(): - """The video twin ('Scan Video Library') must seed with owned_by='video' - so it stays off the music page, and carry its action_config (mode). Music - system automations keep owned_by=None. Regression guard for the seeding - seam in ensure_system_automations().""" + """The video post-download chain twins must seed with owned_by='video' so they + stay off the music page; the standalone 'Scan Video Library' is NO LONGER seeded + (superseded by the chain). Music system automations keep owned_by=None.""" db = MagicMock() db.get_system_automation_by_action.return_value = None # nothing seeded yet created = {} @@ -428,12 +427,32 @@ def test_ensure_system_automations_seeds_video_with_owned_by_and_mode(): engine = AutomationEngine(db) engine.ensure_system_automations() - # Video twin seeded, tagged for the video side, with its mode config. - 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': 'incremental'} + # The standalone scheduled scan is gone; the chain twins are seeded, video-tagged. + assert 'video_scan_library' not in created + assert created['video_scan_server']['owned_by'] == 'video' + assert created['video_update_database']['owned_by'] == 'video' + assert json.loads(created['video_update_database']['action_config']) == {'mode': 'incremental'} # Music automations stay owned_by=None (shown on the music page). assert created['scan_library']['owned_by'] is None assert json.loads(created['scan_library']['action_config']) == {} + + +def test_video_scan_library_system_automation_is_cleaned_up(monkeypatch): + """The old standalone 'Scan Video Library' system automation is deleted once + (superseded by the post-download chain). Guard flag stops it re-running.""" + class _FakeCfg: + def __init__(self): self._d = {} + def get(self, k, default=None): return self._d.get(k, default) + def set(self, k, v): self._d[k] = v + monkeypatch.setattr('config.settings.config_manager', _FakeCfg()) + + db = MagicMock() + db.get_system_automation_by_action.return_value = {'id': 99, 'is_system': 1} + engine = AutomationEngine(db) + + engine._fix_video_scan_default() + db.delete_automation.assert_called_once_with(99) + + engine._fix_video_scan_default() # flag set → no second delete + db.delete_automation.assert_called_once()