fix(api): a paused viewer keeps its HLS session, a refused subtitle publish backs off, and a NUL is a 422 everywhere

Deep-review round 4, backend half.

- The periodic HLS reaper (round 3) applied the "finished and unfetched for 30 s" rule that only a
  NEW session request may apply. A remux finishes long before the film, and a paused player with a
  full buffer fetches nothing, so a one-minute pause killed the session and playback died ~30 s after
  resuming. The periodic pass is `reap_abandoned` now (the long idle limit, finished or not).
- A subtitle `publish` that could not keep its output (full / read-only cache volume) returned
  quietly, so every poll queued the window and the whole-container pass again. It reports failure
  and the pass is backed off like a failed read; a cached VTT that cannot be READ is answered
  `unavailable` (`cache_unreadable`) instead of scheduling reads that can never land.
- The subtitle routes bound `rating_key` / `sub_ord` (they become a cache file name: a 250-digit
  one was ENAMETOOLONG, which pathlib's exists() re-raises -> a 500), and the cache helpers no longer
  throw on such a name.
- One app-wide handler turns psycopg's "text fields cannot contain NUL" DataError into a 422: round 3
  guarded only the by-rating_key lookup, and `contains=`, `contains_group=`, reorder/bulk key lists
  and the collection search still 500'd.
- The hand-written `db.get(...) is None -> 404 "Unknown ..."` siblings use `get_or_404` with a
  `what` (channel / video / user), so the Hungarian UI no longer shows English for a gone row.
This commit is contained in:
2026-09-11 23:08:11 +02:00
parent 86971c574d
commit 31dc4184f5
14 changed files with 278 additions and 36 deletions
+3 -4
View File
@@ -29,6 +29,7 @@ from app.models import (
Video,
VideoState,
)
from app.routes._common import get_or_404
from app.sync.videos import parse_iso8601_duration
from app.utils import iso
from app.youtube.client import YouTubeClient, YouTubeError
@@ -235,8 +236,7 @@ def set_video_state(
status = body.status
if status not in VALID_STATES:
raise HTTPException(status_code=400, detail=f"status must be one of {VALID_STATES}")
if db.get(Video, video_id) is None:
raise HTTPException(status_code=404, detail="Unknown video")
get_or_404(db, Video, video_id, what="video")
now = datetime.now(timezone.utc)
@@ -332,8 +332,7 @@ def set_video_progress(
if position < 0:
position = 0
if db.get(Video, video_id) is None:
raise HTTPException(status_code=404, detail="Unknown video")
get_or_404(db, Video, video_id, what="video")
# Decide whether this position is worth keeping (else clear it).
near_end = duration > 0 and position > duration - FINISH_MARGIN_SECONDS