cache

package
v1.5.8 Latest Latest
Warning

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

Go to latest
Published: Jul 14, 2026 License: GPL-2.0, GPL-3.0 Imports: 18 Imported by: 0

Documentation

Overview

Package cache is the on-disk persistence layer for processed-episode deduplication, per-user language profiles, shared-user tokens, and the scheduler's last-run marker.

The persisted JSON schema (field names, types, tags) is an inviolate contract — the on-disk /config/cache.json file is read-forward / write-back across deploys, so any schema change is a migration, not a refactor.

Index

Constants

View Source
const (
	KeyPrefixTimeline  = "timeline:"
	KeyPrefixScheduler = "scheduler:"
	KeyPrefixStreams   = "streams:"
)

Cache-key prefix constants. The exact literal values are part of the on-disk cache.json schema (inviolate contract item 7) — do NOT change them without a migration.

Variables

This section is empty.

Functions

func DecryptToken added in v1.2.0

func DecryptToken(key []byte, value string) (string, error)

DecryptToken reverses EncryptToken. If the value does not carry the "enc:" prefix (legacy plaintext), it is returned unchanged — this enables transparent migration of pre-encryption cache files.

func DeriveKey added in v1.2.0

func DeriveKey(plexToken string) ([]byte, error)

DeriveKey produces a 32-byte AES-256 key from the admin PLEX_TOKEN using HKDF-SHA256 with a fixed salt and info string. The output is deterministic for a given token — no plex.tv round-trip is needed for decryption on restart.

func EncryptToken added in v1.2.0

func EncryptToken(key []byte, plaintext string) (string, error)

EncryptToken encrypts a plaintext token using AES-256-GCM with a random 12-byte nonce. Returns "enc:" + base64url(nonce || ciphertext).

func IsEncrypted added in v1.2.0

func IsEncrypted(value string) bool

IsEncrypted reports whether a stored value carries the encryption prefix, indicating it was produced by EncryptToken.

Types

type Cache

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

Cache is the concurrent-safe persistent cache. The zero value is usable; prefer New for explicit initialization of the backing maps.

func New

func New() *Cache

New returns a Cache with its maps pre-initialized. The zero value also works (maps are lazily created by the mutation methods) — New is the preferred construction point for application code because it documents intent.

func (*Cache) CheckAndMark added in v1.4.0

func (c *Cache) CheckAndMark(key string) bool

CheckAndMark atomically tests and sets the recent-processed window for key: it returns true and records the current time when key was not processed within the last 5 minutes, or false (leaving the existing timestamp intact) when it was. The check and the mark happen under a single lock acquisition so two concurrent callers cannot both observe "not processed" for the same key — unlike a WasRecentlyProcessed-then-MarkProcessed sequence, which has a TOCTOU gap between its two separate lock acquisitions. Inline-prunes old entries when the map grows past 10000 entries.

func (*Cache) LastSchedulerRun

func (c *Cache) LastSchedulerRun() time.Time

LastSchedulerRun returns the timestamp of the last deep-analysis run. A zero time.Time indicates the scheduler has never run (fresh install or cache reset).

func (*Cache) LearnLanguageProfile

func (c *Cache) LearnLanguageProfile(userID, audioLang, subtitleLang string)

LearnLanguageProfile records a user's audio→subtitle language preference. Empty audioLang is treated as "unknown" and ignored — this prevents the profile map from accumulating an empty-key entry for streams whose language is not reported by Plex.

func (*Cache) LoadFrom

func (c *Cache) LoadFrom(path string) error

LoadFrom reads the cache from the given path. A missing file returns nil (fresh start). Capped at maxCacheSize bytes via atomicfile.ReadBounded. Warns if the file has permissive mode bits set, as tokens are stored here.

func (*Cache) MarkProcessed

func (c *Cache) MarkProcessed(key string)

MarkProcessed records the current time against the key. Inline-prunes old entries when the map grows past 10000 entries to keep memory bounded.

func (*Cache) SaveTo

func (c *Cache) SaveTo(path string) error

SaveTo atomically writes the cache to the given path (temp file + rename) and ensures the final file is 0o600 (user tokens live here). The temp file is removed on any failure path so partial writes don't clutter the dir. User tokens are encrypted at the disk boundary if an encryption key is configured; the in-memory Data.UserTokens map stays plaintext.

The marshal runs under c.mu (via encodeForSave) for a consistent snapshot; the disk write runs lock-free so a concurrent MarkProcessed / WasRecentlyProcessed (the listener goroutine) never blocks on the scheduler goroutine's fsync.

func (*Cache) SetEncryptionKey added in v1.2.0

func (c *Cache) SetEncryptionKey(key []byte)

SetEncryptionKey configures the AES-256 key used to encrypt user tokens at the disk boundary (SaveTo/LoadFrom). The key should be derived from the admin PLEX_TOKEN via DeriveKey. When nil, tokens are stored and read as plaintext (backward-compatible with pre-encryption cache files).

func (*Cache) SetLastSchedulerRun

func (c *Cache) SetLastSchedulerRun(t time.Time)

SetLastSchedulerRun records the supplied timestamp as the most recent scheduler run. Stored as a unix int64 on disk (contract item 7 — persisted schema is frozen).

func (*Cache) SetUserTokens

func (c *Cache) SetUserTokens(tokens map[string]string)

SetUserTokens replaces the stored user-token map wholesale. The supplied map is defensive-copied so callers retain exclusive ownership of the original. Passing nil clears the map to an empty non-nil value.

func (*Cache) SubtitleLangForAudio

func (c *Cache) SubtitleLangForAudio(userID, audioLang string) (string, bool)

SubtitleLangForAudio returns the learned subtitle language for a given audio language and user. Returns ("", false) if no profile exists.

func (*Cache) UserTokens

func (c *Cache) UserTokens() map[string]string

UserTokens returns a defensive copy of the userID → accessToken map. Mutating the returned map does not affect cache state — callers must use SetUserTokens to persist changes.

func (*Cache) WasRecentlyProcessed

func (c *Cache) WasRecentlyProcessed(key string) bool

WasRecentlyProcessed reports whether the given key was marked processed within the last 5 minutes. Used as a short-term dedup window for rapid successive webhook / websocket events on the same episode.

type Data

type Data struct {
	// ProcessedEpisodes tracks recently processed episode keys to avoid
	// re-processing the same episode on rapid successive events.
	// Keys carry a subsystem prefix from keys.go:
	// "streams:{userID}:{ratingKey}:{audioID}:{subID}" (play events),
	// "timeline:{itemID}" (library scans), and "scheduler:{ratingKey}"
	// (deep-analysis runs).
	ProcessedEpisodes map[string]int64 `json:"processed_episodes"`
	// LanguageProfiles maps userID → audioLang → subtitleLang.
	// Empty subtitle string means "no subtitles" for that audio language.
	LanguageProfiles map[string]map[string]string `json:"language_profiles"`
	// UserTokens maps userID → accessToken for shared users.
	UserTokens map[string]string `json:"user_tokens"`
	// LastSchedulerRun is the unix timestamp of the last scheduler run.
	LastSchedulerRun int64 `json:"last_scheduler_run"`
}

Data is the JSON schema persisted to /config/cache.json. Field names and JSON tags are frozen — the on-disk file is read-forward across deploys.

Jump to

Keyboard shortcuts

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