database

package
v2.18.0-beta.2 Latest Latest
Warning

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

Go to latest
Published: Sep 22, 2026 License: GPL-3.0 Imports: 33 Imported by: 0

Documentation

Index

Constants

View Source
const (
	DeckItemKindCard   = "card"
	DeckItemKindScript = "script"

	// DeckIDLength is the length of a deck ID this device mints, in
	// characters of DeckIDAlphabet. DeckIDLegacyLength is the length of IDs
	// allocated before minting moved to devices, which are still valid.
	DeckIDLength       = 12
	DeckIDLegacyLength = 8
	// DeckIDAlphabet is Crockford base32: digits and upper-case letters
	// without I, L, O and U. Core stores and shows IDs lower-case; matching
	// is case-insensitive everywhere.
	DeckIDAlphabet = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
	// DeckIDLegacyAlphabet is the alphabet of legacy IDs, which encoded a row
	// number over every digit and letter. I, L, O and U are distinct
	// characters in them, not misreadings of 1 and 0.
	DeckIDLegacyAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"

	// DeckMaxItems bounds one deck. How many decks a device holds is not
	// bounded: a deck costs well under ten kilobytes, and the background work
	// a deck creates is per item, so this is the limit that matters.
	DeckMaxItems          = 120
	DeckNameMaxLen        = 100
	DeckDescriptionMaxLen = 1000
	DeckZapScriptMaxLen   = 5000
)
View Source
const (
	LibraryIntentNone       = "none"
	LibraryIntentPlayLater  = "play_later"
	LibraryReactionNone     = "none"
	LibraryReactionLiked    = "liked"
	LibraryReactionDisliked = "disliked"
)

Personal state values of the Library sync contract.

View Source
const (
	SynchronousOff    = 0
	SynchronousNormal = 1
	SynchronousFull   = 2
)

SQLite's PRAGMA synchronous integer values, for comparing against EffectivePragmas.Synchronous without a magic number at each call site.

View Source
const (
	TitleCandidateLimit     = 5
	TitleCandidateNameLimit = 256
)
View Source
const CorruptMarkerSuffix = ".corrupt"

CorruptMarkerSuffix names the sidecar file written next to a database to flag detected corruption. It is the DB-independent signal the recovery paths key on: unlike a status row it does not require writing to the (possibly unwritable) database itself.

View Source
const (
	// CurrentMediaIdentityPolicyVersion identifies the immutable role and
	// fingerprint policy emitted by this Core version.
	CurrentMediaIdentityPolicyVersion = 1
)
View Source
const DefaultIntegrityReportRows = 20

DefaultIntegrityReportRows caps PRAGMA integrity_check output so a badly corrupt database cannot flood the log; the first rows identify the damaged pages.

View Source
const DeviceStateKeyActiveProfile = "active_profile_id"

DeviceStateKeyActiveProfile is the DeviceState key holding the ProfileID of the device's active profile.

View Source
const DeviceStateKeyDeckTagsQueue = "deck_tags_queue"

DeviceStateKeyDeckTagsQueue is the DeviceState key holding the decks whose membership tags still have to be brought up to date, as JSON, so the work survives a restart.

View Source
const DeviceStateKeyMediaHistoryIdentitySweep = "media_history_identity_sweep"

DeviceStateKeyMediaHistoryIdentitySweep is the DeviceState key recording the last completed media history identity backfill sweep, as "<policy version>:<media LastGeneratedAt unix>". A matching value means no history row below the current policy version can newly resolve, so sweeps skip the table walk entirely.

View Source
const DeviceStateKeyMediaPreferencesRevision = "media_preferences_revision"

DeviceStateKeyMediaPreferencesRevision invalidates browse cursor totals when durable media preferences change. The counter survives unhide and rebuild.

View Source
const DeviceStateKeyMediaUserDataReconcile = "media_user_data_reconcile"

DeviceStateKeyMediaUserDataReconcile is the DeviceState key marking that the MediaDB projection of media user data still has to be rebuilt from UserDB. It is kept in UserDB so a restore's restart carries it.

View Source
const DeviceStateKeyPlaytimeExtensions = "playtime_extensions"

DeviceStateKeyPlaytimeExtensions is the DeviceState key holding granted playtime extensions: the current session's duration grant and any unexpired per-profile day waivers, as versioned JSON. It stores resolved profile IDs only, never the switch IDs used to authorize a grant.

View Source
const MaxMediaPropertyBinaryBytes = 16 * 1024 * 1024

MaxMediaPropertyBinaryBytes caps decoded binary property payloads hydrated into memory. Larger blobs remain addressable via BlobDBID/BlobSize but Binary is left nil so API handlers can return a controlled error instead of OOMing.

View Source
const UnsetPageSize int64 = 0

UnsetPageSize is the wantPageSize value meaning "this database does not configure page_size, so do not treat whatever it has as a mismatch".

Variables

View Source
var (
	ErrDeckNotFound  = errors.New("deck not found")
	ErrDeckLimit     = errors.New("deck limit reached")
	ErrDeckItemLimit = errors.New("deck item limit reached")
	// ErrDeckItemNotFound and ErrDeckItemRepeated reject an edit that keeps
	// an existing item the deck does not hold, or keeps one twice.
	ErrDeckItemNotFound = errors.New("deck item not found")
	ErrDeckItemRepeated = errors.New("deck item listed more than once")
	ErrDeckOwned        = errors.New("deck is owned by this device")
	ErrDeckReadOnly     = errors.New("deck is read-only")
	ErrInvalidDeckID    = errors.New("invalid deck id")
)
View Source
var (
	ErrMediaWriteConflict               = errors.New("media database write operation conflict")
	ErrMediaWriteLease                  = errors.New("invalid media database write lease")
	ErrMediaWriteCoordinatorUnavailable = errors.New("media database write coordinator unavailable")
)
View Source
var ErrMediaBlobTooLarge = errors.New("media blob too large")

ErrMediaBlobTooLarge indicates a blob exists but exceeds a caller-provided read cap.

View Source
var ErrMediaUserFlagConflict = errors.New("media user flags conflict")

ErrMediaUserFlagConflict reports a user-data row that holds two flags the model forbids together: liked with disliked, or favorite with disliked.

View Source
var ErrSchemaAhead = errors.New("database schema is newer than this binary supports")

ErrSchemaAhead is returned when the database schema version is newer than the binary supports. This happens when switching from a newer binary (e.g. beta) to an older one (e.g. stable).

MediaUserFlags lists every flag in a stable order.

View Source
var TagTypeDisplayPriority = []string{
	"unfinished", "unlicensed", "patch", "region", "video", "disc", "disctotal", "edition",
	"rev", "arcadeboard", "cabinet", "protection", "set", "input", "dump", "alt", "compatibility", "builddate",
	"lang", "distribution", "media", "addon", "release", "year",
	"players", "developer", "publisher", "copyright", "credit",
	"track",
}

TagTypeDisplayPriority orders the eligible disambiguation tag types from most to least important for display. Clients render the emitted disambiguating tags left-to-right and truncate when space runs out, so the most decisive distinctions come first: variant flags (beta/proto/hack) before region, then the specific-variant markers, then extra context. A tag type only appears on an entry when it actually differs across the title's siblings, so a sole differentiator always survives truncation regardless of its rank. Rank is the slice index.

View Source
var ZapScriptTagTypes = TagTypeDisplayPriority

ZapScriptTagTypes is the allowlist of tag types eligible for sibling disambiguation: only these types are considered when deciding whether a title's media differ. It is the same set as TagTypeDisplayPriority (order is irrelevant here, used only for SQL membership), so it aliases the priority list to keep the two in sync. "unknown" is deliberately absent — unclassified tokens never disambiguate.

Functions

func ApplyMediaUserFlags added in v2.18.0

func ApplyMediaUserFlags(
	ctx context.Context,
	db *Database,
	systemID, path string,
	mediaDBID int64,
	changes map[MediaUserFlag]bool,
) (map[MediaUserFlag]bool, error)

ApplyMediaUserFlags records flag changes for one media path in UserDB, the source of truth, then brings the file's user tags in MediaDB in line with the row UserDB holds afterwards. Only the requested flags are written; the UserDB write itself clears a flag the model forbids beside a set one.

The projection compares every flag with the file's current tags and writes only the differences, so a retry after a failed projection still removes a tag the failed attempt left behind. A mediaDBID of 0 skips the projection. It returns the flags whose MediaDB tag changed, with their new value.

func ApplyMediaUserLauncherOverride added in v2.18.0

func ApplyMediaUserLauncherOverride(
	ctx context.Context,
	db *Database,
	systemID, path string,
	mediaDBID int64,
	launcherID string,
) error

ApplyMediaUserLauncherOverride records a launcher override for one media path in UserDB, the source of truth, then writes it to the file's MediaDB property. An empty launcherID clears both. A mediaDBID of 0 skips the projection. It shares a lock with ApplyMediaUserFlags and ReconcileMediaUserData, so a reconcile cannot overwrite an edit with the value it read before the edit.

func BuildTitleZapScript added in v2.10.0

func BuildTitleZapScript(systemID, name string, tags []TagInfo) string

BuildTitleZapScript builds a ZapScript title command string from a system ID, media name, and disambiguating tags. Format: @SystemID/Name (year:YYYY) (type:value) Multiple values of the same type are grouped into one parens as a comma-separated shorthand: (region:eu, region:us). Types are emitted in the order they first appear in the input (callers pass tags pre-sorted by display priority). Only non-empty tags are included; year values must be exactly 4 digits.

func CanonicalScrapePath added in v2.18.0

func CanonicalScrapePath(value string, subtree bool) (string, error)

CanonicalScrapePath accepts indexed URI identities for files, and absolute native filesystem paths otherwise. It never accesses or resolves symlinks.

func CheckSchemaVersion added in v2.11.0

func CheckSchemaVersion(db *sql.DB, migrationFiles embed.FS, migrationDir string) error

CheckSchemaVersion compares the database's migration version against the latest migration embedded in the current binary. Returns ErrSchemaAhead if the database is ahead, preventing the older binary from running against an incompatible schema.

func ClearCorruptMarker added in v2.15.0

func ClearCorruptMarker(dbPath string) error

ClearCorruptMarker removes the corrupt marker sidecar for dbPath. No-op when absent.

func ClearCorruptMarkerFS added in v2.17.0

func ClearCorruptMarkerFS(fs afero.Fs, dbPath string) error

ClearCorruptMarkerFS removes the corrupt marker through fs.

func ComputeMediaIdentityFingerprint added in v2.16.1

func ComputeMediaIdentityFingerprint(identity *MediaIdentity) (string, error)

ComputeMediaIdentityFingerprint returns a deterministic SHA-256 fingerprint for a normalized scanner observation.

func CorruptBackupPath added in v2.17.0

func CorruptBackupPath(dbPath string) string

CorruptBackupPath returns where a database file is renamed aside when it is replaced but worth keeping. Both databases do this, so the mapping lives here rather than being spelled out at each site.

func CorruptMarkerPath added in v2.15.0

func CorruptMarkerPath(dbPath string) string

CorruptMarkerPath returns the sidecar marker path for a database file.

func DecodeTagStrings added in v2.16.0

func DecodeTagStrings(raw string) []string

DecodeTagStrings parses a legacy userdb tags column value. Empty or malformed input decodes to nil because snapshots are best-effort data.

func EncodeDeckCardScripts added in v2.18.0

func EncodeDeckCardScripts(scripts []DeckCardScript) string

EncodeDeckCardScripts serializes a card item's scripts for a UserDB TEXT column. Nil or empty input encodes to the empty string.

func EncodeMediaIdentity added in v2.16.1

func EncodeMediaIdentity(identity *MediaIdentity) string

EncodeMediaIdentity serializes a complete identity snapshot for UserDB.

func EncodeTagStrings added in v2.16.0

func EncodeTagStrings(tags []string) string

EncodeTagStrings serializes legacy flat tags for a userdb TEXT column. Nil or empty input encodes to the empty string.

func GroupTagFiltersByOperator added in v2.7.0

func GroupTagFiltersByOperator(filters []zapscript.TagFilter) (and, not, or []zapscript.TagFilter)

GroupTagFiltersByOperator groups tag filters by operator type for consistent processing. Returns (andFilters, notFilters, orFilters) to enable both SQL generation and in-memory filtering to use the same grouping logic.

func IntegrityReport added in v2.15.0

func IntegrityReport(ctx context.Context, sqlDB *sql.DB, maxRows int) []string

IntegrityReport runs PRAGMA integrity_check(maxRows) against sqlDB and returns the result rows, capped so a badly corrupt database cannot flood the log. A healthy database returns a single "ok" row. Callers hold their own connection lock and nil-check; sqlDB must be non-nil.

func IsCorruptionError added in v2.15.0

func IsCorruptionError(err error) bool

IsCorruptionError reports whether err indicates SQLite database corruption (a malformed disk image or a non-database file). It matches both the typed sqlite3 error codes and the message text, so corruption surfaced through a wrapped string error is still detected.

func IsMarkedCorrupt added in v2.15.0

func IsMarkedCorrupt(dbPath string) bool

IsMarkedCorrupt reports whether the corrupt marker sidecar exists for dbPath.

func IsMintedDeckID added in v2.18.0

func IsMintedDeckID(deckID string) bool

IsMintedDeckID reports whether an ID is one a device minted, rather than a legacy ID issued before minting moved to devices. Only a minted ID can be used to create a deck on an account, so a legacy one is never offered as a create. The ID must already be normalized.

func IsOptimizationCanceled added in v2.18.0

func IsOptimizationCanceled(err error) bool

IsOptimizationCanceled reports cancellation without a concurrent operation or cleanup failure. Joined failures must remain reportable and must not be mistaken for an ordinary interrupted optimization.

func LogEffectivePragmas added in v2.17.0

func LogEffectivePragmas(event *zerolog.Event, p EffectivePragmas) *zerolog.Event

LogEffectivePragmas attaches p's fields to event for a single reportable log line proving what SQLite actually applied, as opposed to what the DSN asked for.

func LogEffectivePragmasForDB added in v2.17.0

func LogEffectivePragmasForDB(
	ctx context.Context, sqlDB *sql.DB, dbLabel string, wantSynchronous, wantPageSize int64,
)

LogEffectivePragmasForDB pins a connection from sqlDB, reads back the pragmas SQLite actually applied, and logs one line labelled dbLabel. It warns instead of the usual info when journal_mode is not "wal" or the effective synchronous/page_size do not match what the DSN asked for — a DSN pragma can silently fail to take (e.g. a filesystem that falls back to a rollback journal instead of WAL), and nothing upstream reported that before this. Best-effort: a failure to acquire a connection or read the pragmas back is logged and does not fail the open.

func MarkCorrupt added in v2.15.0

func MarkCorrupt(dbPath, reason string, now time.Time)

MarkCorrupt writes the corrupt marker sidecar next to dbPath recording that corruption was detected. Best-effort — failures are logged, not returned.

func MediaIdentityFingerprintPayload added in v2.16.1

func MediaIdentityFingerprintPayload(identity *MediaIdentity) ([]byte, error)

MediaIdentityFingerprintPayload returns the canonical cross-service bytes hashed for an observation fingerprint. Display name and raw path are intentionally absent.

func MediaKey added in v2.9.1

func MediaKey(systemID, path string) string

MediaKey builds a composite key for media deduplication: "systemID:path"

func MigrateDownTo added in v2.18.0

func MigrateDownTo(db *sql.DB, migrationFiles embed.FS, migrationDir string, version int64) error

MigrateDownTo rolls a database back to the given schema version, running every later migration's Down step in reverse order. It holds the same lock as MigrateUp because goose keeps its filesystem and dialect in global state. It exists for tests that exercise an upgrade from an older schema; a bare single-step Down would revert whichever migration happens to be newest and stop testing the one intended once another migration lands.

func MigrateUp added in v2.7.0

func MigrateUp(
	db *sql.DB,
	migrationFiles embed.FS,
	migrationDir, dbPath, sidecarPath string,
) error

MigrateUp provides thread-safe database migration using goose. It locks access to goose's global state to prevent race conditions between multiple databases setting their migration filesystems.

dbPath and sidecarPath enable a fast path that skips goose's metadata queries (which on a cold SQLite file can cost hundreds of milliseconds) when a previously-written sidecar reports the live DB file is already at the latest embedded migration version. Pass both as empty strings to disable the sidecar fast path (e.g. in tests against in-memory DBs).

func NewDeckID added in v2.18.0

func NewDeckID() (string, error)

NewDeckID mints a deck ID from the operating system's random source: DeckIDLength characters of DeckIDAlphabet, lower-cased. One byte per character modulo 32 is unbiased because 256 is a multiple of 32.

func NormalizeDeckID added in v2.18.0

func NormalizeDeckID(raw string) (string, error)

NormalizeDeckID trims and lower-cases a deck ID and checks it is a minted ID drawn from DeckIDAlphabet or a legacy ID drawn from DeckIDLegacyAlphabet. Characters an alphabet omits are refused rather than folded, so the stored ID is always the one that was issued.

func NoteCorruption added in v2.15.0

func NoteCorruption(dbPath string, err error, now time.Time) bool

NoteCorruption flags dbPath corrupt when err indicates SQLite corruption, so any path that first touches a malformed page routes into the recovery flow instead of silently failing. It only writes the marker once. Returns true when err was a corruption error.

func PreserveCorruptFile added in v2.17.0

func PreserveCorruptFile(path, dbLabel string)

PreserveCorruptFile renames path aside to its CorruptBackupPath, so a file about to be replaced or deleted survives as a forensic copy. A missing source file is a silent no-op (nothing to preserve); any other stat or rename failure is logged, not returned — recovery must proceed even when the forensic copy could not be made. dbLabel names the database in the log line (e.g. "media", "user").

func ReconcileMediaUserData added in v2.18.0

func ReconcileMediaUserData(ctx context.Context, db *Database) error

ReconcileMediaUserData makes the MediaDB projection of media user data, the five user flag tags and the launcher override property, match UserDB exactly: missing ones are added and ones UserDB no longer holds are removed. It is for when UserDB was replaced as a whole, as by a backup restore, where the projection still describes the database that was replaced. Live edits and the post-index re-apply only ever need to add.

Only the five flag tags and the override property are touched; deck tags and metadata tags stay as they are. A projection already in line writes nothing. Rows whose system or path is not indexed are skipped.

func RemoveSidecars added in v2.15.0

func RemoveSidecars(dbPath string) error

RemoveSidecars deletes the -wal and -shm sidecar files for dbPath. A stale WAL left next to a freshly restored or recreated database would re-corrupt it.

func RemoveSidecarsFS added in v2.17.0

func RemoveSidecarsFS(fs afero.Fs, dbPath string) error

RemoveSidecarsFS deletes database sidecars through fs.

func SetMigrationReporter added in v2.18.0

func SetMigrationReporter(fn MigrationReporter) (restore func())

SetMigrationReporter installs fn for the duration of a startup and returns a function that removes it again. It exists so startup can say that a database is being upgraded while that is actually happening, without threading a parameter through the two databases' MigrateUp interface methods and every mock that implements them.

The returned restore function must be called — a reporter that outlives the startup it belongs to would report into a server that has moved on.

func TagKey added in v2.9.1

func TagKey(tagType, tagValue string) string

TagKey builds a composite key for tag deduplication: "type:value"

func TagTypeDisplayRank added in v2.15.0

func TagTypeDisplayRank(tagType string) int

TagTypeDisplayRank returns the display-importance rank of a tag type (lower is more important). Unknown types sort last. Used to order emitted disambiguating tags.

func TitleKey added in v2.9.1

func TitleKey(systemID, slug string) string

TitleKey builds a composite key for title deduplication: "systemID:slug"

func ValidTitleCandidateName added in v2.18.0

func ValidTitleCandidateName(name string) bool

ValidTitleCandidateName bounds work before normalization, checking bytes before rune counting. UTF-8 has at most four bytes per Unicode code point.

func WaitForLongMediaWrites added in v2.18.0

func WaitForLongMediaWrites(ctx context.Context, mediaDB MediaDBI, poll time.Duration) bool

WaitForLongMediaWrites blocks while an index, optimization, recovery or maintenance job owns the media database, checking again every poll, and reports false if ctx ended first. Those jobs hold long write transactions that a small write would time out behind. Scraping is not waited for: it can run for hours and commits in short transactions.

Types

type BackupInfo added in v2.15.0

type BackupInfo struct {
	CreatedAt  time.Time `json:"createdAt"`
	Name       string    `json:"name"`
	Path       string    `json:"path"`
	QuickCheck string    `json:"quickCheck"`
	Reason     string    `json:"reason,omitempty"`
	Size       int64     `json:"size"`
	Valid      bool      `json:"valid"`
	Manual     bool      `json:"manual"`
}

type BrowseCursor added in v2.10.0

type BrowseCursor struct {
	SortValue string
	SortMode  string
	Phase     string
	DirName   string
	RootView  string
	// Sources is the merged system root's resolved routes, carried forward from
	// the page that discovered them so later pages do not rediscover the scope.
	// Empty for an ordinary path browse, whose scope is the path itself.
	Sources    []BrowseSource
	LastID     int64
	TotalFiles int
	TotalDirs  int
}

BrowseCursor holds the keyset pagination state for browse queries.

media.browse pages directories first (ordered by Name), then files, under a single cursor. Phase selects which stream the cursor resumes: "dirs" uses DirName as the keyset, "files" (or empty, for legacy file-only cursors) uses SortValue/SortMode/LastID. TotalFiles and TotalDirs carry the first-page counts so cursor pages do not rerun the count queries.

type BrowseDirCountOptions added in v2.15.1

type BrowseDirCountOptions struct {
	PathPrefix    string
	Overlay       *BrowseOverlay
	Systems       []systemdefs.System
	ExcludeHidden bool
}

BrowseDirCountOptions contains parameters for the BrowseDirCount query.

type BrowseDirectoriesOptions added in v2.12.0

type BrowseDirectoriesOptions struct {
	PathPrefix    string
	Overlay       *BrowseOverlay
	AfterName     string
	Systems       []systemdefs.System
	Limit         int
	ExcludeHidden bool
}

BrowseDirectoriesOptions contains parameters for the BrowseDirectories query. AfterName is the keyset cursor for directory pagination: only directories whose Name sorts strictly after it are returned (directory names are unique within a parent, so Name alone is a stable keyset). Limit caps the number of directories returned; 0 means no limit (full listing).

type BrowseDirectoryResult added in v2.10.0

type BrowseDirectoryResult struct {
	Name      string
	Path      string
	SystemIDs []string
	FileCount int
	HasCover  bool
}

BrowseDirectoryResult represents a subdirectory found during browse navigation.

type BrowseFileCountOptions added in v2.12.0

type BrowseFileCountOptions struct {
	Letter        *string
	PathPrefix    string
	Overlay       *BrowseOverlay
	Systems       []systemdefs.System
	Tags          []zapscript.TagFilter
	ExcludeHidden bool
}

BrowseFileCountOptions contains parameters for the BrowseFileCount query.

type BrowseFilesOptions added in v2.10.0

type BrowseFilesOptions struct {
	Cursor        *BrowseCursor
	Letter        *string
	PathPrefix    string
	Overlay       *BrowseOverlay
	Sort          string
	Systems       []systemdefs.System
	Tags          []zapscript.TagFilter
	Limit         int
	ExcludeHidden bool
}

BrowseFilesOptions contains parameters for the BrowseFiles query.

type BrowseIndexBucket added in v2.15.0

type BrowseIndexBucket struct {
	Key       string
	SortValue string
	LastID    int64
	Count     int
	Offset    int
	AtStart   bool
}

BrowseIndexBucket is one first-character bucket of a browse scope. SortValue and LastID are the keyset of the row immediately before the bucket's first row, so a media.browse cursor built from them lands a page on the bucket's first item. Offset is the bucket's 0-based position among the scope's files (its row number in the ordered query, so it can't drift from the browse order); it excludes leading directories, which the caller adds. AtStart is true for the bucket that begins the list (no preceding row), in which case the caller should produce an empty cursor.

type BrowseIndexOptions added in v2.15.0

type BrowseIndexOptions struct {
	PathPrefix    string
	Overlay       *BrowseOverlay
	Sort          string
	Systems       []systemdefs.System
	Tags          []zapscript.TagFilter
	ExcludeHidden bool
}

BrowseIndexOptions contains parameters for the BrowseIndex facet query. It mirrors the scoping fields of BrowseFilesOptions so the index describes the exact list a media.browse call would return.

type BrowseIndexResult added in v2.15.0

type BrowseIndexResult struct {
	Scheme     string
	SortMode   string
	Buckets    []BrowseIndexBucket
	TotalFiles int
}

BrowseIndexResult is the ordered set of first-character buckets for a browse scope. Buckets are ordered to match the active sort. Scheme reports the collation used to derive the buckets ("latin"); it is "none" when the directory's effective sort is not alphabetical, in which case Buckets is empty and no rail applies. SortMode is the resolved browse sort mode the buckets were computed under and must be embedded into the seek cursors so the subsequent media.browse page continues in the same order.

type BrowseOverlay added in v2.17.0

type BrowseOverlay struct {
	Sources []BrowseSource
}

type BrowseRouteCount added in v2.12.0

type BrowseRouteCount struct {
	Path         string
	SystemIDs    []string
	FileCount    int
	CountUnknown bool
}

BrowseRouteCount represents a populated browse route and its media count. CountUnknown is set when the route is known to contain media but the exact FileCount could not be computed within the deadline (degraded fallback); callers should treat such routes as present with an unknown count rather than as empty.

type BrowseRouteCountsOptions added in v2.12.0

type BrowseRouteCountsOptions struct {
	Systems       []systemdefs.System
	Routes        []string
	ExcludeHidden bool
}

BrowseRouteCountsOptions contains candidate route paths to resolve against indexed media for system-scoped browse root discovery.

type BrowseSource added in v2.17.0

type BrowseSource struct {
	PathPrefix  string
	IncludeDirs bool
}

BrowseSource is one ordered physical directory contributing to a pathless system-root contents view. Earlier sources have higher overlay priority. IncludeDirs is false for an ancestor route retained only for direct media.

type BrowseSystemRootCandidates added in v2.12.0

type BrowseSystemRootCandidates struct {
	Children map[string][]string
	HasMedia map[string]bool
}

BrowseSystemRootCandidates is the cache-backed result of resolving a list of filesystem roots against system-scoped browse data. Children holds the immediate subdirectory names of each root that contain media for the requested systems; HasMedia is true when a root has any media in its subtree (even purely via descendants).

type BrowseSystemRootCandidatesOptions added in v2.12.0

type BrowseSystemRootCandidatesOptions struct {
	Roots         []string
	Systems       []systemdefs.System
	ExcludeHidden bool
}

BrowseSystemRootCandidatesOptions parameterises the batched lookup used to build `media.browse({systems:[...], path:""})` candidates in two queries against the BrowseDirCounts cache.

type BrowseVirtualScheme added in v2.10.0

type BrowseVirtualScheme struct {
	Scheme    string
	SystemIDs []string
	FileCount int
}

BrowseVirtualScheme represents a virtual URI scheme with indexed content.

type BrowseVirtualSchemesOptions added in v2.12.0

type BrowseVirtualSchemesOptions struct {
	Systems       []systemdefs.System
	ExcludeHidden bool
}

BrowseVirtualSchemesOptions contains parameters for BrowseVirtualSchemes.

type Client added in v2.11.0

type Client struct {
	ClientID   string `json:"clientId"`
	ClientName string `json:"clientName"`
	AuthToken  string `json:"-"`
	// Role is the client's permission role ("admin" or "member"), chosen
	// at pairing approval. See pkg/api/permissions.
	Role       string `json:"role"`
	PairingKey []byte `json:"-"`
	DBID       int64  `json:"-"`
	CreatedAt  int64  `json:"createdAt"`
	LastSeenAt int64  `json:"lastSeenAt"`
}

Client represents a paired API client. AuthToken and PairingKey are hidden from JSON (API uses models.PairedClient instead).

type Conn added in v2.15.0

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

Conn holds a *sql.DB handle that may be swapped at runtime — corruption recovery and backup restore close the old connection and open a new one while other goroutines are still using the database. The swap (Store) and every read (Load) go through an atomic pointer, so reassigning the handle is race-free without serializing queries: SQLite's WAL mode and busy_timeout already handle query concurrency. A reader that loads the handle mid-swap sees either the old connection (returning a clean "database is closed" error) or the new one, never a torn pointer. Both UserDB and MediaDB embed Conn so they manage their connection handle identically.

Invariant: production code never clears the handle back to nil. A runtime swap always goes Open -> Store(new), so once a database has been opened Load() stays non-nil. Several call sites rely on this by reading Load() more than once per method without re-checking: because the handle is never swapped to nil, a guard check (Load() == nil) followed by a later Load() for the query cannot observe nil in between. Only tests call Store(nil). A future change that clears the handle in production would reintroduce that nil-dereference race across those call sites.

func (*Conn) Load added in v2.15.0

func (c *Conn) Load() *sql.DB

Load returns the current handle, or nil when none is set.

func (*Conn) Store added in v2.15.0

func (c *Conn) Store(db *sql.DB)

Store replaces the current handle. Production code only ever stores a freshly opened, non-nil handle; passing nil to clear it is reserved for tests (see the Conn invariant above).

type Database

type Database struct {
	UserDB  UserDBI
	MediaDB MediaDBI
	// DeckTags brings deck membership tags up to date in the background. It
	// is nil where nothing runs that work, such as in tools and most tests.
	DeckTags DeckTagQueue
	// MediaUserData rebuilds the MediaDB projection of media user data in the
	// background. It is nil wherever DeckTags is.
	MediaUserData MediaUserDataReconciler
}

Database is a portable interface for ENV bindings

func (*Database) QueueAllDeckTags added in v2.18.0

func (db *Database) QueueAllDeckTags()

QueueAllDeckTags schedules every deck's tags when a queue is attached.

func (*Database) QueueDeckTags added in v2.18.0

func (db *Database) QueueDeckTags(deckIDs ...string)

QueueDeckTags schedules the given decks' tags when a queue is attached.

func (*Database) QueueMediaUserDataReconcile added in v2.18.0

func (db *Database) QueueMediaUserDataReconcile()

QueueMediaUserDataReconcile schedules the reconcile when one is attached.

type Deck added in v2.18.0

type Deck struct {
	DeckID      string
	Name        string
	Description string
	SourceURL   string
	// PlaylistID is the playlist ID the deck's source served it under, kept
	// verbatim so the local copy and the served playlist are one playlist.
	// Empty for a deck that was not fetched from a link.
	PlaylistID string
	Metadata   json.RawMessage
	Items      []DeckItem
	DBID       int64
	CreatedAt  int64
	UpdatedAt  int64
	FetchedAt  int64
	ItemCount  int
	Owned      bool
}

Deck is a user's list. Items is populated by GetDeck and left nil by ListDecks, which fills ItemCount instead.

type DeckCardScript added in v2.18.0

type DeckCardScript struct {
	Name      string `json:"name,omitempty"`
	ZapScript string `json:"zapscript"`
}

DeckCardScript is one script a card item runs.

func DecodeDeckCardScripts added in v2.18.0

func DecodeDeckCardScripts(raw string) []DeckCardScript

DecodeDeckCardScripts parses a stored scripts column. Empty or malformed input decodes to nil.

type DeckItem added in v2.18.0

type DeckItem struct {
	Kind      string
	Name      string
	ZapScript string
	CardID    string
	Anchor    DeckItemAnchor
	Scripts   []DeckCardScript
	Metadata  json.RawMessage
	DBID      int64
	DeckDBID  int64
	CreatedAt int64
	UpdatedAt int64
	Position  int
}

DeckItem is one member of a deck.

func (*DeckItem) HasAnchor added in v2.18.0

func (i *DeckItem) HasAnchor() bool

HasAnchor reports whether the item is linked to a local file.

type DeckItemAnchor added in v2.18.0

type DeckItemAnchor struct {
	SystemID  string
	Path      string
	MediaName string
	Tags      []string
}

DeckItemAnchor is the per-device link from a game item to the indexed file it was added from, with the scanner identity snapshot that lets the row be re-linked after the media database is rebuilt.

type DeckSyncRow added in v2.18.0

type DeckSyncRow struct {
	DeckID       string
	Snapshot     string
	RejectedCode string
	RejectedHash string
	Revision     int64
	UpdatedAt    int64
	Conflicts    int
	Locked       bool
}

DeckSyncRow is the last copy of an owned deck this device agreed with the account. Snapshot is the agreed deck content as JSON; Revision 0 means the deck was never created on the account.

type DeckTagQueue added in v2.18.0

type DeckTagQueue interface {
	// QueueDeckTags schedules the tags of the given decks, including decks
	// that were just deleted.
	QueueDeckTags(deckIDs ...string)
	// QueueAllDeckTags schedules every deck's tags, and the removal of tags
	// belonging to decks that no longer exist, as after a reindex.
	QueueAllDeckTags()
}

DeckTagQueue brings the deck membership tags in MediaDB in line with the decks in UserDB. Queueing returns at once; the work happens later.

type DirectoryProperty added in v2.18.0

type DirectoryProperty struct {
	Path    string
	TypeTag string
	Text    string
}

DirectoryProperty is one file-backed property attached to a stable (SystemDBID, Path) directory identity in MediaDB.

type EffectivePragmas added in v2.17.0

type EffectivePragmas struct {
	JournalMode       string
	Synchronous       int64
	PageSize          int64
	WALAutoCheckpoint int64
	TempStore         int64
	CacheSize         int64
	BusyTimeout       int64
	ForeignKeys       int64
}

EffectivePragmas is what SQLite actually applied for a connection, read back after open rather than trusted from the DSN. A DSN pragma can silently fail to take (e.g. a filesystem that does not support WAL falls back to a rollback journal), and nothing upstream of this reports that today.

func ReadEffectivePragmas added in v2.17.0

func ReadEffectivePragmas(ctx context.Context, conn *sql.Conn) (EffectivePragmas, error)

ReadEffectivePragmas queries the pragmas SQLite actually has set on conn. Pragmas are per-connection state, so the caller must pass a connection pinned via sqlDB.Conn (or the transaction's connection) rather than the pool — querying through *sql.DB can silently land on a different physical connection than the one being verified.

type FetchedDeckResult added in v2.18.0

type FetchedDeckResult struct {
	// DeckID is the ID the copy is kept under on this device.
	DeckID string
	// Evicted lists the fetched decks removed to make room.
	Evicted []string
	// Changed reports whether the stored deck changed.
	Changed bool
}

FetchedDeckResult reports what keeping a deck fetched from a link did.

type FileInfo

type FileInfo struct {
	SystemID string
	Path     string
	Name     string
}

type GenericDBI

type GenericDBI interface {
	Open() error
	UnsafeGetSQLDb() *sql.DB
	Truncate() error
	Allocate() error
	MigrateUp() error
	Vacuum() error
	Close() error
	GetDBPath() string
}

type HistoryEntry

type HistoryEntry struct {
	Time           time.Time  `json:"time"`
	CreatedAt      time.Time  `json:"createdAt,omitempty"`
	SyncedAt       *time.Time `json:"syncedAt,omitempty"`
	DeviceID       *string    `json:"deviceId,omitempty"`
	BootUUID       string     `json:"bootUuid,omitempty"`
	ID             string     `json:"uuid,omitempty"`
	TokenData      string     `json:"tokenData"`
	TokenValue     string     `json:"tokenValue"`
	TokenID        string     `json:"tokenId"`
	Type           string     `json:"type"`
	DBID           int64      `db:"DBID" json:"id"`
	MonotonicStart int64      `json:"monotonicStart,omitempty"`
	Success        bool       `json:"success"`
	ClockReliable  bool       `json:"clockReliable"`
	IsDeleted      bool       `json:"isDeleted,omitempty"`
}

type InboxMessage added in v2.8.0

type InboxMessage struct {
	CreatedAt time.Time `json:"createdAt"`
	Title     string    `json:"title"`
	Body      string    `json:"body,omitempty"`
	Category  string    `json:"category,omitempty"`
	DBID      int64     `json:"id"`
	Severity  int       `json:"severity"`
	ProfileID int64     `json:"profileId"`
}

type JournalMode added in v2.7.0

type JournalMode string
const (
	JournalModeWAL    JournalMode = "WAL"
	JournalModeDELETE JournalMode = "DELETE"
)

Journal mode constants

type LibraryInventoryState added in v2.18.0

type LibraryInventoryState struct {
	CommittedAt time.Time `json:"committedAt"`
	ConfirmedAt time.Time `json:"confirmedAt"`
	// Endpoint is the Library sync base URL the ordinal cache and the
	// committed inventory belong to.
	Endpoint string `json:"endpoint"`
	// Credential tags the device link the inventory was committed under.
	Credential string `json:"credential"`
	SHA256     string `json:"sha256"`
	// TooLarge is set when the inventory for Generation exceeded what the
	// account accepts, so it is not built again until the index changes.
	TooLarge   bool  `json:"tooLarge,omitempty"`
	Generation int64 `json:"generation"`
	ItemCount  int   `json:"itemCount"`
}

LibraryInventoryState records the inventory this device last committed, so a pass can skip rebuilding a bitmap for an index generation it already uploaded.

type LibraryMediaRow added in v2.18.0

type LibraryMediaRow struct {
	SystemID  string
	Name      string
	Slug      string
	MediaDBID int64
}

LibraryMediaRow is one present media row with the fields its identity observation is built from. Tags are loaded separately so rows outside the synced media types never pay for them.

type LibraryOrdinal added in v2.18.0

type LibraryOrdinal struct {
	ResolvedAt     time.Time
	Fingerprint    string
	Code           string
	SeenGeneration int64
	Ordinal        uint32
}

LibraryOrdinal is the cached answer to resolving one identity fingerprint with Zaparoo Online: the inventory ordinal, or a rejection code with Ordinal 0. SeenGeneration is the last index generation whose inventory walk met the fingerprint, so answers for files long gone can be pruned.

type LibraryStateSyncRow added in v2.18.0

type LibraryStateSyncRow struct {
	IdentityKey   string
	MediaType     string
	SystemID      string
	CoreSlug      string
	Title         string
	Intent        string
	Reaction      string
	RejectedCode  string
	RejectedHash  string
	VariantTags   []string
	PreferredTags []string
	// SentPreferredTags is the version this device last named from its
	// starred copy, so a star that moves is told apart from a version the
	// account chose.
	SentPreferredTags []string
	Revision          int64
	UpdatedAt         int64
	Favorite          bool
	Deleted           bool
	Unmatched         bool
}

LibraryStateSyncRow is the last personal state row this device agreed with the account for one game, or the row it holds no copy of yet.

type Mapping

type Mapping struct {
	Label    string `json:"label"`
	Type     string `json:"type"`
	Match    string `json:"match"`
	Pattern  string `json:"pattern"`
	Override string `json:"override"`
	DBID     int64
	Added    int64 `json:"added"`
	Enabled  bool  `json:"enabled"`
}

type Media

type Media struct {
	Path           string
	ParentDir      string
	SortName       string // write-once copy of MediaTitles.Name; titles never update so no propagation is needed
	DBID           int64
	MediaTitleDBID int64
	SystemDBID     int64
	IsMissing      bool
}

type MediaBlob added in v2.12.0

type MediaBlob struct {
	Hash        string
	ContentType string
	Data        []byte
	DBID        int64
}

MediaBlob is a row from the MediaBlobs content-addressed store. Data is identified by the hex-encoded SHA-256 of its framed content type and bytes.

type MediaDBI

type MediaDBI interface {
	GenericDBI
	BeginTransaction(batchEnabled bool) error
	CommitTransaction() error
	CommitTransactionWithOptions(options TransactionOptions) error
	FlushBatchInserters() error
	RollbackTransaction() error
	Exists() bool
	UpdateLastGenerated() error
	GetLastGenerated() (time.Time, error)

	SetOptimizationStatus(status string) error
	GetOptimizationStatus() (string, error)
	SetOptimizationStep(step string) error
	GetOptimizationStep() (string, error)
	IsOptimizing() bool
	BeginBrowseCacheRebuild()
	EndBrowseCacheRebuild()
	RunBackgroundOptimization(statusCallback func(optimizing bool), pauser *syncutil.Pauser)
	WaitForBackgroundOperations()
	BeginRecovery()
	EndRecovery()
	TrackBackgroundOperation()
	HasBackgroundOperations() bool
	SetIndexingConnBoost(active bool)
	BackgroundOperationDone()

	InvalidateCountCache() error
	RebuildSlugSearchCache() error
	RebuildTagCache() error
	WALCheckpoint() error
	QuickCheck() (bool, error)
	IntegrityReport() []string
	MarkCorrupt(reason string)
	IsMarkedCorrupt() bool
	ClearCorruptMarker() error
	NoteCorruption(err error) bool
	Recreate(keepBackup bool) error

	// On-disk persistence for rebuilt caches. Persist* writes the current
	// in-memory cache atomically; LoadCached* reads it back at startup and
	// returns (false, nil) when a SQL rebuild is still required. A valid
	// selective slug cache may be installed while returning false so covered
	// systems remain searchable during the complete rebuild.
	PersistTagCache() error
	LoadCachedTagCache() (bool, error)
	PersistSlugSearchCache() error
	LoadCachedSlugSearchCache() (bool, error)

	// IndexGeneration is bumped at the end of every successful indexing
	// run. Persisted cache files embed the value they were built against
	// so a stale cache from a previous run is rejected on next load.
	IndexGeneration() (int64, error)
	BumpIndexGeneration() (int64, error)

	// Library sync: a paged walk of present media, the cache of resolve
	// answers keyed by identity fingerprint, and the record of the last
	// committed inventory. All of it is disposable with this database.
	LibraryMediaPage(ctx context.Context, afterMediaDBID int64, limit int) ([]LibraryMediaRow, error)
	GetLibraryOrdinals(ctx context.Context, fingerprints []string) (map[string]LibraryOrdinal, error)
	PutLibraryOrdinals(ctx context.Context, ordinals []LibraryOrdinal) error
	MarkLibraryOrdinalsSeen(ctx context.Context, fingerprints []string, generation int64) error
	PruneLibraryOrdinals(ctx context.Context, generation int64) (int64, error)
	ClearLibraryOrdinalCache(ctx context.Context) error
	GetLibraryInventoryState(ctx context.Context) (LibraryInventoryState, error)
	SetLibraryInventoryState(ctx context.Context, state *LibraryInventoryState) error

	// Slug resolution cache methods
	GetCachedSlugResolution(
		ctx context.Context, systemID, slug string, tagFilters []zapscript.TagFilter,
	) (SlugResolution, bool)
	SetCachedSlugResolution(
		ctx context.Context, systemID, slug string, tagFilters []zapscript.TagFilter, resolution SlugResolution,
	) error
	InvalidateSlugCache(ctx context.Context) error
	InvalidateSlugCacheForSystems(ctx context.Context, systemIDs []string) error
	GetMediaByDBID(ctx context.Context, mediaDBID int64) (SearchResultWithCursor, error)
	GetZapScriptTagsBySystemAndPath(ctx context.Context, systemID, path string) ([]TagInfo, error)

	SetIndexingCacheSize(enable bool)
	SetWALAutoCheckpoint(pages int)
	DropSecondaryIndexes() error
	CreateSecondaryIndexes() error
	SetIndexingStatus(status string) error
	GetIndexingStatus() (string, error)
	GetIndexResumeAttempts() (int, error)
	IncrementIndexResumeAttempts() (int, error)
	ResetIndexResumeAttempts() error
	GetIndexResumeCheckpoint() (string, error)
	SetIndexResumeCheckpoint(checkpoint string) error
	SetScrapingStatus(status string) error
	GetScrapingStatus() (string, error)
	SetScrapingOperation(operation ScrapingOperation) error
	GetScrapingOperation() (ScrapingOperation, bool, error)
	ClearScrapingOperation() error
	SetLastIndexedSystem(systemID string) error
	GetLastIndexedSystem() (string, error)
	SetIndexingSystems(systemIDs []string) error
	GetIndexingSystems() ([]string, error)
	TruncateSystems(systemIDs []string) error

	// Scanner staging: files are streamed into staging tables inside the open
	// batch transaction, then folded into the media tables with set-based SQL
	// so indexing memory does not scale with database size.
	StageScannedMedia(media *ScanStagedMedia) error
	ReconcileStagedSystem(ctx context.Context, systemID string, opts ScanReconcileOpts) (ScanReconcileStats, error)
	ClearScanStage() error
	SeedCanonicalTagDefinitions(ctx context.Context) error

	SearchMediaPathExact(ctx context.Context, systems []systemdefs.System, query string) ([]SearchResult, error)
	SearchMediaWithFilters(ctx context.Context, filters *SearchFilters) ([]SearchResultWithCursor, error)
	SearchMediaBySlug(
		ctx context.Context, systemID string, slug string, tags []zapscript.TagFilter,
	) ([]SearchResultWithCursor, error)
	SearchMediaBySecondarySlug(
		ctx context.Context, systemID string, secondarySlug string, tags []zapscript.TagFilter,
	) ([]SearchResultWithCursor, error)
	SearchMediaBySlugPrefix(
		ctx context.Context, systemID string, slugPrefix string, tags []zapscript.TagFilter,
	) ([]SearchResultWithCursor, error)
	SearchMediaBySlugIn(
		ctx context.Context, systemID string, slugs []string, tags []zapscript.TagFilter,
	) ([]SearchResultWithCursor, error)
	TitleCandidates(ctx context.Context, systemID, name string, limit int) ([]TitleCandidate, error)
	GetTitlesWithPreFilter(
		ctx context.Context, systemID string, minLength, maxLength, minWordCount, maxWordCount int,
	) ([]MediaTitle, error)
	GetLaunchCommandForMedia(ctx context.Context, systemID, path string) (string, error)
	// SearchMediaByProperty finds media by a stored property value. systemID is an optional scope.
	SearchMediaByProperty(ctx context.Context, systemID, property, value string) ([]SearchResult, error)
	// HasMediaPropertyForPath reports whether indexed media already has a stored property.
	HasMediaPropertyForPath(ctx context.Context, systemID, path, property string) (bool, error)
	GetTags(ctx context.Context, systems []systemdefs.System) ([]TagInfo, error)
	GetAllUsedTags(ctx context.Context) ([]TagInfo, error)
	PopulateSystemTagsCache(ctx context.Context) error
	PopulateSystemTagsCacheForSystems(ctx context.Context, systems []systemdefs.System) error
	AnalyzeApproximate() error
	RefreshSlugSearchCacheForSystems(ctx context.Context, systemIDs []string) error
	GetSystemTagsCached(ctx context.Context, systems []systemdefs.System) ([]TagInfo, error)
	InvalidateSystemTagsCache(ctx context.Context, systems []systemdefs.System) error
	SearchMediaPathGlob(systems []systemdefs.System, query string) ([]SearchResult, error)

	// Browse methods for directory-style navigation of indexed content
	BrowseDirectories(ctx context.Context, opts BrowseDirectoriesOptions) ([]BrowseDirectoryResult, error)
	BrowseDirCount(ctx context.Context, opts BrowseDirCountOptions) (int, error)
	BrowseFiles(ctx context.Context, opts *BrowseFilesOptions) ([]SearchResultWithCursor, error)
	// GetMediaCoverStatus returns statuses keyed by MediaDBID. True means a media-
	// or title-level image exists; absent keys mean no cover.
	GetMediaCoverStatus(ctx context.Context, refs []MediaRef) (map[int64]bool, error)
	BrowseFileCount(ctx context.Context, opts BrowseFileCountOptions) (int, error)
	BrowseIndex(ctx context.Context, opts BrowseIndexOptions) (BrowseIndexResult, error)
	BrowseVirtualSchemes(ctx context.Context, opts BrowseVirtualSchemesOptions) ([]BrowseVirtualScheme, error)
	BrowseRootCounts(ctx context.Context, rootDirs []string, excludeHidden bool) (map[string]*int, error)
	BrowseRouteCounts(ctx context.Context, opts BrowseRouteCountsOptions) (map[string]BrowseRouteCount, error)
	BrowseSystemRootCandidates(
		ctx context.Context, opts BrowseSystemRootCandidatesOptions,
	) (result BrowseSystemRootCandidates, cacheReady bool, err error)
	PopulateBrowseCache(ctx context.Context) error
	PopulateBrowseCacheForSystems(ctx context.Context, systemIDs []string) error
	BrowseCacheNeedsRebuild(ctx context.Context) (bool, error)

	IndexedSystems() ([]string, error)
	SystemMediaCounts(
		ctx context.Context, tags []zapscript.TagFilter, excludeHidden bool,
	) ([]SystemMediaCount, error)
	SystemIndexed(system *systemdefs.System) bool
	RandomGame(ctx context.Context, systems []systemdefs.System) (SearchResult, error)
	RandomGameWithQuery(ctx context.Context, query *MediaQuery) (SearchResult, error)
	GetTotalMediaCount() (int, error)
	HasAnyMedia() (bool, error)
	GetMissingMediaCount() (int, error)
	GetScrapedMediaCount(ctx context.Context, scraperID string) (int, error)
	GetTotalScrapedMediaCount(ctx context.Context) (int, error)

	FindSystem(row System) (System, error)
	FindSystemBySystemID(systemID string) (System, error)
	InsertSystem(row System) (System, error)
	FindOrInsertSystem(row System) (System, error)

	FindMediaTitle(row *MediaTitle) (MediaTitle, error)
	InsertMediaTitle(row *MediaTitle) (MediaTitle, error)
	FindOrInsertMediaTitle(row *MediaTitle) (MediaTitle, error)

	FindMedia(row Media) (Media, error)
	InsertMedia(row Media) (Media, error)
	FindOrInsertMedia(row Media) (Media, error)
	DeleteMediaTag(mediaDBID, tagDBID int64) error
	UpdateMediaTags(
		ctx context.Context,
		mediaDBID int64,
		remove []MediaTagRef,
		add []MediaTagRef,
	) error
	SetMediaTagMembership(ctx context.Context, ref MediaTagRef, mediaDBIDs []int64) (bool, error)
	ListMediaTagValues(ctx context.Context, tagType, valuePrefix string) ([]string, error)
	TemporaryRepairJobsPending(ctx context.Context) (bool, error)

	FindTagType(row TagType) (TagType, error)
	InsertTagType(row TagType) (TagType, error)
	FindOrInsertTagType(row TagType) (TagType, error)

	FindTag(row Tag) (Tag, error)
	InsertTag(row Tag) (Tag, error)
	FindOrInsertTag(row Tag) (Tag, error)

	FindMediaTag(row MediaTag) (MediaTag, error)
	InsertMediaTag(row MediaTag) (MediaTag, error)
	FindOrInsertMediaTag(row MediaTag) (MediaTag, error)

	// CleanMediaOrphans removes Media rows where IsMissing=1 together with
	// their associated MediaTags and MediaProperties.  MediaTitles that are
	// no longer referenced by any Media row are also removed (including their
	// MediaTitleTags and MediaTitleProperties).  Tags unreferenced by any join
	// table are pruned.  A VACUUM is issued on success.
	//
	// Returns the count of Media rows deleted, or one of ErrIndexingInProgress,
	// ErrOptimizationInProgress, or ErrTransactionActive when the operation
	// cannot safely run.
	CleanMediaOrphans(ctx context.Context) (int64, error)

	GetAllSystems() ([]System, error)
	// GetExistingMediaUserData returns user-authored data (favourites, launcher
	// overrides) already stored in media.db, for the one-time UserDB backfill.
	GetExistingMediaUserData(ctx context.Context) ([]MediaUserData, error)
	MediaPreferencesRevision(ctx context.Context) (string, error)

	// Per-system query methods for scrapers
	GetTitlesBySystemID(systemID string) ([]TitleWithSystem, error)
	GetMediaBySystemID(systemID string) ([]MediaWithFullPath, error)
	// GetMediaSourceRoots returns distinct local metadata roots for present media in a system.
	GetMediaSourceRoots(ctx context.Context, systemID string) ([]string, error)
	// GetMediaSourcesForScrape returns source rows for a full system or optional scrape scope.
	// Unique is evaluated against the full system before scope filtering.
	GetMediaSourcesForScrape(ctx context.Context, systemID string, scope *ScrapeScope) ([]MediaSource, error)
	// GetScrapeMedia selects present indexed media and their titles within an exact resolved scope.
	GetScrapeMedia(ctx context.Context, scope ScrapeScope) ([]MediaFullRow, error)
	// GetScopedScrapeMediaIDs selects sentinel or force-run markers without loading an entire system.
	GetScopedScrapeMediaIDs(ctx context.Context, scope ScrapeScope, scraperID, runID string) (map[int64]struct{}, error)

	// FindMediaBySystemAndPath returns the Media row matching systemDBID and path,
	// or nil, nil when no row is found.
	FindMediaBySystemAndPath(ctx context.Context, systemDBID int64, path string) (*Media, error)
	FindMediaBySystemAndPaths(ctx context.Context, systemDBID int64, paths []string) (map[string]Media, error)
	// FindMediaIDsByPaths returns the system ID, path, media DBID, and title DBID
	// of every Media row whose Path is in paths, in one query across all systems.
	FindMediaIDsByPaths(ctx context.Context, paths []string) ([]MediaPathID, error)
	// FindSingleContainerLaunchMedia returns the one logical launch target in the
	// direct contents of containerPath for systemDBID, or nil, nil when the
	// container is empty, nested-only, or ambiguous.
	FindSingleContainerLaunchMedia(ctx context.Context, systemDBID int64, containerPath string) (*Media, error)
	// FindSingleContainerLaunchMediaBySystemID is FindSingleContainerLaunchMedia
	// keyed by system ID, for callers that address a system by name rather than
	// by row.
	FindSingleContainerLaunchMediaBySystemID(ctx context.Context, systemID, containerPath string) (*Media, error)
	// ResolveSingletonContainerAliases resolves the given candidate child
	// directories for systemDBID in a single batch query, returning one
	// SingletonContainerAlias per candidate that collapses to a single launch
	// target. Candidates with nested subdirs (recursive FileCount exceeding
	// their direct media rows) or ambiguous contents are omitted.
	// ZapScriptTags are populated via in-memory disambiguation (same approach
	// as the search path) and will be empty for unambiguous titles.
	ResolveSingletonContainerAliases(
		ctx context.Context, systemDBID int64, candidates []SingletonAliasCandidate,
	) ([]SingletonContainerAlias, error)

	// FindMediaBySystemAndPathFold returns the Media row matching systemDBID and
	// path using a case-insensitive path comparison, or nil, nil when no row is
	// found. Intended for scrapers where the incoming path casing may differ from
	// what the indexer recorded (e.g. Windows filesystem with mixed-case system
	// directory names).
	FindMediaBySystemAndPathFold(ctx context.Context, systemDBID int64, path string) (*Media, error)

	// FindMediaBySystemAndPathSuffix returns all Media rows for the given system
	// whose Path ends with "/" + filename. Used when companion XML child paths are
	// root-relative (./file.rom) and the indexed path may be in any subdirectory.
	FindMediaBySystemAndPathSuffix(ctx context.Context, systemDBID int64, filename string) ([]Media, error)

	// MediaHasTag returns true when the Media row has a tag whose full string
	// (type:value) equals tagValue.
	MediaHasTag(ctx context.Context, mediaDBID int64, tagValue string) (bool, error)

	// GetScrapedMediaIDs returns media DBIDs in a system already marked as scraped
	// by scraperID.
	GetScrapedMediaIDs(ctx context.Context, scraperID string, systemDBID int64) (map[int64]struct{}, error)

	// GetScrapeRunMediaIDs returns media DBIDs in a system completed during a
	// specific scraper run.
	GetScrapeRunMediaIDs(ctx context.Context, scraperID, runID string, systemDBID int64) (map[int64]struct{}, error)

	// ClearScrapeRunMarkers removes per-run completion markers after a scraper
	// operation reaches a terminal state.
	ClearScrapeRunMarkers(ctx context.Context, scraperID, runID string) error

	// UpsertMediaTags writes tags to MediaTags for a specific Media row.
	// Exclusive types (TagTypes.IsExclusive=1) delete existing tags of that type
	// for the entity before inserting; additive types use INSERT OR IGNORE.
	UpsertMediaTags(ctx context.Context, mediaDBID int64, tags []TagInfo) error

	// UpsertMediaTitleTags writes tags to MediaTitleTags for a specific MediaTitle row.
	// Exclusive/additive behaviour is identical to UpsertMediaTags.
	UpsertMediaTitleTags(ctx context.Context, mediaTitleDBID int64, tags []TagInfo) error

	// RecomputeTitleDisambiguation recomputes the stored disambiguating tag types
	// for the given MediaTitle DBIDs. Called after writes that change a title's
	// media or tags so reads can rely on the stored, title-global value.
	RecomputeTitleDisambiguation(ctx context.Context, titleDBIDs []int64) error

	// RecomputeSystemDisambiguation recomputes the stored disambiguating tag types
	// for every MediaTitle belonging to the given system DBIDs. Used at index time.
	RecomputeSystemDisambiguation(ctx context.Context, systemDBIDs []int64) error

	// UpsertMediaTitleProperties upserts properties into MediaTitleProperties.
	// Conflicts on (MediaTitleDBID, TypeTagDBID) update data columns; DBID is preserved.
	UpsertMediaTitleProperties(ctx context.Context, mediaTitleDBID int64, props []MediaProperty) error

	// UpsertMediaProperties upserts properties into MediaProperties.
	// Conflicts on (MediaDBID, TypeTagDBID) update data columns; DBID is preserved.
	UpsertMediaProperties(ctx context.Context, mediaDBID int64, props []MediaProperty) error

	// ReplaceDirectoryProperties atomically replaces the complete file-backed
	// property snapshot for one system. It reports whether stored rows changed.
	ReplaceDirectoryProperties(ctx context.Context, systemDBID int64, props []DirectoryProperty) (bool, error)
	// GetDirectoryProperties returns properties for one canonical directory path.
	GetDirectoryProperties(ctx context.Context, systemDBID int64, path string) ([]MediaProperty, error)

	// ApplyScrapeResult atomically writes all scraper metadata for a Media row and
	// writes the sentinel tag last.
	ApplyScrapeResult(ctx context.Context, mediaDBID, mediaTitleDBID int64, write *ScrapeWrite) error

	// ConsumeScrapeImageChanges returns and clears systems with materially changed
	// image properties. all is true when tracking could not resolve a safe targeted
	// set and callers must conservatively invalidate the full cache.
	ConsumeScrapeImageChanges() (systems []string, all bool)

	// FindMediaTitlesWithoutSentinel returns MediaTitle rows for the given system
	// that have no Media row with the given sentinel tag value.
	FindMediaTitlesWithoutSentinel(ctx context.Context, systemDBID int64, sentinelTag string) ([]MediaTitle, error)

	// FindMediaTitleByDBID returns the MediaTitle with the given DBID,
	// or nil, nil when no row is found.
	FindMediaTitleByDBID(ctx context.Context, dbid int64) (*MediaTitle, error)

	// FindMediaTitleBySystemAndSlug returns the MediaTitle matching systemDBID and
	// slug, or nil, nil when no row is found.
	FindMediaTitleBySystemAndSlug(ctx context.Context, systemDBID int64, slug string) (*MediaTitle, error)

	// GetMediaTitleProperties returns all properties for a MediaTitle row,
	// with TypeTagDBID resolved to the tag value string. Binary blobs are capped
	// at MaxMediaPropertyBinaryBytes; larger blobs populate BlobSize but not Binary.
	GetMediaTitleProperties(ctx context.Context, mediaTitleDBID int64) ([]MediaProperty, error)
	GetMediaTitlePropertiesByMediaTitleDBIDs(
		ctx context.Context, mediaTitleDBIDs []int64,
	) (map[int64][]MediaProperty, error)
	GetMediaTitlePropertyMetadata(ctx context.Context, mediaTitleDBID int64) ([]MediaProperty, error)
	GetMediaTitlePropertyMetadataByMediaTitleDBIDs(
		ctx context.Context, mediaTitleDBIDs []int64,
	) (map[int64][]MediaProperty, error)

	// GetMediaProperties returns all properties for a Media row,
	// with TypeTagDBID resolved to the tag value string. Binary blobs are capped
	// at MaxMediaPropertyBinaryBytes; larger blobs populate BlobSize but not Binary.
	GetMediaProperties(ctx context.Context, mediaDBID int64) ([]MediaProperty, error)
	GetMediaPropertiesByMediaDBIDs(ctx context.Context, mediaDBIDs []int64) (map[int64][]MediaProperty, error)
	GetMediaPropertyMetadata(ctx context.Context, mediaDBID int64) ([]MediaProperty, error)
	GetMediaPropertyMetadataByMediaDBIDs(ctx context.Context, mediaDBIDs []int64) (map[int64][]MediaProperty, error)

	// DeleteMediaTitleProperty removes a single property row from MediaTitleProperties
	// identified by (mediaTitleDBID, typeTagDBID). A no-op if the row does not exist.
	DeleteMediaTitleProperty(ctx context.Context, mediaTitleDBID int64, typeTagDBID int64) error

	// DeleteMediaProperty removes a single property row from MediaProperties
	// identified by (mediaDBID, typeTagDBID). A no-op if the row does not exist.
	DeleteMediaProperty(ctx context.Context, mediaDBID int64, typeTagDBID int64) error

	// GetMediaWithTitleAndSystem fetches a Media record together with its parent
	// MediaTitle and System via a single JOIN query. Returns nil, nil when no
	// Media row with the given DBID exists. IsMissing is NOT filtered — metadata
	// remains accessible for missing files.
	GetMediaWithTitleAndSystem(ctx context.Context, mediaDBID int64) (*MediaFullRow, error)
	GetMediaWithTitleAndSystemByIDs(ctx context.Context, mediaDBIDs []int64) (map[int64]MediaFullRow, error)

	// GetMediaTagsByMediaDBID returns the file-level tags (MediaTags) for a
	// single Media row. Does not include title-level tags.
	GetMediaTagsByMediaDBID(ctx context.Context, mediaDBID int64) ([]TagInfo, error)
	GetMediaTagsByMediaDBIDs(ctx context.Context, mediaDBIDs []int64) (map[int64][]TagInfo, error)

	// GetMediaTitleTagsByMediaTitleDBID returns the title-level tags
	// (MediaTitleTags) for a single MediaTitle row.
	GetMediaTitleTagsByMediaTitleDBID(ctx context.Context, mediaTitleDBID int64) ([]TagInfo, error)
	GetMediaTitleTagsByMediaTitleDBIDs(ctx context.Context, mediaTitleDBIDs []int64) (map[int64][]TagInfo, error)

	// GetMediaTagsByMediaRefs returns the merged file-level (MediaTags) and
	// title-level (MediaTitleTags) tags for each referenced media row, keyed
	// by MediaDBID. This is the same tag view media.search attaches to
	// results, unlike GetMediaTagsByMediaDBIDs which is file-level only.
	// Tags are deduplicated by (type, tag) and sorted by type then tag.
	// Untagged media have no map entry. Refs with MediaDBID <= 0 are ignored.
	// The lookup is batched internally under the SQLite parameter limit.
	GetMediaTagsByMediaRefs(ctx context.Context, refs []MediaRef) (map[int64][]TagInfo, error)

	// UpsertMediaBlob inserts a blob into MediaBlobs when no row with the same
	// SHA-256 hash of framed content type and bytes already exists, then returns its DBID.
	// Hash computation is performed internally; callers supply only contentType and raw data.
	UpsertMediaBlob(ctx context.Context, contentType string, data []byte) (int64, error)

	// GetMediaBlob returns the MediaBlob row for the given DBID,
	// or nil, nil when not found.
	GetMediaBlob(ctx context.Context, blobDBID int64) (*MediaBlob, error)
	GetMediaBlobDataCapped(ctx context.Context, blobDBID int64, maxBytes int64) ([]byte, string, error)

	// PruneOrphanedBlobs deletes MediaBlobs rows that are not referenced by
	// any MediaTitleProperties or MediaProperties row. Returns the count of
	// rows deleted. Safe to call from CleanMediaOrphans.
	PruneOrphanedBlobs(ctx context.Context) (int64, error)
}

type MediaDBWriteCoordinator added in v2.17.0

type MediaDBWriteCoordinator interface {
	AcquireMediaWrite(operation MediaWriteOperation) (*MediaWriteLease, error)
	ActiveMediaWriteOperation() MediaWriteOperation
	RunBackgroundOptimizationWithLease(
		statusCallback func(optimizing bool), pauser *syncutil.Pauser, lease *MediaWriteLease,
	) error
}

MediaDBWriteCoordinator is optional process-local write arbitration layered over MediaDBI without expanding that compatibility-sensitive interface.

func GetMediaDBWriteCoordinator added in v2.17.0

func GetMediaDBWriteCoordinator(mediaDB MediaDBI) (MediaDBWriteCoordinator, error)

GetMediaDBWriteCoordinator returns write arbitration supported by current MediaDB implementations.

type MediaFullRow added in v2.12.0

type MediaFullRow struct {
	System System
	Media
	Title MediaTitle
}

MediaFullRow is the result of a joined query fetching a Media record together with its parent MediaTitle and System in a single round-trip.

type MediaHistoryEntry added in v2.7.0

type MediaHistoryEntry struct {
	StartTime      time.Time      `json:"startTime"`
	UpdatedAt      time.Time      `json:"updatedAt,omitempty"`
	CreatedAt      time.Time      `json:"createdAt,omitempty"`
	EndTime        *time.Time     `json:"endTime,omitempty"`
	SyncedAt       *time.Time     `json:"syncedAt,omitempty"`
	DeviceID       *string        `json:"deviceId,omitempty"`
	ProfileID      *string        `json:"profileId,omitempty"`
	MediaIdentity  *MediaIdentity `json:"mediaIdentity,omitempty"`
	BootUUID       string         `json:"bootUuid,omitempty"`
	ClockSource    string         `json:"clockSource,omitempty"`
	SystemID       string         `json:"systemId"`
	ID             string         `json:"uuid,omitempty"`
	LauncherID     string         `json:"launcherId"`
	SystemName     string         `json:"systemName"`
	MediaPath      string         `json:"mediaPath"`
	MediaName      string         `json:"mediaName"`
	Tags           []string       `json:"tags,omitempty"`
	DBID           int64          `db:"DBID" json:"id"`
	WallDuration   int            `json:"wallDuration"`
	DurationSec    int            `json:"durationSec"`
	MonotonicStart int64          `json:"monotonicStart,omitempty"`
	PlayTime       int            `json:"playTime"`
	TimeSkewFlag   bool           `json:"timeSkewFlag"`
	ClockReliable  bool           `json:"clockReliable"`
	IsDeleted      bool           `json:"isDeleted,omitempty"`
}

type MediaHistorySyncRef added in v2.16.0

type MediaHistorySyncRef struct {
	UpdatedAt time.Time
	DBID      int64
}

MediaHistorySyncRef identifies the exact local version acknowledged by a play-history upload. UpdatedAt is a monotonic per-row version watermark.

type MediaHistoryTopEntry added in v2.10.0

type MediaHistoryTopEntry struct {
	LastPlayedAt  time.Time
	SystemID      string
	SystemName    string
	MediaName     string
	MediaPath     string
	TotalPlayTime int
	SessionCount  int
}

type MediaIdentity added in v2.16.0

type MediaIdentity struct {
	MediaType              string             `json:"media_type"`
	CanonicalSystemID      string             `json:"canonical_system_id"`
	DisplayName            string             `json:"display_name"`
	CoreSlug               string             `json:"core_slug"`
	ObservationFingerprint string             `json:"observation_fingerprint"`
	Tags                   []MediaIdentityTag `json:"tags"`
	PolicyVersion          int                `json:"policy_version"`
}

MediaIdentity is Core's versioned scanner-derived identity observation for one indexed media path.

func BuildMediaIdentity added in v2.18.0

func BuildMediaIdentity(
	mediaType slugs.MediaType,
	canonicalSystemID string,
	displayName string,
	coreSlug string,
	tagInfos []TagInfo,
) (MediaIdentity, error)

BuildMediaIdentity returns the identity observation for one indexed media row, the same observation LookupMediaIdentity builds from a path lookup.

func DecodeMediaIdentity added in v2.16.1

func DecodeMediaIdentity(raw string) *MediaIdentity

DecodeMediaIdentity parses a UserDB identity snapshot. Empty or malformed input returns nil so legacy history remains readable.

func LookupMediaIdentity added in v2.16.0

func LookupMediaIdentity(
	ctx context.Context, mediaDB MediaDBI, systemID, path string,
) (MediaIdentity, bool, error)

LookupMediaIdentity resolves an exact indexed media path to Core's complete scanner identity observation. found=false covers both a successful no-row result and rows that can never resolve (unknown system, unbuildable identity); an error is returned only for transient query failures, which remain distinguishable for bounded history enrichment retries.

func (*MediaIdentity) LegacyTags added in v2.16.1

func (identity *MediaIdentity) LegacyTags() []string

LegacyTags returns the complete scanner tag snapshot as sorted type:value strings for compatibility fields retained during API migration.

type MediaIdentityTag added in v2.16.1

type MediaIdentityTag struct {
	Type  string               `json:"type"`
	Value string               `json:"value"`
	Role  MediaIdentityTagRole `json:"role"`
}

type MediaIdentityTagRole added in v2.16.1

type MediaIdentityTagRole string
const (
	MediaIdentityTagRoleIdentity MediaIdentityTagRole = "identity"
	MediaIdentityTagRoleContext  MediaIdentityTagRole = "context"
)

func MediaIdentityTagRoleForPolicy added in v2.16.1

func MediaIdentityTagRoleForPolicy(policyVersion int, tagType string) (MediaIdentityTagRole, error)

MediaIdentityTagRoleForPolicy returns a tag's immutable role under a known identity policy version.

type MediaPathID added in v2.15.0

type MediaPathID struct {
	SystemID       string
	Path           string
	DBID           int64
	MediaTitleDBID int64
}

MediaPathID identifies a Media row and its title by system ID and path, used for batch API response enrichment.

type MediaProperty added in v2.12.0

type MediaProperty struct {
	BlobDBID    *int64
	TypeTag     string
	Text        string
	ContentType string
	Binary      []byte
	TypeTagDBID int64
	BlobSize    int64
}

MediaProperty is a static content property attached to a MediaTitle or Media record. Properties are fetched for display, not filtered by value.

For writes: set TypeTag to the full "type:value" string (e.g. "property:description"). The database layer resolves the TypeTagDBID from TypeTag automatically. To associate binary data with a property, call UpsertMediaBlob first to obtain a BlobDBID, then set that field before upserting the property. For reads: TypeTag is populated from the joined Tags row; TypeTagDBID is also set. ContentType, BlobSize, and Binary are hydrated from the MediaBlobs JOIN and are read-only — do not set them for writes.

type MediaQuery added in v2.7.0

type MediaQuery struct {
	PathGlob   string                `json:"pathGlob,omitempty"`
	PathPrefix string                `json:"pathPrefix,omitempty"`
	Systems    []string              `json:"systems,omitempty"`
	Tags       []zapscript.TagFilter `json:"tags,omitempty"`
}

MediaQuery represents parameters for querying media counts used in random selection

type MediaRef added in v2.17.0

type MediaRef struct {
	MediaDBID      int64
	MediaTitleDBID int64
}

MediaRef identifies a Media row and its MediaTitle row for batch lookups such as cover status and tags.

type MediaSource added in v2.18.0

type MediaSource struct {
	MediaPath  string
	SourcePath string
	SourceKey  string
	SourceRoot string
	SourceKind string
	MediaDBID  int64
	SystemDBID int64
	Unique     bool
}

MediaSource is scanner-owned local metadata provenance for an indexed virtual Media row. Unique is computed across all present rows in the system before any scrape scope is applied.

type MediaTag

type MediaTag struct {
	DBID      int64
	MediaDBID int64
	TagDBID   int64
}

type MediaTagBatchUpdater added in v2.18.0

type MediaTagBatchUpdater interface {
	UpdateMediaTagsBatch(ctx context.Context, updates []MediaTagUpdate) error
}

MediaTagBatchUpdater is optional batch tag editing layered over MediaDBI without expanding that interface. It applies every edit in one transaction.

type MediaTagLink struct {
	MediaDBID int64
	TagDBID   int64
}

type MediaTagRef added in v2.16.1

type MediaTagRef struct {
	Type string
	Tag  string
}

MediaTagRef identifies a tag by its stable type and value.

type MediaTagUpdate added in v2.18.0

type MediaTagUpdate struct {
	Remove    []MediaTagRef
	Add       []MediaTagRef
	MediaDBID int64
}

MediaTagUpdate is one file's tag edit in a batch: removals run before additions, as in MediaDBI.UpdateMediaTags.

type MediaTitle

type MediaTitle struct {
	Slug string
	Name string
	// DisambiguationTypes is the title's stored comma-separated set of tag types
	// whose values differ across its non-missing media (see RecomputeTitleDisambiguation).
	DisambiguationTypes string
	SecondarySlug       sql.NullString
	DBID                int64
	SystemDBID          int64
	SlugLength          int
	SlugWordCount       int
}

type MediaUserData added in v2.15.0

type MediaUserData struct {
	SystemID         string
	Path             string
	LauncherOverride string
	// MediaName, Slug and Tags snapshot the scanner's identity for this path
	// at write time (display name, title slug and complete canonical
	// type:value tags), so the row stays matchable to a canonical game after
	// MediaDB is rebuilt or the file disappears. Empty when no scanner entry
	// existed.
	MediaName   string
	Slug        string
	Tags        []string
	DBID        int64
	CreatedAt   int64
	UpdatedAt   int64
	IsFavorite  bool
	IsHidden    bool
	IsLiked     bool
	IsDisliked  bool
	IsPlayLater bool
}

MediaUserData is the source-of-truth record for user-authored data about a single media path: the favorite, hidden, liked, disliked and play later flags and any per-game launcher override. It lives in UserDB (durable, power-loss safe) and is materialized into media.db's MediaTags/ MediaProperties projection both on edit and on reindex. Keyed by (SystemID, Path) because a Media row's DBID is not stable across a full media.db rebuild. A row with no flag set and an empty LauncherOverride should be deleted rather than kept.

func (*MediaUserData) Flag added in v2.18.0

func (d *MediaUserData) Flag(flag MediaUserFlag) bool

Flag reports the value of one flag.

func (*MediaUserData) HasIntent added in v2.18.0

func (d *MediaUserData) HasIntent() bool

HasIntent reports whether the row records any preference worth keeping.

func (*MediaUserData) ValidateFlags added in v2.18.0

func (d *MediaUserData) ValidateFlags() error

ValidateFlags reports ErrMediaUserFlagConflict when the row holds a forbidden pair. A game cannot be liked and disliked, and a favorite cannot be disliked.

type MediaUserDataReconciler added in v2.18.0

type MediaUserDataReconciler interface {
	QueueMediaUserDataReconcile()
}

MediaUserDataReconciler brings the user flag tags and launcher overrides in MediaDB in line with UserDB after UserDB was replaced as a whole. Queueing returns at once; the work happens later.

type MediaUserFlag added in v2.18.0

type MediaUserFlag string

MediaUserFlag names one boolean preference a user can set on a media path.

const (
	MediaUserFlagFavorite  MediaUserFlag = "favorite"
	MediaUserFlagHidden    MediaUserFlag = "hidden"
	MediaUserFlagLiked     MediaUserFlag = "liked"
	MediaUserFlagDisliked  MediaUserFlag = "disliked"
	MediaUserFlagPlayLater MediaUserFlag = "playlater"
)

type MediaWithFullPath added in v2.7.0

type MediaWithFullPath struct {
	Path           string
	ParentDir      string
	TitleSlug      string
	SystemID       string
	SortName       string
	DBID           int64
	MediaTitleDBID int64
	IsMissing      bool
}

MediaWithFullPath represents a Media item with its associated title and system information.

type MediaWriteArbiter added in v2.17.0

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

MediaWriteArbiter serializes process-local MediaDB mutations. Its zero value is ready to use.

func (*MediaWriteArbiter) Active added in v2.17.0

Active reports current process-local owner, or MediaWriteOperationNone.

func (*MediaWriteArbiter) TryAcquire added in v2.17.0

func (a *MediaWriteArbiter) TryAcquire(operation MediaWriteOperation) (*MediaWriteLease, error)

TryAcquire atomically claims the write slot.

type MediaWriteConflictError added in v2.17.0

type MediaWriteConflictError struct {
	Requested MediaWriteOperation
	Active    MediaWriteOperation
}

MediaWriteConflictError reports which process-local owner blocked a request.

func (*MediaWriteConflictError) Error added in v2.17.0

func (e *MediaWriteConflictError) Error() string

func (*MediaWriteConflictError) Unwrap added in v2.17.0

func (*MediaWriteConflictError) Unwrap() error

type MediaWriteLease added in v2.17.0

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

MediaWriteLease is exclusive ownership of one MediaDB write-operation slot. Release is idempotent. Handoff changes operation type without an unowned gap.

func (*MediaWriteLease) Handoff added in v2.17.0

func (l *MediaWriteLease) Handoff(operation MediaWriteOperation) error

Handoff atomically transfers ownership to another operation type.

func (*MediaWriteLease) Operation added in v2.17.0

func (l *MediaWriteLease) Operation() MediaWriteOperation

Operation reports operation currently owned by this lease.

func (*MediaWriteLease) Release added in v2.17.0

func (l *MediaWriteLease) Release()

Release relinquishes ownership exactly once. Duplicate calls are no-ops.

func (*MediaWriteLease) ValidFor added in v2.17.0

func (l *MediaWriteLease) ValidFor(operation MediaWriteOperation) bool

ValidFor reports whether lease still owns operation.

type MediaWriteOperation added in v2.17.0

type MediaWriteOperation string

MediaWriteOperation identifies one process-local MediaDB write owner.

const (
	MediaWriteOperationNone         MediaWriteOperation = ""
	MediaWriteOperationIndexing     MediaWriteOperation = "indexing"
	MediaWriteOperationScraping     MediaWriteOperation = "scraping"
	MediaWriteOperationOptimization MediaWriteOperation = "optimization"
	MediaWriteOperationMaintenance  MediaWriteOperation = "maintenance"
	MediaWriteOperationRecovery     MediaWriteOperation = "recovery"
)

type MigrationReporter added in v2.18.0

type MigrationReporter func(dbLabel string, pending int)

databaseLabel names a database in logs by its file name, so a line says which of the two is being worked on without the caller passing a label down. MigrationReporter is told that a database is about to have migrations applied, and how many. It is called before the work starts, on the goroutine running it.

type Profile added in v2.16.0

type Profile struct {
	LimitsEnabled *bool   `json:"limitsEnabled,omitempty"`
	DailyLimit    *string `json:"dailyLimit,omitempty"`
	SessionLimit  *string `json:"sessionLimit,omitempty"`
	LastUsedAt    *int64  `json:"lastUsedAt,omitempty"`
	ProfileID     string  `json:"profileId"`
	Name          string  `json:"name"`
	Role          string  `json:"role"`
	SwitchID      string  `json:"switchId"`
	PINHash       string  `json:"-"`
	DBID          int64   `json:"-"`
	CreatedAt     int64   `json:"createdAt"`
	UpdatedAt     int64   `json:"updatedAt"`
}

Profile represents a device profile: a named bucket of preferences and limits with no credentials. PINHash is hidden from JSON (API uses models.ProfileResponse instead). Nil limit fields mean "inherit the global config value"; a "0" duration string means "explicitly unlimited".

type RemoteCommand added in v2.17.0

type RemoteCommand struct {
	DeadlineAt         time.Time
	ExecutionExpiresAt *time.Time
	CreatedAt          time.Time
	UpdatedAt          time.Time
	Origin             json.RawMessage
	Result             json.RawMessage
	CommandID          string
	OperationID        string
	OperationType      string
	ParamsDigest       string
	State              string
	ResultStatus       string
	ErrorCode          string
	ProtocolVersion    int
	ResultReported     bool
}

JournalMode represents SQLite journal mode RemoteCommand is one durable typed-operation ledger entry. State changes are persisted before side effects so a crash can never cause re-execution.

type RestoreInfo added in v2.15.0

type RestoreInfo struct {
	PreRestoreBackup *BackupInfo `json:"preRestoreBackup,omitempty"`
	RestoredFrom     BackupInfo  `json:"restoredFrom"`
}

type ScanReconcileOpts added in v2.16.0

type ScanReconcileOpts struct {
	// Yield runs between set-based reconcile steps and chunked missing-media
	// updates. Background indexers use it for cooperative pacing; nil preserves
	// unpaced callers such as foreground maintenance and tests.
	Yield func() error
	// IncompleteScan means file collection for this system hit errors (an
	// unreadable path, a failed launcher scanner), so the staged set may be a
	// subset of what actually exists. Staged files are still upserted and
	// re-found rows still clear their missing flag, but media absent from the
	// stage keep their current missing state instead of being flagged missing.
	IncompleteScan bool
}

ScanReconcileOpts adjusts how a staged-system reconcile treats the staged file set.

type ScanReconcileStats added in v2.16.0

type ScanReconcileStats struct {
	SystemDBID      int64
	TitlesInserted  int64
	TitlesRenamed   int64
	MediaUpserted   int64
	MediaMissing    int64
	TagsInserted    int64
	TagLinksAdded   int64
	TagLinksDeleted int64
	TouchedTitles   int64
	// SystemKnown is false when the system has no DB row and nothing was staged,
	// meaning the reconcile was a no-op and no Systems row was created.
	SystemKnown bool
}

ScanReconcileStats reports what a staged-system reconcile changed. Counts are per-statement sqlite changes() values, for logging and tests.

type ScanStagedMedia added in v2.16.0

type ScanStagedMedia struct {
	Source        *ScanStagedSource
	Path          string
	ParentDir     string
	Slug          string
	TitleName     string
	SortName      string
	SecondarySlug string
	Tags          []ScanStagedTag
	Properties    []ScanStagedProperty
	SlugLength    int
	SlugWordCount int
}

ScanStagedMedia is one scanned file's parsed fragments, staged into the ScanStage/ScanStageTags tables for set-based reconcile against the media tables. SecondarySlug is empty when the title has none.

type ScanStagedProperty added in v2.16.0

type ScanStagedProperty struct {
	Type string
	Name string
	Text string
}

ScanStagedProperty is one property derived from a scanned file, staged for set-based reconcile after the corresponding Media row exists.

type ScanStagedSource added in v2.18.0

type ScanStagedSource struct {
	Path string
	Key  string
	Root string
	Kind string
}

ScanStagedSource is optional local metadata provenance for a virtual media row, normalized before it reaches database staging.

type ScanStagedTag added in v2.16.0

type ScanStagedTag struct {
	Type  string
	Value string
}

ScanStagedTag is one tag derived from a scanned file, staged for set-based reconcile. Value is the natural (unpadded) form; the DB layer applies tags.PadTagValue when writing the staging row.

type ScrapeJob added in v2.18.0

type ScrapeJob struct {
	// Scope narrows the job the same way ScrapingOperation.Scope does, so a
	// scoped request that is queued behind another still runs scoped.
	Scope       *ScrapeScope `json:"scope,omitempty"`
	ScraperID   string       `json:"scraperId"`
	RunID       string       `json:"runId,omitempty"`
	Systems     []string     `json:"systems"`
	Force       bool         `json:"force"`
	FillMissing bool         `json:"fillMissing,omitempty"`
}

ScrapeJob is shared by explicit requests, index-triggered requests, and recovery. RunID scopes completed-row markers to this particular request.

type ScrapeResultBatchApplier added in v2.14.0

type ScrapeResultBatchApplier interface {
	ApplyScrapeResults(ctx context.Context, targets []ScrapeWriteTarget) error
}

ScrapeResultBatchApplier optionally batches scrape writes for DB implementations that can keep multiple targets in one transaction.

type ScrapeScope added in v2.18.0

type ScrapeScope struct {
	SystemID string `json:"system"`
	Path     string `json:"path"`
	MediaID  int64  `json:"mediaId,omitempty"`
	Subtree  bool   `json:"subtree,omitempty"`
}

ScrapeScope is a resolved selection, persisted unchanged across restarts. Single items pin all three identity fields so a reused DBID cannot select a different path after an index rebuild. A nil scope retains legacy selection.

func (ScrapeScope) Validate added in v2.18.0

func (s ScrapeScope) Validate() error

Validate fails closed for malformed persisted selectors rather than treating an empty or unrecognized selection as a whole-system request.

type ScrapeWrite added in v2.12.0

type ScrapeWrite struct {
	Sentinel   TagInfo
	MediaTags  []TagInfo
	TitleTags  []TagInfo
	TitleProps []MediaProperty
	MediaProps []MediaProperty
	// FillMissing preserves existing fields; zero retains manual scrape semantics.
	FillMissing bool
}

ScrapeWrite is the database-level write payload produced by a scraper for a matched Media row. Sentinel is written after all metadata so interrupted runs can safely retry the record.

type ScrapeWriteTarget added in v2.14.0

type ScrapeWriteTarget struct {
	Write          *ScrapeWrite
	MediaDBID      int64
	MediaTitleDBID int64
}

ScrapeWriteTarget pairs a scraper write payload with the existing Media and MediaTitle rows it should enrich.

type ScrapingOperation added in v2.14.0

type ScrapingOperation struct {
	// Status is authoritative for versioned jobs, so queue acceptance and
	// cancellation do not depend on a second config-key write.
	// Scope narrows the run to one indexed item, file or subtree. A queued job
	// carries its own, or resuming after an index would silently widen a
	// single-file request into a whole-system scrape.
	Scope       *ScrapeScope `json:"scope,omitempty"`
	Status      string       `json:"status,omitempty"`
	ScraperID   string       `json:"scraperId"`
	RunID       string       `json:"runId,omitempty"`
	Systems     []string     `json:"systems"`
	Pending     []ScrapeJob  `json:"pending,omitempty"`
	Version     int          `json:"version,omitempty"`
	Force       bool         `json:"force"`
	FillMissing bool         `json:"fillMissing,omitempty"`
}

ScrapingOperation retains the legacy current-job fields while adding ordinary pending jobs. Version zero is the existing standalone record format.

func (*ScrapingOperation) IsResumable added in v2.18.0

func (o *ScrapingOperation) IsResumable(legacyStatus string) bool

IsResumable prefers the versioned job's atomic state over the legacy status key.

func (*ScrapingOperation) Validate added in v2.18.0

func (o *ScrapingOperation) Validate() error

Validate checks persisted job options before any scraper can execute them.

type SearchCursor added in v2.17.0

type SearchCursor struct {
	SortValue string
	Sort      string
	LastID    int64
}

type SearchFilters added in v2.7.0

type SearchFilters struct {
	Cursor     *int64                `json:"cursor,omitempty"`
	SortCursor *SearchCursor         `json:"-"`
	Letter     *string               `json:"letter,omitempty"`
	PathPrefix string                `json:"pathPrefix,omitempty"`
	Query      string                `json:"query"`
	Sort       string                `json:"sort,omitempty"`
	Systems    []systemdefs.System   `json:"systems,omitempty"`
	Tags       []zapscript.TagFilter `json:"tags,omitempty"`
	Limit      int                   `json:"limit"`
	// ExcludeHidden is set only for discovery, never explicit launch resolution.
	ExcludeHidden bool `json:"-"`
}

SearchFilters represents parameters for filtered media search

type SearchResult

type SearchResult struct {
	SystemID string
	Name     string
	Path     string
	Slug     string
	MediaID  int64
}

type SearchResultWithCursor added in v2.7.0

type SearchResultWithCursor struct {
	SystemID string
	Name     string
	Path     string
	// DisambiguationTypes is the title's stored comma-separated set of tag types
	// that distinguish its variants (see RecomputeTitleDisambiguation). Empty for
	// titles with no variants, which lets the read path skip the tag lookup.
	DisambiguationTypes string
	SortValue           string
	SortMode            string
	Tags                []TagInfo
	ZapScriptTags       []TagInfo
	MediaID             int64
	MediaTitleID        int64 `json:"-"`
	HasCover            bool
}

func (*SearchResultWithCursor) ZapScript added in v2.10.0

func (r *SearchResultWithCursor) ZapScript() string

ZapScript returns the ZapScript title command string for this search result. Uses ZapScriptTags (disambiguating tags only). If ZapScriptTags has not been populated (nil), no tags are emitted — callers that need disambiguation must run the result through attachZapScriptTags, which reads the title's stored DisambiguationTypes (see RecomputeTitleDisambiguation).

type SingletonAliasCandidate added in v2.14.1

type SingletonAliasCandidate struct {
	ChildDir  string
	FileCount int
}

SingletonAliasCandidate identifies a child directory to consider for singleton-container alias resolution. ChildDir must end with a trailing slash. FileCount is the recursive per-system media count for the directory (from the browse cache) — when it exceeds the number of direct media rows, the directory contains nested subdirectories and is not a singleton container.

type SingletonContainerAlias added in v2.14.1

type SingletonContainerAlias struct {
	ChildDir      string
	Tags          []TagInfo
	ZapScriptTags []TagInfo
	Row           MediaFullRow
	HasCover      bool
}

SingletonContainerAlias is the resolved launch media for a child directory whose contents collapse to a single logical launch target.

type SlugResolution added in v2.18.0

type SlugResolution struct {
	Strategy   string
	MediaDBID  int64
	Confidence float64
}

SlugResolution is a title resolution as the slug resolution cache keeps it: the media it landed on, the strategy that found it, and the confidence it scored, so a cache hit reports the same confidence as the first resolution.

type System

type System struct {
	SystemID string
	Name     string
	DBID     int64
}

type SystemMediaCount added in v2.17.0

type SystemMediaCount struct {
	SystemID string
	Count    int
}

type Tag

type Tag struct {
	Tag         string
	DisplayName string
	DBID        int64
	TypeDBID    int64
}

type TagInfo added in v2.7.0

type TagInfo struct {
	Tag   string `json:"tag"`
	Type  string `json:"type"`
	Label string `json:"label,omitempty"`
	Count int64  `json:"count,omitempty"`
}

type TagType

type TagType struct {
	Type        string
	DBID        int64
	IsExclusive bool
}

type TitleCandidate added in v2.18.0

type TitleCandidate struct {
	SystemID   string  `json:"systemId"`
	Name       string  `json:"name"`
	MatchType  string  `json:"matchType"`
	Rank       int     `json:"rank"`
	Confidence float64 `json:"confidence"`
}

TitleCandidate is title-level discovery evidence, not a resolved launch target. Confidence is advisory; only MatchType and Rank have stable client semantics.

type TitleWithSystem added in v2.7.0

type TitleWithSystem struct {
	Slug       string
	Name       string
	SystemID   string
	DBID       int64
	SystemDBID int64
}

TitleWithSystem represents a MediaTitle with its associated System information

type TransactionOptions added in v2.14.1

type TransactionOptions struct {
	WALCheckpoint WALCheckpointMode
}

type UserDBI

type UserDBI interface {
	GenericDBI
	AddHistory(entry *HistoryEntry) error
	GetHistory(lastID int64) ([]HistoryEntry, error)
	CleanupHistory(retentionDays int) (int64, error)
	AddMediaHistory(entry *MediaHistoryEntry) (int64, error)
	UpdateMediaHistoryTime(dbid int64, playTime int) error
	UpdateMediaHistoryIdentity(dbid int64, identity *MediaIdentity) (bool, error)
	// UpdateMediaHistoryIdentityAndPath is UpdateMediaHistoryIdentity plus a
	// MediaPath correction, for backfilling legacy rows recorded under a
	// non-path external identifier (e.g. a MiSTer arcade set name).
	UpdateMediaHistoryIdentityAndPath(dbid int64, path string, identity *MediaIdentity) (bool, error)
	CloseMediaHistory(dbid int64, endTime time.Time, playTime int) error
	GetMediaHistory(systemIDs []string, lastID int64, limit int) ([]MediaHistoryEntry, error)
	GetDistinctMediaHistory(
		ctx context.Context, systemIDs []string, lastID int64, limit int,
	) ([]MediaHistoryEntry, error)
	GetLatestMediaHistory() (MediaHistoryEntry, bool, error)
	GetMediaHistoryTop(systemIDs []string, since *time.Time, limit int) ([]MediaHistoryTopEntry, error)
	CloseHangingMediaHistory() error
	CleanupMediaHistory(retentionDays int, requireSynced bool) (int64, error)
	BackfillMediaHistoryUUIDs() (int64, error)
	GetMediaHistoryIdentityBackfillBatch(
		afterDBID int64, policyVersion int, limit int,
	) ([]MediaHistoryEntry, error)
	ResetMediaHistorySyncAfter(watermark *time.Time) error
	GetMediaHistorySyncBatch(after time.Time, afterDBID int64, limit int) ([]MediaHistoryEntry, error)
	MarkMediaHistorySynced(refs []MediaHistorySyncRef, syncedAt time.Time) error
	HealTimestamps(bootUUID string, trueBootTime time.Time) (int64, error)
	SumMediaPlayTimeForDay(dayStart time.Time) (int64, error)
	SumMediaPlayTimeForDayByProfile(dayStart time.Time, profileID string) (int64, error)
	AddMapping(m *Mapping) error
	GetMapping(id int64) (Mapping, error)
	DeleteMapping(id int64) error
	UpdateMapping(id int64, m *Mapping) error
	GetAllMappings() ([]Mapping, error)
	GetEnabledMappings() ([]Mapping, error)
	GetMediaUserData(systemID, path string) (MediaUserData, bool, error)
	SetMediaUserFavorite(systemID, path string, favorite bool) error
	SetMediaUserHidden(systemID, path string, hidden bool) error
	SetMediaUserFlag(systemID, path string, flag MediaUserFlag, value bool) error
	SetMediaUserLauncherOverride(systemID, path, launcherID string) error
	SetMediaUserSnapshot(systemID, path, mediaName, slug string, tags []string) error
	UpsertMediaUserData(data *MediaUserData) error
	DeleteMediaUserData(systemID, path string) error
	ListMediaUserData() ([]MediaUserData, error)

	// Library sync bookkeeping for personal state.
	ListLibraryStateSync() ([]LibraryStateSyncRow, error)
	UpsertLibraryStateSync(rows []LibraryStateSyncRow) error
	DeleteLibraryStateSync(identityKeys []string) error
	ClearLibraryStateSync() error

	// Decks
	CreateDeck(deck *Deck) error
	GetDeck(deckID string) (*Deck, error)
	ListDecks() ([]Deck, error)
	UpdateDeck(deckID string, edit func(deck *Deck) error) (*Deck, error)
	DeleteDeck(deckID string) (bool, error)
	UpsertRemoteDeck(deck *Deck) (bool, error)
	UpsertFetchedDeck(deck *Deck, maxCopies int) (FetchedDeckResult, error)
	SetDeckItemAnchor(itemDBID int64, anchor *DeckItemAnchor) error
	CountOwnedDecks() (int, error)
	RenameDeck(oldID, newID string) error

	// Library sync bookkeeping for decks.
	ListDeckSync() ([]DeckSyncRow, error)
	GetDeckSync(deckID string) (DeckSyncRow, bool, error)
	UpsertDeckSync(rows []DeckSyncRow) error
	DeleteDeckSync(deckIDs []string) error
	ClearDeckSync() error
	UpdateZapLinkHost(host string, zapscript int) error
	GetZapLinkHost(host string) (supported, found bool, err error)
	GetSupportedZapLinkHosts() ([]string, error)
	PruneExpiredZapLinkHosts(olderThan time.Duration) (int64, error)
	UpdateZapLinkCache(url string, zapscript string) error
	GetZapLinkCache(url string) (string, error)
	AddInboxMessage(msg *InboxMessage) (*InboxMessage, error)
	GetInboxMessages() ([]InboxMessage, error)
	DeleteInboxMessage(id int64) error
	DeleteAllInboxMessages() (int64, error)
	CreateClient(c *Client) error
	GetClientByToken(authToken string) (*Client, error)
	ListClients() ([]Client, error)
	ReplaceAllClients(clients []Client) error
	DeleteClient(clientID string) error
	UpdateClientLastSeen(authToken string, lastSeenAt int64) error
	CountClients() (int, error)
	CreateProfile(p *Profile) error
	GetProfile(profileID string) (*Profile, error)
	GetProfileBySwitchID(switchID string) (*Profile, error)
	ListProfiles() ([]Profile, error)
	UpdateProfile(p *Profile) error
	ActivateProfile(profileID string, lastUsedAt int64) error
	DeleteProfile(profileID string) error
	SetDeviceState(key, value string) error
	GetDeviceState(key string) (string, bool, error)
	DeleteDeviceState(key string) error
	ClaimRemoteCommand(command *RemoteCommand) (*RemoteCommand, bool, error)
	TransitionRemoteCommand(commandID, fromState, toState string, executionExpiresAt *time.Time) (bool, error)
	StoreRemoteCommandResult(
		commandID, fromState, status string, result json.RawMessage, errorCode string,
	) (bool, error)
	MarkRemoteCommandResultReported(commandID string) error
	ListUnreportedRemoteCommands(limit int) ([]RemoteCommand, error)
	ListRecentRemoteCommands(limit int) ([]RemoteCommand, error)
	PruneRemoteCommands(before time.Time) (int64, error)
	Backup(reason string, manual bool) (BackupInfo, error)
	BackupForUpdate(targetVersion string) (BackupInfo, func() error, error)
	BackupForTransfer(ctx context.Context, reason string) (BackupInfo, func() error, error)
	EnsureRecentBackup(maxAge time.Duration) (BackupInfo, bool, error)
	ListBackups() ([]BackupInfo, error)
	RestoreBackup(name string) (RestoreInfo, error)
	IntegrityReport() []string
	MarkCorrupt(reason string)
	IsMarkedCorrupt() bool
	ClearCorruptMarker() error
	NoteCorruption(err error) bool
	RecoverFromCorruption() (RestoreInfo, error)
}

type WALCheckpointMode added in v2.14.1

type WALCheckpointMode int
const (
	WALCheckpointAuto WALCheckpointMode = iota
	WALCheckpointSkip
	WALCheckpointForce
)

Directories

Path Synopsis
Package container decides when a directory of indexed media collapses to a single logical launch target, such as a disc folder holding one cue sheet beside its bin tracks.
Package container decides when a directory of indexed media collapses to a single logical launch target, such as a disc folder holding one cue sheet beside its bin tracks.
Package perfmetrics captures coarse process and SQLite resource counters for long media operations.
Package perfmetrics captures coarse process and SQLite resource counters for long media operations.
Package scraper defines the metadata scraper types and generic run loop.
Package scraper defines the metadata scraper types and generic run loop.
gamelistxml
Package gamelistxml implements a scraper that reads EmulationStation gamelist.xml files to enrich the Zaparoo MediaDB with developer, publisher, genre, rating, year, artwork paths, and descriptions.
Package gamelistxml implements a scraper that reads EmulationStation gamelist.xml files to enrich the Zaparoo MediaDB with developer, publisher, genre, rating, year, artwork paths, and descriptions.
localmedia
Package localmedia imports EmulationStation-style media folder artwork.
Package localmedia imports EmulationStation-style media folder artwork.
misterarcade
Package misterarcade imports the MiSTer arcade catalog's metadata onto indexed .mra rows.
Package misterarcade imports the MiSTer arcade catalog's metadata onto indexed .mra rows.
misterdocs
Package misterdocs imports metadata from MiSTer Downloader content installed under docs/<system> directories.
Package misterdocs imports metadata from MiSTer Downloader content installed under docs/<system> directories.
mra
Package mra reads the MAME set name out of a MiSTer arcade descriptor.
Package mra reads the MAME set name out of a MiSTer arcade descriptor.
pinuppopper
Package pinuppopper imports table metadata and artwork from a PinUP Popper install into the media database.
Package pinuppopper imports table metadata and artwork from a PinUP Popper install into the media database.

Jump to

Keyboard shortcuts

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