import path from "node:path"; import { readdir, readFile } from "node:fs/promises"; import { availabilityFromJsonField } from "../lib/availability"; import { loadAvailability, recordAvailability, } from "../lib/availability-server"; import type { Paths } from "../lib/paths"; export type BackfillAvailabilityOpts = { channelSlug: string; paths: Paths; onLog?: (msg: string) => void; signal?: AbortSignal; }; export type BackfillAvailabilityResult = { wrote: number; skippedExisting: number; skippedNoMeta: number; }; export async function backfillAvailabilityFromMetadata({ channelSlug, paths, onLog, signal, }: BackfillAvailabilityOpts): Promise { const log = onLog ?? ((m: string) => console.log(m)); const dataDir = path.join(paths.channelsDir, channelSlug, "data"); const ids = await readdir(dataDir).catch(() => [] as string[]); let wrote = 0; let skippedExisting = 0; let skippedNoMeta = 0; for (const id of ids) { if (signal?.aborted) break; const videoDir = path.join(dataDir, id); if (await loadAvailability(videoDir)) { skippedExisting++; continue; } let metaRaw: string; try { metaRaw = await readFile( path.join(videoDir, "metadata.info.json"), "utf8", ); } catch { skippedNoMeta++; continue; } let parsed: { availability?: unknown; webpage_url?: unknown }; try { parsed = JSON.parse(metaRaw); } catch { skippedNoMeta++; continue; } await recordAvailability(videoDir, { availability: availabilityFromJsonField(parsed.availability), observedAt: new Date().toISOString(), source: "backfill", webpageUrl: typeof parsed.webpage_url === "string" ? parsed.webpage_url : undefined, }).catch((err) => { log( `Warn ${id}: failed to record availability history: ${(err as Error)?.message ?? String(err)}`, ); }); wrote++; } log( `Availability backfill: wrote ${wrote}, skipped ${skippedExisting} existing, ${skippedNoMeta} without metadata.`, ); return { wrote, skippedExisting, skippedNoMeta }; }