experience

package
v0.3.1 Latest Latest
Warning

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

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

Documentation

Overview

Package experience contains protocol-independent viewer experience logic.

Index

Constants

View Source
const PlaylistExpandLimit = 500
View Source
const ReceiptCapabilityNamespace = "experience.receipt"

ReceiptCapabilityNamespace is the shared header namespace for experience receipts. It is not the ticket namespace and it is not an authority.

View Source
const SeriesNextUpLimit = frontend.SeriesNextUpLimit

SeriesNextUpLimit is the indexed window for one series' next-up listing. The lookup is keyed by the series, never the library.

Variables

View Source
var ErrReceipt = errors.New("experience: invalid interaction receipt")
View Source
var ErrRowViewNotFound = fmt.Errorf("experience: row view unavailable: %w", frontend.ErrNotFound)
View Source
var ErrRowViewStale = frontend.ErrRowViewStale

Functions

func ArrangeHome

func ArrangeHome(ctx context.Context, source core.Recommender, viewer *core.User, hc core.HubContext, section string, rows []core.Hub, candidates []core.NeutralHomeRow) ([]int, error)

ArrangeHome selects original producer indices. Providers use their declared layout contract directly; rendering sidecars never enter a neutral layout.

func ArrangeLayout

func ArrangeLayout(candidates []LayoutCandidate, finalize func([]LayoutCandidate) ([]string, error)) ([]int, error)

ArrangeLayout returns indices into candidates in the engine's final order. Duplicate identifiers retain the last candidate, matching existing layout rendering. Unknown identifiers are ignored.

func ArrangeNeutralHome

func ArrangeNeutralHome(ctx context.Context, source core.NeutralHomeLayoutSource, viewer *core.User, situation core.HomeSituation, section string, rows []core.NeutralHomeRow) ([]int, error)

ArrangeNeutralHome returns candidate indices in the shared layout's order. Indices point at the caller's original sidecars; duplicate producer IDs use the last sidecar, matching the established Home layout behavior.

func AssembleHome

func AssembleHome(candidates []HomeCandidate, includeContinue, promotedOnly bool, continueID string, renderContinue func(), render func(HomeCandidate) string, accept func(HomeCandidate))

AssembleHome places the continue row at its ranked position and deduplicates rendered identifiers. The renderer returns the stable identifier it actually used; this can differ from the producer's candidate identifier.

func AssemblePostPlay

func AssemblePostPlay[P any](ctx context.Context, selection PostPlaySelection, viewer *core.User, key core.RatingKey, count int,
	next func() (core.RatingKey, P, bool), picks func(PostPlayRow) (P, bool), render func(core.Hub) P) ([]P, error)

AssemblePostPlay retains next-up, personal picks and related row ordering. Rendering runs at each original boundary, before fetching the next source.

func CandidateObservationSource

func CandidateObservationSource(releaseID string) (string, bool)

CandidateObservationSource names a genuine candidate release without exposing its raw identity. Empty, whitespace-only or unbounded IDs cannot identify a stable offer and have no observation timestamp.

func CandidatePurpose

func CandidatePurpose(h core.Hub) core.RowPurpose

func CandidateRows

func CandidateRows(candidates []RankedCandidate) []frontend.Row

CandidateRows projects already-authorized neutral selection data; source-specific Context is deliberately absent from the candidate contract.

func CanonicalTMDBPerson

func CanonicalTMDBPerson(id string) (int64, bool)

CanonicalTMDBPerson accepts only a "tmdb:" prefix plus the canonical base-10 form of a positive int64. Leading zeros, signs, and surrounding space fail.

func ColdHomeRows

func ColdHomeRows(ctx context.Context, viewer *core.User, libraries []Library, visibility Visibility, limit int, excluded []core.RatingKey, browse func(context.Context, core.BrowseQuery) (core.Page, error)) ([]frontend.Row, error)

ColdHomeRows fills a neutral Home that has no ranked shelves left after policy filtering. Plex does not call it. Ordering follows date added: a movie or episode uses its own release or air date, and a series or season uses its latest aired episode. The page defaults to 50 and never exceeds 100.

func CompleteHome

func CompleteHome(includePlaylists bool, playlists, discovery, live, selectRows, layout func())

CompleteHome orders the remaining home producers and finalizes the response. The adapter owns rendered metadata and only exposes these operations here.

func DetailCanPlay

func DetailCanPlay(ctx context.Context, req frontend.ItemRequest, protocol core.Protocol, capabilities frontend.Capabilities, broker EstimatePlanner) *bool

DetailCanPlay asks the broker about authorization without preparing a source. A missing trusted peer or an indeterminate broker failure stays unknown.

func DetailExtras

func DetailExtras(ctx context.Context, source ExtrasSource, parent *core.Title, language string, wanted map[int]bool) ([]extras.Extra, error)

DetailExtras selects enabled clips in source order. Numeric dialect filters are parsed by the adapter; an empty selection retains the complete list.

func DiscoveryAvailabilityRow

func DiscoveryAvailabilityRow(id string) bool

Apply source suitability to discovery, including mixed Home shelves. Personal saved lists and playback state must never disappear because sources change.

func DiscoverySearch

func DiscoverySearch(source *discovery.Features) func(context.Context, *core.User, string, int) ([]core.Title, bool)

DiscoverySearch uses natural search only when enabled and successful.

func DownloadsAllowed

func DownloadsAllowed(viewer *core.User, ready, allowFriends bool, policy AccessPolicy) bool

DownloadsAllowed is shared capability admission; handlers still enforce it.

func FavoriteAllowed

func FavoriteAllowed(ctx context.Context, viewer *core.User, subject FavoriteSubject, visibility Visibility, sources FavoriteSources) bool

FavoriteAllowed decides whether the viewer may favorite this subject from current dependencies. Callers still validate the active viewer. Person credits use the supplied Visibility, including whatever title lookup it has.

func FavoriteTitlesCached

func FavoriteTitlesCached(ctx context.Context, viewer *core.User, keys []core.RatingKey, visibility Visibility, cached func(context.Context, []core.RatingKey) (map[core.RatingKey]core.Title, error)) (bool, error)

FavoriteTitlesCached rechecks title policy using only the injected local catalogue read. It does not call FavoriteSources and replaces Visibility's title lookup for this call so a parent read cannot leave the cache. A cache error is returned and the decision is false, so the caller fails closed. A missing title or a hidden or missing parent is a deny without an error.

func HomePurpose

func HomePurpose(h core.Hub) core.RowPurpose

homePurpose translates producer identifiers at the Plex boundary; no wire context or hub identifier is included in the neutral candidate.

func HubRows

func HubRows(hubs []core.Hub) []frontend.Row

HubRows converts trusted recommender candidates to neutral rows; wire contexts stay on the producer side. Authorization and pagination are performed by experience.RowsService before returning these candidates.

func IndexVirtualPaths

func IndexVirtualPaths(ctx context.Context, catalog VirtualPathCatalog, viewer *core.Viewer, device core.Device) (map[core.RatingKey]string, error)

IndexVirtualPaths walks each authorized library once and returns every path. Callers cache the map and look up pages in it.

func ItemMarkers

func ItemMarkers(ctx context.Context, source MarkerSource, key core.RatingKey, info core.MediaInfo) []frontend.Marker

ItemMarkers converts validated marker ranges from known media into the neutral detail representation. Estimates do not identify a release timeline.

func LayoutRows

func LayoutRows(ctx context.Context, source core.NeutralHomeLayoutSource, libraries []Library, req frontend.RowRequest, sit core.HomeSituation, rows []frontend.Row) ([]frontend.Row, error)

LayoutRows applies shared Home placement to neutral frontend rows.

func LibrarySection

func LibrarySection(libraries []Library, kind core.MediaType) (int, bool)

LibrarySection maps a title or episode to the configured library of that kind. Seasons and episodes belong to the show library. Unknown kinds use the movie library.

func LiveEntries

func LiveEntries(ctx context.Context, viewer *core.User, source LiveGuideSource, now, until time.Time, image func(livetv.Channel) string) ([]frontend.RowEntry, error)

LiveEntries projects allowed channels and their current and next programme. The caller supplies the guide horizon; stream and provider URLs never leave this projection. Image is an optional server-controlled logo resolver.

func LoadSeriesNextUp

func LoadSeriesNextUp(ctx context.Context, series core.RatingKey, resumable bool, children func(context.Context, core.RatingKey, frontend.ItemQuery) (core.Page, error), userData func(context.Context, []core.RatingKey) (map[core.RatingKey]core.WatchStatus, error)) (*core.Title, error)

LoadSeriesNextUp loads one series' episodes through Children and selects the next episode. The children callback is invoked only with that series key.

func MarkerMedia

func MarkerMedia(viewer *core.User, device core.Device, key core.RatingKey, estimate core.MediaInfo, lookup func(int64, string, core.RatingKey) (*core.PlaySession, bool)) core.MediaInfo

MarkerMedia selects an existing source only for the authenticated viewer, device and title. A missing device never selects a session.

func MaySetViewerRating

func MaySetViewerRating(actor *core.Viewer, active *core.User) bool

MaySetViewerRating is the owner-console rule for changing a household rating cap. Delegated sessions and non-owners cannot write it.

func MergeShareFilter

func MergeShareFilter(filter, share core.Filter) (out core.Filter, none bool)

MergeShareFilter narrows filter by a plex.tv share predicate. Allow lists intersect, so a client's own contentRating filter can only narrow a share; deny lists and collection names add; rating bounds tighten. none reports that the two allow lists have no rating in common.

func ObserveVersionOffers

func ObserveVersionOffers(ctx context.Context, port VersionObservationPort, parent core.RatingKey, offers []VersionOffer, slotCount int, observed time.Time) ([]*time.Time, error)

ObserveVersionOffers attaches persisted first-observation times for offers paired by index with slotCount result slots. Offers past slotCount are not observed. An empty observable set does not call the port. A port error returns no timestamps. Returned times are copies. Sources sent to the port keep offer order and contain only candidate hashes, keyed with the parent.

func PickAudio

func PickAudio(info core.MediaInfo, langs []string) int

PickAudio selects an audio stream position, or -1 when none exists.

func PlaylistDuration

func PlaylistDuration(items []core.RatingKey, get func(core.RatingKey) *core.Title) time.Duration

PlaylistDuration uses cached runtimes and visits every member so a cache can schedule missing titles. The total stays unknown until every runtime is known.

func PlaylistEntries

func PlaylistEntries(ctx context.Context, viewer *core.User, source PlaylistRowSource, limit int) ([]frontend.RowEntry, bool, error)

PlaylistEntries returns newest playlists first. Cross-viewer records are discarded even if a source accidentally returns them.

func PreviewAvailability

func PreviewAvailability(ctx context.Context, u *core.User, limit int, load func(context.Context, int) []core.Hub, filter func(context.Context, *core.User, []core.Title) ([]core.Title, error)) []core.Hub

PreviewAvailability filters suggested titles before the preview's row cap. A viewer with no exclusions retains the source's exact first response.

func PrioritizeFavoriteShows

func PrioritizeFavoriteShows(titles []core.Title, favorites map[core.RatingKey]bool)

PrioritizeFavoriteShows stably promotes episodes of favorite shows without changing order within groups.

func PruneReceiptShard

func PruneReceiptShard(ctx context.Context, db *sql.DB, now time.Time, shard int64) (int64, error)

PruneReceiptShard retires at most HeaderBatchMax expired receipts, including abandoned devices. The header DELETE trigger retires typed payloads and claims atomically. It uses the same shard lock as issuance before touching either table; live receipts are never evicted to recover capacity.

func RecentPlaylists

func RecentPlaylists(ctx context.Context, viewer *core.User, source PlaylistRowSource, limit int) ([]core.Playlist, bool, error)

RecentPlaylists preserves source records for adapters that render item counts.

func RecentlyAdded

func RecentlyAdded(ctx context.Context, viewer *core.User, kind core.MediaType, count int, visibility Visibility, browse func(context.Context, core.BrowseQuery) (core.Page, error)) ([]core.Title, bool, error)

RecentlyAdded loads the catalogue candidates for a library in added order.

func RecentlyViewed

func RecentlyViewed(ctx context.Context, viewer *core.User, libraryID int, visibility Visibility, history func(context.Context, int64, time.Time, int) ([]core.HistoryEntry, error), titles func(context.Context, []core.RatingKey) []core.Title, libraryFor func(core.MediaType) (int, bool)) ([]core.Title, error)

RecentlyViewed loads unique history entries in their most recent order.

func ResolveAudio

func ResolveAudio(want *core.StreamInfo, pos int, info core.MediaInfo) int

ResolveAudio maps an issued audio identity onto a position in the current MediaInfo.Streams. Callers needing an encoder index must translate that position through the selected StreamInfo.Index. The caller must supply a non-nil issued stream identity.

func ResolveSubtitle

func ResolveSubtitle(want *core.StreamInfo, pos int, info core.MediaInfo) int

ResolveSubtitle maps a stored choice onto a stream position, or -1. The choice is language, codec, and source (sidecar URL, or embedded versus external). A matching track at a new index beats whatever now sits in the old slot: variant lists reorder, and that slot is not the track the viewer picked.

func RowEntryIdentity

func RowEntryIdentity(e frontend.RowEntry) string

RowEntryIdentity returns the stable neutral identity of a well-formed entry.

func RowViewCollectionMembers

func RowViewCollectionMembers(ctx context.Context, source RowViewCollectionSource, user *core.User, collection core.RatingKey, refs []core.NeutralEntryRef) bool

RowViewCollectionMembers adapts one call's collection source into neutral member keys. A nil source stays a nil port and fails closed.

func RowViewHydrateEntries

func RowViewHydrateEntries(ctx context.Context, viewer *core.User, refs []core.NeutralEntryRef, one RowViewEntry, batch RowViewTitleBatch, read RowViewTitleRead, visibility Visibility, guide RowViewGuide) ([]frontend.RowEntry, error)

RowViewHydrateEntries rehydrates titles and inherited parents for this pass only. A title or parent batch error falls back to RowViewHydrateLegacy for the entire original ref list; the guide memo from this call survives that fallback and is not shared with any other viewer, device or pass. A home-release read failure is not-found and does not fall back. Non-title and virtual-season entries use the legacy port. Batch-built title entries leave Handle empty. Visibility is copied per chunk so a parent read cannot leak into the caller's policy or a later chunk.

func RowViewHydrateLegacy

func RowViewHydrateLegacy(ctx context.Context, viewer *core.User, refs []core.NeutralEntryRef, one RowViewEntry, guide RowViewGuide) ([]frontend.RowEntry, error)

RowViewHydrateLegacy resolves mixed entries and loads the live guide at most once in this call. A later call loads the guide again. Source errors are returned unchanged, with a nil entry slice. An empty ref list returns a non-nil empty slice.

func RowViewMembers

func RowViewMembers(ctx context.Context, items RowViewCollectionItems, viewer *core.User, collection core.RatingKey, refs []core.NeutralEntryRef) bool

RowViewMembers reports whether every title ref is still a member of the viewer-visible collection. The scan is bounded and fails closed when membership cannot be established. It keeps no result for a later pass.

func RowViewResolveBatchTitles

func RowViewResolveBatchTitles(ctx context.Context, viewer *core.User, device core.Device, protocol core.Protocol, refs []core.NeutralEntryRef, one func(context.Context, *core.User, core.Device, core.Protocol, core.NeutralEntryRef) (frontend.RowEntry, error), batch func(context.Context, []core.RatingKey) (map[core.RatingKey]core.Title, error), get func(context.Context, *core.User, core.RatingKey) (*core.Title, error), visibility Visibility, live func(context.Context, *core.User) ([]frontend.RowEntry, error)) ([]frontend.RowEntry, error)

RowViewResolveBatchTitles binds this call's viewer, device and protocol into the neutral title hydrator. Nothing in the adapter is reused for a later pass.

func RowViewRevision

func RowViewRevision(ctx context.Context, db *sql.DB, user *core.User) (string, error)

RowViewRevision reads shared preference and layout versions, so snapshots created by another node fail closed after a relevant change.

func SameDayUnavailable

func SameDayUnavailable(ctx context.Context, title *core.Title, now time.Time, filter func(context.Context, []core.Title) []core.Title, source core.AvailabilitySource) bool

SameDayUnavailable preserves the release-evidence check used for next-up.

func SectionAllowed

func SectionAllowed(viewer *core.User, id int) bool

SectionAllowed applies a viewer's library restrictions to an existing section. An empty restriction list means all sections, as it does for an owner.

func SelectCinemaTrailers

func SelectCinemaTrailers(feature core.RatingKey, candidates []CinemaCandidate, limit int) []frontend.Extra

SelectCinemaTrailers picks up to limit trailers of other unwatched movies. It checks at most twelve candidates, matching cinema-trailer selection: the feature itself, non-movies, and watched titles are skipped, and each movie contributes its first playable trailer or teaser.

func SeriesNextUp

func SeriesNextUp(eps []core.Title, st map[core.RatingKey]core.WatchStatus, series core.RatingKey, resumable bool, unavailable func(*core.Title) bool) *core.Title

SeriesNextUp selects that series' next regular episode. Specials are skipped. When resumable, an in-progress episode wins; otherwise the first unwatched episode after the last completed one is returned, or S1E1 when none are watched. Episodes of any other series are ignored.

func SeriesNextUpQuery

func SeriesNextUpQuery() frontend.ItemQuery

SeriesNextUpQuery is the bounded children request for one series' episodes.

func ShowOnDeck

func ShowOnDeck(eps []core.Title, st map[core.RatingKey]core.WatchStatus, unavailable func(*core.Title) bool) *core.Title

ShowOnDeck selects the resumed or next regular episode in the viewer's order.

func TitleAllowed

func TitleAllowed(policy AccessPolicy, viewer *core.User, title *core.Title) bool

TitleAllowed applies the mandatory content guards and the owner's policy.

func UniqueRelated

func UniqueRelated(ctx context.Context, viewer *core.User, visibility Visibility, items []core.Title, seen map[core.RatingKey]bool, limit int) []core.Title

UniqueRelated excludes previously served titles, recording all newly seen keys in row order so later rows cannot repeat them.

func ViewerRestricted

func ViewerRestricted(policy AccessPolicy, u *core.User) bool

ViewerRestricted chooses inherited visibility for limited or unknown policies.

func VirtualPath

func VirtualPath(ctx context.Context, catalog VirtualPathCatalog, viewer *core.Viewer, device core.Device, title core.Title) (string, error)

VirtualPath builds the relative path for one already identified title. The caller supplies the title; this function does not look up or enrich it. Sibling names still come from the authorized library and children. A title absent from that tree is not found. A port or page error returns no path.

func VirtualPaths

func VirtualPaths(ctx context.Context, catalog VirtualPathCatalog, viewer *core.Viewer, device core.Device, keys []core.RatingKey) (map[core.RatingKey]string, error)

VirtualPaths resolves at most 50 distinct keys from at most 256 inputs. Hidden or unsupported keys are omitted. One requested set shares each library scan. A malformed or truncated page, or any port error, returns no paths.

Types

type AccessPolicy

type AccessPolicy interface {
	Allowed(*core.User, *core.Title) bool
}

AccessPolicy adds owner-defined restrictions to the mandatory title guard.

type AdditionalRows

type AdditionalRows struct {
	Playlists  PlaylistRowSource
	Watchlist  DiscoveryRowSource
	Personal   DiscoveryRowSource
	Live       LiveGuideSource
	Visibility Visibility
	Restricted func(*core.User) bool
	Now        func() time.Time
}

AdditionalRows composes viewer-owned playlists, discovery and live guide rows. Sources are invoked in that order; a failure stops subsequent sources.

func (AdditionalRows) Rows

type CandidateComposer

type CandidateComposer struct {
	Ranker     CandidateRanker
	Resume     ResumeCandidates
	Libraries  []Library
	Visibility Visibility
	Restricted func(*core.User) bool
}

CandidateComposer selects one placement and incorporates continuation before the row service authorizes entries and issues interaction receipts.

func (CandidateComposer) Rows

type CandidateRanker

CandidateRanker assembles one ranked pool for a visit. A non-nil predicate must be applied to the generated pools before ranking and limiting.

type CandidateRowSource

type CandidateRowSource struct {
	Load       func(context.Context, frontend.RowRequest) ([]RankedCandidate, error)
	Additional func(context.Context, frontend.RowRequest) ([]frontend.Row, error)
	Kind       func(string) string
}

func (CandidateRowSource) Rows

type CinemaCandidate

type CinemaCandidate struct {
	Title   core.Title
	Extras  []frontend.Extra
	Watched bool
}

CinemaCandidate is one movie already authorized for the viewer.

type Continue

type Continue struct {
	Filter       func(context.Context, *core.User, []core.Title) []core.Title
	State        core.State
	EpisodeOrder interface {
		Grouped(context.Context, *core.User, []core.RatingKey) (map[core.RatingKey]bool, error)
	}
	Now                func() time.Time
	Grandchildren      func(context.Context, core.RatingKey) ([]core.Title, error)
	NextEpisode        func(context.Context, core.RatingKey) (*core.Title, error)
	Visible            func(context.Context, *core.User, *core.Title) bool
	SameDayUnavailable func(context.Context, *core.Title) bool
	GetTitles          func(context.Context, []core.RatingKey) []core.Title
	SectionAllowed     func(*core.User, core.MediaType) bool
	OnError            func(error)
	FavoriteShows      func(context.Context, *core.User) (map[core.RatingKey]bool, error)
	// Unrestricted is a trusted policy capability; nil fails closed. It is
	// supplied only when the catalogue and policy authorize the legacy path.
	Unrestricted func(*core.User) bool
}

Continue coordinates resume and next-episode selection using viewer-scoped dependencies.

func (Continue) InProgress

func (c Continue) InProgress(ctx context.Context, viewer *core.User, t core.MediaType, limit int) ([]core.Title, map[core.RatingKey]core.WatchStatus)

func (Continue) Row

func (c Continue) Row(ctx context.Context, viewer *core.User, kind core.MediaType, limit int) ([]core.Title, map[core.RatingKey]core.WatchStatus)

Row applies the viewer's Continue Watching preferences after resume selection.

func (Continue) Shelf

func (c Continue) Shelf(ctx context.Context, viewer *core.User, kind core.MediaType, limit int) ([]core.Title, map[core.RatingKey]core.WatchStatus, bool)

Shelf selects one overflow candidate before applying preferences and the shelf limit.

type DetailRelated

type DetailRelated struct {
	Source     core.Recommender
	Visibility Visibility
	Get        func(context.Context, core.RatingKey) (*core.Title, error)
}

DetailRelated owns source authorization and title selection for detail shelves. Parent and availability checks during rendering retain their established dialect semantics; postplay uses its explicit selection policy.

func (DetailRelated) Rows

func (s DetailRelated) Rows(ctx context.Context, viewer *core.User, key core.RatingKey, count int) ([]core.Hub, error)

func (DetailRelated) SourceVisible

func (s DetailRelated) SourceVisible(ctx context.Context, viewer *core.User, key core.RatingKey) bool

type DiscoveryCandidate

type DiscoveryCandidate struct {
	Index   int
	Purpose core.RowPurpose
	Items   []core.Title
}

DiscoveryCandidate contains a source row's neutral purpose and item candidates.

func FilterDiscovery

func FilterDiscovery(ctx context.Context, viewer *core.User, visibility Visibility, rows []DiscoveryCandidate) []DiscoveryCandidate

FilterDiscovery applies viewer visibility to each row before it is returned.

func LoadDiscovery

func LoadDiscovery(ctx context.Context, viewer *core.User, visibility Visibility, limit int, load func(context.Context, int) []DiscoveryCandidate) []DiscoveryCandidate

LoadDiscovery expands full candidate pages when visibility filtering leaves a row short. Sources with fewer results than requested signal exhaustion.

type DiscoveryRowSource

type DiscoveryRowSource interface {
	Rows(context.Context, *core.User, int) ([]RankedCandidate, error)
}

DiscoveryRowSource returns context-free row candidates for one viewer.

type DiscoverySelection

type DiscoverySelection struct {
	Visibility   Visibility
	Unrestricted func(*core.User) bool
}

DiscoverySelection chooses visible candidates before frontend rendering. Unrestricted is a trusted host policy capability; nil fails closed.

func (DiscoverySelection) Load

func (s DiscoverySelection) Load(ctx context.Context, viewer *core.User, limit int, load func(context.Context, int) []DiscoveryCandidate) []DiscoveryCandidate

type EstimatePlanner

type EstimatePlanner interface {
	Plan(context.Context, frontend.PlanRequest) (frontend.PlayPlan, error)
}

EstimatePlanner answers whether a viewer may play an item without opening a source.

type ExtrasSource

type ExtrasSource interface {
	Enabled() bool
	List(context.Context, *core.Title, string) ([]extras.Extra, error)
}

type FavoriteKind

type FavoriteKind string

FavoriteKind is the neutral subject class for a favorite decision.

const (
	FavoriteKindTitle      FavoriteKind = "title"
	FavoriteKindPerson     FavoriteKind = "person"
	FavoriteKindCollection FavoriteKind = "collection"
	FavoriteKindChannel    FavoriteKind = "channel"
)

type FavoriteSources

type FavoriteSources struct {
	Title      func(context.Context, core.RatingKey) (*core.Title, error)
	Collection func(context.Context, *core.User, core.RatingKey) (*core.Collection, error)
	// ChannelAllowed checks one channel. It is the live-TV path; title,
	// person, and collection decisions must not call it.
	ChannelAllowed func(context.Context, *core.User, string) (bool, error)
	// ChannelIDs lists every allowed channel. ChannelAllowed replaces it.
	ChannelIDs func(context.Context, *core.User) ([]string, error)
	PersonPage func(context.Context, int64, int, int) ([]core.Title, error)
}

FavoriteSources are the current-policy lookups. A nil function is a missing dependency and fails closed. These functions are not used by the cached path.

type FavoriteSubject

type FavoriteSubject struct {
	Kind FavoriteKind
	ID   string
}

FavoriteSubject is a favorite identity with no protocol or storage attached.

type FirstObservedLedger

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

FirstObservedLedger records genuine offer-observation times in the shared database. It neither discovers nor resolves releases. Source identities are hashed at rest; callers must supply a stable, source-qualified identity.

func NewFirstObservedLedger

func NewFirstObservedLedger(db *sql.DB) (*FirstObservedLedger, error)

func (*FirstObservedLedger) FirstObservedAt

func (l *FirstObservedLedger) FirstObservedAt(ctx context.Context, parent core.RatingKey, source string) (*time.Time, error)

FirstObservedAt returns nil when no genuine observation has been recorded.

func (*FirstObservedLedger) Observe

func (l *FirstObservedLedger) Observe(ctx context.Context, parent core.RatingKey, source string, observed time.Time) (time.Time, error)

Observe records the first committed genuine observation atomically. The caller must supply the time the offer was actually observed, not its catalogue date, publication date or a time invented for an unknown offer.

func (*FirstObservedLedger) ObserveBatch

func (l *FirstObservedLedger) ObserveBatch(ctx context.Context, parent core.RatingKey, sources []string, observed time.Time) (map[string]time.Time, error)

ObserveBatch records a bounded group of source-qualified offers in one transaction. A failure rolls back the entire group. Empty input does not write or infer an observation.

type HomeAssembly

type HomeAssembly[P any] struct {
	Render      func(core.Hub) P
	Identity    func(P) string
	Report      func(P) (core.Hub, core.NeutralHomeRow)
	Continue    func() (core.Hub, P)
	Playlists   func() (P, bool)
	Discovery   func() []core.Hub
	Live        func() (P, bool)
	InLibraries func(core.Hub) bool
	Keep        func(P) bool
	Layout      func([]P, []core.Hub, []core.NeutralHomeRow) []P
}

HomeAssembly owns row inclusion, deduplication and ordering while adapters supply rendered projections and preserve their exact producer sidecars. Report records layout facts at paint time. Layout receives those facts and must not read the wire value again.

func (HomeAssembly[P]) Rows

func (a HomeAssembly[P]) Rows(hubs []core.Hub, includeContinue, promotedOnly, includePlaylists bool, purpose func(core.Hub) core.RowPurpose) ([]P, map[string]core.Hub)

type HomeCandidate

type HomeCandidate struct {
	Index       int
	Purpose     core.RowPurpose
	Promoted    bool
	InLibraries bool
}

HomeCandidate carries neutral row placement information. Index identifies the producer's candidate without exposing its wire representation.

type HomeSelection

type HomeSelection struct {
	Source     core.Recommender
	Visibility Visibility
	Restricted bool
}

HomeSelection obtains ranked shelves from the configured source. Policy-aware sources filter before ranking; finite custom sources are checked afterwards.

func (HomeSelection) Filter

func (s HomeSelection) Filter(ctx context.Context, viewer *core.User, hubs []core.Hub) []core.Hub

func (HomeSelection) Rows

func (s HomeSelection) Rows(ctx context.Context, viewer *core.User, hc core.HubContext, typ core.MediaType, count int) ([]core.Hub, error)

type LayoutCandidate

type LayoutCandidate struct {
	ID        string
	Title     string
	Type      core.MediaType
	Purpose   core.RowPurpose
	ItemCount int
}

LayoutCandidate is a row available to a layout engine. ItemCount includes non-title entries such as live channels or playlists.

type Library

type Library struct {
	ID   int
	Name string
	Type core.MediaType
	// Virtual is true only when the host asserts this library has no physical
	// locations. False means the host did not assert virtual origin.
	Virtual bool
}

Library is a host-configured, protocol-neutral library.

type LibraryPageExclusions

type LibraryPageExclusions struct {
	Keys                           []core.RatingKey
	ExcludeGenres, ExcludeKeywords []string
	MaxContentRating               string
	Movie, Television              core.Filter
}

LibraryPageExclusions is what an indexed library page can push into SQL.

type LiveGuideSource

type LiveGuideSource interface {
	AllowedChannels(context.Context, *core.User) ([]livetv.Channel, error)
	Guide(context.Context, *core.User, time.Time, time.Time) ([]livetv.Programme, error)
}

LiveGuideSource must enforce channel and programme access for the viewer.

type MarkerSource

type MarkerSource interface {
	CachedMarkers(context.Context, core.RatingKey, core.MediaInfo) ([]markers.Marker, error)
}

MarkerSource finds locally cached ranges in an already-probed release.

type PeopleRead

type PeopleRead struct {
	Viewer          *core.User
	SectionFor      func(core.MediaType) (int, bool)
	Policy          AccessPolicy
	FilterAvailable func(context.Context, []core.Title) []core.Title
	Titles          func(context.Context, []core.RatingKey) (map[core.RatingKey]core.Title, error)
	Finish          func(context.Context) error
	Close           func()
}

PeopleRead owns request-local policy and title facts. Finish must reject any intervening durable policy change; Close releases the host policy lease. Neither callback may publish partial results on failure.

type PersonCandidate

type PersonCandidate struct {
	TMDB int64
	Name string
}

PersonCandidate is a credited person whose name a people search matched.

type PlaylistRowSource

type PlaylistRowSource interface {
	List(context.Context, int64) ([]core.Playlist, error)
}

PlaylistRowSource is scoped to a viewer's own playlists.

type PlaylistSelection

type PlaylistSelection struct {
	Playlists     core.Playlists
	Collections   core.Collections
	Visibility    Visibility
	Children      func(context.Context, core.RatingKey) ([]core.Title, error)
	Grandchildren func(context.Context, core.RatingKey) ([]core.Title, error)
}

PlaylistSelection expands containers through viewer-bound metadata sources. URI parsing and playlist presentation belong to the requesting adapter.

func (PlaylistSelection) Expand

func (s PlaylistSelection) Expand(ctx context.Context, viewer *core.User, key core.RatingKey) ([]core.RatingKey, error)

func (PlaylistSelection) Get

func (s PlaylistSelection) Get(ctx context.Context, viewer *core.User, key core.RatingKey) (*core.Playlist, error)

type PlexIssuedIdentity

type PlexIssuedIdentity struct {
	Audio            *core.StreamInfo
	AudioPosition    int
	AudioSet         bool
	Subtitle         *core.StreamInfo
	SubtitlePosition int
	SubtitleSet      bool
}

PlexIssuedIdentity is the selection resolvePlaySelection already holds after the Plex adapter has accepted a stream ID from the item-scoped issued registry. Positions are indexes into the full MediaInfo.Streams slice from that issuance. They are not StreamInfo.Index values and they are not audio-only ordinals.

TranslatePlexTrackIdentity does not look up stream IDs, does not see the item, and does not authorize a caller-built StreamInfo. Copy Audio and Subtitle only from a registry reference whose item is the one being played.

AudioSet must equal AudioPosition >= 0, which is how the play resolver derives audio set-ness. Pass AudioPosition -1 and AudioSet false when the client omitted audio. The zero value is not an omission.

SubtitleSet false is omission. SubtitleSet true with a nil subtitle and position -1 is explicit subtitle-off. A nil subtitle with SubtitleSet true and any other position, including an out-of-range or wrong-type slot, is a legacy position-only choice and is preserved.

The translator does not retain either pointer and does not pin a source.

type PostPlayRow

type PostPlayRow struct {
	Purpose core.RowPurpose
	Items   []core.Title
}

PostPlayRow is a viewer-scoped set of ranked recommendations. The presentation and protocol-specific identifiers belong to the frontend.

func LoadPostPlay

func LoadPostPlay(ctx context.Context, viewer *core.User, visibility Visibility, count int, seen map[core.RatingKey]bool, load func(context.Context, int) []core.PostPlayPick) PostPlayRow

LoadPostPlay expands the candidate prefix when policy or deduplication leaves the requested row short. The source signals exhaustion by returning fewer candidates than requested. Work is capped at 1000 candidates.

func NextUp

func NextUp(ctx context.Context, viewer *core.User, key core.RatingKey, next func(context.Context, core.RatingKey) (*core.Title, error), visibility Visibility) PostPlayRow

NextUp selects a permitted successor to an episode. Missing successors and lookup failures produce no row, matching the optional nature of next-up.

func PostPlay

func PostPlay(ctx context.Context, picks []core.PostPlayPick, viewer *core.User, visibility Visibility, seen map[core.RatingKey]bool, limit int) PostPlayRow

PostPlay constructs a recommendation row in the supplied rank order.

type PostPlaySelection

type PostPlaySelection struct {
	Visibility   Visibility
	Unrestricted bool
	Picks        func(context.Context, int) []core.PostPlayPick
	Related      func(context.Context, int) ([]core.Hub, error)
}

PostPlaySelection preserves the established owner selection semantics while restricted viewers use inherited visibility before deduplication and limits. Unrestricted is a trusted host policy decision, never a request option.

func (PostPlaySelection) RelatedRows

func (s PostPlaySelection) RelatedRows(ctx context.Context, viewer *core.User, count int, seen map[core.RatingKey]bool) ([]core.Hub, error)

func (PostPlaySelection) Row

func (s PostPlaySelection) Row(ctx context.Context, viewer *core.User, count int, seen map[core.RatingKey]bool) PostPlayRow

type PreviewRows

type PreviewRows struct {
	Availability func(context.Context, int) []core.Hub
	Filter       func(context.Context, *core.User, []core.Title) ([]core.Title, error)
	Watchlist    func(context.Context, *core.User, int) ([]core.Hub, error)
	Personal     func(context.Context, *core.User, int) ([]core.Hub, error)
	Live         func(context.Context, *core.User, int) (core.Hub, error)
	LiveEnabled  func() bool
}

PreviewRows composes the owner preview using the same source ordering and section projection.

func (PreviewRows) Rows

func (a PreviewRows) Rows(ctx context.Context, u *core.User, hc core.HubContext, section string, limit int) []core.Hub

type RankedCandidate

type RankedCandidate struct {
	ID        string
	Purpose   core.RowPurpose
	Title     string
	Type      core.MediaType
	Style     string
	Promoted  bool
	Items     []core.Title
	More      bool
	Key       core.RatingKey
	ItemCount int
}

RankedCandidate carries protocol-neutral selection and presentation data.

func DetailRows

func DetailRows(ctx context.Context, viewer *core.User, key core.RatingKey, limit int, visibility Visibility,
	next func(context.Context, core.RatingKey) (*core.Title, error), postPlay func(context.Context, int) []core.PostPlayPick,
	related func(context.Context, int) ([]RankedCandidate, error)) ([]RankedCandidate, error)

DetailRows composes policy-filtered next-up, post-play and related shelves. Related supplies neutral candidates; no renderer-specific context is exposed.

type ReceiptOptions

type ReceiptOptions struct {
	DB            *sql.DB
	Active        func(context.Context, int64) (*core.User, error)
	Allowed       func(context.Context, *core.User, core.Device, core.Protocol, ReceiptSubject) bool
	AllowedAction func(context.Context, *core.User, core.Device, core.Protocol, ReceiptSubject, frontend.RowInteractionKind) bool
	Record        func(context.Context, *core.User, core.Device, core.Protocol, ReceiptSubject, frontend.RowInteractionKind) error
	Now           func() time.Time
	TTL           time.Duration
	MaxPerDevice  int
	PolicyLease   func(context.Context) (func(), error)
	// Generation, when set, is stored on the authority row and rechecked on
	// report. Nil stores 0 and does not recheck, which is the mixed-version
	// behavior for callers that have no generation.
	Generation func(context.Context, *core.User, core.Device, core.Protocol) (int64, error)
	// contains filtered or unexported fields
}

type ReceiptSubject

type ReceiptSubject struct {
	Kind, ID   string
	Purpose    core.RowPurpose
	Rank       int
	Impression *core.NeutralImpression
}

ReceiptSubject is server-owned neutral information, never supplied by a client report.

type Receipts

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

func NewReceipts

func NewReceipts(o ReceiptOptions) (*Receipts, error)

func (*Receipts) Issue

func (s *Receipts) Issue(ctx context.Context, viewer *core.User, device core.Device, protocol core.Protocol, subject ReceiptSubject) (string, error)

Issue creates an opaque, surface-scoped receipt. A device identifier is required.

func (*Receipts) IssueBatch

func (s *Receipts) IssueBatch(ctx context.Context, viewer *core.User, device core.Device, protocol core.Protocol, subjects []ReceiptSubject) ([]string, error)

IssueBatch atomically issues receipts and shared admission in input order.

func (*Receipts) Report

func (s *Receipts) Report(ctx context.Context, interaction frontend.RowInteraction) error

Report rechecks membership and authorization. Duplicate actions are ignored across nodes. Recording is at-most-once: a sink failure is not retried.

func (*Receipts) ReportRowInteraction

func (s *Receipts) ReportRowInteraction(ctx context.Context, i frontend.RowInteraction) error

type Recent

type Recent struct {
	Visibility   Visibility
	Unrestricted func(*core.User) bool
}

Recent owns recent-list selection. Unrestricted is a trusted host policy capability; nil enforces inherited visibility and availability.

func (Recent) Added

func (r Recent) Added(ctx context.Context, viewer *core.User, kind core.MediaType, count int, browse func(context.Context, core.BrowseQuery) (core.Page, error)) ([]core.Title, bool, error)

func (Recent) Viewed

func (r Recent) Viewed(ctx context.Context, viewer *core.User, libraryID int, history func(context.Context, int64, time.Time, int) ([]core.HistoryEntry, error), titles func(context.Context, []core.RatingKey) []core.Title, libraryFor func(core.MediaType) (int, bool)) ([]core.Title, error)

type RelatedArtworkDeps

type RelatedArtworkDeps struct {
	Authorize func(context.Context, *core.Viewer, core.Device, core.RatingKey) (*core.Viewer, error)
	List      func(context.Context, *core.Viewer, core.Device, core.RatingKey) ([]frontend.Image, bool, error)
}

RelatedArtworkDeps uses the same cache-only authorization and descriptor source as item artwork; it never resolves media or fetches catalogue data.

type RelatedArtworkService

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

func NewRelatedArtworkService

func NewRelatedArtworkService(deps RelatedArtworkDeps) (*RelatedArtworkService, error)

func (*RelatedArtworkService) RelatedArtwork

func (s *RelatedArtworkService) RelatedArtwork(ctx context.Context, viewer *core.Viewer, device core.Device, key core.RatingKey) (frontend.RelatedArtwork, error)

type RelatedCandidate

type RelatedCandidate struct {
	Index int
	Items []core.Title
}

RelatedCandidate contains protocol-neutral item candidates for one row. Index identifies the source row for its rendering adapter.

func LoadRelated

func LoadRelated(ctx context.Context, viewer *core.User, visibility Visibility, count int, seen map[core.RatingKey]bool, load func(context.Context, int) ([]RelatedCandidate, error)) ([]RelatedCandidate, error)

LoadRelated filters before deduplication and limits, expanding candidate requests while a full source page leaves a row short.

type ResumeCandidates

type ResumeCandidates interface {
	Continue(context.Context, *core.User, int) ([]core.Title, error)
}

ResumeCandidates supplies viewer-scoped continuation titles.

type RowEntryPolicy

type RowEntryPolicy struct {
	Visibility Visibility
	Favorite   func(context.Context, *core.User, FavoriteSubject) bool
	Playlist   func(context.Context, *core.User, core.RatingKey) bool
}

RowEntryPolicy shares admission between rows and persisted row interactions.

func (RowEntryPolicy) Entry

func (RowEntryPolicy) Row

func (p RowEntryPolicy) Row(ctx context.Context, u *core.User, row frontend.Row) bool

func (RowEntryPolicy) Subject

func (p RowEntryPolicy) Subject(ctx context.Context, u *core.User, kind, id string) bool

type RowInteractionPolicy

type RowInteractionPolicy struct {
	Libraries         []Library
	Visibility        Visibility
	Get               func(context.Context, core.RatingKey) (*core.Title, error)
	AllowEntry        func(context.Context, *core.User, string, string) bool
	CollectionMembers func(context.Context, *core.User, core.RatingKey, []core.NeutralEntryRef) bool
	Impression        func(*core.User, core.NeutralImpression)
	DetailOpen        func(context.Context, *core.User, *core.Title)
}

RowInteractionPolicy rechecks persisted impressions against current scope before recording personalization events.

func (RowInteractionPolicy) Allowed

func (p RowInteractionPolicy) Allowed(ctx context.Context, u *core.User, subject ReceiptSubject) bool

func (RowInteractionPolicy) Record

type RowSelection

type RowSelection struct {
	Visibility          Visibility
	PrioritizeAvailable func(context.Context, []core.Title) []core.Title
	SameDayUnavailable  func(context.Context, *core.Title) bool
}

RowSelection applies product policy before wire rendering, preserving source order.

func (RowSelection) Items

func (s RowSelection) Items(ctx context.Context, viewer *core.User, row core.Hub) []core.Title

type RowServiceDeps

type RowServiceDeps struct {
	Source     RowSource
	Visibility Visibility
	Active     func(context.Context, *core.User) bool
	AllowRow   func(context.Context, *core.User, frontend.Row) bool
	AllowEntry func(context.Context, *core.User, frontend.RowEntry) bool
	Layout     func(context.Context, frontend.RowRequest, []frontend.Row) ([]frontend.Row, error)
	Receipts   *Receipts
	Situation  func(context.Context, frontend.RowRequest) (core.HomeSituation, error)
	// HomeFallback supplies bounded library rows when a neutral Home has no
	// discovery shelves left after policy filtering. Plex does not use it.
	HomeFallback func(context.Context, frontend.RowRequest) ([]frontend.Row, error)
	// RankedSource disables retries when the source ranks policy-filtered candidates once.
	RankedSource bool
}

RowServiceDeps are host-owned dependencies. AllowRow and AllowEntry must recheck current membership and visibility, including non-title entries.

type RowSource

type RowSource interface {
	Rows(context.Context, frontend.RowRequest) ([]frontend.Row, error)
}

RowSource supplies neutral candidate rows for one viewer and device.

type RowViewCollectionItems

type RowViewCollectionItems func(ctx context.Context, viewer *core.User, collection core.RatingKey, offset, limit int) (RowViewCollectionPage, error)

RowViewCollectionItems loads one page of a viewer's collection. A nil function means membership cannot be established.

type RowViewCollectionPage

type RowViewCollectionPage struct {
	Keys  []core.RatingKey
	Total int
}

RowViewCollectionPage is one viewer-scoped page of collection member keys. It carries no protocol or wire context.

type RowViewCollectionSource

type RowViewCollectionSource interface {
	Items(context.Context, *core.User, core.RatingKey, int, int) (core.Page, error)
}

type RowViewEntry

type RowViewEntry func(ctx context.Context, ref core.NeutralEntryRef) (frontend.RowEntry, error)

RowViewEntry resolves one entry the batch path does not own. Callers bind device and protocol outside this port. The returned entry is passed through, including a handle if the caller set one. This hydrator does not mint one.

type RowViewGuide

type RowViewGuide func(ctx context.Context) ([]frontend.RowEntry, error)

RowViewGuide returns the viewer's current live guide for a single policy pass.

type RowViewOptions

type RowViewOptions struct {
	DB     *sql.DB
	TTL    time.Duration
	Now    func() time.Time
	Active func(context.Context, int64) (*core.User, error)
	Build  func(context.Context, frontend.RowViewRequest) ([]RowViewSnapshot, error)
	// ValidRow checks current placement, collection and viewer preference scope,
	// including the snapshot revision; it must fail closed on uncertainty.
	ValidRow     func(context.Context, *core.User, core.Device, core.Protocol, RowViewSnapshot) bool
	ResolveEntry func(context.Context, *core.User, core.Device, core.Protocol, core.NeutralEntryRef) (frontend.RowEntry, error)
	// ResolveEntries may batch rehydration; every call must perform fresh policy checks.
	ResolveEntries func(context.Context, *core.User, core.Device, core.Protocol, []core.NeutralEntryRef) ([]frontend.RowEntry, error)
	Receipts       *Receipts
}

type RowViewService

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

func NewRowViewService

func NewRowViewService(o RowViewOptions) (*RowViewService, error)

func NewRuntimeRowViews

func NewRuntimeRowViews(d RuntimeRowViews) (*RowViewService, error)

func (*RowViewService) ListRowViews

func (s *RowViewService) ListRowViews(ctx context.Context, req frontend.RowViewRequest) ([]frontend.RowView, error)

func (*RowViewService) ResolveRowView

type RowViewSnapshot

type RowViewSnapshot struct {
	Row       frontend.Row
	Situation core.HomeSituation
	Placement frontend.Placement
	ViewID    string
	Item      core.RatingKey
	Entries   []core.NeutralEntryRef
	Truncated bool
	Revision  string
}

RowViewSnapshot is produced by the trusted row ranker after policy and layout. Entries are identities only; ResolveEntry must rehydrate them.

type RowViewTitleBatch

type RowViewTitleBatch func(ctx context.Context, keys []core.RatingKey) (map[core.RatingKey]core.Title, error)

RowViewTitleBatch loads titles by key from the local catalogue.

type RowViewTitleRead

type RowViewTitleRead func(ctx context.Context, key core.RatingKey) (*core.Title, error)

RowViewTitleRead loads one title, including an existing home-release admission.

type RowsService

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

RowsService produces viewer-scoped, policy-checked rows.

func NewRowsService

func NewRowsService(d RowServiceDeps) (*RowsService, error)

func (*RowsService) Rows

func (s *RowsService) Rows(ctx context.Context, req frontend.RowRequest, protocol core.Protocol) ([]frontend.Row, error)

Rows filters each tagged entry before limiting and never accepts client-made interaction identifiers. Candidate requests expand to a bounded prefix when filtering would otherwise leave a row short.

func (*RowsService) SnapshotRows

func (s *RowsService) SnapshotRows(ctx context.Context, req frontend.RowRequest, protocol core.Protocol) ([]frontend.Row, error)

SnapshotRows builds and authorizes the same ranked rows without issuing impressions; a paged row view issues receipts only for delivered entries.

type RuntimeRowViews

type RuntimeRowViews struct {
	DB                  *sql.DB
	CurrentAvailable    func(context.Context, []core.Title) []core.Title
	Protocol            core.Protocol
	Receipts            *Receipts
	Visibility          Visibility
	Libraries           []Library
	Active              func(context.Context, int64) (*core.User, error)
	CachedGet           func(context.Context, core.RatingKey) (*core.Title, error)
	CachedGetMany       func(context.Context, []core.RatingKey) (map[core.RatingKey]core.Title, error)
	CachedVirtualSeason func(context.Context, *core.User, core.RatingKey) (*core.Title, error)
	Collections         core.Collections
	Playlists           core.Playlists
	Live                LiveGuideSource
	Situation           func(frontend.RowRequest) core.HomeSituation
	Snapshot            func(context.Context, frontend.RowRequest, core.HomeSituation) ([]frontend.Row, error)
}

RuntimeRowViews binds persistence, snapshot construction and current policy revalidation inside Experience. The app supplies source capabilities only.

type ScopedCollection

type ScopedCollection struct {
	Collections core.Collections
	Visibility  Visibility
	Now         func() time.Time
	// Unrestricted is a trusted host capability; nil enforces inherited policy.
	Unrestricted func(*core.User) bool
}

ScopedCollection selects upcoming members only through an enabled, viewer-and-device-scoped collection capability.

func (ScopedCollection) Counts

func (s ScopedCollection) Counts(ctx context.Context, viewer *core.User, collections []core.Collection, device string) []core.Collection

func (ScopedCollection) Get

func (s ScopedCollection) Get(ctx context.Context, viewer *core.User, key core.RatingKey, device string, count bool) (*core.Collection, error)

Get authorizes a collection and optionally calculates its upcoming count.

func (ScopedCollection) Items

func (s ScopedCollection) Items(ctx context.Context, viewer *core.User, key core.RatingKey, deviceClass string) ([]core.Title, error)

func (ScopedCollection) Page

func (s ScopedCollection) Page(ctx context.Context, viewer *core.User, collection *core.Collection, key core.RatingKey, device string, offset, size int) (core.Page, error)

func (ScopedCollection) Placeholder

func (s ScopedCollection) Placeholder(ctx context.Context, viewer *core.User, key core.RatingKey, requested *core.RatingKey, device string) (*core.Title, core.RatingKey, error)

Placeholder resolves a collection-scoped future title and authorizes both the title and its containing library before returning the source record.

type Search struct {
	Visibility Visibility
	Vibe       VibeSearch
	Natural    func(context.Context, *core.User, string, int) ([]core.Title, bool)
	Catalog    func(context.Context, string, []core.MediaType, int) ([]core.Title, error)
	// Unrestricted is a trusted host policy capability. Nil uses the
	// restricted, inherited-policy and availability path.
	Unrestricted func(*core.User) bool
}

Search resolves discovery-first keyword searches for the viewer's libraries. Natural is optional; false falls back to the catalogue.

func (Search) Find

func (s Search) Find(ctx context.Context, viewer *core.User, query string, types []core.MediaType, limit int) ([]core.Title, error)

func (Search) Keyword

func (s Search) Keyword(ctx context.Context, viewer *core.User, query string, types []core.MediaType, limit int) ([]core.Title, error)

Keyword preserves the catalogue's candidate window and applies title policy. Section searches use this fixed window rather than natural search or refill.

func (Search) Resolve

func (s Search) Resolve(ctx context.Context, viewer *core.User, query string, types []core.MediaType, limit int, collections bool) (SearchResults, error)

Resolve chooses collection search before keyword search when requested.

type SearchResults

type SearchResults struct {
	Collection       *core.Collection
	Items            []core.Title
	CollectionSearch bool
}

SearchResults retains whether an optional collection source handled the query. A handled empty result must not fall back to keyword search.

type Service

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

func NewService

func NewService(d ServiceDeps) (*Service, error)

func (*Service) AddPlaylistItems

func (s *Service) AddPlaylistItems(ctx context.Context, viewer *core.Viewer, key core.RatingKey, items []core.RatingKey) error

func (*Service) Ancestors

func (s *Service) Ancestors(ctx context.Context, viewer *core.Viewer, device core.Device, key core.RatingKey) ([]core.Title, error)

Ancestors returns only authorized parents, failing closed if a parent is hidden.

func (*Service) AuthorizeArtwork

func (s *Service) AuthorizeArtwork(ctx context.Context, viewer *core.Viewer, device core.Device, key core.RatingKey) (*core.Viewer, error)

func (*Service) Children

func (s *Service) Children(ctx context.Context, viewer *core.Viewer, device core.Device, key core.RatingKey, q frontend.ItemQuery) (core.Page, error)

func (*Service) Collections

Collections filters member visibility before calculating totals and paging.

func (*Service) CreatePlaylist

func (s *Service) CreatePlaylist(ctx context.Context, viewer *core.Viewer, name string, items []core.RatingKey) (*core.Playlist, error)

func (*Service) DeletePlaylist

func (s *Service) DeletePlaylist(ctx context.Context, viewer *core.Viewer, key core.RatingKey) error

func (*Service) EntityFavorites

func (s *Service) EntityFavorites(ctx context.Context, viewer *core.Viewer, refs []frontend.FavoriteRef) (map[frontend.FavoriteRef]bool, error)

func (*Service) Facets

func (s *Service) Facets(ctx context.Context, viewer *core.Viewer, device core.Device, viewID, name string) ([]core.FacetValue, error)

Facets are computed only from titles the viewer may see.

func (*Service) FavoriteItem

func (s *Service) FavoriteItem(ctx context.Context, viewer *core.Viewer, _ core.Device, key core.RatingKey) (frontend.Membership, error)

func (*Service) Item

func (*Service) LatestAdded

func (s *Service) LatestAdded(ctx context.Context, viewer *core.Viewer, device core.Device, viewID string, limit int) ([]frontend.LatestItem, error)

LatestAdded returns library roots, with TV grouped as series.

func (*Service) LatestAddedGrouped

func (s *Service) LatestAddedGrouped(ctx context.Context, viewer *core.Viewer, device core.Device, viewID string, limit int, group bool) ([]frontend.LatestItem, error)

func (*Service) LibraryCount

func (s *Service) LibraryCount(ctx context.Context, viewer *core.Viewer, viewID string) (int, error)

LibraryCount is the indexed size of one library. It does not browse or hydrate titles. A viewer who cannot open the library gets zero.

func (*Service) LibraryItems

func (s *Service) LibraryItems(ctx context.Context, viewer *core.Viewer, device core.Device, viewID string, q frontend.ItemQuery) (core.Page, error)

LibraryItems scans catalogue pages and filters before applying the requested page.

func (*Service) LocalEpisodes

func (s *Service) LocalEpisodes(ctx context.Context, viewer *core.Viewer, key core.RatingKey) ([]core.Title, bool, error)

LocalEpisodes lists aired episodes already stored for a show or season. The bool is false when the catalogue page was capped, so a caller that needs every episode can omit a count instead of inventing one. It does not refresh the show or its seasons.

func (*Service) MovePlaylistItem

func (s *Service) MovePlaylistItem(ctx context.Context, viewer *core.Viewer, key core.RatingKey, from, to int) error

func (*Service) NamedFacetIDs

func (s *Service) NamedFacetIDs(ctx context.Context, viewer *core.Viewer, _ core.Device, key core.RatingKey, kind string, names []string) (map[string]string, error)

NamedFacetIDs maps exact credited genre or studio names on one visible title to the same identifier namespace as library facets.

func (*Service) NotInterested

func (s *Service) NotInterested(ctx context.Context, viewer *core.Viewer, _ core.Device, q frontend.PageQuery) (frontend.NotInterestedPage, error)

func (*Service) NotInterestedItem

func (s *Service) NotInterestedItem(ctx context.Context, viewer *core.Viewer, _ core.Device, key core.RatingKey) (frontend.NotInterestedEntry, error)

func (*Service) PageArtworkTitles

func (s *Service) PageArtworkTitles(ctx context.Context, viewer *core.Viewer, keys []core.RatingKey) ([]core.Title, error)

PageArtworkTitles loads one grid and drops titles this viewer cannot see.

func (*Service) People

func (s *Service) People(ctx context.Context, viewer *core.Viewer, device core.Device, query frontend.PeopleQuery) (result []frontend.PersonDetail, err error)

func (*Service) Person

func (s *Service) Person(ctx context.Context, viewer *core.Viewer, device core.Device, id string, options frontend.PersonReadOptions) (result frontend.PersonDetail, err error)

func (*Service) PersonCredit

func (s *Service) PersonCredit(ctx context.Context, viewer *core.Viewer, device core.Device, item core.RatingKey, tmdb int64) (result frontend.PersonDetail, err error)

func (*Service) PersonFacetIDs

func (s *Service) PersonFacetIDs(ctx context.Context, viewer *core.Viewer, _ core.Device, key core.RatingKey, ids []int64) (map[int64]string, error)

PersonFacetIDs maps requested credits on one currently visible title to the same identifiers used by library person facets. An episode-only credit may have no corresponding library-wide facet and therefore may not resolve via ResolveFacet for that library.

func (*Service) Playlists

func (s *Service) Playlists(ctx context.Context, viewer *core.Viewer, q frontend.PageQuery) (frontend.PlaylistPage, error)

Playlists lists the viewer's own playlists. Membership is the titles this viewer is allowed to see.

func (*Service) RemoveFromContinue

func (s *Service) RemoveFromContinue(ctx context.Context, v *core.Viewer, k core.RatingKey) error

func (*Service) RemovePlaylistItem

func (s *Service) RemovePlaylistItem(ctx context.Context, viewer *core.Viewer, key core.RatingKey, index int) error

func (*Service) ResolveEventSubject

func (s *Service) ResolveEventSubject(ctx context.Context, viewer *core.Viewer, _ core.Device, ref frontend.FavoriteRef) (frontend.ResolvedEventSubject, error)

ResolveEventSubject rechecks visibility on delivery rather than trusting the event's subject. Unknown and inaccessible entities return the same error.

func (*Service) ResolveExternal

func (s *Service) ResolveExternal(ctx context.Context, viewer *core.Viewer, _ core.Device, req frontend.ExternalRequest) (core.RatingKey, error)

ResolveExternal resolves an IMDb ID to a currently visible catalogue item. Episode suffixes use canonical TMDB numbering, regardless of viewer display order.

func (*Service) ResolveFacet

func (s *Service) ResolveFacet(ctx context.Context, viewer *core.Viewer, device core.Device, viewID, kind, id string) (core.FacetValue, error)

ResolveFacet only resolves identifiers among the viewer's current visible facets.

func (*Service) Resume

func (s *Service) Resume(ctx context.Context, viewer *core.Viewer, _ core.Device, viewID string, types []core.MediaType, offset, limit int, requireExact bool) (frontend.ActivityPage, error)

Resume reads only in-progress state, without next-up or Home Designer data.

func (*Service) Rows

func (s *Service) Rows(ctx context.Context, req frontend.RowRequest) ([]frontend.Row, error)

func (*Service) Search

func (s *Service) Search(ctx context.Context, viewer *core.Viewer, device core.Device, query string, q frontend.ItemQuery) (frontend.SearchPage, error)

func (*Service) SetEntityFavorite

func (s *Service) SetEntityFavorite(ctx context.Context, viewer *core.Viewer, ref frontend.FavoriteRef, value bool) error

func (*Service) SetFavorite

func (s *Service) SetFavorite(ctx context.Context, v *core.Viewer, k core.RatingKey, on bool) error

func (*Service) SetLikes

func (s *Service) SetLikes(ctx context.Context, viewer *core.Viewer, key core.RatingKey, likes *bool) error

SetLikes delegates to the state's atomic shared-rating operation.

func (*Service) SetNotInterested

func (s *Service) SetNotInterested(ctx context.Context, v *core.Viewer, k core.RatingKey, on bool) error

func (*Service) SetPlayed

func (s *Service) SetPlayed(ctx context.Context, v *core.Viewer, k core.RatingKey) error

func (*Service) SetRating

func (s *Service) SetRating(ctx context.Context, v *core.Viewer, k core.RatingKey, rating float64) error

func (*Service) SetUnplayed

func (s *Service) SetUnplayed(ctx context.Context, v *core.Viewer, k core.RatingKey) error

func (*Service) SetWatchlist

func (s *Service) SetWatchlist(ctx context.Context, v *core.Viewer, k core.RatingKey, on bool) error

func (*Service) Similar

func (s *Service) Similar(ctx context.Context, viewer *core.Viewer, device core.Device, key core.RatingKey, q frontend.ItemQuery) (frontend.SearchPage, error)

Similar applies policy before pagination and marks truncated candidate sets unknown.

func (*Service) SnapshotRows

func (s *Service) SnapshotRows(ctx context.Context, req frontend.RowRequest) ([]frontend.Row, error)

SnapshotRows is the same authorized Home as Rows without issuing impressions. A second surface that paints those rows, such as Silo Discover, uses it so the bandit is not charged twice for one visit.

func (*Service) StoredItem

func (s *Service) StoredItem(ctx context.Context, req frontend.ItemRequest) (frontend.ItemDetail, error)

StoredItem reads a title already in the catalogue. Resume, Next Up, and other lists use it so one stale show cannot refresh from a provider.

func (*Service) UnplayedEpisodeCounts

func (s *Service) UnplayedEpisodeCounts(ctx context.Context, viewer *core.Viewer, keys []core.RatingKey) (map[core.RatingKey]int, error)

UnplayedEpisodeCounts is the aired-episode badge for a list of shows and seasons. It does not load those episodes.

func (*Service) UserData

func (s *Service) UserData(ctx context.Context, viewer *core.Viewer, keys []core.RatingKey) (map[core.RatingKey]core.WatchStatus, error)

func (*Service) Views

func (s *Service) Views(ctx context.Context, viewer *core.Viewer) ([]frontend.View, error)

func (*Service) WatchlistItem

func (s *Service) WatchlistItem(ctx context.Context, viewer *core.Viewer, _ core.Device, key core.RatingKey) (frontend.Membership, error)

func (*Service) WithPersonCredit

func (s *Service) WithPersonCredit(ctx context.Context, viewer *core.Viewer, device core.Device, item core.RatingKey, tmdb int64, read func(*core.User, frontend.PersonDetail, core.Title) error) error

WithPersonCredit keeps a host's cache-only descriptor read in the same durable policy proof as the exact current credit. The callback must not publish data; its output is valid only after this method returns success.

type ServiceDeps

type ServiceDeps struct {
	// BeginPeopleRead supplies fresh, fenced policy facts for the optional People API.
	BeginPeopleRead     func(context.Context, *core.User) (PeopleRead, error)
	NotInterestedSource func(context.Context, *core.User) ([]frontend.NotInterestedEntry, error)
	ByExternalID        func(context.Context, string, string) (*core.Title, error)
	Active              func(context.Context, int64) (*core.User, error)
	Libraries           []Library
	// LiveLibraries, when set, is read on each surface call so configured
	// names stay current. IDs and media types stay stable. Nil uses Libraries.
	LiveLibraries func() []Library
	Visibility    Visibility
	Get           func(context.Context, *core.User, core.RatingKey) (*core.Title, error)
	// CachedGet applies catalogue visibility without a network refresh.
	CachedGet func(context.Context, core.RatingKey) (*core.Title, error)
	// CachedGetMany reads current cached rows in bounded batches without refresh.
	CachedGetMany func(context.Context, []core.RatingKey) (map[core.RatingKey]core.Title, error)
	// ParentTitles resolves distinct parents using the same semantics as GetTitle.
	ParentTitles func(context.Context, []core.RatingKey) (map[core.RatingKey]core.Title, error)
	// CachedVirtualSeason resolves a viewer's already-computed episode layout.
	CachedVirtualSeason func(context.Context, *core.User, core.RatingKey) (*core.Title, error)
	Children            func(context.Context, *core.User, core.RatingKey) ([]core.Title, error)
	Grandchildren       func(context.Context, *core.User, core.RatingKey) ([]core.Title, error)
	// SeasonEpisodes pages one catalogue season. ok is false when this viewer
	// uses a custom episode group and the caller must use Grandchildren.
	SeasonEpisodes func(context.Context, *core.User, core.RatingKey, int, int, int) (core.Page, bool, error)
	Browse         func(context.Context, core.BrowseQuery) (core.Page, error)
	// PageExclusions are viewer-specific SQL exclusions for an indexed library
	// page: hidden titles, blocked genres, blocked keywords, the policy
	// content-rating cap, and parsed plex.tv share filters. Nil keeps the
	// catalogue scan, which still applies filters the SQL page cannot see.
	PageExclusions func(context.Context, *core.User) LibraryPageExclusions
	// CountBrowsable is the indexed library size. The view list uses it instead
	// of browsing titles.
	CountBrowsable func(context.Context, core.MediaType) (int, error)
	// CreditItems enumerates cached credit-title keys without browsing or hydration.
	CreditItems func(context.Context, core.RatingKey, int) ([]core.RatingKey, error)
	// PeopleIndex lists credited people with a name word starting with every
	// term, most credited first. PersonCredits lists a person's most popular
	// credited titles of the given types. With both, people searches use the
	// name index instead of walking every credited title. Optional.
	PeopleIndex   func(context.Context, []string, int) ([]PersonCandidate, error)
	PersonCredits func(context.Context, int64, []core.MediaType, int) ([]core.RatingKey, error)
	AiredEpisodes func(context.Context, core.RatingKey, int, int) (core.Page, error)
	// StoredEpisodes answers a recursive episode listing from the stored
	// episodes in key order. Optional; without it the listing walks the
	// library.
	StoredEpisodes StoredEpisodeDeps
	AddedTimes     func(context.Context, []core.RatingKey) (map[core.RatingKey]time.Time, error)
	// RecentAir is the indexed date-added window. Nil keeps the catalogue scan.
	RecentAir func(context.Context, core.MediaType, int, int) ([]core.Title, bool, error)
	// CountUnplayedAired counts aired episodes with no play. Nil keeps the episode list.
	CountUnplayedAired func(context.Context, int64, []core.RatingKey, string) (map[core.RatingKey]int, error)
	SimilarSource      func(context.Context, core.RatingKey, int) ([]core.Title, error)
	// Related is the same detail recommender Plex uses. Jellyfin and Emby
	// Similar flatten its policy-filtered rows.
	Related               core.Recommender
	FilterRecommendations func(context.Context, *core.User, []core.Title) ([]core.Title, error)
	Collections           core.Collections
	Playlists             core.Playlists
	// Scheduled lists metadata-only future episodes for one season. Nil means
	// the host has not opted in. Callers must not resolve a release from it.
	Scheduled func(context.Context, core.RatingKey) ([]core.Title, error)
	// Grouped reports shows whose episode order is not the canonical season
	// tree. Future episodes stay off those shows.
	Grouped  func(context.Context, *core.User, []core.RatingKey) (map[core.RatingKey]bool, error)
	State    core.State
	Search   Search
	Rows     *RowsService
	Protocol core.Protocol
	// Favorites manages the stable, per-viewer Favorites playlist mapping.
	Favorites               func(context.Context, *core.User, core.RatingKey, bool) (bool, error)
	IsFavorite              func(context.Context, *core.User, core.RatingKey) (bool, error)
	IsFavoriteMany          func(context.Context, *core.User, []core.RatingKey) (map[core.RatingKey]bool, error)
	IsNotInterestedMany     func(context.Context, *core.User, []core.RatingKey) (map[core.RatingKey]bool, error)
	IsWatchlistedMany       func(context.Context, int64, []core.RatingKey) (map[core.RatingKey]bool, error)
	FavoriteMembership      func(context.Context, *core.User, core.RatingKey) (frontend.Membership, error)
	IsNotInterested         func(context.Context, *core.User, core.RatingKey) (bool, error)
	SetNotInterestedSource  func(context.Context, *core.User, core.RatingKey, bool) error
	IsWatchlisted           func(context.Context, *core.User, core.RatingKey) (bool, error)
	WatchlistMembership     func(context.Context, *core.User, core.RatingKey) (frontend.Membership, error)
	SetWatchlistSource      func(context.Context, *core.User, core.RatingKey, bool) error
	RemoveContinue          func(context.Context, *core.User, core.RatingKey) error
	CanFavoriteEntity       func(context.Context, *core.User, frontend.FavoriteRef) bool
	SetEntityFavoriteSource func(context.Context, *core.User, frontend.FavoriteRef, bool) error
	EntityFavoritesSource   func(context.Context, *core.User, []frontend.FavoriteRef) (map[frontend.FavoriteRef]bool, error)

	// Enrich must use cached or estimated data; it must never resolve releases.
	Enrich func(context.Context, *core.User, core.Device, *core.Title) (frontend.ItemDetail, error)
}

ServiceDeps contains trusted host dependencies, never provided by a frontend.

type StoredEpisodeDeps

type StoredEpisodeDeps struct {
	// Page returns stored, aired, non-adult episodes after a key, in key
	// order, of the series a browse filter admits.
	Page func(context.Context, core.Filter, core.RatingKey, int) ([]core.Title, error)
	// Count counts Page's whole set.
	Count func(context.Context, core.Filter) (int, error)
	// Unranked reports that no stored episode has a popularity.
	Unranked func(context.Context) (bool, error)
	// Customized reports that the viewer, or the owner's defaults, change
	// some series' episode set (an episode group or hidden specials).
	Customized func(context.Context, *core.User) (bool, error)
}

StoredEpisodeDeps let a recursive episode listing read the catalogue's stored episodes in key order instead of walking every series. All are required for that path; without them the listing walks the library.

type TrackPreferenceSource

type TrackPreferenceSource interface {
	Select(context.Context, int64, core.RatingKey, core.MediaInfo, string) (trackprefs.Selection, error)
	GetRules(context.Context, int64) (trackprefs.Rules, error)
}

TrackPreferenceSource supplies persisted viewer rules and show choices.

type TrackPreferences

type TrackPreferences struct {
	Source   TrackPreferenceSource
	GetTitle func(context.Context, core.RatingKey) (*core.Title, error)
}

TrackPreferences applies persisted choices beneath explicit client choices. GetTitle is optional; an episode with no original language uses its show's language when it can be loaded, and otherwise retains the empty language.

func (TrackPreferences) AudioFilters

func (p TrackPreferences) AudioFilters(ctx context.Context, viewer *core.User, info core.MediaInfo, selected TrackSelection) ([]string, error)

AudioFilters applies the viewer's rules for the selected stream's channel count. Missing preferences return no filters; lookup failures are reported.

func (TrackPreferences) Select

func (p TrackPreferences) Select(ctx context.Context, viewer *core.User, key core.RatingKey, info core.MediaInfo, original string, selected TrackSelection) (TrackSelection, error)

Select returns the original selection unchanged if preferences are absent or their lookup fails. A lookup error is returned for the caller to log.

type TrackSelection

type TrackSelection struct {
	Audio, Subtitle       int
	AudioSet, SubtitleSet bool
}

TrackSelection holds positions in MediaInfo.Streams, not StreamInfo.Index. Set flags distinguish an omitted selection from position zero, and an explicit subtitle-off selection (-1) from an omitted subtitle choice.

func TranslatePlexTrackIdentity

func TranslatePlexTrackIdentity(issued PlexIssuedIdentity, probed core.MediaInfo) (TrackSelection, error)

TranslatePlexTrackIdentity maps an issued Plex selection onto the current probed MediaInfo the way plex resolvePlaySelection does, before viewer preferences and before applySelections.

A nil audio identity leaves AudioPosition unchanged, including when it is negative, out of range, or not an audio stream. Preferences still see a nonnegative legacy position as set. A non-nil audio identity is passed to ResolveAudio only when that position is nonnegative. An unmatched result is position -1 and AudioSet false so preferences can run. The identity's Type is not rechecked.

A nil subtitle identity leaves SubtitleSet and SubtitlePosition unchanged. SubtitleSet false becomes position -1. A non-nil subtitle identity is passed to ResolveSubtitle only when SubtitleSet is true. An unmatched result is position -1 and SubtitleSet false. Type is not rechecked.

The result positions are indexes in probed.Streams. Encoder indexes still come from the selected StreamInfo.Index.

type UnsupportedPlexTrackIdentityError

type UnsupportedPlexTrackIdentityError struct {
	Reason string
}

UnsupportedPlexTrackIdentityError means the issued selection is not a shape plex resolvePlaySelection produces. Callers must handle it themselves. The returned TrackSelection is empty and must not be applied. Every value of this type matches errors.Is, whatever Reason is set.

func (UnsupportedPlexTrackIdentityError) Error

func (UnsupportedPlexTrackIdentityError) Is

type VersionObservationPort

type VersionObservationPort interface {
	ObserveBatch(context.Context, core.RatingKey, []string, time.Time) (map[string]time.Time, error)
}

VersionObservationPort records first-seen times for privacy-preserving candidate sources. ObserveBatch is one atomic group for a parent title: a failure leaves every offer's observation unknown.

type VersionOffer

type VersionOffer struct {
	ID          string
	Name        string
	Edition     string
	Media       core.MediaInfo
	AddedAt     time.Time
	Publication *time.Time
	// contains filtered or unexported fields
}

VersionOffer is one candidate without protocol fields. ID is the existing ReleaseVariant.ID, unchanged. It is not a scoped client handle: Jellyfin and Emby pass it to VersionHandles.Issue, which replaces it before MediaSources, and Plex maps the release to an integer Media/Part id. Publication is the release publication time when known. It is not a file modification time. source is the hashed observation identity only.

Final merge: profile SelectionKey and remembered-release invalidation are absent on b5315d63131029ddb5df7b91161e31240367f602. When they land they must not copy ReleaseVariant.ID or source onto an unscoped client id, and must keep this ID pass-through and parent+source observations.

func VersionOfferFrom

func VersionOfferFrom(title *core.Title, variant core.ReleaseVariant) VersionOffer

VersionOfferFrom converts one candidate. It does not resolve a file, part list, etag, or modification time, and it does not record an observation or replace the candidate id.

func VersionOffersFrom

func VersionOffersFrom(title *core.Title, variants []core.ReleaseVariant) []VersionOffer

VersionOffersFrom converts candidates in their existing order.

type VibeSearch

type VibeSearch struct {
	Source     VibeSource
	Visibility Visibility
	// Unrestricted is a trusted host policy capability; nil fails closed.
	Unrestricted func(*core.User) bool
}

VibeSearch resolves an optional natural-language collection source.

func (VibeSearch) Find

func (s VibeSearch) Find(ctx context.Context, viewer *core.User, query string) (*core.Collection, []core.Title, bool, error)

type VibeSource

type VibeSource interface {
	VibeCollection(context.Context, *core.User, string) (*core.Collection, []core.Title, bool, error)
}

VibeSource returns a viewer-scoped ephemeral collection and candidate items.

type VirtualPathCatalog

type VirtualPathCatalog struct {
	Views        func(context.Context, *core.Viewer) ([]VirtualPathView, error)
	LibraryItems func(context.Context, *core.Viewer, core.Device, string, int, int) (VirtualPathPage, error)
	Children     func(context.Context, *core.Viewer, core.Device, core.RatingKey, int, int) (VirtualPathPage, error)
}

VirtualPathCatalog is the authorized browse port for virtual library paths. Views, LibraryItems and Children must already enforce viewer policy. A nil function fails closed. This service does not resolve a source, release or playback detail; names come from libpath, which depends only on core and is shared with WebDAV rather than reimplemented here.

type VirtualPathPage

type VirtualPathPage struct {
	Items []core.Title
	Total int
}

VirtualPathPage is one authorized page of catalogue titles.

type VirtualPathView

type VirtualPathView struct {
	ID   string
	Type core.MediaType
}

VirtualPathView is one viewer-authorized library section. It carries no protocol or wire context.

type Visibility

type Visibility struct {
	Policy          AccessPolicy
	SectionFor      func(core.MediaType) (int, bool)
	GetTitle        func(context.Context, core.RatingKey) (*core.Title, error)
	FilterAvailable func(context.Context, []core.Title) []core.Title
	Sessions        func() []*core.PlaySession
}

Visibility applies content, library and inherited show restrictions. The section lookup returns false for media types with no configured library.

func BindVisibility

func BindVisibility(policy AccessPolicy, get func(context.Context, core.RatingKey) (*core.Title, error), available func(context.Context, []core.Title) []core.Title, sessions func() []*core.PlaySession, libraries []Library) Visibility

BindVisibility is the shared library, policy and availability port. Frontends supply sources; they do not decide which library a media type uses.

func (Visibility) Available

func (v Visibility) Available(ctx context.Context, viewer *core.User, titles []core.Title) []core.Title

Available filters discovery evidence, retaining a viewer's open sources. Callers must separately enforce title policy.

func (Visibility) CollectionAllowed

func (v Visibility) CollectionAllowed(viewer *core.User, c *core.Collection) bool

CollectionAllowed checks the library restriction for a collection.

func (Visibility) Playable

func (v Visibility) Playable(ctx context.Context, viewer *core.User, title *core.Title) bool

Playable checks inherited show policy without requiring discovery evidence.

func (Visibility) TitleAllowed

func (v Visibility) TitleAllowed(viewer *core.User, title *core.Title) bool

func (Visibility) TitlesAllowed

func (v Visibility) TitlesAllowed(viewer *core.User, titles []core.Title) []core.Title

TitlesAllowed filters the title/section guard without discovery filtering.

func (Visibility) UpcomingItems

func (v Visibility) UpcomingItems(ctx context.Context, viewer *core.User, items []core.Title, now func() time.Time) []core.Title

UpcomingItems retains the existing placeholder discovery rules for a page.

func (Visibility) Visible

func (v Visibility) Visible(ctx context.Context, viewer *core.User, title *core.Title) bool

Visible includes availability in addition to the playback policy.

Jump to

Keyboard shortcuts

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