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 ¶
- type DB
- func (d *DB) BackedOffProviders(ctx context.Context, mediaType api.MediaType, mediaID, language string, ...) ([]api.ProviderID, error)
- func (d *DB) BackupInto(ctx context.Context, dest string) error
- func (d *DB) BatchUpdateSessionActivity(ctx context.Context, tokenHashes []string, now time.Time) error
- func (d *DB) CleanupDrift(ctx context.Context, drift api.ConfigDrift) error
- func (d *DB) CleanupExpiredOIDCStates(ctx context.Context, now time.Time, maxAge time.Duration) (int64, error)
- func (d *DB) CleanupExpiredSessions(ctx context.Context, now time.Time, idleTimeout, absTimeout time.Duration) (int64, error)
- func (d *DB) ClearManualLock(ctx context.Context, mediaType api.MediaType, mediaID, language string) error
- func (d *DB) Close(ctx context.Context) error
- func (d *DB) ConsumeOIDCState(ctx context.Context, state string) (nonce, codeVerifier, redirectURI string, err error)
- func (d *DB) CreateAPIKey(ctx context.Context, key *api.Key) error
- func (d *DB) CreateOIDCState(ctx context.Context, state, nonce, codeVerifier, redirectURI string) error
- func (d *DB) CreatePasskey(ctx context.Context, cred *api.PasskeyCredential) error
- func (d *DB) CreateSession(ctx context.Context, sess *api.Session) error
- func (d *DB) CreateUser(ctx context.Context, user *api.User) error
- func (d *DB) CurrentScore(ctx context.Context, mediaType api.MediaType, mediaID, language string) (score int, mediaImported time.Time, found bool, err error)
- func (d *DB) DeleteAPIKey(ctx context.Context, id, userID int64) error
- func (d *DB) DeletePasskey(ctx context.Context, id, userID int64) error
- func (d *DB) DeleteSession(ctx context.Context, tokenHash string) error
- func (d *DB) DeleteStateByPaths(ctx context.Context, videoPaths []string) (api.CleanupResult, error)
- func (d *DB) DeleteSubtitleFile(ctx context.Context, mediaType api.MediaType, mediaID, language string, ...) error
- func (d *DB) DeleteUser(ctx context.Context, id int64) error
- func (d *DB) DeleteUserSessions(ctx context.Context, userID int64, exceptHash string) error
- func (d *DB) DownloadedRefs(ctx context.Context, mediaType api.MediaType, mediaID, language string) ([]api.DownloadedRef, error)
- func (d *DB) GetAPIKeyByHash(ctx context.Context, hash string) (*api.Key, error)
- func (d *DB) GetBackoffByPrefix(ctx context.Context, mediaType api.MediaType, mediaIDPrefix string) ([]api.BackoffEntry, error)
- func (d *DB) GetBackoffItems(ctx context.Context) ([]api.BackoffEntry, error)
- func (d *DB) GetManualLocks(ctx context.Context) ([]api.ManualLockEntry, error)
- func (d *DB) GetPasskeyByCredentialID(ctx context.Context, credID []byte) (*api.PasskeyCredential, error)
- func (d *DB) GetPasskeysByUserID(ctx context.Context, userID int64) ([]api.PasskeyCredential, error)
- func (d *DB) GetPollTimestamp(ctx context.Context, key api.PollKey) (time.Time, error)
- func (d *DB) GetScanStates(ctx context.Context, mediaType api.MediaType, mediaIDPrefix string) ([]api.ScanStateRow, error)
- func (d *DB) GetSessionByHash(ctx context.Context, tokenHash string) (*api.Session, error)
- func (d *DB) GetState(ctx context.Context, q *api.StateQuery) ([]api.StateEntry, error)
- func (d *DB) GetSubtitleFiles(ctx context.Context, mediaType api.MediaType, mediaIDPrefix string) ([]api.SubtitleFileRow, error)
- func (d *DB) GetSyncOffset(ctx context.Context, path string) (int64, error)
- func (d *DB) GetUserByEmail(ctx context.Context, email string) (*api.User, error)
- func (d *DB) GetUserByID(ctx context.Context, id int64) (*api.User, error)
- func (d *DB) GetUserByOIDCSub(ctx context.Context, issuer, sub string) (*api.User, error)
- func (d *DB) GetUserByUsername(ctx context.Context, username string) (*api.User, error)
- func (d *DB) HistoryMediaIDs(ctx context.Context, mediaType api.MediaType, mediaIDPrefix string) ([]string, error)
- func (d *DB) IsManuallyLocked(ctx context.Context, mediaType api.MediaType, mediaID, language string) (bool, error)
- func (d *DB) LastScanTime(ctx context.Context) (string, error)
- func (d *DB) ListAPIKeysByUserID(ctx context.Context, userID int64) ([]api.Key, error)
- func (d *DB) ListUsers(ctx context.Context) ([]api.User, error)
- func (d *DB) ManualDownloadCount(ctx context.Context, mediaType api.MediaType, mediaID, language string) (int, error)
- func (d *DB) ManualSubtitlePaths(ctx context.Context, mediaType api.MediaType, mediaID, language string) ([]string, error)
- func (d *DB) NextManualNumber(ctx context.Context, mediaType api.MediaType, mediaID, language string) int
- func (d *DB) PasskeyCountForUser(ctx context.Context, userID int64) (int, error)
- func (d *DB) RecentlyScanned(ctx context.Context, cutoff time.Time) (map[string]bool, error)
- func (d *DB) ReconcileState(ctx context.Context) (api.ReconcileResult, error)
- func (d *DB) RecordNoResult(ctx context.Context, mediaType api.MediaType, mediaID, language string, ...) error
- func (d *DB) RecordScanState(ctx context.Context, rec *api.ScanRecord) error
- func (d *DB) RecordSubtitleFiles(ctx context.Context, mediaType api.MediaType, mediaID string, ...) (bool, error)
- func (d *DB) RenamePasskey(ctx context.Context, id, userID int64, name string) error
- func (d *DB) SaveDownload(ctx context.Context, rec *api.DownloadRecord) error
- func (d *DB) SetPollTimestamp(ctx context.Context, key api.PollKey, t time.Time) error
- func (d *DB) SetSyncOffset(ctx context.Context, path string, offsetMs int64) error
- func (d *DB) Stats(ctx context.Context) (downloads, attempts int, err error)
- func (d *DB) TotalSubtitleFiles(ctx context.Context) (int, error)
- func (d *DB) UpdatePasskeyAfterLogin(ctx context.Context, credID []byte, signCount uint32, flags api.PasskeyFlags) error
- func (d *DB) UpdateSessionActivity(ctx context.Context, tokenHash string, now time.Time) error
- func (d *DB) UpdateUser(ctx context.Context, user *api.User) error
- func (d *DB) UpsertSubtitleFile(ctx context.Context, mediaType api.MediaType, mediaID string, ...) error
- func (d *DB) UserCount(ctx context.Context) (int, error)
- type MediaKey
- type StatFunc
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 ¶
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 ¶
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 (*DB) CleanupDrift ¶
CleanupDrift applies DB cleanup for config changes.
func (*DB) CleanupExpiredOIDCStates ¶
func (*DB) CleanupExpiredSessions ¶
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 ¶
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 (*DB) CreateOIDCState ¶
func (*DB) CreatePasskey ¶
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) 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 (*DB) DeleteUserSessions ¶
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 (*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 ¶
GetBackoffItems returns all items currently in adaptive search backoff.
func (*DB) GetManualLocks ¶
GetManualLocks returns all media+language pairs with manual overrides.
func (*DB) GetPasskeyByCredentialID ¶
func (*DB) GetPasskeysByUserID ¶
func (*DB) GetPollTimestamp ¶
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 (*DB) GetSessionByHash ¶
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 (*DB) GetSyncOffset ¶
GetSyncOffset returns the current offset_ms for a subtitle file. Returns 0 if the path is not found.
func (*DB) GetUserByEmail ¶
func (*DB) GetUserByOIDCSub ¶
func (*DB) GetUserByUsername ¶
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) ListAPIKeysByUserID ¶
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 (*DB) RecentlyScanned ¶
func (*DB) ReconcileState ¶
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 (*DB) RecordSubtitleFiles ¶
func (*DB) RenamePasskey ¶
func (*DB) SaveDownload ¶
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 ¶
SetPollTimestamp stores the last poll timestamp for a given key.
func (*DB) SetSyncOffset ¶
SetSyncOffset updates the offset_ms for a subtitle file by path.
func (*DB) UpdatePasskeyAfterLogin ¶
func (*DB) UpdateSessionActivity ¶
func (*DB) UpsertSubtitleFile ¶
Source Files
¶
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. |