store

package
v0.1.21 Latest Latest
Warning

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

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

Documentation

Overview

Package store provides SQLite-backed state for adaptive search and subtitle state.

The package is split into focused files by concern:

  • store.go — shared helpers (appendPrefixFilter, queryRows, placeholders, deferRollback)
  • store_db.go — DB struct, Open/Close lifecycle
  • store_backoff.go — adaptive search backoff (search_attempts table)
  • store_state.go — subtitle_state (download history), stats, queries
  • store_manual.go — manual download locks
  • store_coverage.go — subtitle_files + scan_state (coverage tracking)
  • store_poll.go — poll_state (Sonarr/Radarr timestamp tracking)
  • store_maint.go — cleanup, reconciliation, config-drift handling
  • auth.go — auth_users / sessions / passkeys / api keys / totp / oidc

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type DB

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

DB wraps the SQLite database.

func Open

func Open(ctx context.Context, path string) (*DB, error)

Open creates or opens the SQLite database and applies the schema. Tables use IF NOT EXISTS so the schema is safe to run on every startup.

func (*DB) BackedOffProviders

func (d *DB) BackedOffProviders(ctx context.Context, mediaType api.MediaType, mediaID, language string, maxAttempts int) ([]api.ProviderID, error)

BackedOffProviders returns the names of providers that should be skipped for a given media+language due to adaptive backoff. A provider is backed off if its next_retry is in the future, or if it has reached maxAttempts. Providers not in the table are never backed off (new providers auto-eligible). maxAttempts must be non-negative; negative values are treated as 0 (disabled).

func (*DB) BackupInto

func (d *DB) BackupInto(ctx context.Context, dest string) error

BackupInto writes a consistent snapshot of the database to dest using SQLite's VACUUM INTO. Unlike a raw file copy, this produces a single standalone, defragmented database file and is safe under WAL mode (it runs inside a read transaction, so it never captures a torn or mid-checkpoint state). dest must not already exist.

func (*DB) BatchUpdateSessionActivity

func (d *DB) BatchUpdateSessionActivity(ctx context.Context, tokenHashes []string, now time.Time) error

func (*DB) CleanupDrift

func (d *DB) CleanupDrift(ctx context.Context, drift api.ConfigDrift) error

CleanupDrift applies DB cleanup for config changes.

func (*DB) CleanupExpiredOIDCStates

func (d *DB) CleanupExpiredOIDCStates(ctx context.Context, now time.Time, maxAge time.Duration) (int64, error)

func (*DB) CleanupExpiredSessions

func (d *DB) CleanupExpiredSessions(ctx context.Context, now time.Time, idleTimeout, absTimeout time.Duration) (int64, error)

func (*DB) ClearManualLock

func (d *DB) ClearManualLock(ctx context.Context, mediaType api.MediaType, mediaID, language string) error

ClearManualLock removes the manual lock for a media+language, allowing automated scans and upgrades to resume.

func (*DB) Close

func (d *DB) Close(ctx context.Context) error

Close closes prepared statements and the database. The context allows the caller to bound shutdown time (e.g. SIGTERM grace period).

func (*DB) ConsumeOIDCState

func (d *DB) ConsumeOIDCState(ctx context.Context, state string) (nonce, codeVerifier, redirectURI string, err error)

func (*DB) CreateAPIKey

func (d *DB) CreateAPIKey(ctx context.Context, key *api.Key) error

func (*DB) CreateOIDCState

func (d *DB) CreateOIDCState(ctx context.Context, state, nonce, codeVerifier, redirectURI string) error

func (*DB) CreatePasskey

func (d *DB) CreatePasskey(ctx context.Context, cred *api.PasskeyCredential) error

func (*DB) CreateSession

func (d *DB) CreateSession(ctx context.Context, sess *api.Session) error

func (*DB) CreateUser

func (d *DB) CreateUser(ctx context.Context, user *api.User) error

func (*DB) CurrentScore

func (d *DB) CurrentScore(ctx context.Context, mediaType api.MediaType, mediaID, language string) (score int, mediaImported time.Time, found bool, err error)

CurrentScore returns the best auto-download score and media import time for a media+language pair. Returns found=false if no auto-download exists.

func (*DB) DeleteAPIKey

func (d *DB) DeleteAPIKey(ctx context.Context, id, userID int64) error

func (*DB) DeletePasskey

func (d *DB) DeletePasskey(ctx context.Context, id, userID int64) error

func (*DB) DeleteSession

func (d *DB) DeleteSession(ctx context.Context, tokenHash string) error

func (*DB) DeleteStateByPaths

func (d *DB) DeleteStateByPaths(ctx context.Context, videoPaths []string) (api.CleanupResult, error)

DeleteStateByPaths finds subtitle_state rows where video_path matches any of the given paths. Deletes those rows and their search_attempts entries. Returns the subtitle file paths from deleted rows so the caller can clean up files from disk as a fallback (the arr usually deletes them already).

func (*DB) DeleteSubtitleFile

func (d *DB) DeleteSubtitleFile(ctx context.Context, mediaType api.MediaType, mediaID, language string, variant api.Variant, source api.SubtitleSource, path string) error

func (*DB) DeleteUser

func (d *DB) DeleteUser(ctx context.Context, id int64) error

func (*DB) DeleteUserSessions

func (d *DB) DeleteUserSessions(ctx context.Context, userID int64, exceptHash string) error

func (*DB) DownloadedRefs

func (d *DB) DownloadedRefs(ctx context.Context, mediaType api.MediaType, mediaID, language string) ([]api.DownloadedRef, error)

DownloadedRefs returns every distinct (release_name, provider) pair from history for this media+language. Empty release names are skipped (legacy rows from providers that did not expose a release name have release_name NULL or "", and an empty string can never match a search result's non-empty ReleaseName anyway).

Used by the manual search popup to mark every previously-saved subtitle, not just the most recent one.

func (*DB) GetAPIKeyByHash

func (d *DB) GetAPIKeyByHash(ctx context.Context, hash string) (*api.Key, error)

func (*DB) GetBackoffByPrefix

func (d *DB) GetBackoffByPrefix(ctx context.Context, mediaType api.MediaType, mediaIDPrefix string) ([]api.BackoffEntry, error)

GetBackoffByPrefix returns backoff entries for media IDs matching a prefix. Used to show next retry times in the coverage UI.

func (*DB) GetBackoffItems

func (d *DB) GetBackoffItems(ctx context.Context) ([]api.BackoffEntry, error)

GetBackoffItems returns all items currently in adaptive search backoff.

func (*DB) GetManualLocks

func (d *DB) GetManualLocks(ctx context.Context) ([]api.ManualLockEntry, error)

GetManualLocks returns all media+language pairs with manual overrides.

func (*DB) GetPasskeyByCredentialID

func (d *DB) GetPasskeyByCredentialID(ctx context.Context, credID []byte) (*api.PasskeyCredential, error)

func (*DB) GetPasskeysByUserID

func (d *DB) GetPasskeysByUserID(ctx context.Context, userID int64) ([]api.PasskeyCredential, error)

func (*DB) GetPollTimestamp

func (d *DB) GetPollTimestamp(ctx context.Context, key api.PollKey) (time.Time, error)

GetPollTimestamp returns the last poll timestamp for a given key (e.g. "sonarr", "radarr"). Returns zero time and nil error if no timestamp has been stored yet.

func (*DB) GetScanStates

func (d *DB) GetScanStates(ctx context.Context, mediaType api.MediaType, mediaIDPrefix string) ([]api.ScanStateRow, error)

func (*DB) GetSessionByHash

func (d *DB) GetSessionByHash(ctx context.Context, tokenHash string) (*api.Session, error)

func (*DB) GetState

func (d *DB) GetState(ctx context.Context, q *api.StateQuery) ([]api.StateEntry, error)

GetState returns subtitle state, most recent first. Accepts optional filters; zero-value fields mean no filter.

func (*DB) GetSubtitleFiles

func (d *DB) GetSubtitleFiles(ctx context.Context, mediaType api.MediaType, mediaIDPrefix string) ([]api.SubtitleFileRow, error)

func (*DB) GetSyncOffset

func (d *DB) GetSyncOffset(ctx context.Context, path string) (int64, error)

GetSyncOffset returns the current offset_ms for a subtitle file. Returns 0 if the path is not found.

func (*DB) GetUserByEmail

func (d *DB) GetUserByEmail(ctx context.Context, email string) (*api.User, error)

func (*DB) GetUserByID

func (d *DB) GetUserByID(ctx context.Context, id int64) (*api.User, error)

func (*DB) GetUserByOIDCSub

func (d *DB) GetUserByOIDCSub(ctx context.Context, issuer, sub string) (*api.User, error)

func (*DB) GetUserByUsername

func (d *DB) GetUserByUsername(ctx context.Context, username string) (*api.User, error)

func (*DB) HistoryMediaIDs

func (d *DB) HistoryMediaIDs(ctx context.Context, mediaType api.MediaType, mediaIDPrefix string) ([]string, error)

HistoryMediaIDs returns distinct media IDs that have download history matching the given type and optional prefix.

func (*DB) IsManuallyLocked

func (d *DB) IsManuallyLocked(ctx context.Context, mediaType api.MediaType, mediaID, language string) (bool, error)

IsManuallyLocked checks if a media+language has any manual override, meaning it should be excluded from all automated actions.

func (*DB) LastScanTime

func (d *DB) LastScanTime(ctx context.Context) (string, error)

func (*DB) ListAPIKeysByUserID

func (d *DB) ListAPIKeysByUserID(ctx context.Context, userID int64) ([]api.Key, error)

func (*DB) ListUsers

func (d *DB) ListUsers(ctx context.Context) ([]api.User, error)

func (*DB) ManualDownloadCount

func (d *DB) ManualDownloadCount(ctx context.Context, mediaType api.MediaType, mediaID, language string) (int, error)

ManualDownloadCount returns how many manual downloads exist for a media+language.

func (*DB) ManualSubtitlePaths

func (d *DB) ManualSubtitlePaths(ctx context.Context, mediaType api.MediaType, mediaID, language string) ([]string, error)

ManualSubtitlePaths returns the subtitle file paths from all manual download rows for a media+language. Used by maybeRevertManualLock to check which manual files still exist on disk.

func (*DB) NextManualNumber

func (d *DB) NextManualNumber(ctx context.Context, mediaType api.MediaType, mediaID, language string) int

NextManualNumber atomically returns the next manual subtitle file number for a media+language. Uses MAX+1 in a single query so concurrent callers cannot get the same number (SQLite serializes writes via WAL).

Supports manual paths in both forms produced by ManualSubtitlePath:

  • movie.fr.N.srt (standard variant)
  • movie.fr.hi.N.srt (HI variant)
  • movie.fr.forced.N.srt (forced variant)

The index N is always the last component before .srt. Using rtrim(...,'0123456789') + substr gives us the suffix starting just before the number, from which we extract the integer. This is portable SQL, avoids fragile INSTR/SUBSTR arithmetic against a language token, and handles the variant forms uniformly.

func (*DB) PasskeyCountForUser

func (d *DB) PasskeyCountForUser(ctx context.Context, userID int64) (int, error)

func (*DB) RecentlyScanned

func (d *DB) RecentlyScanned(ctx context.Context, cutoff time.Time) (map[string]bool, error)

func (*DB) ReconcileState

func (d *DB) ReconcileState(ctx context.Context) (api.ReconcileResult, error)

ReconcileState checks subtitle_state entries against the filesystem and cleans up stale records. Delegates to the reconcile subpackage.

func (*DB) RecordNoResult

func (d *DB) RecordNoResult(ctx context.Context, mediaType api.MediaType, mediaID, language string, providerName api.ProviderID,
	bp api.BackoffParams,
) error

RecordNoResult records a no-result search for a specific provider with exponential backoff. Uses a single upsert with SQLite POWER() to compute next_retry inline, eliminating the round-trip and race window of the former two-query approach.

func (*DB) RecordScanState

func (d *DB) RecordScanState(ctx context.Context, rec *api.ScanRecord) error

func (*DB) RecordSubtitleFiles

func (d *DB) RecordSubtitleFiles(ctx context.Context, mediaType api.MediaType, mediaID string, files []api.SubtitleFile) (bool, error)

func (*DB) RenamePasskey

func (d *DB) RenamePasskey(ctx context.Context, id, userID int64, name string) error

func (*DB) SaveDownload

func (d *DB) SaveDownload(ctx context.Context, rec *api.DownloadRecord) error

SaveDownload records a subtitle download. For auto downloads, updates the existing row if one exists (preserving media_imported), or inserts a new one. For manual downloads, always inserts a new row (acts as the lock). Clears adaptive backoff on success.

func (*DB) SetPollTimestamp

func (d *DB) SetPollTimestamp(ctx context.Context, key api.PollKey, t time.Time) error

SetPollTimestamp stores the last poll timestamp for a given key.

func (*DB) SetSyncOffset

func (d *DB) SetSyncOffset(ctx context.Context, path string, offsetMs int64) error

SetSyncOffset updates the offset_ms for a subtitle file by path.

func (*DB) Stats

func (d *DB) Stats(ctx context.Context) (downloads, attempts int, err error)

Stats returns basic DB statistics for monitoring.

func (*DB) TotalSubtitleFiles

func (d *DB) TotalSubtitleFiles(ctx context.Context) (int, error)

func (*DB) UpdatePasskeyAfterLogin

func (d *DB) UpdatePasskeyAfterLogin(ctx context.Context, credID []byte, signCount uint32, flags api.PasskeyFlags) error

func (*DB) UpdateSessionActivity

func (d *DB) UpdateSessionActivity(ctx context.Context, tokenHash string, now time.Time) error

func (*DB) UpdateUser

func (d *DB) UpdateUser(ctx context.Context, user *api.User) error

func (*DB) UpsertSubtitleFile

func (d *DB) UpsertSubtitleFile(ctx context.Context, mediaType api.MediaType, mediaID string, f *api.SubtitleFile) error

func (*DB) UserCount

func (d *DB) UserCount(ctx context.Context) (int, error)

type MediaKey

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

MediaKey identifies a media+language combination. Used by maintenance routines (store_maint.go) to propagate affected rows across tables.

func NewMediaKey

func NewMediaKey(typ api.MediaType, id, lang string) (MediaKey, error)

NewMediaKey constructs a MediaKey from its components. It returns an error if typ is not a valid MediaType, allowing callers to handle corrupted data gracefully rather than crashing the process.

type StatFunc

type StatFunc func(path string) (os.FileInfo, error)

StatFunc checks file existence. Defaults to os.Stat. Override in tests to avoid filesystem dependency.

Directories

Path Synopsis
Package authdb provides SQLite-backed persistence for authentication data (users, sessions, passkeys, API keys, TOTP, OIDC state).
Package authdb provides SQLite-backed persistence for authentication data (users, sessions, passkeys, API keys, TOTP, OIDC state).
Package coveragedb provides SQLite-backed persistence for subtitle file coverage tracking and scan state.
Package coveragedb provides SQLite-backed persistence for subtitle file coverage tracking and scan state.
Package migrations defines the schema DDL and migration steps for the subflux SQLite database.
Package migrations defines the schema DDL and migration steps for the subflux SQLite database.
Package reconcile extracts the subtitle_state reconciliation subsystem.
Package reconcile extracts the subtitle_state reconciliation subsystem.
Package storetest provides shared contract test suites for api.Store implementations.
Package storetest provides shared contract test suites for api.Store implementations.
Package txutil provides shared transaction helpers for store sub-packages.
Package txutil provides shared transaction helpers for store sub-packages.

Jump to

Keyboard shortcuts

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