mediacache

package
v3.0.0 Latest Latest
Warning

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

Go to latest
Published: Jul 13, 2026 License: AGPL-3.0 Imports: 12 Imported by: 0

Documentation

Overview

Package mediacache implements an opt-in, on-disk, thread-safe LRU cache for raw Deezer CDN stream payloads.

Keys are of the form "trackID.format" (e.g. "12345.MP3_320" or "67890.FLAC"). The stored bytes are exactly the response body bytes fetched from the CDN (stripe-encrypted ciphertext for tracks; plaintext for some podcasts/previews). Decryption (via deezer.NewStripeDecryptor) and all higher-level decode/playback still occur in the normal pipeline; the cache never stores plaintext audio.

Persistence: entries survive process restarts. On New the directory is scanned to rebuild the in-memory index. Recency is approximated by file mtime for the initial ordering after a restart; during a run Get/Put update both mtime (via Chtimes) and an in-memory sequence number for precise LRU.

On-disk filenames: every committed entry gets its OWN unique filename (a hex-encoding of the key plus a monotonic sequence, e.g. "31322e4d50335f333230-2a"). The actual path is stored in the index entry and Get opens exactly that path. Replacing a key never reuses or overwrites the previous file — a new commit renames its temp to a brand-new name and only then swaps the index pointer, leaving the old file to be reclaimed best-effort. This is what makes replace/evict safe on Windows, where an open file cannot be renamed over or deleted (see TestReplaceWhileReaderOpen).

Atomicity: Put returns a WriteCloser that writes to a temp file. The entry only becomes visible to Get (and is added to the LRU) when Close() returns successfully after an atomic rename to a fresh, unique final name. Partial writes, crashes, or Close without full data leave no visible entry (temps are cleaned on New and on failed commits).

Eviction: performed synchronously on successful Put commit. We evict the least-recently-used *complete* entries until under maxBytes. We never evict a key that currently has an in-progress Put (tracked cheaply via an in-memory set). We do not track open Get readers (to avoid refcounting overhead); on Unix an unlinked file remains readable by existing fds until they are closed. On Windows a busy file cannot be deleted, so that victim's eviction (and the best-effort removal of a replaced entry's old file) is skipped and the file is left as an orphan; the next New reclaims it once no reader holds it. This never blocks a commit or a rename.

Concurrency: all public methods are safe for concurrent use. No background goroutines are spawned.

Recovery: New rebuilds the index from index.json (falling back to a filesystem scan if it is missing or corrupt), drops entries whose file is missing or size-mismatched, and reclaims (best-effort deletes) any data file that is not referenced by a live index entry — orphans left by a crashed replace or by a delete that a reader was blocking. An undeletable orphan is left in place and retried on the next New.

Usage from the coordinator (see integration notes at end of file):

  • The cache lives under the normal config dir (e.g. <config.Dir()>/mediacache).
  • It is OFF by default; a config toggle (size>0) creates it.
  • In the source download path the body is obtained from cache when possible or wrapped with TeeReader on miss before being fed to decrypt/plain path.
  • PrepareStream (token dance + CDN URL resolution) can be skipped or short-circuited when a hit is known in advance (the higher layer may keep a tiny side cache of "known cached track+format" metadata).

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Cache

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

Cache is an on-disk LRU cache of raw stream payloads.

func New

func New(dir string, maxBytes int64) (*Cache, error)

New creates or opens the cache in dir. maxBytes is the soft limit (entries are evicted on Put commits to stay at or below it). dir is created if needed. Partial, zero-length, and temp files are cleaned; a corrupt index is ignored.

func (*Cache) Clear

func (c *Cache) Clear() error

Clear removes all entries (best-effort) and resets the cache. It does not remove the directory itself.

func (*Cache) Get

func (c *Cache) Get(key string) (io.ReadCloser, int64, bool)

Get returns a reader for a cached entry (if present), its size, and true. A hit bumps the entry to most-recent (both in-memory seq and file mtime). The caller must Close the returned ReadCloser.

func (*Cache) GetMeta

func (c *Cache) GetMeta(key string) (StreamMeta, bool)

GetMeta returns stored metadata for the key and whether it was present.

func (*Cache) GetMetaForTrack

func (c *Cache) GetMetaForTrack(trackID string) (StreamMeta, bool)

GetMetaForTrack returns metadata for the first key with the prefix "<trackID>." (so callers do not need to know the exact format under which a track was cached). Returns ok=false when absent.

func (*Cache) Put

func (c *Cache) Put(key string) (io.WriteCloser, error)

Put returns a WriteCloser that receives the raw bytes. The bytes are written to a temporary file. Only when Close returns nil is the entry atomically renamed into place, added to the index, and made visible to Get. The caller is responsible for calling Close. If Close is never called (or returns an error) no partial entry is left.

func (*Cache) PutMeta

func (c *Cache) PutMeta(key string, m StreamMeta) error

PutMeta stores (or overwrites) the minimal stream metadata for a cache key. It is persisted in index.json immediately. Safe with or without a body entry for the same key. Callers should store the meta that was current when the body was (or will be) cached.

func (*Cache) Stats

func (c *Cache) Stats() (entries int, bytes int64)

Stats returns the current number of entries and total bytes stored.

func (*Cache) TeeReader

func (c *Cache) TeeReader(key string, src io.Reader) io.Reader

TeeReader returns a reader that consumes src while simultaneously writing a copy through the cache under key. On clean EOF the cache entry is committed. On any error (or non-EOF termination) the partial cache write is discarded. The returned reader does not close src; the caller remains responsible for src's lifecycle (typical for http response bodies).

type StreamMeta

type StreamMeta struct {
	Format     string
	Encrypted  bool
	GainDB     float64
	Preview    bool
	DurationMS int64 // milliseconds; 0 if unknown (playback callers often supply separately)
}

StreamMeta is the minimal subset of StreamPlan fields persisted per cache key so that a cached ciphertext can yield a fully playable plan with no PrepareStream / token / get_url network round-trips.

Jump to

Keyboard shortcuts

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