import type { Database } from "bun:sqlite"; import { mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { filesAt, objectBytes } from "../mirror/queries.ts"; import { buildTarball } from "./tar.ts"; /** * A save's tarball from `//.tar.gz`, built there on first request. Built and * written in one synchronous run, so a strip landing mid-build can't leave a stale file behind. */ export function cachedTarball( tarballDir: string, db: Database, repo: string, saveId: string, ): Uint8Array { const path = join(tarballDir, repo, `${saveId}.tar.gz`); try { return readFileSync(path); } catch (error) { if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error; } const files = filesAt(db, saveId).map((file) => { const bytes = objectBytes(db, file.hash); if (!bytes) throw new Error(`${repo}: object ${file.hash} is missing from the mirror`); return { path: file.path, executable: file.executable, bytes }; }); const tarball = buildTarball(`${repo}-${saveId.slice(0, 8)}`, files); mkdirSync(join(tarballDir, repo), { recursive: true }); writeFileSync(`${path}.tmp`, tarball); renameSync(`${path}.tmp`, path); return tarball; } /** Drops cached tarballs a strip event reaches: one save's, or every one of the repo's for a whole-history strip. */ export function dropTarballs(tarballDir: string, repo: string, saveId: string): void { const target = saveId === "" ? join(tarballDir, repo) : join(tarballDir, repo, `${saveId}.tar.gz`); rmSync(target, { recursive: true, force: true }); }