Reconcile stale running scrapes and allow per-scrape delete

This commit is contained in:
Fisto
2026-09-17 08:40:28 +02:00
parent 0b72422afe
commit d886d22d89
7 changed files with 83 additions and 4 deletions
+19
View File
@@ -208,6 +208,25 @@ export class ScrapeEngine {
return { ...meta };
}
/**
* A scrape left `running` or `queued` on disk has no live job (the daemon that owned it is
* gone). Pause it so the UI shows the truth and Resume can pick it up.
*/
reconcileStaleJobs(): string[] {
const stale: string[] = [];
for (const meta of listScrapes(this.options.rootDir)) {
if (meta.state !== "running" && meta.state !== "queued") continue;
meta.state = "paused";
meta.updatedAt = new Date(this.now()).toISOString();
writeScrapeMeta(join(this.options.rootDir, meta.id), meta);
stale.push(meta.id);
}
return stale;
}
/** Cache previews for rows a finished or paused scrape never reached. Returns the count. */
async backfillPreviews(scrapeId: string): Promise<number> {
const meta = this.active.get(scrapeId) ?? readScrapeMeta(join(this.options.rootDir, scrapeId));
+26 -1
View File
@@ -25,7 +25,13 @@ import { betterCandidate } from "../src/dedupe.js";
import { aspectCondition, resolutionCondition } from "../src/filter-presets.js";
import { isNearLimit, planRetention } from "../src/retention.js";
import { isoFromSnowflake, snowflakeFromIso } from "../src/snowflake.js";
import { dirSizeBytes, listScrapes, ScrapeStore } from "../src/store.js";
import {
dirSizeBytes,
listScrapes,
readScrapeMeta,
ScrapeStore,
writeScrapeMeta
} from "../src/store.js";
import type { CoreAttachment, CoreMessage } from "../src/types.js";
let failures = 0;
@@ -877,6 +883,25 @@ void (async () => {
assert.equal(existsSync(join(dir, `${poster?.hash}.webp`)), true);
});
await test("stale running scrapes are paused on reconcile", async () => {
const backend = new FakeBackend([{ messages: [message("1400")] }]);
const engine = engineFor(root, backend);
const started = await engine.start({ channelId: "170", guildId: null, mode: "links", backend });
await engine.processQueue();
// Simulate a daemon that died mid-job: the meta still says running.
const dir = join(root, started.id);
const meta = readScrapeMeta(dir);
assert.ok(meta);
meta.state = "running";
writeScrapeMeta(dir, meta);
const fresh = new ScrapeEngine({ rootDir: root, previews: false });
const stale = fresh.reconcileStaleJobs();
assert.deepEqual(stale, [started.id]);
assert.equal(readScrapeMeta(dir)?.state, "paused");
});
await test("markPreview stores the animated gif reference", () => {
const dir = join(root, "anim-store");
mkdirSync(dir, { recursive: true });