1import type { Database } from "bun:sqlite";2import { rmSync } from "node:fs";3import { join } from "node:path";4import { inTurn } from "../bucket/reader.ts";5import type { SyncTarget } from "../bucket/sync.ts";6import { closeMirror } from "../mirror/store.ts";7import { keepState, setting } from "./db.ts";891011let running: Promise<void> | null = null;1213export type Removal = { repo: string; done: number; total: number; error: string };141516export function removal(kepr: Database): Removal | null {17 const repo = setting(kepr, "removing_repo");18 if (repo === null) return null;19 return {20 repo,21 done: Number(setting(kepr, "removing_done") ?? 0),22 total: Number(setting(kepr, "removing_total") ?? 0),23 error: setting(kepr, "removing_error") ?? "",24 };25}262728export function startRemoval(target: SyncTarget, repo: string): boolean {29 if (running) return false;30 running = deleteRepo(target, repo).finally(() => {31 running = null;32 });33 return true;34}353637export function removalRun(): Promise<void> | null {38 return running;39}4041async function deleteRepo(target: SyncTarget, repo: string): Promise<void> {42 const { bucket, kepr, mirrorDir } = target;43 keepState(kepr, "removing_repo", repo);44 keepState(kepr, "removing_error", null);45 try {46 const keys = await bucket.repoKeys(repo);47 keepState(kepr, "removing_total", String(keys.length));48 keepState(kepr, "removing_done", "0");49 let done = 0;50 51 await inTurn(keys, 4, async (key) => {52 await bucket.removeRepoKey(repo, key);53 done += 1;54 if (done % 20 === 0) keepState(kepr, "removing_done", String(done));55 });56 keepState(kepr, "removing_done", String(done));57 forgetRepo(kepr, mirrorDir, repo);58 keepState(kepr, "removing_repo", null);59 } catch (error) {60 61 keepState(kepr, "removing_error", error instanceof Error ? error.message : String(error));62 }63}646566export function forgetRepo(kepr: Database, mirrorDir: string, repo: string): void {67 closeMirror(mirrorDir, repo);68 for (const suffix of [".db", ".db-wal", ".db-shm"]) {69 rmSync(join(mirrorDir, `${repo}${suffix}`), { force: true });70 }71 const children = ["issue_entries", "issue_files", "issue_followers"];72 73 const own = [74 "blocked_reporters",75 "issues",76 "release_settings",77 "downloads",78 "download_days",79 "installs",80 "repo_text",81 ];82 kepr.transaction(() => {83 for (const table of children) {84 kepr85 .query(`DELETE FROM ${table} WHERE issue_id IN (SELECT id FROM issues WHERE repo = ?)`)86 .run(repo);87 }88 for (const table of own) kepr.query(`DELETE FROM ${table} WHERE repo = ?`).run(repo);89 kepr.query("DELETE FROM renamed WHERE old = ? OR new = ?").run(repo, repo);90 kepr.query("DELETE FROM repos WHERE name = ?").run(repo);91 })();92}