1import type { Database } from "bun:sqlite";2import { type Bucket, BucketWriteError, KEPR_PREFIX } from "../bucket/reader.ts";3import { filesAt, objectBytes, tarballGeneration } from "../mirror/queries.ts";4import { knownSave } from "../mirror/store.ts";5import { buildTarball } from "./tar.ts";67const tarballsOf = (repo: string) => `${KEPR_PREFIX}tarballs/${repo}/`;89101112131415export async function cachedTarball(16 bucket: Bucket,17 db: Database,18 repo: string,19 saveId: string,20): Promise<Uint8Array | null> {21 for (;;) {22 const generation = tarballGeneration(db);23 const key = `${tarballsOf(repo)}${generation}/${saveId}.tar.gz`;24 const stored = await bucket.get(key);25 26 27 28 29 const read = db.transaction(() => {30 if (tarballGeneration(db) !== generation) return "moved";31 if (stored) return "stored";32 if (!knownSave(db, saveId)) return "unknown";33 return filesAt(db, saveId).map((file) => {34 const bytes = objectBytes(db, file.hash);35 if (!bytes) throw new Error(`${repo}: object ${file.hash} is missing from the mirror`);36 return { path: file.path, bytes, executable: file.executable };37 });38 })();39 if (read === "moved") continue;40 if (read === "stored") return stored;41 if (read === "unknown") return null;42 const tarball = buildTarball(`${repo}-${saveId.slice(0, 8)}`, read);43 try {44 await bucket.put(key, tarball, "application/gzip");45 } catch (error) {46 if (!(error instanceof BucketWriteError)) throw error;47 }48 if (tarballGeneration(db) === generation) return tarball;49 }50}515253export async function dropTarballs(bucket: Bucket, repo: string): Promise<void> {54 for (const key of await bucket.list(tarballsOf(repo))) await bucket.remove(key);55}