catalog

package
v0.3.6 Latest Latest
Warning

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

Go to latest
Published: Nov 9, 2026 License: GPL-3.0 Imports: 50 Imported by: 0

Documentation

Overview

Package catalog implements core.Catalog: a SQLite mirror of a filtered TMDB slice plus on-demand fetches for anything else.

Index

Constants

View Source
const DefaultCrowdTTL = 72 * time.Hour

DefaultCrowdTTL is how long a stored co-engagement list is fresh.

Variables

View Source
var ErrSeekUnavailable = core.ErrSeekUnavailable

ErrSeekUnavailable reports that a seek's title is gone from the page's order (deleted, hidden or filtered out); the caller pages by offset.

View Source
var ErrSyncInProgress = fmt.Errorf("catalog: sync already in progress")

Sync (re)builds the browsable set from TMDB discover. A second call while one is running returns ErrSyncInProgress; use SyncProgress to poll.

Functions

func FoldSortTitle added in v0.3.0

func FoldSortTitle(s string) string

FoldSortTitle normalizes an explicit sort title to lowercase with Latin diacritics and ligatures folded to their base ASCII letters.

func HomeReleaseAgeAdmitted added in v0.3.3

func HomeReleaseAgeAdmitted(t *core.Title, now time.Time) bool

HomeReleaseAgeAdmitted reports admission based on a known cinema date.

func MeetsThreshold

func MeetsThreshold(cfg config.Catalog, t core.Title, now time.Time) bool

MeetsThreshold reports whether a title qualifies for the browsable set under cfg: popularity >= MinPopularity OR votes >= MinVotes, and released. A saved 0/0 (Off) means the Off floor (config.Catalog.Bars).

func ReadViewerRulesInputs

func ReadViewerRulesInputs(ctx context.Context, tx *sql.Tx) (viewerpolicy.Inputs, error)

ReadViewerRulesInputs is the per-viewer rules source of a viewer-policy inputs rebuild: what every stored custom document reads. Inherit and exempt documents read a subset of the global rules, which the config source has.

func ReadViewerRulesRevision

func ReadViewerRulesRevision(ctx context.Context, db *sql.DB, id int64) (int64, error)

ReadViewerRulesRevision is an indexed point read, also used by durable Home snapshots.

func RebuildLibraryCounts

func RebuildLibraryCounts(ctx context.Context, db *sql.DB) error

RebuildLibraryCounts recounts the trigger-kept library totals (the browse and dashboard tallies and the series' episode rating histograms) from the stored titles, and the vocabularies' facet identifiers (facet_ids.go), for a bulk load that set the catalogue's triggers aside (the performance fixture).

func RuleIdentity

func RuleIdentity(rule core.LibraryExclusion) string

func SortTitle added in v0.3.0

func SortTitle(s string) string

SortTitle returns the normalized lowercase sort key for a display title: strips invisible format characters, removes a leading English article ("the ", "a ", "an "), and folds Latin accents/diacritics and ligatures to their base ASCII letters so titleSort and firstCharacter share one collation.

func TierUnclassifiedTables

func TierUnclassifiedTables(ctx context.Context, db *sql.DB) ([]string, error)

TierUnclassifiedTables lists the tables in db that are neither a touch source nor declared untouched: a new table must be classified before the tier migration can be trusted with it.

func WithReadWaitBudget added in v0.3.0

func WithReadWaitBudget(ctx context.Context, d time.Duration) context.Context

WithReadWaitBudget bounds the total time reads under ctx wait on TMDB, across every title they touch. A page that lists thirty titles with no details yet waits at most d in all, not 2 s for each; the rest are served as stored and refreshed in the background.

Types

type Catalog

type Catalog struct {
	// contains filtered or unexported fields
}

Catalog implements core.Catalog.

func New

func New(db *sql.DB, client TMDB, cfg config.Catalog, log *slog.Logger, opts ...Option) (*Catalog, error)

New creates a catalogue. client may be nil (mirror-only / offline mode).

func (*Catalog) AiredEpisodeTallies

func (c *Catalog) AiredEpisodeTallies(ctx context.Context, userID int64, shows []core.RatingKey, settled time.Time, judge []core.RatingKey) (map[core.RatingKey]core.AiredEpisodeTally, error)

AiredEpisodeTallies counts, for each show, the aired episodes CachedAiredEpisodes lists and how many of them userID has played (a watch row with a view), by season, without reading the episodes. Aired episodes released after settled, or named in judge, are not counted: their keys are returned for the caller to judge one by one. A zero settled counts every aired episode. One statement per airedManyBatch shows.

func (*Catalog) ApplyProviderCertification added in v0.3.0

func (c *Catalog) ApplyProviderCertification(ctx context.Context, key core.RatingKey, label, provider string) error

ApplyProviderCertification records a certification an owner-configured rating provider (OMDb, MDBList) reports for a movie or show: IMDb's US certificate. It fills titles TMDB rates only abroad, "not rated" or not at all, and never replaces TMDB's rating for the region or the US.

func (*Catalog) ArtworkRows

func (c *Catalog) ArtworkRows(ctx context.Context, keys []core.RatingKey, facts core.TitleFacts) (map[core.RatingKey]core.Title, error)

ArtworkRows is ArtworkTitles for an artwork request that lists no credited people's portraits (frontend.WithArtworkRows): stored movies and shows come as their rows with the facts the catalogue's policy for ctx's viewer reads and facts, never read in full nor put in the title cache. They are kept as that viewer's partial titles (exportCache) for the image reads that follow a grant; a title already cached in full is served from the cache. Other keys are read as ArtworkTitles reads them.

func (*Catalog) ArtworkTitle

func (c *Catalog) ArtworkTitle(ctx context.Context, key core.RatingKey) (*core.Title, error)

ArtworkTitle is CachedGet that also admits a title held only in memory.

func (*Catalog) ArtworkTitles

func (c *Catalog) ArtworkTitles(ctx context.Context, keys []core.RatingKey) (map[core.RatingKey]core.Title, error)

ArtworkTitles is CachedGetMany that also admits titles held only in memory. A title of the viewer's current export page comes as that page read it (exportCache): the facts its policy reads, so an export's signer does not read the whole library a second time.

func (*Catalog) BackfillHomeReleases added in v0.2.34

func (c *Catalog) BackfillHomeReleases(ctx context.Context, limit int) (int, error)

BackfillHomeReleases fetches at most limit details, never from a browse. Missing and future dates are revisited daily; failures also cool down for a day so an unavailable title cannot starve the remaining catalogue. Recently reached dates are rechecked daily; settled dates are revisited after 90 days to pick up later corrections.

func (*Catalog) BackgroundBusy

func (c *Catalog) BackgroundBusy() int

BackgroundBusy is how many refreshes run behind reads now: the performance gate waits for none before each measured request.

func (*Catalog) Browse

func (c *Catalog) Browse(ctx context.Context, q core.BrowseQuery) (core.Page, error)

Browse queries the browsable mirror entirely in SQL, with generation-stamped in-memory count and page caching so repeated library/recommendation reads return without hitting disk.

func (*Catalog) BrowsePlaylistInSQL

func (*Catalog) BrowsePlaylistInSQL() bool

func (*Catalog) BrowseStoredEpisodes added in v0.3.0

func (c *Catalog) BrowseStoredEpisodes(ctx context.Context, q core.BrowseQuery) (core.Page, error)

BrowseStoredEpisodes pages stored, aired episodes in the library without refreshing shows or seasons from TMDB. Sequential key-ordered pages advance via a cached key cursor so deep offsets stay O(limit).

func (*Catalog) BrowseStoredSeasons added in v0.3.0

func (c *Catalog) BrowseStoredSeasons(ctx context.Context, q core.BrowseQuery) (core.Page, error)

BrowseStoredSeasons pages stored, listable seasons in the library without refreshing shows or seasons from TMDB.

func (*Catalog) ByExternalID

func (c *Catalog) ByExternalID(ctx context.Context, source, id string) (*core.Title, error)

ByExternalID looks up by imdb ("tt123") or tvdb id.

func (*Catalog) ByExternalIDForType added in v0.2.53

func (c *Catalog) ByExternalIDForType(ctx context.Context, source, id string, typ core.MediaType) (*core.Title, error)

ByExternalIDForType looks up an external ID restricted to typ ("movie", "show", or "episode"), avoiding cross-type integer ID collisions on TVDB and allowing stored non-adult titles even when gated on browse shelves.

func (*Catalog) CachedAddedTimes added in v0.3.0

func (c *Catalog) CachedAddedTimes(ctx context.Context, keys []core.RatingKey) (map[core.RatingKey]time.Time, error)

CachedAddedTimes reads date added without fetching or resolving metadata. Missing, unknown and future dates are omitted. A later aired episode replaces a series or season premiere; an unaired episode does not.

func (*Catalog) CachedAiredEpisodes added in v0.3.0

func (c *Catalog) CachedAiredEpisodes(ctx context.Context, show core.RatingKey, offset, limit int) (core.Page, error)

CachedAiredEpisodes returns locally stored, aired, non-adult episodes in descending air-date order. A zero show key selects all shows. It performs no network access or viewer filtering; callers must enforce viewer policy before applying their final limit.

func (*Catalog) CachedAiredEpisodesMany

func (c *Catalog) CachedAiredEpisodesMany(ctx context.Context, shows []core.RatingKey, limit int) (map[core.RatingKey]core.Page, error)

CachedAiredEpisodesMany is CachedAiredEpisodes(show, 0, limit) for several shows: per show the same aired episodes, newest first, and the same total. A page of shows costs two statements per airedManyBatch shows instead of a count, a list and their hydration per show.

func (*Catalog) CachedCreditItems added in v0.3.0

func (c *Catalog) CachedCreditItems(ctx context.Context, after core.RatingKey, limit int) ([]core.RatingKey, error)

CachedCreditItems pages existing cached credit membership, including titles opened on demand. It never fetches metadata or hydrates filmographies. Viewer and inherited-title policy belongs to the consuming Experience.

func (*Catalog) CachedCreditTitles added in v0.3.0

func (c *Catalog) CachedCreditTitles(ctx context.Context, keys []core.RatingKey, cfg config.Catalog, now time.Time) (map[core.RatingKey]core.Title, error)

CachedCreditTitles is a read-only People snapshot. Existing cached-read gates may conservatively omit a title; every returned title is additionally checked against the caller's immutable config and time, including inherited parents. The caller must hold the config lease and fence durable changes separately.

func (*Catalog) CachedDiscoveryEpisodes added in v0.2.9

func (c *Catalog) CachedDiscoveryEpisodes(ctx context.Context, shows []core.RatingKey) (map[core.RatingKey][]core.Title, error)

CachedDiscoveryEpisodes returns regular episode metadata without hydration or remote lookups. Unknown/future dates are retained so callers can distinguish an unmirrored premiere from a known but unreleased episode.

func (*Catalog) CachedGet added in v0.3.0

func (c *Catalog) CachedGet(ctx context.Context, key core.RatingKey) (*core.Title, error)

CachedGet applies the same catalogue visibility gates as Get without a network refresh. Missing or hidden records fail closed; callers may use the normal catalogue path separately to populate the local mirror.

func (*Catalog) CachedGetMany added in v0.3.0

func (c *Catalog) CachedGetMany(ctx context.Context, keys []core.RatingKey) (map[core.RatingKey]core.Title, error)

func (*Catalog) CachedPortraitTitles

func (c *Catalog) CachedPortraitTitles(ctx context.Context, subjects map[core.RatingKey][]int64, cfg config.Catalog, now time.Time) (map[core.RatingKey]core.Title, error)

CachedPortraitTitles reads fresh visibility facts and the requested credits, never the minute cache. Movie/show portraits need no episode counts, trailers, alternate names or other people's credits. Child titles keep the full reader because their visibility can inherit parent facts. The caller owns the policy lease and must repeat this read and fence changes before publishing results.

func (*Catalog) CachedTitle

func (c *Catalog) CachedTitle(ctx context.Context, key core.RatingKey) (*core.Title, error)

CachedTitle returns a mirrored title without contacting TMDB.

func (*Catalog) CachedTitles added in v0.2.6

func (c *Catalog) CachedTitles(ctx context.Context, keys []core.RatingKey) ([]core.Title, error)

CachedTitles returns released, non-adult mirrored titles in requested order, without refreshing remote metadata. Batch consumers such as recommendation builders use this to avoid one full hydration round trip per history entry.

func (*Catalog) CachedVisibilityRows

func (c *Catalog) CachedVisibilityRows(ctx context.Context, keys []core.RatingKey, facts core.TitleFacts) (map[core.RatingKey]core.Title, error)

CachedVisibilityRows is CachedGetMany for a visibility decision: stored rows that pass the list gates, with the facts the catalogue's policy for ctx's viewer reads (viewerPolicyFacts) and facts, the caller's own, and without credits. The partial titles are not cached.

func (*Catalog) CachedVisibleTitles

func (c *Catalog) CachedVisibleTitles(ctx context.Context, keys []core.RatingKey, cfg config.Catalog, now time.Time) (map[core.RatingKey]core.Title, error)

CachedVisibleTitles is CachedCreditTitles for a visibility decision only: the stored rows, plus the facts visibility rules and the release gate read when they read any, without credits or alternative titles.

func (*Catalog) CaptureConfigEvidence added in v0.3.0

func (c *Catalog) CaptureConfigEvidence(expected config.Catalog) (ConfigEvidence, bool)

CaptureConfigEvidence compares while holding cfgMu. The caller must own or synchronize expected for the duration of the call.

func (*Catalog) Children

func (c *Catalog) Children(ctx context.Context, key core.RatingKey) ([]core.Title, error)

Children: seasons of a show, episodes of a season.

func (*Catalog) Collection

func (c *Catalog) Collection(ctx context.Context, tmdbCollection int64) (string, []core.Title, error)

Collection returns a TMDB franchise and its parts sorted by release date.

func (*Catalog) ConsoleCount

func (c *Catalog) ConsoleCount(ctx context.Context, f core.Filter) (int, bool, error)

ConsoleCount returns a bounded cache lookup and starts at most one background count per expression. Stateful counts expire after five seconds as state has a separate generation from catalogue metadata.

func (*Catalog) ConsoleFacets

func (c *Catalog) ConsoleFacets(ctx context.Context, typ core.MediaType, facet string) ([]core.FacetValue, bool, error)

ConsoleFacets never starts a library-sized count on the HTTP request. Values are a generation-stamped snapshot; pending tells the UI to retry progressively. A nil slice means no snapshot has completed yet; a non-nil empty slice is a valid cached result, including while its replacement is being refreshed.

func (*Catalog) ConsoleGateChanged

func (c *Catalog) ConsoleGateChanged()

ConsoleGateChanged records a new published hidden set. Ordinary browse filters carry the generations they read, so only release-gate results are keyed by it (consoleGateKey); the catalogue's other caches stay.

func (*Catalog) ContinueEpisodes

func (c *Catalog) ContinueEpisodes(ctx context.Context, episodes []core.RatingKey) (map[core.RatingKey]*core.Title, error)

ContinueEpisodes returns locally available next episodes and schedules recovery for missing metadata. A Home read must never wait for a provider: hydration is bounded, deduplicated and detached by the ordinary catalogue read path. Keep NextStoredEpisodes strictly local for other snapshot consumers.

func (*Catalog) CountBrowsable added in v0.3.0

func (c *Catalog) CountBrowsable(ctx context.Context, typ core.MediaType) (int, error)

CountBrowsable is the library size behind a view list. The (type, browsable, adult) prefix of catalog_titles_type_browse_sort answers it without reading title rows.

func (*Catalog) CountStoredEpisodes added in v0.3.0

func (c *Catalog) CountStoredEpisodes(ctx context.Context, show core.Filter) (int, error)

CountStoredEpisodes counts StoredEpisodesByKey's whole set. Like a browse total, a large count is served from the last one while it recounts.

func (*Catalog) CountStoredSeasons added in v0.3.0

func (c *Catalog) CountStoredSeasons(ctx context.Context, show core.Filter) (int, error)

CountStoredSeasons counts the stored, listable seasons in the library for the browse filter show.

func (*Catalog) CountUnplayedAired added in v0.3.0

func (c *Catalog) CountUnplayedAired(ctx context.Context, userID int64, keys []core.RatingKey, maxRating string) (map[core.RatingKey]int, error)

CountUnplayedAired counts aired episodes a viewer has not played, for each show or season key. It uses the aired index and the watch primary key. It does not load episode rows or refresh metadata. An empty max rating counts every aired episode. A set rating drops unrated and stricter ones.

func (*Catalog) CountWatched

func (c *Catalog) CountWatched(ctx context.Context, f core.Filter, userID int64) (int, error)

CountWatched counts the titles matching f that userID has played (a watch row with a view), driven from the viewer's watch rows: its cost follows the viewer's history, not the library.

func (*Catalog) CrowdGraphBatch added in v0.2.7

func (c *Catalog) CrowdGraphBatch(ctx context.Context, keys []core.RatingKey, maxPerKey, maxTargets int) (map[core.RatingKey][]core.RatingKey, map[core.RatingKey]core.Title, error)

CrowdGraphBatch reads stored co-engagement lists from catalog_crowd for a batch of keys without contacting TMDB, and resolves up to maxTargets of the referenced titles from the local mirror. Used for 2-hop crowd expansion.

func (*Catalog) CrowdRecommendations

func (c *Catalog) CrowdRecommendations(ctx context.Context, key core.RatingKey, pages int, fetch bool) (titles []core.Title, fetched bool, err error)

CrowdRecommendations returns TMDB's crowd co-engagement list for a title (/recommendations only: never metadata similarity), best first, from up to pages pages (1–2). A stored list younger than the crowd TTL with enough pages is served from the local mirror. Otherwise, with fetch set, TMDB is asked and fetched reports it (callers budget those calls); without fetch, or when TMDB fails, a stale list is served when there is one and nil otherwise. Titles not yet released are dropped.

func (*Catalog) DigitalEvidenceMovies

func (c *Catalog) DigitalEvidenceMovies(ctx context.Context, since time.Time) ([]core.Title, error)

DigitalEvidenceMovies excludes settled home releases at the database boundary. Unknown and future home dates remain gated, regardless of cinema age.

func (*Catalog) DiscoveryVisibleForViewer

func (c *Catalog) DiscoveryVisibleForViewer(u *core.User, t *core.Title) bool

func (*Catalog) EpisodesUnranked added in v0.3.0

func (c *Catalog) EpisodesUnranked(ctx context.Context) (bool, error)

EpisodesUnranked reports that no stored episode has a popularity, so a popularity order of episodes is their key order. TMDB gives episodes no popularity; this checks the index's two ends rather than assuming it.

func (*Catalog) Facet

func (c *Catalog) Facet(ctx context.Context, t core.MediaType, facet string) ([]core.FacetValue, error)

Facet returns value counts over the browsable set.

func (*Catalog) FacetFilter added in v0.3.0

func (c *Catalog) FacetFilter(ctx context.Context, f core.Filter, facet string) ([]core.FacetValue, error)

FacetFilter returns value counts over the browsable set matching f.

func (*Catalog) FacetPage

func (c *Catalog) FacetPage(ctx context.Context, f core.Filter, facet string, offset, limit int) ([]core.FacetValue, int, bool, error)

FacetPage is a page of facet's values (a FacetFilter facet with a presence: "studioAll") over the browsable set matching f, in byte order, and how many values there are. ok is false when the presence does not answer f; FacetFilter counts it whole. Per value the count is facetFromTally's; the total is

counts(range) + Σ over the values the pending deltas and unsettled
titles touch of [count > 0] - [tally > 0].

The statement reads the first presence rows in order and returns the page alone: it needs offset+limit survivors of the correction, and reads further only when the correction removed some.

func (*Catalog) FillPending

func (c *Catalog) FillPending() int

FillPending is how many export ids the mirror still has to fetch.

func (*Catalog) FillPosters

func (c *Catalog) FillPosters(ctx context.Context, titles []core.Title, unknown func(*core.Title) bool, wait time.Duration)

FillPosters sets PosterPath on movies and shows that have none and whose poster is unknown (unknown reports which), from the poster cache or a TMDB lookup that answers within wait. Titles without one after that keep an empty path; frontends render Ether's generated poster for them. It never reorders titles and never writes rows.

func (*Catalog) FilterVisibility added in v0.2.12

func (c *Catalog) FilterVisibility(ctx context.Context, ts []core.Title) []core.Title

FilterVisibility enriches remote summaries with already-known local facets; there are no detail fetches or indexer calls. Unknown metadata stays unknown, and for a restricted viewer an undetailed summary their rules cannot judge is dropped (fail closed). An explicit search context (core.WithExplicitSearch) skips rules the owner limited to browsing.

func (*Catalog) Findable

func (c *Catalog) Findable(ctx context.Context, key core.RatingKey) (bool, error)

Findable reports whether key is a stored title or a long-tail entry: one a search can return and Get can open.

func (*Catalog) FreezeCacheMinute

func (c *Catalog) FreezeCacheMinute()

FreezeCacheMinute keys the catalogue's caches by its write generation alone, without the wall-clock minute, so a measured replay hits and misses the same way whenever it crosses a minute (the performance gate). Production never calls it.

func (*Catalog) Get

func (c *Catalog) Get(ctx context.Context, key core.RatingKey) (*core.Title, error)

Get returns a title from the mirror, fetching on demand when missing/stale.

func (*Catalog) Grandchildren

func (c *Catalog) Grandchildren(ctx context.Context, key core.RatingKey) ([]core.Title, error)

Grandchildren: all episodes of a show.

func (*Catalog) HomeReleases added in v0.3.0

func (c *Catalog) HomeReleases(ctx context.Context, keys []core.RatingKey) (map[core.RatingKey]time.Time, error)

func (*Catalog) InvalidateConsoleReleaseEvidence

func (c *Catalog) InvalidateConsoleReleaseEvidence(ctx context.Context, id string)

func (*Catalog) LibraryPosters added in v0.3.0

func (c *Catalog) LibraryPosters(ctx context.Context, typ core.MediaType, limit int) ([]core.Title, error)

LibraryPosters returns the first poster paths for a library collage. It reads the popularity index and does not hydrate title rows.

func (*Catalog) List

func (c *Catalog) List(ctx context.Context, list string, t core.MediaType, limit int) ([]core.Title, error)

List returns TMDB curated/trending lists with a short in-memory TTL.

func (*Catalog) LoadRowFacts

func (c *Catalog) LoadRowFacts(ctx context.Context, titles []core.Title) []core.Title

LoadRowFacts is a bounded read at row assembly, never a visibility predicate. Warm canonical titles reuse the catalogue cache. Each cold chunk reads release, home-release and mandatory safety facts together with indexed key lookups. Missing remote summaries remain in memory until background mirroring succeeds.

func (*Catalog) MatchPeople added in v0.3.0

func (c *Catalog) MatchPeople(ctx context.Context, terms []string, limit int) ([]PersonMatch, error)

MatchPeople returns up to limit credited people, most credited first, whose name has a word starting with each of terms (lower-case words, as split by the caller). The match is a candidate set: on PostgreSQL it is a substring match, so callers recheck names with their own word rules.

func (*Catalog) MatchesConfig added in v0.3.0

func (c *Catalog) MatchesConfig(expected config.Catalog) bool

MatchesConfig reports whether the catalog actually installed the config captured by a held config-policy lease. UpdateConfig can reject invalid visibility rules and retain the previous config; checking the Store alone therefore does not prove the catalog read used that snapshot. This method is a prework check, not an authorization or a durable epoch fence.

func (*Catalog) MaterializeMetadata added in v0.3.3

func (c *Catalog) MaterializeMetadata(ctx context.Context, provider string, req core.MetadataRequest) (*core.Title, error)

MaterializeMetadata is called by collection/row builds, not by Get, Browse or Search. Existing canonical titles are reused without replacing their data. New records are available to the requesting collection but are not admitted to the global browse library just because a provider returned them.

func (*Catalog) MemoryTitle

func (c *Catalog) MemoryTitle(ctx context.Context, key core.RatingKey) (t *core.Title, stored bool, err error)

MemoryTitle tells an artwork reader how to read key without promoting it. stored reports a row (read it as usual); otherwise t is the title held in memory, or nil when the catalogue knows nothing of key. Reading a search hit's thumbnail must not store the hit: only opening it does.

func (*Catalog) MigrateTiers

func (c *Catalog) MigrateTiers(ctx context.Context) error

MigrateTiers runs the tier migration once, to completion or the first error (tests and tools; the server runs it from Run, with retries).

func (*Catalog) MovieReleaseTimeline added in v0.3.3

func (c *Catalog) MovieReleaseTimeline(ctx context.Context, key core.RatingKey) ([]ReleaseDate, error)

MovieReleaseTimeline returns a movie's release dates from TMDB, sorted by date. It contacts TMDB (the owner opened one title's release tab); there is no mirror of every country's premieres and TV dates.

func (*Catalog) NextEpisode

func (c *Catalog) NextEpisode(ctx context.Context, episode core.RatingKey) (*core.Title, error)

NextEpisode returns the next regular episode (specials, season 0, are skipped as a target; calling it on a special jumps to S1E1-order sequence).

func (*Catalog) NextStoredEpisode added in v0.3.0

func (c *Catalog) NextStoredEpisode(ctx context.Context, episode core.RatingKey) (*core.Title, error)

NextStoredEpisode is the next regular episode already in the catalogue. It does not refresh the show or season from a provider. Home, library rows, and model builds use it so one stale show cannot stall the request. A special still jumps to the first regular episode, matching NextEpisode.

func (*Catalog) NextStoredEpisodes added in v0.3.3

func (c *Catalog) NextStoredEpisodes(ctx context.Context, episodes []core.RatingKey) (map[core.RatingKey]*core.Title, error)

NextStoredEpisodes is NextStoredEpisode for several episodes; episodes with no stored next episode are absent from the result. Each show's window is read on its own; the episodes returned are hydrated together. Continue Watching asks this for each of up to fifty shows, and hydrating per show (or, before, every episode of each window) read titles' facts and their shows' seasons fifty times per Home request.

func (*Catalog) OnTierMigrated

func (c *Catalog) OnTierMigrated(fn func())

OnTierMigrated registers fn to run once the tier migration finishes (the tail index rebuilds without the removed rows' exclusion).

func (*Catalog) PersistsArtwork

func (c *Catalog) PersistsArtwork(ctx context.Context, key core.RatingKey) bool

PersistsArtwork reports whether facts derived from key's artwork (blurhashes, UltraBlur colours) may be written: yes for a stored title and for every key that is not a title; no for a movie, show, season or episode with no row. Search and list hits are read-only: their artwork facts stay in memory, or every search typed would add rows.

func (*Catalog) PersonByFacetID

func (c *Catalog) PersonByFacetID(ctx context.Context, id string) (int64, bool, error)

PersonByFacetID is the credited person whose core.FacetID("person", tmdb) is id, read by index; ok is false when no credit names such a person.

func (*Catalog) PersonByTMDB added in v0.3.2

func (c *Catalog) PersonByTMDB(ctx context.Context, person int64) (core.Person, []string, core.RatingKey, error)

PersonByTMDB resolves one TMDB person from their credits: the person's name and portrait, the credit kinds they hold ("cast", "director", "writer", "crew", in that order) and a title key carrying the portrait credit (for per-title person image routes). It returns core.ErrNotFound when the catalogue credits nobody under that id. Both reads ride the catalog_credits_person index, so the cost scales with the person's own credits, never with the library.

func (*Catalog) PersonCreditForms

func (c *Catalog) PersonCreditForms(ctx context.Context, person int64, types []core.MediaType) ([]core.PersonCreditForm, error)

PersonCreditForms returns the distinct forms (name, and whether a portrait is attached) a person's credits take on titles of types, with PersonCreditKeys' title gate. It reads the person's own credit index, never the library, and returns a handful of rows however many titles credit them.

func (*Catalog) PersonCreditKeys added in v0.3.0

func (c *Catalog) PersonCreditKeys(ctx context.Context, person int64, types []core.MediaType, limit int) ([]core.RatingKey, error)

PersonCreditKeys returns up to limit titles of types that credit person, most popular first. Like the credit walk it replaces, it includes titles opened on demand (not browsable); callers apply the viewer's visibility.

func (*Catalog) PersonCreditTitles

func (c *Catalog) PersonCreditTitles(ctx context.Context, persons []int64, types []core.MediaType, limit int, facts core.TitleFacts) (map[int64][]core.PersonCredit, map[core.RatingKey]core.Title, error)

PersonCreditTitles is PersonCreditsMany with the credited titles' visibility rows (CachedVisibilityRows, with facts) read in the same statements: a people search's credits and the rows it judges them by.

func (*Catalog) PersonCreditsMany

func (c *Catalog) PersonCreditsMany(ctx context.Context, persons []int64, types []core.MediaType, limit int) (map[int64][]core.PersonCredit, error)

PersonCreditsMany is PersonCreditKeys for several people at once: each person's most popular credited titles of the given types (at most limit each), with the person's credit rows on them, most popular first.

func (*Catalog) PersonFormKeys

func (c *Catalog) PersonFormKeys(ctx context.Context, person int64, types []core.MediaType, forms []core.PersonCreditForm, after core.RatingKey, limit int) ([]core.RatingKey, error)

PersonFormKeys returns up to limit keys after after, ascending, of the titles of types that credit person in one of forms, with PersonCreditKeys' title gate.

func (*Catalog) PlexOnDeckPage

func (c *Catalog) PlexOnDeckPage(ctx context.Context, uid int64, offset, limit int, f core.Filter, episodesOnly bool) (core.WatchPage, error)

PlexOnDeckPage starts with the viewer's indexed watch rows, chooses one candidate per show, applies catalogue policy, and pages before hydration.

func (*Catalog) PlexOnDeckPageSince

func (c *Catalog) PlexOnDeckPageSince(ctx context.Context, uid int64, offset, limit int, f core.Filter, episodesOnly bool, since time.Time) (core.WatchPage, error)

PlexOnDeckPageSince is PlexOnDeckPage within a window (PMS's OnDeckWindow): a show or movie last watched before since is left out, except a season premiere (episode 1 of a season after the first) that aired since then. A zero since keeps every row.

func (*Catalog) PlexRecentPage

func (c *Catalog) PlexRecentPage(ctx context.Context, q core.BrowseQuery, kinds []core.MediaType) (core.Page, error)

PlexRecentPage merges independently indexed movie and episode streams in SQL. Only the requested page is hydrated; totals use the catalogue's count cache.

func (*Catalog) PolicyInputs

func (c *Catalog) PolicyInputs(expected config.Catalog) (viewerpolicy.Inputs, bool)

PolicyInputs lists what the installed global rules read, and whether the installed config is expected (a rejected update keeps the previous rules).

func (*Catalog) PopularScheduledMovies added in v0.3.0

func (c *Catalog) PopularScheduledMovies(ctx context.Context, from, until time.Time, limit int) ([]ScheduleEntry, error)

PopularScheduledMovies returns the most popular mirrored movies released in [from, until), most popular first.

func (*Catalog) PosterLookups

func (c *Catalog) PosterLookups() int64

PosterLookups reports how many TMDB poster lookups this catalogue started.

func (*Catalog) PreloadStored

func (c *Catalog) PreloadStored(ctx context.Context, keys []core.RatingKey)

PreloadStored reads in batches the mirrored titles loadByKey would read one at a time, and caches each exactly as loadByKey does (detail time included) for this generation and minute. A list of episodes checks every episode's series; later Get and CachedGet calls then hit the cache. Titles already cached, missing or unreadable are left to loadByKey.

func (*Catalog) PreviewVisibility added in v0.2.12

func (c *Catalog) PreviewVisibility(ctx context.Context, specs []core.LibraryExclusion) (any, error)

func (*Catalog) PreviewVisibilityPolicy added in v0.3.0

func (c *Catalog) PreviewVisibilityPolicy(ctx context.Context, specs []core.LibraryExclusion, fallback string) (any, error)

func (*Catalog) PruneConsoleReleaseEvidence

func (c *Catalog) PruneConsoleReleaseEvidence(ctx context.Context, now int64) error

PruneConsoleReleaseEvidence drops at most one fixed-size expired chunk per monitor cycle. It never scans the title library or blocks a browse request.

func (*Catalog) PutViewerRules

func (c *Catalog) PutViewerRules(ctx context.Context, id int64, p ViewerRules) error

func (*Catalog) RankingDetails

func (c *Catalog) RankingDetails(ctx context.Context, items []core.Title) ([]core.Title, error)

RankingDetails is SearchDetails for a ranking window (core.WithRankingCandidates): items with the titles it returned partial read in full, as a browse without the window returns them.

func (*Catalog) RebuildRank

func (c *Catalog) RebuildRank(ctx context.Context) (RankRebuild, error)

RebuildRank refreshes every title's rank from its current popularity: it rebuilds the idle copy and makes it the live one. Writers never wait on it for longer than one short transaction: while the copy is being built the triggers leave it alone and record changed keys in catalog_rank_pending; the copy is filled in key-ordered chunks of their own transactions, its indexes built one at a time, and the pending keys applied in the short transaction that swaps the view.

func (*Catalog) RecentAir added in v0.3.0

func (c *Catalog) RecentAir(ctx context.Context, typ core.MediaType, offset, limit int) ([]core.Title, bool, error)

RecentAir returns browsable titles ordered by date added: a movie's release, or a show's latest episode already aired. It reads the released and aired indexes and stops after a bounded window. It does not refresh metadata. offset skips that many titles in the same order. A show with no aired episode is omitted. done is true when this window reached the end.

func (*Catalog) RecentAirExcluding added in v0.3.0

func (c *Catalog) RecentAirExcluding(ctx context.Context, typ core.MediaType, exclude []core.RatingKey, offset, limit int) ([]core.Title, bool, error)

RecentAirExcluding is RecentAir without the titles in exclude: the evidence window's hidden titles, which a viewer never sees, so a page of them does not use up the bounded window.

func (*Catalog) RecentAirFiltered added in v0.3.4

func (c *Catalog) RecentAirFiltered(ctx context.Context, typ core.MediaType, f core.Filter, offset, limit int) ([]core.Title, bool, error)

RecentAirFiltered applies an availability decision before selecting the window.

func (*Catalog) RecentEpisodes

func (c *Catalog) RecentEpisodes(ctx context.Context, show core.RatingKey, since time.Time) ([]core.Title, error)

RecentEpisodes returns only local, released episode identities for artwork badges. Callers apply viewer state through core.State instead of a SQL join.

func (*Catalog) RecentEvidenceEpisodes added in v0.3.4

func (c *Catalog) RecentEvidenceEpisodes(ctx context.Context, since time.Time) ([]core.Title, error)

RecentEpisodes is the bounded evidence window used before episode paging.

func (*Catalog) RecentMovies added in v0.2.9

func (c *Catalog) RecentMovies(ctx context.Context, since time.Time) ([]core.Title, error)

RecentMovies reads the release-date index, without refreshing any title.

func (*Catalog) RecentShows added in v0.2.9

func (c *Catalog) RecentShows(ctx context.Context, since time.Time) ([]core.Title, error)

RecentShows only reads the local premiere-date index.

func (*Catalog) RecordConsoleReleaseEvidence

func (c *Catalog) RecordConsoleReleaseEvidence(ctx context.Context, key core.RatingKey, r core.Release, source, policy string, verifiedUntil, cacheUntil int64, cached int) error

RecordConsoleReleaseEvidence stores normalized observations on ingestion or verification. cached=-1 means unknown; addon cache hints never enter here.

func (*Catalog) RefreshRatings added in v0.3.0

func (c *Catalog) RefreshRatings(ctx context.Context, limit int) (int, error)

RefreshRatings re-selects the stored rating of up to limit detailed movies and shows from TMDB, walking the catalogue by key until the current rating model has covered every title. It is a no-op once done. It returns how many titles it checked.

func (*Catalog) RefreshSchedule added in v0.3.0

func (c *Catalog) RefreshSchedule(ctx context.Context, show core.RatingKey) error

RefreshSchedule mirrors a followed show's current and announced seasons, so the calendar can list their episodes without contacting TMDB on its request path. Fresh shows and seasons cost no request (Get's TTLs apply: an hour for a returning series' newest season); a settled show is left alone.

func (*Catalog) RefreshStoredTitle added in v0.3.0

func (c *Catalog) RefreshStoredTitle(ctx context.Context, key core.RatingKey) (*core.Title, error)

RefreshStoredTitle drops the minute cache and reads the stored row again. Portrait bytes use it when a credit changed inside the current cache minute.

func (*Catalog) RefreshTitle added in v0.2.37

func (c *Catalog) RefreshTitle(ctx context.Context, key core.RatingKey, persist bool) (*core.Title, error)

RefreshTitle updates canonical details from TMDB without a stale fallback.

func (*Catalog) RefreshViewerRules

func (c *Catalog) RefreshViewerRules(ctx context.Context) error

RefreshViewerRules follows viewer removal or merge after the lifecycle transaction.

func (*Catalog) RegisterMetadataProvider added in v0.3.3

func (c *Catalog) RegisterMetadataProvider(id string, provider core.MetadataProvider)

RegisterMetadataProvider replaces one provider atomically. Unregistering also prevents an outstanding resolution from materializing after revocation.

func (*Catalog) ReleaseMatches added in v0.2.8

func (c *Catalog) ReleaseMatches(ctx context.Context, releases []core.Release) (map[string][]core.Title, error)

ReleaseMatches joins a feed page to local metadata through bounded indexed lookups. It never spends a metadata API call per feed item. Ambiguous names are ignored; explicit identifier conflicts always defeat a textual match. Season packs are expanded only to locally known episodes, never to invented episode identities.

func (*Catalog) RememberListSummaries

func (c *Catalog) RememberListSummaries(ctx context.Context, titles []core.Title)

RememberListSummaries makes fresh source summaries readable immediately. The bounded existing hydration scheduler owns all mirroring behind the read; no caller waits for its writer or carries request QueryStats into that work.

func (*Catalog) ReplacePopularityIndexes

func (c *Catalog) ReplacePopularityIndexes(ctx context.Context) error

ReplacePopularityIndexes runs the post-start index swap now (benchmarks and tools; Run does it once the rank is built).

func (*Catalog) ResolveShowParents added in v0.3.0

func (c *Catalog) ResolveShowParents(ctx context.Context, keys []core.RatingKey) (map[core.RatingKey]core.Title, error)

ResolveShowParents batches fresh mirrored shows and falls back to Get for stale, missing, or non-show keys. Individual Get failures are omitted, as inherited visibility treats them as inaccessible.

func (*Catalog) Run

func (c *Catalog) Run(ctx context.Context)

Run performs an initial sync and then refreshes on RefreshInterval until ctx ends. Intended to be run in its own goroutine by the app wiring. With WithResumeSchedule the initial sync waits until the interval has passed since the last recorded sync.

func (*Catalog) RunTail

func (c *Catalog) RunTail(ctx context.Context)

RunTail keeps this node's tail index current until ctx ends. Run it on every node that serves search; nodes that never search (source roles) do not, and report the index as disabled.

func (*Catalog) RunTrends added in v0.2.12

func (c *Catalog) RunTrends(ctx context.Context)

RunTrends has an independent schedule: a slow catalogue crawl cannot delay charts. App lifetime owns and joins it, and only the cluster leader polls.

func (*Catalog) ScheduledEpisodes added in v0.2.45

func (c *Catalog) ScheduledEpisodes(ctx context.Context, key core.RatingKey) ([]core.Title, error)

ScheduledEpisodes is deliberately separate from Children/Get: discovery, autoplay, downloads and playback must never acquire future episodes through it.

func (*Catalog) ScheduledMovies added in v0.3.0

func (c *Catalog) ScheduledMovies(ctx context.Context, keys []core.RatingKey, from, until time.Time) ([]ScheduleEntry, error)

ScheduledMovies returns the given movies released in [from, until).

func (*Catalog) ScheduledPremieres added in v0.3.0

func (c *Catalog) ScheduledPremieres(ctx context.Context, shows []core.RatingKey, from, until time.Time) ([]ScheduleEntry, error)

ScheduledPremieres returns announced seasons of shows premiering in [from, until) whose episodes are not mirrored yet, so a calendar can still mark the premiere.

func (*Catalog) ScheduledShows added in v0.3.0

func (c *Catalog) ScheduledShows(ctx context.Context, shows []core.RatingKey, from, until time.Time, limit int) ([]ScheduleEntry, error)

ScheduledShows returns the dated episodes of shows released in [from, until), including future ones, and each episode's season size. Episodes inherit their show's content rating so callers can apply viewer policy. It never contacts TMDB: only mirrored seasons are listed (see RefreshSchedule).

func (*Catalog) Search

func (c *Catalog) Search(ctx context.Context, query string, types []core.MediaType, limit int) ([]core.Title, error)

Search reaches all of TMDB; matched titles join the browsable set.

func (*Catalog) SearchAll added in v0.2.53

func (c *Catalog) SearchAll(ctx context.Context, query string, types []core.MediaType, limit int) ([]core.Title, error)

SearchAll searches local and remote titles without applying browse-shelf visibility or home-release gating (used when importing external watch state).

func (*Catalog) SearchDetails

func (c *Catalog) SearchDetails(ctx context.Context, items []core.Title) ([]core.Title, error)

SearchDetails returns items with the titles a candidate search (the core.SearchCandidates in ctx) returned partial read in full, each keeping its search score: the page its caller shows, as a search without SearchCandidates would have returned it. Other titles are unchanged.

func (*Catalog) SearchListDetails

func (c *Catalog) SearchListDetails(ctx context.Context, items []core.Title) ([]core.Title, error)

SearchListDetails is SearchDetails for titles shown as list items (core.WithSearchListFacts): the partial ones gain the facts a list shows (hydrateListSlice) on the rows the search read, not cached, each keeping its search score.

func (*Catalog) SearchPeople added in v0.3.0

func (c *Catalog) SearchPeople(ctx context.Context, terms []string, limit int) ([]core.Person, error)

SearchPeople returns up to limit credited people matching terms, most credited first.

func (*Catalog) SearchVisible added in v0.3.2

func (c *Catalog) SearchVisible(t *core.Title) bool

SearchVisible skips rules limited to browsing, for explicit search results.

func (*Catalog) SeasonEpisodePage added in v0.3.0

func (c *Catalog) SeasonEpisodePage(ctx context.Context, show core.RatingKey, season, offset, limit int) (core.Page, error)

SeasonEpisodePage returns one window of a single catalogue season. The count and the page both use catalog_titles_show (show_key, season, episode), so a season of thousands of episodes does not load the rest of the show. limit is 1..1000. The first page checks season freshness, serving stored episodes while stale metadata refreshes. Later pages do not fetch.

func (*Catalog) SeedGenres

func (c *Catalog) SeedGenres(mediaType string, m map[int]string)

SeedGenres pre-populates the genre id->name cache (tests, offline warm-up).

func (*Catalog) SetConsoleAvailabilityPolicy

func (c *Catalog) SetConsoleAvailabilityPolicy(policy func() string)

SetConsoleAvailabilityPolicy binds the same cached policy stamp used by the availability service. A changed source/quality configuration retracts evidence immediately, without rewriting every title or parsing release payloads.

func (*Catalog) SetConsoleGatePolicies

func (c *Catalog) SetConsoleGatePolicies(provider func() (string, string))

Expected policies are computed from settings/feed generations, never from title scans. Published generations remain immutable while being read.

func (*Catalog) SetNow added in v0.3.0

func (c *Catalog) SetNow(now func() time.Time)

SetNow replaces the clock used for release checks and the minute snapshot. The swap is atomic so a background browse can keep reading it.

func (*Catalog) SetRankReady

func (c *Catalog) SetRankReady(on bool)

SetRankReady turns the rank's read paths on or off, so a benchmark can compare the same pages both ways. Production sets it by building the rank.

func (*Catalog) SetTMDB added in v0.3.0

func (c *Catalog) SetTMDB(client TMDB)

SetTMDB replaces the remote metadata client. Nil answers from the local catalogue only.

func (*Catalog) SetTierBackup

func (c *Catalog) SetTierBackup(fn TierBackupFunc)

SetTierBackup wires the backup check the tier migration waits for.

func (*Catalog) SetTierMigrationPaused

func (c *Catalog) SetTierMigrationPaused(ctx context.Context, paused bool) error

SetTierMigrationPaused pauses or resumes the tier migration. It applies on every node: the flag is in catalog_meta.

func (*Catalog) SetTitleTags

func (c *Catalog) SetTitleTags(ctx context.Context, key core.RatingKey, tags []string) error

SetTitleTags writes bounded owner annotations without changing provider facts.

func (*Catalog) SetViewerRulesBus

func (c *Catalog) SetViewerRulesBus(bus clusterbus.Bus)

func (*Catalog) ShowTrajectories added in v0.2.7

func (c *Catalog) ShowTrajectories(ctx context.Context, shows []core.RatingKey) (map[core.RatingKey]core.ShowTrajectory, error)

ShowTrajectories computes season/episode rating trajectories for a batch of shows from local mirror data without contacting TMDB.

func (*Catalog) Similar

func (c *Catalog) Similar(ctx context.Context, key core.RatingKey, limit int) ([]core.Title, error)

Similar returns TMDB recommendations ∪ similar (recommendations first).

func (*Catalog) StandaloneEpisodes added in v0.2.7

func (c *Catalog) StandaloneEpisodes(ctx context.Context, minRating float64, limit int) ([]core.Title, error)

StandaloneEpisodes returns acclaimed mirrored episodes from anthology series or top-rated standalone chapters that can be watched without a multi-season commitment.

func (*Catalog) Stats

func (c *Catalog) Stats(ctx context.Context) (Stats, error)

Stats returns mirror counts, last sync time and current sync progress. The counts come from catalog_stats_tally, so they are exact on every call at a cost that does not grow with the library. The last snapshot answers when the database cannot (a busy or timed-out status call).

func (*Catalog) StoredEpisodeCandidates

func (c *Catalog) StoredEpisodeCandidates(ctx context.Context, shows []core.RatingKey) (map[core.RatingKey][]core.Title, error)

StoredEpisodeCandidates has the same episode membership and order as StoredGrandchildren, but reads only keys, numbering, release dates and adult flags. It supports next-episode selection and counts for viewers with no title restrictions. Callers must load a chosen title fully before availability filters, restricted-viewer policies or rendering it.

func (*Catalog) StoredEpisodesByKey added in v0.3.0

func (c *Catalog) StoredEpisodesByKey(ctx context.Context, show core.Filter, after core.RatingKey, limit int) ([]core.Title, error)

StoredEpisodesByKey returns up to limit stored, aired, non-adult episodes after the key after, in key order (series, season, episode), of the series the browse filter show admits. It reads the key range and probes each episode's series, so a page costs the same however large the library; it refreshes nothing.

func (*Catalog) StoredGrandchildren added in v0.3.0

func (c *Catalog) StoredGrandchildren(ctx context.Context, shows []core.RatingKey) (map[core.RatingKey][]core.Title, error)

StoredGrandchildren returns, for each of shows it can answer from the mirror, exactly what Grandchildren would: the episodes of the show's listable seasons, in season and episode order. It answers a show only when Grandchildren would not wait on TMDB: the show has its details and every listable season is detailed with episodes stored. A stale season that Grandchildren would refresh behind the read is refreshed behind this read too. Shows it omits must go through Grandchildren. A list page counting many shows' episodes reads them in a few batched statements instead of a dozen per show.

func (*Catalog) StoredSeasonsByKey added in v0.3.0

func (c *Catalog) StoredSeasonsByKey(ctx context.Context, show core.Filter, after core.RatingKey, limit int) ([]core.Title, error)

StoredSeasonsByKey returns up to limit stored, listable seasons after the key after, in key order (series, season), of the series the browse filter show admits. It never refreshes shows or seasons from TMDB.

func (*Catalog) StoredTitle added in v0.2.37

func (c *Catalog) StoredTitle(ctx context.Context, key core.RatingKey) (*core.Title, error)

func (*Catalog) StudioByFacetID

func (c *Catalog) StudioByFacetID(ctx context.Context, id string) (string, bool, error)

StudioByFacetID is the stored studio whose core.FacetID("studio", name) is id, read by index; ok is false when no title carries such a studio.

func (*Catalog) SupportVisibility added in v0.3.3

func (c *Catalog) SupportVisibility(ctx context.Context, key core.RatingKey) (*core.Title, string, error)

SupportVisibility examines one locally stored item without a provider fetch. It reports the first known catalogue gate; "visible" only means these catalogue gates passed, not that viewer policy or availability passed.

func (*Catalog) Sync

func (c *Catalog) Sync(ctx context.Context) error

func (*Catalog) SyncProgress

func (c *Catalog) SyncProgress() SyncProgress

SyncProgress returns the current sync progress snapshot.

func (*Catalog) TailStatus

func (c *Catalog) TailStatus() TailStatus

TailStatus reports the long-tail index state.

func (*Catalog) TierMigrationDone

func (c *Catalog) TierMigrationDone(ctx context.Context) bool

TierMigrationDone reports whether nothing is left for the tier migration (it finished, or the database never needed it).

func (*Catalog) TierMigrationStatus

func (c *Catalog) TierMigrationStatus(ctx context.Context) TierMigration

TierMigrationStatus is the stored progress, with the pause flag.

func (*Catalog) TierTouchedKeys

func (c *Catalog) TierTouchedKeys(ctx context.Context) ([]core.RatingKey, error)

TierTouchedKeys returns the movie and show keys the touch sources name (seasons and episodes collapsed to their show), sorted.

func (*Catalog) TitleOpenable

func (c *Catalog) TitleOpenable(t *core.Title) bool

TitleOpenable is the shared, pure admission predicate over loaded facts. No fetching, mirroring or database access belongs in this function.

func (*Catalog) TitleSeasonSchedule added in v0.3.3

func (c *Catalog) TitleSeasonSchedule(ctx context.Context, show core.RatingKey, season int) ([]DatedEpisode, error)

TitleSeasonSchedule mirrors one season when stale and returns every episode TMDB lists for it, aired or announced.

func (*Catalog) TitleShowSchedule added in v0.3.3

func (c *Catalog) TitleShowSchedule(ctx context.Context, show core.RatingKey, since time.Time, limit int) (*ShowSchedule, error)

TitleShowSchedule refreshes a show's newest seasons when stale (the same TTLs as the calendar's background pass) and reads its schedule from the mirror. since bounds the episode window; limit bounds its size.

func (*Catalog) TitleTags

func (c *Catalog) TitleTags(ctx context.Context, key core.RatingKey) ([]string, error)

func (*Catalog) TrendStatus added in v0.2.12

func (c *Catalog) TrendStatus(ctx context.Context) []TrendStatus

func (*Catalog) UndatedSearchHit

func (c *Catalog) UndatedSearchHit(ctx context.Context, key core.RatingKey) bool

UndatedSearchHit reports a long-tail search hit with no row whose release date is not known yet. Search shows those, so artwork that requires a released title admits them.

func (*Catalog) UnratedTitles added in v0.3.0

func (c *Catalog) UnratedTitles(ctx context.Context, after core.RatingKey, limit int) ([]core.Title, error)

UnratedTitles lists browsable, detailed movies and shows without a rated certification, by key after the given one, for a rating provider to look up. Titles carry only their key, type and ids.

func (*Catalog) UpdateConfig

func (c *Catalog) UpdateConfig(cfg config.Catalog)

UpdateConfig swaps the catalogue config (wire to config.Store.Subscribe).

func (*Catalog) UpsertCollectionTitles

func (c *Catalog) UpsertCollectionTitles(ctx context.Context, titles []core.Title) error

UpsertCollectionTitles mirrors one source refresh in a single transaction. Call only from the collection build worker, never a serving path.

func (*Catalog) UpsertDetailedTitle added in v0.3.0

func (c *Catalog) UpsertDetailedTitle(ctx context.Context, t core.Title) error

UpsertDetailedTitle stores a title as a fresh detail record. A later Get serves the row without a metadata refresh until it is stale. The scorecard fixture uses this: its seed is the source of truth, and a failed refresh must not be repeated for every library row.

func (*Catalog) UpsertTitle

func (c *Catalog) UpsertTitle(ctx context.Context, t core.Title, browsable bool) error

UpsertTitle stores a title (exported for tests/seeders and other components that need to insert known titles). Browsable/added_at are preserved once set. The row is not a fresh detail record, so a later Get may refresh it.

func (*Catalog) UseTailExports

func (c *Catalog) UseTailExports(movies, tv io.Reader) error

UseTailExports builds the long-tail index from TMDB export readers (gzipped JSON lines) and installs it, for tests and tools that check a migrated catalogue's search.

func (*Catalog) ViewerRules

func (c *Catalog) ViewerRules(id int64) ViewerRules

func (*Catalog) ViewerRulesJudge

func (c *Catalog) ViewerRulesJudge(u *core.User) (int64, bool)

ViewerRulesJudge is u's own rules revision and whether the rules that apply to u (their own, or the household's they inherit) judge titles on their facts, so that a catalogue write can change what u may see.

func (*Catalog) ViewerRulesRevision

func (c *Catalog) ViewerRulesRevision(u *core.User) int64

func (*Catalog) Visible added in v0.2.12

func (c *Catalog) Visible(t *core.Title) bool

Visible applies every visibility rule, as browsing and recommendations do.

func (*Catalog) VisibleForViewer

func (c *Catalog) VisibleForViewer(u *core.User, t *core.Title) bool

func (*Catalog) WaitRefreshes added in v0.3.0

func (c *Catalog) WaitRefreshes()

WaitRefreshes blocks until no metadata refresh started by a read is still running.

func (*Catalog) WarmDetails added in v0.2.7

func (c *Catalog) WarmDetails(ctx context.Context, limit int) (int, error)

WarmDetails enriches up to limit browsable titles whose detailed metadata (credits, crew, keywords, studios, runtime, content rating, seasons and crowd recommendations) has not been fetched yet, plus returning series whose detail TTL has expired. New releases (<120 days old) are warmed first, followed by the most popular and most-voted titles. Settled titles are marked detail=1 and never re-queried.

func (*Catalog) WarmSearch added in v0.3.3

func (c *Catalog) WarmSearch(ctx context.Context)

WarmSearch starts building the title search index in the background so the first search does not wait for it.

func (*Catalog) WriteGeneration added in v0.3.0

func (c *Catalog) WriteGeneration() uint64

WriteGeneration changes when a catalogue write drops the read caches.

type ConfigEvidence added in v0.3.0

type ConfigEvidence struct {
	// contains filtered or unexported fields
}

ConfigEvidence is an opaque, non-authorizing observation of the exact installed config incarnation. It detects change-and-revert as well as a currently different value. It is valid only for the issuing Catalog.

func (ConfigEvidence) Current added in v0.3.0

func (e ConfigEvidence) Current() bool

Current is a postwork check; it must not be treated as a durable policy fence or used instead of checking the policy epoch transaction.

type DatedEpisode added in v0.3.3

type DatedEpisode struct {
	Key      core.RatingKey
	Season   int
	Episode  int
	Title    string
	Summary  string
	Released time.Time
	Runtime  time.Duration
	Still    string
	Rating   float64
}

DatedEpisode is one episode in a show's schedule. Released is zero when TMDB has not announced an air date.

type ExportSource

type ExportSource interface {
	Each(ctx context.Context, kind tmdb.ExportKind, fn func(tmdb.ExportRow) error) (tmdb.ExportInfo, error)
	// EachNamed is Each with each row's original title or name.
	EachNamed(ctx context.Context, kind tmdb.ExportKind, fn func(tmdb.ExportRow, string) error) (tmdb.ExportInfo, error)
}

ExportSource streams TMDB's daily ID exports; *tmdb.Exports is one.

type Mirror

type Mirror struct {
	// contains filtered or unexported fields
}

Mirror exposes bounded local reads for catalogue consumers. It owns schema knowledge; callers never compose SQL against catalog_* tables.

func NewMirror

func NewMirror(db *sql.DB, now func() time.Time) *Mirror

func (*Mirror) AirDates added in v0.2.34

func (m *Mirror) AirDates(ctx context.Context, now time.Time, premiere bool) (map[core.RatingKey]int64, error)

AirDates returns dates from locally mirrored regular episodes, excluding future air dates. For premieres, the highest numbered season with an aired episode must have an aired episode one.

func (*Mirror) CollectionArt

func (m *Mirror) CollectionArt(ctx context.Context, id int64) (core.Collection, error)

func (*Mirror) Fingerprint

func (m *Mirror) Fingerprint(ctx context.Context) (MirrorFingerprint, error)

func (*Mirror) FranchiseRatedCounts

func (m *Mirror) FranchiseRatedCounts(ctx context.Context, level int) (map[int64]int, error)

FranchiseRatedCounts is, per franchise Franchises lists, how many of its browsable movies a viewer capped at level may watch (rated, at or below the cap; contentrating.Exceeds), in one grouped read over the franchises' members.

func (*Mirror) Franchises

func (m *Mirror) Franchises(ctx context.Context) ([]core.Collection, error)

func (*Mirror) Snapshot

func (m *Mirror) Snapshot(ctx context.Context) ([]MirrorItem, error)

Snapshot reads every browsable title with its facets and credits, most popular first. Ranges are read one after another, so the result is not one point in time; the index that consumes it rebuilds when the fingerprint moves.

func (*Mirror) Titles

func (m *Mirror) Titles(ctx context.Context, keys []core.RatingKey) ([]core.Title, error)

Titles returns hydrated, released, non-adult mirror entries in requested order. It never fetches remote metadata, so collection serving stays bounded and offline.

func (*Mirror) TitlesIncludingFuture added in v0.2.37

func (m *Mirror) TitlesIncludingFuture(ctx context.Context, keys []core.RatingKey) ([]core.Title, error)

TitlesIncludingFuture is a local metadata read for collection-scoped authorization. Its caller must enforce membership and viewer policy.

type MirrorFingerprint

type MirrorFingerprint struct {
	Count            int
	MaxFetch, MaxAdd int64
}

type MirrorItem

type MirrorItem struct {
	Title    core.Title
	AddedAt  int64
	Detailed bool
}

type MovieYearProgress added in v0.3.0

type MovieYearProgress struct {
	Running   bool   `json:"running"`
	Finished  bool   `json:"finished"`
	Pending   int    `json:"pending"`
	Stored    int    `json:"stored"`
	Refreshed int    `json:"refreshed"`
	LastError string `json:"lastError,omitempty"`
}

MovieYearProgress is the upgrade correction shown on Catalog health. Stored counts movies whose year was taken from a date already in the catalogue. Refreshed counts movies whose detail was fetched because that date was not stored. Pending is how many movies are still unmarked.

type Option

type Option func(*Catalog)

Option customises a Catalog.

func WithExports

func WithExports(src ExportSource) Option

WithExports syncs the title list from TMDB's daily ID exports. Without it, or when no export is available, the sync walks /discover.

func WithNow

func WithNow(fn func() time.Time) Option

func WithResumeSchedule

func WithResumeSchedule() Option

WithResumeSchedule makes Run continue the refresh schedule recorded in the database instead of syncing as soon as it starts: the first sync waits until RefreshInterval has passed since the last successful sync (by any node). A cluster uses it because Run restarts on every leader handoff; with no recorded sync Run still syncs at once.

func WithSaferRatingSQL added in v0.3.0

func WithSaferRatingSQL(f SaferRatingSQL) Option

func WithStartupContext

func WithStartupContext(ctx context.Context) Option

WithStartupContext carries cancellation and upgrade progress into constructor backfills.

func WithTailIndex

func WithTailIndex(dir string, free func(string) (int64, error)) Option

WithTailIndex keeps the long-tail search index in dir (the data directory's tmdb-exports). free reports the free bytes of a path; nil skips the check.

type PersonMatch added in v0.3.0

type PersonMatch struct {
	TMDB    int64
	Name    string
	Credits int
}

PersonMatch is a credited person a people search may return.

type Queryer

type Queryer interface {
	QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
}

Queryer runs read queries: a *sql.DB, *sql.Conn or *sql.Tx.

type RankRebuild

type RankRebuild struct {
	Side  string
	Rows  int64
	Took  time.Duration
	Built time.Time
}

RankRebuild is what one rebuild did.

type ReleaseDate added in v0.3.3

type ReleaseDate struct {
	Country       string
	Kind          string // premiere, limited, theatrical, digital, physical, tv
	Date          time.Time
	Note          string
	Certification string
}

ReleaseDate is one dated release of a movie in one country.

type SaferRatingSQL added in v0.3.0

type SaferRatingSQL interface {
	HideExceeding(keyColumn string) string
}

SaferRatingSQL is the optional Plex overlay predicate for viewer limits.

type ScheduleEntry added in v0.3.0

type ScheduleEntry struct {
	Title core.Title
	// SeasonEpisodes is the highest mirrored episode number of an
	// episode's season (TMDB lists a season's future episodes too), so
	// callers can mark a season finale.
	SeasonEpisodes int
	// ShowPoster is an episode's or season's series poster path.
	ShowPoster string
	Playable   bool
}

ScheduleEntry is one dated title in the owner's release calendar, read from the local mirror only. Playable reports whether the catalogue would offer it right now (released, not adult, past the home-release gate).

type SeasonSummary added in v0.3.3

type SeasonSummary struct {
	Season   int
	Title    string
	Summary  string
	Released time.Time
	Poster   string
	// Episodes is the mirrored episode count (TMDB lists announced
	// episodes too); Aired counts those already released.
	Episodes int
	Aired    int
	// Unmirrored: Episodes is TMDB's count; Aired is unknown.
	Unmirrored bool
}

SeasonSummary is one season of a show, announced or aired.

type ShowSchedule added in v0.3.3

type ShowSchedule struct {
	Seasons []SeasonSummary
	// Window is every mirrored episode released from `since` onward, plus
	// announced episodes without a date, in air order.
	Window []DatedEpisode
	// Last is the most recently aired episode; Next the first announced
	// one after now (dated, or the first undated one of a later episode).
	Last, Next *DatedEpisode
}

ShowSchedule is a show's seasons plus its episodes around today.

type Stats

type Stats struct {
	Trending           []TrendStatus     `json:"trending"`
	Movies             int               `json:"movies"`
	Shows              int               `json:"shows"`
	Seasons            int               `json:"seasons"`
	Episodes           int               `json:"episodes"`
	HomeReleaseChecked int               `json:"homeReleaseChecked"`
	HomeReleasePending int               `json:"homeReleasePending"`
	BrowsableMovies    int               `json:"browsableMovies"`
	BrowsableShows     int               `json:"browsableShows"`
	Collections        int               `json:"collections"`
	LastSync           time.Time         `json:"lastSync,omitempty"`
	Sync               SyncProgress      `json:"sync"`
	YearCorrection     MovieYearProgress `json:"yearCorrection"`
	TierMigration      TierMigration     `json:"tierMigration,omitzero"`
	// Tail is this node's long-tail search index.
	Tail TailStatus `json:"tail"`
}

Stats is the admin-UI snapshot of the mirror.

type StorageCount

type StorageCount struct {
	Type      string
	Browsable bool
	Detail    *bool
	Rows      int64
}

StorageCount counts catalogue rows: by type and browsable state (Detail nil), or by browsable and detail state (Type empty).

func StorageCounts

func StorageCounts(ctx context.Context, q Queryer) ([]StorageCount, error)

StorageCounts is what the mirror holds, for support bundles. Each grouping reads one narrow covering index (type,browsable,… and browsable,detail,…), never the table.

type SyncProgress

type SyncProgress struct {
	Running     bool      `json:"running"`
	Phase       string    `json:"phase,omitempty"` // e.g. "movie/popularity", "tv/votes/1994"
	PagesDone   int       `json:"pagesDone"`
	TitlesSeen  int       `json:"titlesSeen"`
	TitlesAdded int       `json:"titlesAdded"`
	StartedAt   time.Time `json:"startedAt,omitempty"`
	FinishedAt  time.Time `json:"finishedAt,omitempty"`
	LastError   string    `json:"lastError,omitempty"`
}

SyncProgress describes a running or last-finished catalogue sync.

type TMDB

type TMDB interface {
	MovieDetails(ctx context.Context, id int64) (*tmdb.MovieDetails, error)
	TVDetails(ctx context.Context, id int64) (*tmdb.TVDetails, error)
	SeasonDetails(ctx context.Context, tvID int64, season int) (*tmdb.SeasonDetails, error)
	EpisodeExternalIDs(ctx context.Context, tvID int64, season, episode int) (*tmdb.ExternalIDs, error)
	CollectionDetails(ctx context.Context, id int64) (*tmdb.CollectionDetails, error)
	DiscoverMovies(ctx context.Context, p tmdb.DiscoverParams) (*tmdb.Paged[tmdb.Movie], error)
	DiscoverTV(ctx context.Context, p tmdb.DiscoverParams) (*tmdb.Paged[tmdb.TVShow], error)
	SearchMovies(ctx context.Context, q string, page int) (*tmdb.Paged[tmdb.Movie], error)
	SearchTV(ctx context.Context, q string, page int) (*tmdb.Paged[tmdb.TVShow], error)
	SearchMulti(ctx context.Context, q string, page int) (*tmdb.Paged[tmdb.MultiResult], error)
	TrendingMovies(ctx context.Context, window string, page int) (*tmdb.Paged[tmdb.Movie], error)
	TrendingTV(ctx context.Context, window string, page int) (*tmdb.Paged[tmdb.TVShow], error)
	TrendingAll(ctx context.Context, window string, page int) (*tmdb.Paged[tmdb.MultiResult], error)
	MovieList(ctx context.Context, list string, page int) (*tmdb.Paged[tmdb.Movie], error)
	TVList(ctx context.Context, list string, page int) (*tmdb.Paged[tmdb.TVShow], error)
	MovieSimilar(ctx context.Context, id int64, page int) (*tmdb.Paged[tmdb.Movie], error)
	MovieRecommendations(ctx context.Context, id int64, page int) (*tmdb.Paged[tmdb.Movie], error)
	TVSimilar(ctx context.Context, id int64, page int) (*tmdb.Paged[tmdb.TVShow], error)
	TVRecommendations(ctx context.Context, id int64, page int) (*tmdb.Paged[tmdb.TVShow], error)
	FindByExternalID(ctx context.Context, source, id string) (*tmdb.FindResult, error)
	Genres(ctx context.Context, mediaType string) ([]tmdb.Genre, error)
}

TMDB is the subset of the TMDB client the catalogue needs. *tmdb.Client satisfies it.

type TailStatus

type TailStatus struct {
	// Enabled: this node keeps a tail index (nodes that serve search).
	Enabled   bool      `json:"enabled"`
	Ready     bool      `json:"ready"`
	Building  bool      `json:"building"`
	Day       time.Time `json:"day,omitempty"`
	Titles    int       `json:"titles"`
	Bytes     int64     `json:"bytes"`
	BuiltAt   time.Time `json:"builtAt,omitempty"`
	LastError string    `json:"lastError,omitempty"`
}

TailStatus describes the long-tail search index.

type TierBackupFunc

type TierBackupFunc func(ctx context.Context, since time.Time) (string, error)

TierBackupFunc returns the name of a backup (or pre-upgrade snapshot) taken at or after since, taking one when there is none. The tier migration removes nothing until it returns without an error.

type TierMigration

type TierMigration struct {
	// State is "" (never armed: nothing to migrate), "pending",
	// "planning", "waiting_backup", "removing", "paused", "sweeping",
	// "done" or "error".
	State  string `json:"state,omitempty"`
	Paused bool   `json:"paused,omitempty"`
	// Touched is how many titles the touch sources name.
	Touched int `json:"touched,omitempty"`
	// Scanned rows were read by the plan; Keep of them stay and Remove
	// are to be removed (in this run, after the cursor).
	Scanned int `json:"scanned,omitempty"`
	Keep    int `json:"keep,omitempty"`
	Remove  int `json:"remove,omitempty"`
	// Removed rows are gone. Rescued were planned for removal and kept by
	// a batch's re-check. Promoted were stored again by the final sweep.
	Removed    int       `json:"removed,omitempty"`
	Rescued    int       `json:"rescued,omitempty"`
	Promoted   int       `json:"promoted,omitempty"`
	Backup     string    `json:"backup,omitempty"`
	StartedAt  time.Time `json:"startedAt,omitzero"`
	FinishedAt time.Time `json:"finishedAt,omitzero"`
	LastError  string    `json:"lastError,omitempty"`
}

TierMigration is the tier migration's progress, shown in the catalogue status. It is stored in catalog_meta so every cluster node reports it.

type TierSource

type TierSource struct {
	Table, Column string
	// Text: a decimal key in a TEXT column. Scan: JSON or text scanned
	// for keys. BareID: a key or a bare TMDB id.
	Text, Scan, BareID bool
	Where              string
}

TierSource is one touch source, for tests that seed every source.

func TierSources

func TierSources() []TierSource

TierSources lists the touch sources.

type TrendStatus added in v0.2.12

type TrendStatus struct {
	Source      string         `json:"source"`
	Scope       string         `json:"scope"`
	Window      string         `json:"window"`
	MediaType   core.MediaType `json:"mediaType"`
	Titles      int            `json:"titles"`
	FetchedAt   *time.Time     `json:"fetchedAt,omitempty"`
	AttemptedAt *time.Time     `json:"attemptedAt,omitempty"`
	State       string         `json:"state"`
}

type ViewerRules

type ViewerRules struct {
	Mode              string                  `json:"mode"`
	Exclusions        []core.LibraryExclusion `json:"exclusions"`
	VisibilityDefault string                  `json:"visibilityDefault"`
	Exemptions        []string                `json:"exemptions"`
}

ViewerRules keeps catalogue preferences separate from mandatory safety policy. Exemptions identify the exact global rule, so reordering cannot change them.

type VisibilityPreview added in v0.2.12

type VisibilityPreview struct {
	Total          int                     `json:"total"`
	Hidden         int                     `json:"hidden"`
	Visible        int                     `json:"visible"`
	Movies         int                     `json:"movies"`
	Shows          int                     `json:"shows"`
	UnknownCountry int                     `json:"unknownCountry"`
	Items          []core.Title            `json:"items"`
	Shown          []core.Title            `json:"shown"`
	Rules          []VisibilityRulePreview `json:"rules"`
}

type VisibilityRulePreview added in v0.3.0

type VisibilityRulePreview struct {
	Movies int `json:"movies"`
	Shows  int `json:"shows"`
}

Source Files

Jump to

Keyboard shortcuts

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