Documentation
¶
Overview ¶
Package integrity contains the bridge's proactive consistency watchers — long-lived goroutines that walk durable state on a schedule and reconcile drift between what the bridge thinks exists (SQLite manifest, track_variants table) and what actually exists on disk.
The library-source-file watcher lives in internal/manifest's Scanner.RunPeriodic (6 h default, plus optional fsnotify). The upscale-variant watcher lives here. Both pair with the equivalent reactive paths in internal/api (download serving stat-on-open; the manifest scanner's per-walk diff) — the schedulers handle the "operator did something while the bridge wasn't looking" cases.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type OrphanSidecarSweeper ¶ added in v0.1.4
type OrphanSidecarSweeper struct {
// contains filtered or unexported fields
}
OrphanSidecarSweeper walks `outputDir/transcoded/` on a cadence (configured via `cfg.Integrity.OrphanSidecarSweepIntervalSec`) and unlinks `.flac` files whose absolute path is NOT present in the current `track_variants.sidecar_path` snapshot. The forward half of the operator-triggered `bridge upscale --gc` sweep, which `VariantWatcher` (in variants.go) does NOT cover — that type handles the REVERSE direction (rows whose sidecar file disappeared on disk).
**Disabled by default** — opt-in via a non-zero interval. The existing operator workflow of "run `--gc` manually when storage gets tight" stays correct; this knob exists for the hands-off-operator profile.
**Snapshot-then-walk** (NOT walk-then-snapshot): the sweeper takes the `track_variants.sidecar_path` projection BEFORE the filesystem walk so a concurrent `UpsertVariant` writer cannot produce a false-positive orphan (under the reverse order, the new sidecar lands on disk BEFORE the new row is in the snapshot — and the sweeper would unlink the file behind a row that hasn't yet rolled into its view). SQLite WAL mode gives every SELECT a consistent snapshot natively, so `AllSidecarPaths` is safe to call without an explicit transaction wrapper.
**Chunked walking**: at most `gcChunkSize` files are stat'd / unlinked per tick. Operators on libraries with hundreds of thousands of variants get steady progress across multiple ticks rather than one long-running pass that competes with library scanning. Per-tick wall-clock stays bounded.
**Walk pointer survives across ticks**: when a tick hits the chunk cap, the next tick's walk picks up from where the prior tick stopped via filename-relative-ordering — `filepath.Walk` is deterministic in lexical order, so we can track the `lastProcessedPath` cursor and skip-until on the next pass. Pre-cursor the sweeper would re-walk the same first 100 files every tick forever on libraries with >100 sidecars.
Threading: one long-lived goroutine spun up by Start; stops on ctx cancellation OR the stopFn closing the done channel. Mirrors `VariantWatcher` exactly so cmd/bridge's wiring + shutdown ordering treats both watchers symmetrically.
func NewOrphanSidecarSweeper ¶ added in v0.1.4
func NewOrphanSidecarSweeper(lister SidecarLister, outputDir string, interval time.Duration) *OrphanSidecarSweeper
NewOrphanSidecarSweeper constructs a sweeper. interval ≤ 0 disables the sweeper entirely — Start returns a no-op stopFn. Used by operators on minimal deploys who run `bridge upscale --gc` manually.
`outputDir` is the absolute path of the variant tree to walk. Typically `<cfg.DataDir>/transcoded/` resolved via `cfg.Upscale.EffectiveVariantsDir`. The sweeper does NOT re-resolve this per tick — a config edit that moves the variants directory at runtime requires a bridge restart for the change to take effect (same operational shape as VariantWatcher's interval).
func (*OrphanSidecarSweeper) SetOnTickComplete ¶ added in v0.1.4
func (s *OrphanSidecarSweeper) SetOnTickComplete(fn func(unlinked int))
SetOnTickComplete is a test-only seam mirroring VariantWatcher.SetOnTickComplete. Production wires nil; Go's linker drops the call when unused.
func (*OrphanSidecarSweeper) Start ¶ added in v0.1.4
func (s *OrphanSidecarSweeper) Start(ctx context.Context) (stopFn func())
Start spins up the long-lived sweep goroutine and returns a stopFn the caller `defer`s on shutdown. The goroutine fires one immediate sweep at boot, then ticks every `interval`. A cancelled ctx AND the returned stopFn both cleanly stop the loop.
Idempotent: a duplicate Start returns a stopFn that closes the SAME `s.done` channel the active run goroutine selects on. `startOnce` + `stopOnce` mirror the VariantWatcher fix from CodeRabbit Major on PR #209.
Interval ≤ 0 returns a no-op stopFn — no goroutine is spawned at all. This is the production "disabled by default" path; explicit zero in the YAML config opts out.
type PublishFunc ¶
PublishFunc is the domain-specific publish callback fired once per sweep that observed at least one missing sidecar. The integrity package keeps the wire-shape construction at the cmd/bridge wiring layer so it can build the typed api.UpscaleDeletedEvent without an upward import cycle — the integrity package only knows "I observed these disappearances", not the broker's serialization concerns.
`paths` and `variantIDs` are positional but NOT zipped 1:1: callers treat them as the set of paths affected AND the set of variantIDs that disappeared somewhere in those paths. Same semantic the api.UpscaleDeletedEvent struct already documents.
type SidecarLister ¶ added in v0.1.4
SidecarLister is the integrity-package-local read surface for `track_variants.sidecar_path` projection. `manifest.Store` will be wired via a thin adapter in cmd/bridge; the explicit interface lets tests inject fakes without spinning a real SQLite store.
Returns a SET of sidecar paths (`map[string]struct{}` for O(1) lookup against thousands of filesystem entries during a walk). Bare `[]string` was rejected: per-file lookup against a slice is O(n) and a 50k-variant library would O(n²)-walk on every tick.
type VariantDeleter ¶
VariantDeleter removes one variant row by (source_path, variant_id). The Store's DeleteVariant transactionally bumps `tracks.indexed_at` so iOS delta-sync observes the removal on the next manifest fetch. Per-row error tolerance: a Watcher tick logs and continues on per-row failure, but still publishes the events for the rows that DID delete.
type VariantLister ¶
type VariantLister interface {
AllVariants() ([]VariantSnapshot, error)
}
VariantLister enumerates every row in the track_variants table. The watcher consumes the snapshot per tick and stats each sidecar path. Mirrors the manifest.Store method shape; cmd/bridge wires the adapter.
type VariantSnapshot ¶
VariantSnapshot is the integrity-package-local projection of one track_variants row. Mirrors `api.VariantSummary` but stays internal to integrity so the package doesn't import internal/api (which would create an upward dependency cycle — api consumes integrity events via the broker, not the other way round).
type VariantWatcher ¶
type VariantWatcher struct {
// contains filtered or unexported fields
}
VariantWatcher walks the track_variants table on a cadence configurable via cfg.Integrity.VariantSweepInterval (default 1 h) and reconciles rows whose sidecar file no longer exists on disk. Reasons a sidecar might disappear outside the bridge: operator `rm -rf <DataDir>/transcoded/`, backup software with eager retention, disk-image rebuild that preserved the SQLite DB but not the sidecar tree.
On every miss, the row is removed via the supplied Deleter (bumps `tracks.indexed_at` so iOS delta-sync sees the disappearance) AND a single batched `upscale.deleted` SSE event is published per tick — iOS reconciles immediately without waiting for a manifest re-sync.
Threading: one long-lived goroutine spun up by Start; stops on the supplied ctx's cancellation. Time.NewTicker is reset on every tick (we use a manual select loop) so the first sweep fires immediately at boot — closes the "operator deleted variant files while the bridge was down" case without waiting for the first interval to elapse.
func NewVariantWatcher ¶
func NewVariantWatcher(lister VariantLister, deleter VariantDeleter, publish PublishFunc, interval time.Duration) *VariantWatcher
NewVariantWatcher constructs a watcher. interval ≤ 0 disables the watcher entirely — Start returns a no-op stopFn. Used by operators on minimal deploys who only run `--gc` manually.
func (*VariantWatcher) SetOnTickComplete ¶
func (w *VariantWatcher) SetOnTickComplete(fn func(deletedCount int))
SetOnTickComplete is a test-only seam. Production wires nil. Same convention as transcode.Pool's SetOnStateChange — the test harness can register a callback once at construction without exposing internal channels.
func (*VariantWatcher) Start ¶
func (w *VariantWatcher) Start(ctx context.Context) (stopFn func())
Start spins up the long-lived sweep goroutine and returns a stopFn the caller `defer`s on shutdown. The goroutine fires one immediate sweep at boot, then ticks every `interval`. A cancelled context AND the returned stopFn both cleanly stop the loop; either is sufficient (they're equivalent paths).
Idempotent: a duplicate Start returns a stopFn that closes the SAME `w.done` channel the active run goroutine is selecting on. Both `startOnce` and `stopOnce` live on the struct so a second Start's stopFn doesn't pointlessly close a fresh per-call channel the run loop never sees. Calling Start with interval ≤ 0 returns a no-op stopFn — no goroutine is spawned at all (avoids the per-process resource cost on minimal deploys).