67 lines
3.8 KiB
TypeScript
67 lines
3.8 KiB
TypeScript
import type Database from 'better-sqlite3';
|
|
|
|
const initialized = new WeakSet<Database.Database>();
|
|
export function ensureGameFileSchema(handle: Database.Database): void {
|
|
if (initialized.has(handle)) return;
|
|
// Worker readers open only after the controller has initialized the schema.
|
|
if (handle.readonly) {
|
|
initialized.add(handle);
|
|
return;
|
|
}
|
|
handle.exec(`
|
|
CREATE TABLE IF NOT EXISTS game_file_libraries (
|
|
id TEXT PRIMARY KEY, name TEXT NOT NULL COLLATE NOCASE UNIQUE,
|
|
game TEXT NOT NULL CHECK(game IN ('t5','t6')), description TEXT NOT NULL,
|
|
created_at TEXT NOT NULL
|
|
);
|
|
CREATE TABLE IF NOT EXISTS game_file_versions (
|
|
id TEXT PRIMARY KEY, library_id TEXT NOT NULL REFERENCES game_file_libraries(id),
|
|
version TEXT NOT NULL, description TEXT NOT NULL, dlc TEXT NOT NULL,
|
|
state TEXT NOT NULL CHECK(state IN ('importing','ready','failed')),
|
|
size_bytes INTEGER NOT NULL DEFAULT 0, manifest TEXT NOT NULL DEFAULT '[]',
|
|
manifest_sha256 TEXT, created_at TEXT NOT NULL, published_at TEXT,
|
|
storage_location TEXT NOT NULL, source_host_id TEXT NOT NULL, source_volume TEXT NOT NULL,
|
|
error TEXT, operation_id TEXT, UNIQUE(library_id, version)
|
|
);
|
|
CREATE TABLE IF NOT EXISTS host_library_caches (
|
|
host_id TEXT NOT NULL, version_id TEXT NOT NULL REFERENCES game_file_versions(id),
|
|
status TEXT NOT NULL CHECK(status IN ('missing','downloading','verifying','ready','failed','outdated')),
|
|
bytes_downloaded INTEGER NOT NULL DEFAULT 0, bytes_total INTEGER NOT NULL DEFAULT 0,
|
|
manifest_sha256 TEXT, error TEXT, updated_at TEXT NOT NULL, operation_id TEXT,
|
|
PRIMARY KEY(host_id, version_id)
|
|
);
|
|
CREATE TABLE IF NOT EXISTS game_file_entries (
|
|
version_id TEXT NOT NULL REFERENCES game_file_versions(id), path TEXT NOT NULL,
|
|
size INTEGER NOT NULL, sha256 TEXT NOT NULL, PRIMARY KEY(version_id,path)
|
|
);
|
|
CREATE TABLE IF NOT EXISTS game_file_upload_entries (
|
|
version_id TEXT NOT NULL REFERENCES game_file_versions(id), path TEXT NOT NULL,
|
|
size INTEGER NOT NULL, last_modified INTEGER NOT NULL,
|
|
PRIMARY KEY(version_id,path)
|
|
);
|
|
CREATE TABLE IF NOT EXISTS game_file_cleanup (
|
|
version_id TEXT PRIMARY KEY REFERENCES game_file_versions(id),
|
|
started_at TEXT NOT NULL, removed_at TEXT
|
|
);
|
|
INSERT OR IGNORE INTO game_file_entries(version_id,path,size,sha256)
|
|
SELECT v.id,json_extract(f.value,'$.path'),json_extract(f.value,'$.size'),json_extract(f.value,'$.sha256')
|
|
FROM game_file_versions v,json_each(v.manifest) f;
|
|
CREATE TRIGGER IF NOT EXISTS game_file_versions_immutable BEFORE UPDATE ON game_file_versions
|
|
WHEN OLD.state = 'ready' BEGIN SELECT RAISE(ABORT, 'Published game-file versions are immutable'); END;
|
|
CREATE INDEX IF NOT EXISTS idx_host_library_caches_version ON host_library_caches(version_id);
|
|
`);
|
|
const versionColumns = handle.prepare('PRAGMA table_info(game_file_versions)').all() as {
|
|
name: string;
|
|
}[];
|
|
if (!versionColumns.some((column) => column.name === 'last_activity_at'))
|
|
handle.exec('ALTER TABLE game_file_versions ADD COLUMN last_activity_at TEXT');
|
|
const cacheColumns = handle.prepare('PRAGMA table_info(host_library_caches)').all() as {
|
|
name: string;
|
|
}[];
|
|
if (!cacheColumns.some((column) => column.name === 'bytes_verified'))
|
|
handle.exec('ALTER TABLE host_library_caches ADD COLUMN bytes_verified INTEGER');
|
|
if (!cacheColumns.some((column) => column.name === 'transfer_progress'))
|
|
handle.exec('ALTER TABLE host_library_caches ADD COLUMN transfer_progress TEXT');
|
|
initialized.add(handle);
|
|
}
|