Thread context.Context from main through watcher, encoder, and metadata
clients so a Ctrl-C during a multi-hour encode immediately kills the
ffmpeg/ffprobe/opusenc children instead of waiting for them to finish.
- main: signal.NotifyContext replaces the manual sigChan + done goroutine
- watcher.Start: takes ctx, exits on ctx.Done(); processFn signature is
now func(context.Context, string) error
- encoder: Transcode and every helper (extractAudio, encodeOpus,
encodeVideo, GetMediaInfo, GetStreamLanguages, calculateZscaleWidth)
take ctx; every exec.Command becomes exec.CommandContext so the child
is SIGKILL'd on cancel
- metadata: FetchMovieMetadata, FetchSeriesMetadata, fetchTVMazeJSON
take ctx and use http.NewRequestWithContext
Mover stays ctx-free intentionally: a rename is fast enough that
mid-cancel cleanup is the next-restart's problem. processFile's
deferred RemoveAll(workDir) and failToFailed still run after cancel,
so partial output dies in the work dir and the source moves to failed/.
Two related hardening fixes for the input folder poller (bug #22):
1. Partial-write protection. A freshly-dropped .mkv now has to present
the same (mtime, size) on two consecutive ticks before processFn is
called. A file mid-rsync that grows or has a moving mtime is skipped
until it settles. The per-file (mtime, size) state lives in
Watcher.seen and is rebuilt from the current glob each tick so the
map cannot grow unboundedly.
2. Failure quarantine. When processFn returns an error, the file's
(failedAt, mtime) is recorded in Watcher.failed and subsequent ticks
skip it until either the backoff elapses or its mtime changes (user
replaced or touched it). Previously a permanently-broken input -- e.g.
a video-only mkv that trips the "no audio streams found" path added
in the multi-audio fix -- would be retried and logged every 15 s
forever.
Backoff is 5 minutes: comfortably longer than the 10-30 s polling
interval clamp so we are not effectively retrying every tick, but
short enough that an operator fixing the underlying problem by
replacing the file sees it picked up promptly on the next stable scan.