state

package
v0.2.34 Latest Latest
Warning

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

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

Documentation

Overview

Package state implements per-user watch state and playlists over SQLite: core.State and core.Playlists.

Semantics follow Plex:

  • Progress at >= 90% of duration marks the item watched (view count increments once per viewing, the resume offset clears).
  • Progress below 5% is treated as "not really started": no resume point is kept. Between 5% and 90% the item is in progress.
  • Scrobble marks watched unconditionally; Unscrobble clears the view count and resume point (the user's rating is kept).

Index

Constants

View Source
const (
	WatchedThreshold = 0.90
	StartedThreshold = 0.05
)

WatchedThreshold and StartedThreshold are the fractions of duration at which an item counts as watched / meaningfully started.

Variables

This section is empty.

Functions

func Fraction

func Fraction(ws core.WatchStatus) float64

Fraction returns offset/duration, or 0 when the duration is unknown.

func ImportPlaylistTx

func ImportPlaylistTx(ctx context.Context, tx *sql.Tx, id, userID int64, title string, keys []core.RatingKey, now int64) (int64, error)

ImportPlaylistTx preserves imported playlist IDs while enforcing ownership.

func IsInProgress

func IsInProgress(ws core.WatchStatus) bool

IsInProgress reports whether a status has a resume point.

func IsWatched

func IsWatched(ws core.WatchStatus) bool

IsWatched reports whether a status counts as watched (watched at least once).

Types

type Event

type Event struct {
	Kind      EventKind
	UserID    int64
	Key       core.RatingKey
	Offset    time.Duration
	Duration  time.Duration
	Fraction  float64
	Rating    float64 // EventRated only
	State     string  // raw timeline state for EventProgress: playing, paused, stopped
	Completed bool
	At        time.Time
	Imported  bool // reviewed history import; never an active playback timeline
}

Event is a watch-state change. Fraction is offset/duration (0 when the duration is unknown). Completed is true for EventCompleted/EventScrobble.

type EventKind

type EventKind string

EventKind identifies a state change subscribers may react to.

const (
	EventProgress   EventKind = "progress"   // a timeline update (incl. start/stop)
	EventCompleted  EventKind = "completed"  // crossed the watched threshold
	EventScrobble   EventKind = "scrobble"   // explicitly marked watched
	EventUnscrobble EventKind = "unscrobble" // explicitly marked unwatched
	EventRated      EventKind = "rated"
)

type ExportEntry

type ExportEntry struct {
	Key    core.RatingKey
	Rating float64
	At     time.Time
}

ExportEntry is the state-owned, source-neutral view used by CSV exports.

type ImportBatch

type ImportBatch struct{ Records, History []ImportRecord }

type ImportRecord

type ImportRecord struct {
	UserID           int64
	Key              core.RatingKey
	Views            int
	Offset, Duration time.Duration
	Rating           float64
	ViewedAt         time.Time
	RatingsOnly      bool
}

ImportRecord uses native time units; source-specific parsing belongs to the importer. RatingsOnly preserves existing progress and completion counts.

type Options

type Options struct {
	Logger *slog.Logger
	Now    func() time.Time
	// Availability, when set (a clustered node on a shared PostgreSQL
	// database), makes timeline progress survive a database outage: a
	// Progress the database cannot take is held in memory (the latest per
	// user and title) and written when the database answers again, so
	// players are never blocked or failed by the outage. Nil (standalone)
	// writes progress synchronously as before.
	Availability *store.Availability
}

Options tune a State. Now is injectable for tests.

type Rollup

type Rollup struct {
	Total      int
	Watched    int  // viewCount > 0
	InProgress int  // has a resume point
	AllWatched bool // Total > 0 && Watched == Total
	AnyWatched bool
}

Rollup summarises the watched state of a set of children (the episodes of a season/show) for Plex's viewedLeafCount-style fields.

func Summarize

func Summarize(children []core.RatingKey, statuses map[core.RatingKey]core.WatchStatus) Rollup

Summarize rolls up children using an already-fetched status map. Missing entries count as untouched.

type State

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

State implements core.State and core.Playlists.

func New

func New(db *sql.DB, opts Options) *State

New builds a State over an already-migrated database (see store.Open).

func (*State) Add

func (s *State) Add(ctx context.Context, userID int64, key core.RatingKey, items []core.RatingKey) error

Add appends items to a playlist.

func (*State) ChildrenStatus

func (s *State) ChildrenStatus(ctx context.Context, userID int64, children []core.RatingKey) (Rollup, map[core.RatingKey]core.WatchStatus, error)

ChildrenStatus fetches statuses for children and rolls them up.

func (*State) Close

func (s *State) Close(ctx context.Context) error

Close writes progress still held from a database outage (bounded by ctx). It is a no-op on a standalone server.

func (*State) Create

func (s *State) Create(ctx context.Context, userID int64, title string, items []core.RatingKey) (*core.Playlist, error)

Create makes a playlist with the given items in order.

func (*State) Delete

func (s *State) Delete(ctx context.Context, userID int64, key core.RatingKey) error

Delete removes a playlist. Deleting another user's playlist is ErrNotFound.

func (*State) ExportEntries

func (s *State) ExportEntries(ctx context.Context, userID int64, ratings bool) ([]ExportEntry, error)

func (*State) Get

func (s *State) Get(ctx context.Context, userID int64, key core.RatingKey) (core.WatchStatus, error)

Get returns the status for one key. A never-touched key returns a zero status (with Key set) and no error.

func (*State) GetMany

func (s *State) GetMany(ctx context.Context, userID int64, keys []core.RatingKey) (map[core.RatingKey]core.WatchStatus, error)

GetMany returns a status for every requested key (zero when untouched).

func (*State) GetPlaylist

func (s *State) GetPlaylist(ctx context.Context, userID int64, key core.RatingKey) (*core.Playlist, error)

Get returns one playlist owned by the user, or core.ErrNotFound.

func (*State) History

func (s *State) History(ctx context.Context, userID int64, since time.Time, limit int) ([]core.HistoryEntry, error)

History returns the viewing log, newest first. A zero since means all.

func (*State) ImportTransaction

func (s *State) ImportTransaction(ctx context.Context, prepare func(*sql.Tx) (ImportBatch, error)) error

ImportTransaction commits a reviewed import with the caller's own bookkeeping and list writes. State owns every watch/history write and emits ordinary state events only after the complete transaction succeeds. Repeated history is inert.

func (*State) InProgress

func (s *State) InProgress(ctx context.Context, userID int64, limit int) ([]core.WatchStatus, error)

InProgress returns items with a resume point, most recently viewed first.

func (*State) InProgressAny

func (s *State) InProgressAny(ctx context.Context) ([]core.RatingKey, error)

InProgressAny lists distinct keys in progress for any user.

func (*State) List

func (s *State) List(ctx context.Context, userID int64) ([]core.Playlist, error)

List returns the user's playlists (with items), most recently updated first.

func (*State) Move

func (s *State) Move(ctx context.Context, userID int64, key core.RatingKey, from, to int) error

Move moves the item at from so it lands at index to (both 0-based, to is the final position in the resulting list).

func (*State) OutboxStatus

func (s *State) OutboxStatus() (st store.OutboxStatus, ok bool)

OutboxStatus reports timeline writes held through a database outage; ok is false when progress is written synchronously (standalone).

func (*State) Playlists

func (s *State) Playlists() core.Playlists

Playlists returns s as a core.Playlists.

func (*State) Progress

func (s *State) Progress(ctx context.Context, userID int64, key core.RatingKey, offset, duration time.Duration, state string) error

Progress records a timeline update. state is Plex's timeline state ("playing", "paused", "stopped", "buffering"). See the package doc for the 5%/90% rules. A duration of 0 keeps the previously known duration; with no known duration the offset is kept as a resume point and nothing is ever marked watched by progress alone.

On a clustered node a Progress the database cannot take within a short timeout is held and written later (see Options.Availability); it then returns nil.

func (*State) Rate

func (s *State) Rate(ctx context.Context, userID int64, key core.RatingKey, rating float64) error

Rate sets the user's rating (0-10; 0 clears it).

func (*State) Remove

func (s *State) Remove(ctx context.Context, userID int64, key core.RatingKey, itemIndex int) error

Remove deletes the item at itemIndex (0-based).

func (*State) Scrobble

func (s *State) Scrobble(ctx context.Context, userID int64, key core.RatingKey) error

Scrobble marks an item watched: view count increments, offset clears.

func (*State) ScrobbleMany

func (s *State) ScrobbleMany(ctx context.Context, userID int64, keys []core.RatingKey) error

ScrobbleMany marks several items watched in one transaction — the Plex front end expands a show or season scrobble into its episode keys and calls this. Each key gets a history entry and an EventScrobble.

func (*State) Subscribe

func (s *State) Subscribe(fn func(Event)) func()

Subscribe registers fn for state events (scrobbles, progress, ratings) so Trakt sync, recommendations, prefetch etc. can react. It returns an unsubscribe function. Handlers run synchronously after the change is committed; a panicking handler is recovered and logged.

func (*State) Unscrobble

func (s *State) Unscrobble(ctx context.Context, userID int64, key core.RatingKey) error

Unscrobble marks an item unwatched: view count and offset clear. The user's rating and history log are kept (history is a log, not status).

func (*State) UnscrobbleMany

func (s *State) UnscrobbleMany(ctx context.Context, userID int64, keys []core.RatingKey) error

UnscrobbleMany marks several items unwatched in one transaction (show or season "mark unwatched").

Jump to

Keyboard shortcuts

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