import type { Paths } from "../lib/paths"; import type { AutoQueueOrder } from "../jobs/autoQueuePolicy"; import { buildRecencyKeys, makeRecencyComparator } from "./recencyIndex"; // Recency ordering for the per-channel BATCH controllers (the digest and // backfill lanes), as distinct from the auto-queue runners. // // The runners already sort by upload date through recencyIndex; the batches did // not, so "newest first" meant one thing on two of the four pipelines and // nothing on the other two. This is the shared half — one call site's worth of // glue, so digest and backfill cannot drift into two different notions of what // "newest" means. // // Three things are settled here rather than at each call site: // // * `order: "listed"` returns null, and a null comparator means DO NOT SORT. // That is what reproduces today byte-for-byte: the historical order is // reproduced by not sorting at all, never by sorting with an identity // comparator, which would still perturb ties on some engines. // * `interpolate: false`. A batch's candidates come from a readdir of the // channel's data/ dir, so every one of them is downloaded and has a // metadata.info.json for the tail read. Playlist interpolation exists for // videos with nothing on disk, which by construction cannot be here. // * every id is owned by this channel. That is not a convenience — it is what // keeps layer 1 out of the wrong key space (see recencyIndex's layer-1 // comment: candidate ids here are DIRECTORY names). // // Never throws. A recency lookup failing is not a reason to refuse to digest a // channel, so a broken or mid-rebuild index degrades to the listed order. export async function batchRecencyComparator( paths: Paths, channelSlug: string, ids: ReadonlyArray, order: AutoQueueOrder, ): Promise<((a: string, b: string) => number) | null> { if (order === "listed" || ids.length === 0) return null; try { const owner = new Map(); for (const id of ids) owner.set(id, channelSlug); const keys = await buildRecencyKeys({ paths, meta: [{ slug: channelSlug }], candidateIds: new Set(ids), owner, interpolate: false, }); return makeRecencyComparator(keys, order); } catch { return null; } }