import { safeRevalidate } from "../../lib/safeRevalidate"; import { getPaths } from "yt-dlp-transcript-common/lib/paths"; import { runManagedFunction, type StreamActionResult, } from "yt-dlp-transcript-common/jobs/streamCommand"; import { evictClipWindows, evictClipWindowsSummary, } from "yt-dlp-transcript-common/controller/evictClipWindows"; // ONE ENQUEUE OF THE CLIP-WINDOW EVICTION, for the callers that have one: the // control on /storage and the `/api/ops/evict-clips` adapter. // // Deliberately NOT in `actions.ts`: that file carries "use server", so every // non-type export in it is a server action, and a shared helper exported from // there would put an unguarded enqueue on the wire under its own endpoint. Same // reason `lib/repointJob.ts` and `lib/savedVideosJob.ts` exist. // // A JOB AND NOT AN INLINE ACTION, unlike the five small things on /storage: a // corpus-wide pass is a readdir of every video directory on the machine // (eleven thousand on the largest channel here), it DELETES, and the operator // is entitled to a record and a log naming every file that went. // // `channelSlug` is set only for a one-channel run. That is what makes the // snapshot regen land where it should: the run changes `totalMediaBytes` and // `totalClipsBytes`, so the report IS stale afterwards — and a corpus-wide run // deliberately regenerates nothing rather than queueing a full walk of every // channel behind itself. export async function enqueueEvictClipWindows(opts: { slug?: string; olderThanDays: number; dryRun?: boolean; }): Promise { const paths = getPaths(); return runManagedFunction({ kind: "evict-clips", // Parallel: it touches `clips/`, which no other kind reads or writes, and // the `needsMedia` guard is what keeps it off a channel mid-relocation. queueKey: "", paths, ...(opts.slug ? { channelSlug: opts.slug } : {}), fn: async (onLog, signal) => { onLog( `${opts.dryRun ? "Previewing" : "Evicting"} clip windows ` + `${opts.slug ? `in ${opts.slug}` : "across every channel"} ` + `not touched for ${opts.olderThanDays} day(s).`, ); // SAY THE LIMITATION IN THE LOG, not only in the button's help text. A // window a umtool report still cites is indistinguishable from one // nothing wants — the manifests live in a umtool project the editor // cannot see — so eviction is by age and the record says so. onLog( "By age only: nothing here can know whether a report still cites a " + "window. An evicted one is re-fetchable; it costs a fetch, not data.", ); const result = await evictClipWindows({ paths, ...(opts.slug ? { slug: opts.slug } : {}), olderThanDays: opts.olderThanDays, ...(opts.dryRun ? { dryRun: true } : {}), onLog, signal, }); onLog(evictClipWindowsSummary(result)); safeRevalidate(["/storage"]); if (opts.slug) safeRevalidate([`/channels/${opts.slug}`]); }, }); }