resolve

package
v0.1.162 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 21, 2026 License: GPL-2.0, GPL-3.0 Imports: 9 Imported by: 0

Documentation

Overview

Package resolve is the one home for typed-reference resolution (S7): it maps the wire references — FileRef (one stored subtitle file) and MediaRef (the arr-known video of a media item) — to absolute filesystem paths using server-derived facts only. No handler on the file/preview/sync/download verbs accepts a client-supplied path; every path a handler touches comes out of this package (or, for orphans, out of the filehandlers TTL handle table).

Failure taxonomy (mapped by handlers):

  • ErrMediaNotFound / ErrSubtitleNotFound -> 404 with the machine code of the same name: the reference does not resolve to a known row/item.
  • ErrAmbiguous -> 500-class internal invariant: a FileRef matching more than one store row means the store's uniqueness assumption broke; resolution never guesses.
  • ErrPathInvariant -> 500-class: a server-derived path failed the media-roots containment check that runs as defense-in-depth on every resolved value. Server-derived paths should never fail it, so a hit is logged at ERROR as an invariant breach, never answered as a 4xx.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrMediaNotFound means the MediaRef (or a FileRef's surrounding media)
	// does not resolve to a known media item with a video file.
	ErrMediaNotFound = errors.New("media not found")
	// ErrSubtitleNotFound means the FileRef matches no stored subtitle row.
	ErrSubtitleNotFound = errors.New("subtitle not found")
	// ErrAmbiguous means the FileRef matched more than one stored row — an
	// internal invariant breach, never a guess.
	ErrAmbiguous = errors.New("ambiguous file reference")
	// ErrPathInvariant means a server-derived path failed the containment
	// validation that runs as defense-in-depth.
	ErrPathInvariant = errors.New("resolved path failed containment validation")
)

Sentinel errors; see the package doc for the handler status mapping.

Functions

func WriteError

func WriteError(w http.ResponseWriter, r *http.Request, err error)

WriteError maps a resolution error onto the JSON error envelope: the two not-found sentinels answer 404 with their machine codes; everything else (ambiguity, path invariant, store failure) is a 500-class internal error. One mapping site so every reference-taking handler answers identically.

Types

type FileRef

type FileRef struct {
	MediaType api.MediaType `json:"media_type"`
	MediaID   string        `json:"media_id"`
	Language  string        `json:"language"`
	Variant   string        `json:"variant"`
	Source    string        `json:"source"`
	Ordinal   int           `json:"ordinal,omitempty"`
}

FileRef addresses exactly one stored subtitle file: the store row identity (media_type, media_id, language, variant, source) plus the manual-sibling ordinal parsed from the row's filename (api.ManualOrdinal; 0 = the unnumbered auto file). A bare quad is NOT unique — manual ordinals share a quad, and sync offsets are path-keyed in the store, so ambiguous resolution would corrupt offset bookkeeping.

MediaID is the store media identifier (e.g. "tmdb-1271", "tvdb-121361-s01e05"), matching the /api/files wire fields.

func FileRefFromQuery

func FileRefFromQuery(q url.Values) (*FileRef, error)

FileRefFromQuery parses a FileRef from GET query parameters (media_type, media_id, language, variant, source, ordinal). Variant defaults to standard and source to external; ordinal defaults to 0. Shape errors are returned for the handler's 400.

func (*FileRef) Validate

func (ref *FileRef) Validate() error

Validate checks the reference's field shape (not its existence). Only external rows are file-addressable: embedded tracks live inside the video container and have no per-file path.

type FileStore

type FileStore interface {
	GetSubtitleFiles(ctx context.Context, mediaType api.MediaType, mediaIDPrefix string) ([]api.SubtitleEntry, error)
}

FileStore is the store surface resolution needs: the per-media subtitle rows (key-derived identity incl. path; satisfied by api.Store).

type MediaRef

type MediaRef struct {
	MediaType api.MediaType `json:"media_type"`
	MediaID   int           `json:"media_id"`
	Season    int           `json:"season,omitempty"`
	Episode   int           `json:"episode,omitempty"`
}

MediaRef addresses the VIDEO of a media item by arr identity: the arr internal ID (Radarr movie ID, Sonarr series ID) plus season/episode for episodes. Resolution asks the arr for the current file path, so it never returns a stale store path. The json field names match the existing download wire contract (media_id = arr ID).

func MediaRefFromQuery

func MediaRefFromQuery(q url.Values) (*MediaRef, error)

MediaRefFromQuery parses a MediaRef from GET query parameters (media_type, media_id [arr id], season, episode). Shape errors are returned for the handler's 400.

func (*MediaRef) Validate

func (ref *MediaRef) Validate() error

Validate checks the reference's field shape (not its existence).

type PathValidator

type PathValidator interface {
	ValidatePath(ctx context.Context, path string) error
}

PathValidator is the containment check run on every resolved path as defense-in-depth (satisfied by api.ConfigProvider).

type RadarrMovie

type RadarrMovie interface {
	GetMovieByID(ctx context.Context, id int) (arrapi.Movie, error)
}

RadarrMovie is the Radarr surface MediaRef resolution needs.

type Resolver

type Resolver struct {
	Store FileStore
	State func() *State
}

Resolver resolves typed references against the store and the arrs.

func (*Resolver) SubtitlePath

func (r *Resolver) SubtitlePath(ctx context.Context, ref *FileRef) (string, error)

SubtitlePath resolves a FileRef to the row's absolute subtitle path.

func (*Resolver) SubtitleRow

func (r *Resolver) SubtitleRow(ctx context.Context, ref *FileRef) (*api.SubtitleEntry, error)

SubtitleRow resolves a FileRef to its single store row. It filters the media item's rows by the full reference identity; zero matches answer ErrSubtitleNotFound, more than one ErrAmbiguous (see package doc).

func (*Resolver) VideoPath

func (r *Resolver) VideoPath(ctx context.Context, ref *MediaRef) (string, error)

VideoPath resolves a MediaRef to the arr-known video file path: Radarr's movie file for movies, the season/episode's episode file for episodes. An unconfigured arr, an unknown item, or an item without a file answers ErrMediaNotFound.

func (*Resolver) VideoPathForFile

func (r *Resolver) VideoPathForFile(ctx context.Context, ref *FileRef) (string, error)

VideoPathForFile resolves the video path belonging to a FileRef's media: first the resolved row's own subtitle_state join (VideoPath), then any sibling row of the same media item that carries one. A media item whose rows carry no video path (nothing was ever downloaded for it) answers ErrMediaNotFound.

type SonarrEpisodes

type SonarrEpisodes interface {
	GetEpisodes(ctx context.Context, seriesID int) ([]arrapi.Episode, error)
}

SonarrEpisodes is the Sonarr surface MediaRef resolution needs.

type State

type State struct {
	Cfg    PathValidator
	Sonarr SonarrEpisodes
	Radarr RadarrMovie
}

State is the per-request snapshot of hot-reloadable dependencies. Arr clients are nil when the corresponding arr is not configured.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL