Help us shape the future of Go! Take the 2026 Go Developer Survey

state

package
v0.2.32 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