video: longer adaptive slskd wait + stop the automations page blink

two issues:
1. slskd search gave up at 22s (then stopped the search), but slskd gathers peers
   over its full ~60s window — so most movie searches were killed before results
   arrived. now waits up to 55s but returns early once results SETTLE (12+ hits, or
   no new hits for ~12s) so fast searches don't burn the whole window. matches the
   music side's longer wait.
2. the automations page re-rendered the WHOLE system section every 8s poll
   (replaceChild) → blink + wiped the socket's live progress. now it skips the
   rebuild unless something structural changed (added/removed/toggled/ran); live
   progress comes via socket, the countdown ticks locally.
This commit is contained in:
BoulderBadgeDad 2026-06-26 09:54:18 -07:00
parent b55c245fe6
commit 8fe73ac924
3 changed files with 39 additions and 6 deletions

View file

@ -261,11 +261,17 @@ def _fail_or_retry(db, dl, error_msg) -> None:
_archive_history(db, dl, {"status": "failed", "error": err, "completed_at": completed})
def _search_for_retry(query, max_seconds=22):
"""A bounded blocking slskd search for the retry worker (shorter than the UI's).
def _search_for_retry(query, max_seconds=55):
"""A bounded blocking slskd search for the retry worker + the auto-grab automation.
slskd gathers peer responses over its full search window (~60s by default), so a short
wait misses almost everything. We poll up to ``max_seconds`` but return EARLY once
results have arrived and settled break on either plenty of hits (12+) or no new hits
for ~12s after getting some, so fast searches don't burn the whole window.
Always STOPS the slskd search when done otherwise it keeps running its full timeout
(~60s) and, since the bounded auto-grab fires searches back-to-back, they pile up on
slskd. Stopping each one keeps concurrent slskd searches the worker pool size."""
and, since the auto-grab fires searches back-to-back, they pile up. Stopping each one
keeps concurrent slskd searches the worker pool size."""
from core.video.slskd_search import poll_search, start_search, stop_search
res = start_search(query)
sid = res.get("id")
@ -273,11 +279,20 @@ def _search_for_retry(query, max_seconds=22):
return {"hits": [], "total_files": 0}
deadline = time.monotonic() + max_seconds
last = {"hits": [], "total_files": 0}
prev, settle = 0, 0 # track hit growth; settle = consecutive no-growth polls (~1.5s each)
try:
while time.monotonic() < deadline:
last = poll_search(sid)
if len(last.get("hits") or []) >= 12:
break
n = len(last.get("hits") or [])
if n >= 12:
break # plenty — stop waiting
if n > prev:
settle = 0 # still arriving — keep waiting
elif n > 0:
settle += 1
if settle >= 8: # ~12s with no new hits → results have settled
break
prev = n
time.sleep(1.5)
return last
finally:

View file

@ -103,6 +103,13 @@ def test_video_page_exposes_reload_hook():
# --- the System list is shown in a logical pipeline order -----------------
def test_video_automations_poll_does_not_rebuild_when_unchanged():
# regression: the 8s poll used to replaceChild the whole section every time → blink +
# wiped live progress. It must skip the rebuild when nothing structural changed.
assert 'if (sig === _lastSig) return;' in _VAUTO
assert 'var sig = JSON.stringify(' in _VAUTO
def test_video_system_automations_are_sorted_by_pipeline_order():
# The API returns newest-created-first (jumbled); the page re-sorts by an
# explicit order so it reads scans → processors → library → maintenance.

View file

@ -91,10 +91,21 @@
'<span class="auto-stat"><strong>' + sys.length + '</strong> System</span>';
}
var _lastSig = null;
function load() {
return getJSON('/api/automations').then(function (d) {
var all = Array.isArray(d) ? d : (d && d.automations) || [];
var sys = sortSystem(all.filter(isVideoAutomation));
// Re-rendering the whole System section every 8s poll destroys + recreates every
// card — that's the blink, and it wipes the live progress the socket patches in.
// Only rebuild when something STRUCTURAL changed (added/removed/toggled/ran);
// live progress arrives via socket, the "Next: in Xm" countdown ticks locally.
var sig = JSON.stringify(sys.map(function (a) {
return [a.id, a.enabled, a.name, a.trigger_type, a.action_type,
a.last_run, a.next_run, a.run_count, a.last_result];
}));
if (sig === _lastSig) return;
_lastSig = sig;
renderStats(sys);
renderSystem(sys);
renderHubOnce();