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:
@@ -7,6 +7,7 @@ never synced. Videos in a playlist that aren't in our shared catalog yet are fet
|
||||
ingested (with a stub channel) so the mirror is faithful."""
|
||||
import hashlib
|
||||
import logging
|
||||
from collections.abc import Callable
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from sqlalchemy import delete, select
|
||||
@@ -271,8 +272,16 @@ def plan_push(db: Session, user: User, pl: Playlist, *, live=None) -> dict:
|
||||
_MAX_CONSECUTIVE_FAILURES = 5
|
||||
|
||||
|
||||
#: Returned by `_Breaker.run` when the write did not happen (refused or failed).
|
||||
FAILED = object()
|
||||
|
||||
|
||||
class _Breaker:
|
||||
"""Consecutive-failure circuit breaker for a single push run. Any success re-arms it."""
|
||||
"""Consecutive-failure circuit breaker for a single push run. Any success re-arms it.
|
||||
|
||||
Drive it with `run()` — it owns the whole open-check / call / record-outcome sequence, so a
|
||||
write site can't accidentally implement two thirds of the protocol (forgetting to record a
|
||||
failure would silently disable the breaker for that operation)."""
|
||||
|
||||
def __init__(self, limit: int = _MAX_CONSECUTIVE_FAILURES) -> None:
|
||||
self.limit = limit
|
||||
@@ -296,6 +305,22 @@ class _Breaker:
|
||||
self.limit,
|
||||
)
|
||||
|
||||
def run(self, op: Callable[[], object], on_fail: Callable[[], None]) -> object:
|
||||
"""Attempt one YouTube write. Returns the op's result, or `FAILED` when the breaker is
|
||||
already tripped (op not called) or the op raised YouTubeError. `on_fail` records the item
|
||||
in the caller's failure list either way."""
|
||||
if self.tripped:
|
||||
on_fail()
|
||||
return FAILED
|
||||
try:
|
||||
out = op()
|
||||
except YouTubeError:
|
||||
on_fail()
|
||||
self.hit(False)
|
||||
return FAILED
|
||||
self.hit(True)
|
||||
return out
|
||||
|
||||
|
||||
def push_playlist(db: Session, user: User, pl: Playlist, *, live=None) -> dict:
|
||||
"""Push a local playlist to YouTube: create it if needed, then reconcile membership
|
||||
@@ -313,16 +338,11 @@ def push_playlist(db: Session, user: User, pl: Playlist, *, live=None) -> dict:
|
||||
created = 1
|
||||
# Brand-new playlist: just append every video in order.
|
||||
for vid in desired:
|
||||
if not breaker.open():
|
||||
failures.append(vid)
|
||||
continue
|
||||
try:
|
||||
yt.add_playlist_item(pl.yt_playlist_id, vid)
|
||||
if breaker.run(
|
||||
lambda v=vid: yt.add_playlist_item(pl.yt_playlist_id, v),
|
||||
lambda v=vid: failures.append(v),
|
||||
) is not FAILED:
|
||||
inserted += 1
|
||||
breaker.hit(True)
|
||||
except YouTubeError:
|
||||
failures.append(vid)
|
||||
breaker.hit(False)
|
||||
if not failures:
|
||||
pl.synced_fingerprint = fingerprint(pl.name, desired)
|
||||
pl.dirty = False
|
||||
@@ -354,30 +374,22 @@ def push_playlist(db: Session, user: User, pl: Playlist, *, live=None) -> dict:
|
||||
# Delete extras (local is authoritative).
|
||||
for it in current:
|
||||
if it["video_id"] not in desired_set:
|
||||
if not breaker.open():
|
||||
failures.append(it["video_id"])
|
||||
continue
|
||||
try:
|
||||
yt.delete_playlist_item(it["item_id"])
|
||||
if breaker.run(
|
||||
lambda i=it: yt.delete_playlist_item(i["item_id"]),
|
||||
lambda i=it: failures.append(i["video_id"]),
|
||||
) is not FAILED:
|
||||
deleted += 1
|
||||
breaker.hit(True)
|
||||
except YouTubeError:
|
||||
failures.append(it["video_id"])
|
||||
breaker.hit(False)
|
||||
# Insert missing (append; the reorder pass below fixes positions).
|
||||
cur_set = set(cur_vids)
|
||||
for vid in desired:
|
||||
if vid not in cur_set:
|
||||
if not breaker.open():
|
||||
failures.append(vid)
|
||||
continue
|
||||
try:
|
||||
item_id_by_vid[vid] = yt.add_playlist_item(pl.yt_playlist_id, vid)
|
||||
item_id = breaker.run(
|
||||
lambda v=vid: yt.add_playlist_item(pl.yt_playlist_id, v),
|
||||
lambda v=vid: failures.append(v),
|
||||
)
|
||||
if item_id is not FAILED:
|
||||
item_id_by_vid[vid] = item_id
|
||||
inserted += 1
|
||||
breaker.hit(True)
|
||||
except YouTubeError:
|
||||
failures.append(vid)
|
||||
breaker.hit(False)
|
||||
# Reorder to match `desired` via insertion-sort moves over a local model.
|
||||
model = [v for v in cur_vids if v in desired_set] + [
|
||||
v for v in desired if v not in cur_set and v in item_id_by_vid
|
||||
@@ -387,17 +399,14 @@ def push_playlist(db: Session, user: User, pl: Playlist, *, live=None) -> dict:
|
||||
continue # failed insert; skip
|
||||
if i < len(model) and model[i] == want:
|
||||
continue
|
||||
if not breaker.open():
|
||||
failures.append(want)
|
||||
continue
|
||||
try:
|
||||
yt.move_playlist_item(item_id_by_vid[want], pl.yt_playlist_id, want, i)
|
||||
reordered += 1
|
||||
breaker.hit(True)
|
||||
except YouTubeError:
|
||||
failures.append(want)
|
||||
breaker.hit(False)
|
||||
if breaker.run(
|
||||
lambda w=want, pos=i: yt.move_playlist_item(
|
||||
item_id_by_vid[w], pl.yt_playlist_id, w, pos
|
||||
),
|
||||
lambda w=want: failures.append(w),
|
||||
) is FAILED:
|
||||
continue
|
||||
reordered += 1
|
||||
j = model.index(want)
|
||||
model.pop(j)
|
||||
model.insert(i, want)
|
||||
|
||||
Reference in New Issue
Block a user