fix(r5): address the /code-review high round — 10 findings

- HLS sweep only deletes directories matching the session-name shape; the root
  is admin-configurable and may be a shared path
- playlist unwrap picks the first entry that actually downloaded, not entries[0]
- concat quoting keeps backslashes literal (ffmpeg treats them so inside '')
- ArrowUp/Down defer while the focused element can still scroll that way
- the staging fallback skips yt-dlp working files (.part-Frag/.ytdl/.temp/.fNNN)
- the boot HLS sweep runs off the event loop
- volume persistence is debounced (was a setItem per wheel notch) and a spin no
  longer loses notches to the iframe's lagging getVolume
- _Breaker.run owns the whole open/call/record protocol; four call sites shrink
- should_prune extracted and unit-tested, plus RSS error-vs-empty tests
- the worker exits non-zero when the schema never lands
This commit is contained in:
2026-07-23 23:25:06 +02:00
parent 5130584d0a
commit 2a7f49c981
9 changed files with 307 additions and 79 deletions
+15 -5
View File
@@ -15,6 +15,7 @@ segments. The remux is CPU-light (video copy + a tiny audio transcode), so it ru
CPU-only prod host; only P3's full transcode is CPU-heavy.
"""
import logging
import re
import shutil
import subprocess
import threading
@@ -33,6 +34,11 @@ log = logging.getLogger("siftlode.plex")
_HLS_ROOT = Path(settings.plex_hls_dir) if settings.plex_hls_dir else Path(settings.download_root) / ".plex-hls"
_SEG_SECONDS = 6
_SESSION_IDLE_S = 600 # reap a session with no access for this long
# A session directory is named `{rating_key}_{int(start_s)}_{tag}_{aoff:+.2f}` (see start_session).
# `sweep_orphans` deletes ONLY names matching this shape: `_HLS_ROOT` is admin-configurable
# (PLEX_HLS_DIR), so it may legitimately point at a shared scratch path we do not own — a blanket
# "delete every directory under the root" would take the neighbours with it.
_SESSION_DIR_RE = re.compile(r"^.+_\d+_[^_]+_[+-]\d+\.\d{2}$")
_lock = threading.Lock()
_sessions: dict[str, "HlsSession"] = {}
@@ -255,12 +261,16 @@ def wait_for(path: Path, timeout: float = 20.0) -> bool:
def sweep_orphans() -> int:
"""Delete segment directories no live session owns.
"""Delete OUR segment directories that no live session owns.
Sessions live only in THIS process, so at startup every directory under `_HLS_ROOT` is a
leftover from a crashed or killed run — and nothing else ever reaps them (the download GC
Sessions live only in THIS process, so at startup every session directory under `_HLS_ROOT` is
a leftover from a crashed or killed run — and nothing else ever reaps them (the download GC
deliberately skips `.plex-hls` as a system tree), so a few restarts mid-playback leave
gigabytes of `.ts` segments behind forever."""
gigabytes of `.ts` segments behind forever.
Only entries whose name matches `_SESSION_DIR_RE` are touched: the root is admin-configurable,
so anything else under it belongs to someone else and is left alone. Blocking (rmtree) — call
it off the event loop."""
with _lock:
live = {s.dir for s in _sessions.values()}
dropped = 0
@@ -269,7 +279,7 @@ def sweep_orphans() -> int:
except OSError: # the root doesn't exist yet — nothing to sweep
return 0
for p in entries:
if p.is_dir() and p not in live:
if p.is_dir() and p not in live and _SESSION_DIR_RE.match(p.name):
shutil.rmtree(p, ignore_errors=True)
dropped += 1
return dropped