import type { SelectedGame } from './SelectedGame' /** * One title as it exists on this machine: what was written, and where. * * This is the shape `state.json` carries, keyed `:`. Every path in it * is something the store put there and may therefore delete — which is why an * uninstall reads the record rather than guessing at paths. * * The catalog's `access` block is deliberately *not* part of it. Whether somebody may * download a title is the server's answer to a question asked now; a copy of it on disk * would go stale the moment a purchase or a refund happened, and a stale "yes" is the * dangerous direction. What is installed stays installed either way. */ export interface InstalledRecord extends Omit { /** The unpacked archive's directory; null for a hosted entry, which has none. */ readonly payload: string | null readonly executable: string | null /** `bundle` for a macOS `.app` the archive already contained, `exe` for a binary. */ readonly executableKind: string | null readonly icon: string | null /** The menu entry actually written, which is not always the one intended. */ readonly menuEntry: string | null readonly url: string | null } /** * The scope a state record belongs to. * * `scope` is what the engine writes today. `system` is what the Batocera store * wrote before the shared core existed, and there the two were the same string — * so an installed machine keeps working without a migration. */ export function recordScope (scope: string | null, system: string | null): string { return scope ?? system ?? '' } /** Two scopes may both carry a game called `foo` — key on both. */ export function gameKey (record: { readonly name: string; readonly scope: string }): string { return `${record.scope}:${record.name}` } /** Resolve user-typed `name` or `scope:name` arguments to state keys. */ export function matchStateKeys ( installed: ReadonlyMap, names: readonly string[] ): readonly string[] { const wanted = new Set(names.map((name: string): string => name.toLowerCase())) const keys: string[] = [] for (const [key, record] of installed) { if (wanted.has(key.toLowerCase()) || wanted.has(record.name.toLowerCase())) keys.push(key) } return keys } /** Keep only the games the user named, by bare name or `scope:name`. */ export function limitToNames ( games: readonly SelectedGame[], names: readonly string[] ): readonly SelectedGame[] { const wanted = new Set(names.map((name: string): string => name.toLowerCase())) return games.filter((game: SelectedGame): boolean => wanted.has(game.name.toLowerCase()) || wanted.has(gameKey(game).toLowerCase())) }