diff --git a/core/automation/blocks.py b/core/automation/blocks.py index 474f375e..7705fe6e 100644 --- a/core/automation/blocks.py +++ b/core/automation/blocks.py @@ -274,6 +274,8 @@ ACTIONS: list[dict] = [ "description": "Remove old searches from Soulseek", "available": True}, {"type": "video_clean_completed_downloads", "label": "Clean Completed Downloads", "icon": "check-square", "scope": "video", "description": "Clear completed downloads and empty directories", "available": True}, + {"type": "video_full_cleanup", "label": "Full Cleanup", "icon": "trash", "scope": "video", + "description": "Clear quarantine, download queue, import folder, and search history in one sweep", "available": True}, ] diff --git a/core/automation/handlers/registration.py b/core/automation/handlers/registration.py index f9fa9c1d..f9262b24 100644 --- a/core/automation/handlers/registration.py +++ b/core/automation/handlers/registration.py @@ -164,6 +164,10 @@ def register_all(deps: AutomationDeps) -> None: 'video_clean_completed_downloads', lambda config: auto_clean_completed_downloads(config, deps), ) + engine.register_action_handler( + 'video_full_cleanup', + lambda config: auto_full_cleanup(config, deps), + ) engine.register_action_handler( 'clean_completed_downloads', lambda config: auto_clean_completed_downloads(config, deps), diff --git a/core/automation_engine.py b/core/automation_engine.py index 7f01bae4..90d58769 100644 --- a/core/automation_engine.py +++ b/core/automation_engine.py @@ -201,6 +201,14 @@ SYSTEM_AUTOMATIONS = [ 'initial_delay': 300, 'owned_by': 'video', }, + { + 'name': 'Full Cleanup', + 'trigger_type': 'schedule', + 'trigger_config': {'interval': 12, 'unit': 'hours'}, + 'action_type': 'video_full_cleanup', + 'initial_delay': 900, + 'owned_by': 'video', + }, # Video twin of music's 'Auto-Deep Scan Library', split into TWO because Movies # and TV are independent libraries — a TV scan never pulls in new movies and # vice-versa. Fixed weekly deep scan (re-read + prune removed) at 02:00 server- diff --git a/tests/automation/test_handler_registration.py b/tests/automation/test_handler_registration.py index 9a220239..6ad8c2b0 100644 --- a/tests/automation/test_handler_registration.py +++ b/tests/automation/test_handler_registration.py @@ -62,6 +62,7 @@ EXPECTED_ACTION_NAMES = frozenset({ 'video_add_airing_episodes', 'video_clean_search_history', 'video_clean_completed_downloads', + 'video_full_cleanup', }) # Action names that MUST register a guard (duplicate-run prevention). diff --git a/tests/automation/test_video_maintenance_twins.py b/tests/automation/test_video_maintenance_twins.py index b2af55b3..5369d5c8 100644 --- a/tests/automation/test_video_maintenance_twins.py +++ b/tests/automation/test_video_maintenance_twins.py @@ -92,3 +92,24 @@ def test_video_clean_completed_downloads_reuses_the_music_handler(): handlers = _registered_handlers() assert "video_clean_completed_downloads" in handlers assert "clean_completed_downloads" in handlers + + +# ── Phase 4: Full Cleanup ─────────────────────────────────────────────────── + +def test_video_full_cleanup_is_video_scoped_only(): + assert "video_full_cleanup" in _action_types("video") + assert "video_full_cleanup" not in _action_types("music") + assert "full_cleanup" in _action_types("music") + assert "full_cleanup" not in _action_types("video") + + +def test_video_full_cleanup_seeds_one_video_owned_system_row(): + rows = _system_by_action("video_full_cleanup") + assert len(rows) == 1 and rows[0]["owned_by"] == "video" + assert rows[0]["trigger_config"] == {"interval": 12, "unit": "hours"} + + +def test_video_full_cleanup_reuses_the_music_handler(): + handlers = _registered_handlers() + assert "video_full_cleanup" in handlers + assert "full_cleanup" in handlers