Continue new scrapes where the last complete one started
This commit is contained in:
@@ -657,15 +657,27 @@ export class ScrapeStore {
|
||||
.run(preview.hash, preview.bytes, preview.ext ?? "webp", id);
|
||||
}
|
||||
|
||||
/** Message span of the walk, for completeness checks. */
|
||||
dbSummary(): { messages: number; oldest: string | null; newest: string | null } {
|
||||
/** Message span of the walk, for completeness checks and incremental scrapes. */
|
||||
dbSummary(): {
|
||||
messages: number;
|
||||
oldest: string | null;
|
||||
newest: string | null;
|
||||
newestId: string | null;
|
||||
} {
|
||||
const row = this.db
|
||||
.prepare(
|
||||
"SELECT COUNT(*) AS messages, MIN(timestamp) AS oldest, MAX(timestamp) AS newest FROM messages"
|
||||
"SELECT COUNT(*) AS messages, MIN(timestamp) AS oldest, MAX(timestamp) AS newest, MAX(id) AS newestId FROM messages"
|
||||
)
|
||||
.get() as { messages: number; oldest: string | null; newest: string | null } | undefined;
|
||||
.get() as
|
||||
| { messages: number; oldest: string | null; newest: string | null; newestId: string | null }
|
||||
| undefined;
|
||||
|
||||
return { messages: row?.messages ?? 0, oldest: row?.oldest ?? null, newest: row?.newest ?? null };
|
||||
return {
|
||||
messages: row?.messages ?? 0,
|
||||
oldest: row?.oldest ?? null,
|
||||
newest: row?.newest ?? null,
|
||||
newestId: row?.newestId ?? null
|
||||
};
|
||||
}
|
||||
|
||||
/** Preview state for the whole scrape and per kind, for the backfill UI. */
|
||||
|
||||
@@ -1437,6 +1437,23 @@ void (async () => {
|
||||
reopened.close();
|
||||
});
|
||||
|
||||
await test("dbSummary reports the message span and newest id", () => {
|
||||
const dir = join(root, "summary");
|
||||
mkdirSync(dir, { recursive: true });
|
||||
const store = new ScrapeStore(dir);
|
||||
store.insertPage([
|
||||
message("1800", { timestamp: "2026-09-16T00:00:00.000Z" }),
|
||||
message("1801", { timestamp: "2026-09-17T00:00:00.000Z" })
|
||||
]);
|
||||
|
||||
const summary = store.dbSummary();
|
||||
assert.equal(summary.messages, 2);
|
||||
assert.equal(summary.oldest, "2026-09-16T00:00:00.000Z");
|
||||
assert.equal(summary.newest, "2026-09-17T00:00:00.000Z");
|
||||
assert.equal(summary.newestId, "1801");
|
||||
store.close();
|
||||
});
|
||||
|
||||
await test("message links build for guilds and DMs", () => {
|
||||
assert.equal(messageUrlFor("123", "456", "789"), "https://discord.com/channels/123/456/789");
|
||||
assert.equal(messageUrlFor(null, "456", "789"), "https://discord.com/channels/@me/456/789");
|
||||
|
||||
+43
-3
@@ -632,8 +632,32 @@ export class MediaService {
|
||||
|
||||
try {
|
||||
const mode = input.mode ?? "links";
|
||||
const afterId = input.after ? snowflakeFromIso(input.after) : null;
|
||||
const beforeId = input.before ? snowflakeFromIso(input.before) : null;
|
||||
let afterId = input.after ? snowflakeFromIso(input.after) : null;
|
||||
let beforeId = input.before ? snowflakeFromIso(input.before) : null;
|
||||
let fromDate = input.after ?? null;
|
||||
|
||||
// Continuing a channel: stop where the best completed scrape of it starts, so a new
|
||||
// walk only covers what is newer instead of re-fetching the whole history.
|
||||
if (!afterId && !beforeId) {
|
||||
const previous = this.previousCompleteScrape(input.channelId);
|
||||
if (previous) {
|
||||
const store = new ScrapeStore(scrapeDir(this.rootDir, previous.id));
|
||||
try {
|
||||
const summary = store.dbSummary();
|
||||
if (summary.newestId) {
|
||||
afterId = summary.newestId;
|
||||
fromDate = summary.newest;
|
||||
this.deps.log?.(
|
||||
`continuing ${input.channelId} after ${summary.newestId} ` +
|
||||
`(from scrape ${previous.id})`
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
store.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let backend: ScrapeBackend;
|
||||
|
||||
if (input.backend === "puppet") {
|
||||
@@ -672,7 +696,7 @@ export class MediaService {
|
||||
downloadDir: input.downloadDir ?? null,
|
||||
afterId,
|
||||
beforeId,
|
||||
fromDate: input.after ?? null,
|
||||
fromDate,
|
||||
toDate: input.before ?? null,
|
||||
estimatedMessages
|
||||
});
|
||||
@@ -747,6 +771,22 @@ export class MediaService {
|
||||
return result?.messages?.find(candidate => candidate?.id === messageId) ?? result?.messages?.[0] ?? null;
|
||||
}
|
||||
|
||||
/** The completed scrape of a channel that walked the most, if any. */
|
||||
private previousCompleteScrape(channelId: string): ScrapeMeta | null {
|
||||
const candidates = this.engine
|
||||
.list()
|
||||
.filter(
|
||||
meta =>
|
||||
meta.channelId === channelId &&
|
||||
meta.state === "done" &&
|
||||
meta.messages > 0 &&
|
||||
meta.mode === "links"
|
||||
);
|
||||
|
||||
if (!candidates.length) return null;
|
||||
return candidates.sort((a, b) => b.messages - a.messages)[0] ?? null;
|
||||
}
|
||||
|
||||
/** Continue a paused scrape from its cursor with the same backend family. */
|
||||
async resumeScrape(scrapeId: string): Promise<ScrapeMeta | null> {
|
||||
const meta = this.engine.get(scrapeId);
|
||||
|
||||
Reference in New Issue
Block a user