Files
mediahive/mediahive/models/events.py
T
LeoVasanko 2a39e1f0ea Fix scanner rescan bugs and wasted work
Bugs fixed:
- Partial rescans no longer replace whole index entries: Upsert events now
  carry the scanned torrent paths and IndexStore merges partial rebuilds,
  so touching one season no longer drops the others from listings.
- Deleted torrents are now detected: discovery reports the full candidate
  set via a new Sync event after each completed scan, and the store prunes
  file entries/episodes/seasons/items whose torrents vanished.
- Seen mtimes are committed only after a scan completes successfully, so
  cancelled/failed scans retry their items.
- Showreel worker no longer rebroadcasts stale whole items; it sends narrow
  MovieShowreel/EpisodeReel events that update only reel fields.
- Permanently failing reel generations (e.g. DoVi/libplacebo on GPU-less
  machines) and short-video re-queueing are no longer retried every scan:
  reel outcomes persist in reel-state.json with exponential backoff.

Optimizations:
- Persist scan state (scan-state.json) and ffmpeg probe results
  (probe-cache.json, keyed by mtime+size, failures included) under
  .mediahive/, written only when changed. A warm restart over an unchanged
  library drops from ~57 s + 467 queued reel tasks to ~0.2 s with an empty
  queue (measured on a 163-item root).
- Bounded parallelism (semaphores + gather) for cast-profile downloads,
  TMDb title lookups, and season-detail fetches.
- Incremental TMDb-id index bookkeeping instead of rebuilding per upsert;
  removed dead trigger_scan.

See docs/scanning-review.md for the full findings/fixes/measurements report.
2026-09-04 00:00:53 +00:00

77 lines
2.0 KiB
Python

"""Event types shared between scanner and WebSocket.
These types are used as:
- Internal scan events (scanner → server queue)
- WebSocket messages (server → clients)
"""
from __future__ import annotations
import msgspec
from .data import Movie, Series, TaskInfo
from .tmdb import Person
class Upsert(msgspec.Struct, tag="upsert"):
"""Single item inserted or updated.
``scanned`` lists the media-root-relative torrent paths whose content was
(re)scanned to build this item. When present, the store merges the item
into the existing entry instead of replacing it wholesale: only data
belonging to the scanned torrents is replaced. ``None`` means full
replacement (legacy behaviour).
"""
kind: str # "movie" or "series"
id: str
item: Movie | Series
people: dict[int, Person] | None = None
scanned: list[str] | None = None
class Remove(msgspec.Struct, tag="remove"):
"""Single item removed."""
kind: str
id: str
class Sync(msgspec.Struct, tag="sync"):
"""Full set of media-root-relative torrent paths currently on disk.
Sent by the scanner after a successfully completed discovery pass so the
store can drop entries whose files no longer exist. Internal only —
never forwarded to WebSocket clients.
"""
paths: list[str]
class MovieShowreel(msgspec.Struct, tag="movie-showreel"):
"""Reel worker result for a movie (internal, scanner → store)."""
id: str
showreel_images: list[str] | None = None
showreel_source_sets: list[list[str]] | None = None
class EpisodeReel(msgspec.Struct, tag="episode-reel"):
"""Reel worker result for one episode (internal, scanner → store)."""
id: str
season: int
episode: int
reel_image: str | None = None
reel_sources: list[str] | None = None
class Task(msgspec.Struct, tag="task"):
"""Task progress broadcast."""
data: TaskInfo
# Union of scan events (scanner → server) and WS broadcast messages
ScanEvent = Upsert | Sync | MovieShowreel | EpisodeReel | Task