Video deep scan: pure read/reconcile, not a Plex disk-scan trigger
A deep scan is the equivalent of music's full refresh — it READS the server's current state into video.db and prunes what's gone. It should NOT tell Plex to rescan its disk. The deep-scan action types were wired to auto_video_scan_library (nudge Plex + read); point them at the read-only auto_video_update_database in 'deep' mode instead. Update-db phase wording no longer says "new" for a full re-read; deep-scan block descriptions clarify it's a read, not a disk-scan. Registration test asserts the deep scans route to the read-only handler and never nudge the server.
This commit is contained in:
parent
14a32f6006
commit
5a53ffc8c2
4 changed files with 33 additions and 8 deletions
|
|
@ -254,9 +254,9 @@ ACTIONS: list[dict] = [
|
|||
# these). Scope + deep mode are baked in by the registration wrapper, so no config
|
||||
# fields — drag one in and it just deep-scans that library.
|
||||
{"type": "video_deep_scan_tv", "label": "Deep Scan TV Library", "icon": "search", "scope": "video",
|
||||
"description": "Deep-rescan the TV library: re-read every show and prune ones the server no longer has (never touches movies)", "available": True},
|
||||
"description": "Full reconcile of the TV library: re-read every show from the server and drop ones it no longer has (a read, NOT a Plex disk-scan; never touches movies)", "available": True},
|
||||
{"type": "video_deep_scan_movies", "label": "Deep Scan Movie Library", "icon": "search", "scope": "video",
|
||||
"description": "Deep-rescan the Movie library: re-read every movie and prune ones the server no longer has (never touches TV)", "available": True},
|
||||
"description": "Full reconcile of the Movie library: re-read every movie from the server and drop ones it no longer has (a read, NOT a Plex disk-scan; never touches TV)", "available": True},
|
||||
{"type": "video_add_airing_episodes", "label": "Wishlist Today's Airings", "icon": "calendar", "scope": "video",
|
||||
"description": "Sonarr-style: add every episode airing today (for shows you follow) to the wishlist, skipping ones you already own", "available": True},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -179,16 +179,19 @@ def register_all(deps: AutomationDeps) -> None:
|
|||
lambda config: auto_video_scan_library(config, deps),
|
||||
)
|
||||
# Per-library deep scans (the video twin of music's 'Auto-Deep Scan Library',
|
||||
# split because Movies and TV are independent libraries). Distinct action types
|
||||
# so the system seeder — which keys on action_type — treats them as two separate
|
||||
# automations; both reuse the one handler, scoped via media_type in action_config.
|
||||
# split because Movies and TV are independent libraries). A deep scan READS the
|
||||
# server's current state into video.db + prunes what's gone (a full reconcile) —
|
||||
# it does NOT tell Plex to rescan its disk, so it runs through the read-only
|
||||
# update-database handler in 'deep' mode, not the nudge+read scan-library one.
|
||||
# Distinct action types so the seeder (which keys on action_type) sees two
|
||||
# separate automations; both reuse the one handler, scoped via media_type.
|
||||
engine.register_action_handler(
|
||||
'video_deep_scan_movies',
|
||||
lambda config: auto_video_scan_library({**config, 'media_type': 'movie', 'mode': config.get('mode') or 'deep'}, deps),
|
||||
lambda config: auto_video_update_database({**config, 'media_type': 'movie', 'mode': config.get('mode') or 'deep'}, deps),
|
||||
)
|
||||
engine.register_action_handler(
|
||||
'video_deep_scan_tv',
|
||||
lambda config: auto_video_scan_library({**config, 'media_type': 'show', 'mode': config.get('mode') or 'deep'}, deps),
|
||||
lambda config: auto_video_update_database({**config, 'media_type': 'show', 'mode': config.get('mode') or 'deep'}, deps),
|
||||
)
|
||||
# Post-download chain: scan the server, then (on the scan-done event) update the DB.
|
||||
engine.register_action_handler(
|
||||
|
|
|
|||
|
|
@ -239,8 +239,11 @@ def auto_video_update_database(
|
|||
or (config.get('_event_data') or {}).get('media_type')
|
||||
or 'all')
|
||||
lib_label = {'movie': 'Movie', 'show': 'TV'}.get(media_type, 'video')
|
||||
# 'deep'/'full' re-read the whole library; 'incremental' only grabs new items.
|
||||
phase = (f'Re-reading the {lib_label} library from the server…' if mode in ('deep', 'full')
|
||||
else f'Reading new {lib_label} media into SoulSync…')
|
||||
try:
|
||||
deps.update_progress(automation_id, phase=f'Reading new {lib_label} media into SoulSync…', progress=40)
|
||||
deps.update_progress(automation_id, phase=phase, progress=40)
|
||||
result = run_video_scan(mode, media_type) or {}
|
||||
if result.get('state') == 'in_progress':
|
||||
deps.update_progress(automation_id, status='finished', phase='Skipped',
|
||||
|
|
|
|||
|
|
@ -181,6 +181,25 @@ class TestActionHandlerRegistration:
|
|||
assert not missing, f"register_all dropped: {missing}"
|
||||
assert not extra, f"register_all added unexpected: {extra}"
|
||||
|
||||
def test_deep_scans_route_to_readonly_update_in_deep_mode(self, monkeypatch):
|
||||
"""A deep scan is a full READ + reconcile (music's full-refresh equivalent) —
|
||||
it must NOT nudge Plex to rescan its disk. So the deep-scan action types route
|
||||
to the read-only update-database handler in 'deep' mode, scoped per library,
|
||||
never to the nudge+read scan-library handler."""
|
||||
import core.automation.handlers.registration as reg
|
||||
seen = []
|
||||
monkeypatch.setattr(reg, 'auto_video_update_database',
|
||||
lambda config, deps: seen.append(config) or {'status': 'completed'})
|
||||
monkeypatch.setattr(reg, 'auto_video_scan_library',
|
||||
lambda *a, **k: pytest.fail('deep scan must not nudge the server'))
|
||||
engine = _RecordingEngine()
|
||||
register_all(_build_deps(engine))
|
||||
|
||||
engine.action_handlers['video_deep_scan_tv']['handler']({'_automation_id': 'x'})
|
||||
engine.action_handlers['video_deep_scan_movies']['handler']({})
|
||||
assert seen[0]['media_type'] == 'show' and seen[0]['mode'] == 'deep'
|
||||
assert seen[1]['media_type'] == 'movie' and seen[1]['mode'] == 'deep'
|
||||
|
||||
def test_guarded_actions_have_a_guard(self):
|
||||
engine = _RecordingEngine()
|
||||
register_all(_build_deps(engine))
|
||||
|
|
|
|||
Loading…
Reference in a new issue