commit 3ceadf7606a69ca9b95e9a7a00622c2344e3bce9
parent 02318dfc0e426f2bc020b0c5ffb4ec5fed9e92f6
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Fri, 25 Sep 2026 18:47:40 -0400
common: a new video dir rebases the channel's title memo instead of dropping it
The source-3 title memo was keyed by data/'s mtime and dropped whole when
it changed, so every new dir a download lane added re-read every
metadata.info.json on the channel (8,049 on the-quartering-rumble). It is
now rebased: one readdir keeps the titles of the dirs still present and
only new dirs are read; a removed dir's title goes with it (release 8
review, V). videoTitles.test.ts: the memo case asserts 1 read for a new
dir and a fall-through for a removed one.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Diffstat:
2 files changed, 44 insertions(+), 9 deletions(-)
diff --git a/common/controller/videoTitles.test.ts b/common/controller/videoTitles.test.ts
@@ -289,22 +289,38 @@ test("metadata titles are memoized per channel until data/ changes", async () =>
assert.equal(second.get("b")?.title, "Bravo");
assert.equal(videoTitleMetadataReadCount() - r1, 0);
- // A new video dir changes data/'s mtime: the channel's memo is dropped and
- // every id is read again ("a" now has no file, so no title).
+ // A new video dir changes data/'s mtime: the memo is REBASED — the dirs
+ // still there keep their titles and only the new one is read (release 8
+ // review, V). "a"'s dir is still there, so its memoized title stands.
await writeInfo(paths, "c", { id: "c", title: "Charlie" });
const r2 = videoTitleMetadataReadCount();
const third = await readChannelVideoTitles(paths, SLUG, ["a", "b", "c"]);
- assert.equal(third.has("a"), false);
+ assert.equal(third.get("a")?.title, "Alpha");
+ assert.equal(third.get("b")?.title, "Bravo");
assert.equal(third.get("c")?.title, "Charlie");
- assert.equal(videoTitleMetadataReadCount() - r2, 3);
+ assert.equal(videoTitleMetadataReadCount() - r2, 1);
+
+ // A REMOVED dir takes its title with it: the rebase keeps only the dirs
+ // still present, so "a" falls through, is read, and has no title.
+ await rm(path.join(dataDir, "a"), { recursive: true, force: true });
+ // Two changes inside one filesystem timestamp tick would share an mtime;
+ // pin a distinct one so the rebase certainly fires.
+ const later = new Date("2026-01-02T00:00:00Z");
+ await utimes(dataDir, later, later);
+ const r3 = videoTitleMetadataReadCount();
+ const fourth = await readChannelVideoTitles(paths, SLUG, ["a", "b", "c"]);
+ assert.equal(fourth.has("a"), false);
+ assert.equal(fourth.get("c")?.title, "Charlie");
+ assert.equal(videoTitleMetadataReadCount() - r3, 1);
// A miss is not memoized: a title that lands later is found.
+ await mkdir(path.join(dataDir, "a"), { recursive: true });
await writeFile(
path.join(dataDir, "a", "metadata.info.json"),
JSON.stringify({ id: "a", title: "Alpha again" }),
);
- const fourth = await readChannelVideoTitles(paths, SLUG, ["a"]);
- assert.equal(fourth.get("a")?.title, "Alpha again");
+ const fifth = await readChannelVideoTitles(paths, SLUG, ["a"]);
+ assert.equal(fifth.get("a")?.title, "Alpha again");
} finally {
resetVideoTitleMemo();
await rm(dir, { recursive: true, force: true });
diff --git a/common/controller/videoTitles.ts b/common/controller/videoTitles.ts
@@ -29,7 +29,7 @@
// invariant).
import { existsSync } from "node:fs";
-import { open as openFile, stat } from "node:fs/promises";
+import { open as openFile, readdir, stat } from "node:fs/promises";
import path from "node:path";
import { open } from "lmdb";
import type { Paths } from "../lib/paths";
@@ -156,7 +156,10 @@ async function readMetadataTitle(videoDir: string): Promise<string | null> {
// 8,049 such dirs: 5,024 ms cold / 224 ms warm per render, measured 2026-09-25.
//
// Per channel, keyed by the `data/` directory's mtime: adding or removing a
-// video dir changes it and drops the channel's entry. Rewriting a
+// video dir changes it, and the entry is then REBASED rather than dropped — the
+// titles of the dirs still there carry over (one readdir, no file reads), and
+// only the new dirs are read (release 8 review, V: a busy download lane adds a
+// dir every few minutes, and each one used to re-read all 8,049). Rewriting a
// metadata.info.json INSIDE an existing dir does not — accepted, because a
// video's title does not change after download. Misses (no file yet, e.g. a dir
// mid-download) are NOT memoized, so a title that lands later is picked up.
@@ -196,8 +199,24 @@ async function memoForChannel(
}
const memo = titleMemo();
let entry = memo.get(key);
- if (!entry || entry.dataDirMtimeMs !== mtimeMs) {
+ if (!entry) {
entry = { dataDirMtimeMs: mtimeMs, titles: new Map() };
+ } else if (entry.dataDirMtimeMs !== mtimeMs) {
+ // Keep the titles of the dirs that are still there; a removed dir's title
+ // goes with it, so a re-listed id falls through to the other sources.
+ let present: Set<string> | null = null;
+ try {
+ present = new Set(await readdir(dataDir));
+ } catch {
+ present = null;
+ }
+ const kept = new Map<string, string>();
+ if (present) {
+ for (const [id, title] of entry.titles) {
+ if (present.has(id)) kept.set(id, title);
+ }
+ }
+ entry = { dataDirMtimeMs: mtimeMs, titles: kept };
}
// Re-insert so iteration order is least-recently-used first.
memo.delete(key);