Documentation
¶
Overview ¶
Package library persists per-user metadata about every torrent the user has streamed. Stores the magnet URI so we can re-play from disk-cached pieces after restart, and tracks resume position so the player can seek to where the user left off.
Index ¶
- func DismissKey(kind string, tmdbID int) string
- type Entry
- type PrimaryFileLookup
- type Store
- func (s *Store) Close()
- func (s *Store) Delete(id, userID int, includeAll bool) error
- func (s *Store) DeleteAll(userID int, includeAll bool) (int64, error)
- func (s *Store) DeleteAllIncognito() error
- func (s *Store) DeleteIncognito(userID int) error
- func (s *Store) DismissRecommendation(userID int, kind string, tmdbID int) error
- func (s *Store) DismissedRecommendations(userID int) (map[string]bool, error)
- func (s *Store) GetByHash(userID int, infoHash string) (*Entry, error)
- func (s *Store) GetByHashPublic(userID int, infoHash string) (*Entry, error)
- func (s *Store) GetByID(id int, userID int, includeAll bool) (*Entry, error)
- func (s *Store) List(userID int, includeAll bool, limit int) ([]Entry, error)
- func (s *Store) RefreshStalePrimary(lookup PrimaryFileLookup) (int, error)
- func (s *Store) UpdateResume(id, userID int, resumeSeconds, durationSeconds float64, fileIndex int, ...) error
- func (s *Store) Upsert(in UpsertInput) (*Entry, error)
- type UpsertInput
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func DismissKey ¶
DismissKey builds the canonical set key for a dismissed recommendation so the store and the generator agree on the identity (kind + tmdbID).
Types ¶
type Entry ¶
type Entry struct {
ID int `json:"id"`
UserID int `json:"userId"`
InfoHash string `json:"infoHash"`
Magnet string `json:"magnet"`
Name string `json:"name"`
PrimaryFileIndex int `json:"primaryFileIndex"`
// LastFileIndex is the file the user actually last watched (for multi-file
// torrents / season packs). -1 = never tracked → fall back to primary.
LastFileIndex int `json:"lastFileIndex"`
TotalSize int64 `json:"totalSize"`
ResumeSeconds float64 `json:"resumeSeconds"`
DurationSeconds float64 `json:"durationSeconds"`
Kind string `json:"kind"` // "video" | "audio" | ""
LastPlayedAt time.Time `json:"lastPlayedAt"`
AddedAt time.Time `json:"addedAt"`
}
Entry is one row in the library — a torrent the user has touched.
type PrimaryFileLookup ¶
PrimaryFileLookup is the contract a one-shot migration needs from any metadata source: given an info_hash, return the primary file index (or false when no snapshot exists).
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
func New ¶
New wires the library store onto the shared Postgres pool. Schema is applied centrally (internal/db migrations).
func (*Store) Close ¶
func (s *Store) Close()
Close is a no-op: the shared pool's lifecycle is owned by main.
func (*Store) Delete ¶
Delete removes an entry. Returns an error if the entry isn't owned by user (unless admin).
func (*Store) DeleteAll ¶
DeleteAll removes every library entry owned by the user. Admins can pass includeAll=true to wipe the entire history across users (rarely useful — meant mostly for support / DB resets).
func (*Store) DeleteAllIncognito ¶
DeleteAllIncognito removes every incognito-flagged entry across all users. Called once at startup: after a restart the in-memory heartbeat map (which the reaper relies on) is empty, so any incognito row left in the DB is orphaned and would persist forever — defeating the purpose of incognito.
func (*Store) DeleteIncognito ¶
DeleteIncognito removes all incognito-flagged entries for a user. Called when the user ends their incognito session (logout or toggle-off).
func (*Store) DismissRecommendation ¶
DismissRecommendation records that the user wants to never see this recommended title again. Idempotent: re-dismissing the same (kind, tmdb_id) is a no-op (PRIMARY KEY conflict is ignored). Scoped per user. kind is "movie" | "tv".
func (*Store) DismissedRecommendations ¶
DismissedRecommendations returns the user's dismissed recommendations as a set keyed by "kind:tmdbID" (e.g. "movie:603"). Used by the generator to exclude them from both the seed and the result list. Empty map when none / nil store.
func (*Store) GetByHash ¶
GetByHash returns the user's library entry for a given info_hash (if any). Incognito entries are returned regardless — callers need the entry ID for resume updates.
func (*Store) GetByHashPublic ¶
GetByHashPublic is like GetByHash but filters out incognito entries. Used by endpoints that expose data to the user's UI.
func (*Store) List ¶
List returns the user's library, ordered by most recently played first. includeAll lets admin see everyone.
func (*Store) RefreshStalePrimary ¶
func (s *Store) RefreshStalePrimary(lookup PrimaryFileLookup) (int, error)
RefreshStalePrimary scans library rows whose primary_file_index is still 0 (the column default — almost always means "never had a real pickPrimaryFile computed against the latest code") and refreshes them from the supplied lookup. Returns the number of rows updated.
Why this exists: before pickPrimaryFile learned to skip featurettes/extras, every series-pack entry got primary_file_index=0 — a file that's commonly "Featurettes/.../Behind The Scenes.mkv" sorted to the start of the torrent. That stale 0 then leaked through LibraryPage into the player, which happily streamed the wrong file. A one-shot startup migration cures all pre-fix rows without forcing the user to re-add anything.
Only positive lookups (primaryFile > 0) update anything — keeps audio-only torrents where pickPrimaryFile returns -1 untouched, since 0 may be the correct "first track" choice for those.
func (*Store) UpdateResume ¶
func (s *Store) UpdateResume(id, userID int, resumeSeconds, durationSeconds float64, fileIndex int, includeAll bool) error
UpdateResume saves the current playback position. Called periodically by the player. Optionally updates duration if not yet known.
func (*Store) Upsert ¶
func (s *Store) Upsert(in UpsertInput) (*Entry, error)
Upsert inserts a fresh row OR updates an existing (user, info_hash) tuple, refreshing last_played_at to now. Used on every /stream/add call. When incognito=true the row is written with incognito=1 so it is excluded from normal queries and deleted when the user ends their incognito session.