From 1cbb33a4e2e34441751ebe89acc53f64585342f3 Mon Sep 17 00:00:00 2001 From: openhands Date: Mon, 3 Aug 2026 19:52:09 +0200 Subject: [PATCH] perf: speed up catalog audit by batching file checks and parallelizing source fetches --- src/lib/services/catalog-audit.ts | 73 ++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 25 deletions(-) diff --git a/src/lib/services/catalog-audit.ts b/src/lib/services/catalog-audit.ts index 8707823b..843e353c 100644 --- a/src/lib/services/catalog-audit.ts +++ b/src/lib/services/catalog-audit.ts @@ -1,5 +1,4 @@ -import { existsSync } from "node:fs"; -import path from "node:path"; +import { readdir } from "node:fs/promises"; import { sql } from "drizzle-orm"; import { db } from "@/lib/db"; import { fetchSourceFurnidata } from "@/lib/services/clone-import"; @@ -141,6 +140,13 @@ export async function runCatalogAudit( return; } + const [iconFiles, nitroFiles] = await Promise.all([ + readdir(iconDir).catch(() => [] as string[]), + readdir(nitroDir).catch(() => [] as string[]), + ]); + const iconSet = new Set(iconFiles); + const nitroSet = new Set(nitroFiles); + onEvent?.({ type: "progress", message: "Loading items_base…" }); let items: Array<{ @@ -236,7 +242,7 @@ export async function runCatalogAudit( }); // Only include items that actually have a .nitro asset — a catalog // entry for a furni without a bundle would be un-placeable in-game. - if (existsSync(path.join(nitroDir, `${item.item_name}.nitro`))) { + if (nitroSet.has(`${item.item_name}.nitro`)) { missingCatalogEntries.push({ classname: item.item_name, itemId: item.id, @@ -305,8 +311,7 @@ export async function runCatalogAudit( const missingNitroClassnames: string[] = []; for (let i = 0; i < items.length; i++) { const item = items[i]; - const nitroPath = path.join(nitroDir, `${item.item_name}.nitro`); - if (!existsSync(nitroPath)) { + if (!nitroSet.has(`${item.item_name}.nitro`)) { missingNitroClassnames.push(item.item_name); issues.push({ type: "missing_nitro", @@ -331,8 +336,7 @@ export async function runCatalogAudit( const missingIconClassnames: string[] = []; for (let i = 0; i < items.length; i++) { const item = items[i]; - const iconPath = path.join(iconDir, `${item.item_name}_icon.png`); - if (!existsSync(iconPath)) { + if (!iconSet.has(`${item.item_name}_icon.png`)) { missingIconClassnames.push(item.item_name); issues.push({ type: "missing_icon", @@ -383,7 +387,7 @@ export async function runCatalogAudit( // and drop the ones the repair fixed from the issue list. const stillMissing: string[] = []; for (const classname of missingIconClassnames) { - if (!existsSync(path.join(iconDir, `${classname}_icon.png`))) { + if (!iconSet.has(`${classname}_icon.png`)) { stillMissing.push(classname); } } @@ -438,7 +442,7 @@ export async function runCatalogAudit( // and drop the ones the repair fixed from the issue list. const stillMissing: string[] = []; for (const classname of missingNitroClassnames) { - if (!existsSync(path.join(nitroDir, `${classname}.nitro`))) { + if (!nitroSet.has(`${classname}.nitro`)) { stillMissing.push(classname); } } @@ -601,27 +605,46 @@ export async function runCatalogAudit( onEvent?.({ type: "progress", message: "Comparing with clone sources…" }); const sources = await listSources(); - for (const source of sources) { - if (!source.furnidataUrl) continue; - try { - const entries = await fetchSourceFurnidata(source.furnidataUrl); - for (const entry of entries) { - if (!itemByName.has(entry.classname)) { - missingFromSources.push({ - sourceName: source.name, - sourceId: source.id, - classname: entry.classname, - itemName: entry.name, - }); + const sourceResults = await Promise.all( + sources + .filter((s) => s.furnidataUrl) + .map(async (source) => { + try { + const entries = await fetchSourceFurnidata(source.furnidataUrl); + return { + source, + entries, + error: null, + } as const; + } catch { + return { + source, + entries: [] as Array<{ classname: string; name: string }>, + error: true, + } as const; } - } - } catch { + }), + ); + + for (const result of sourceResults) { + if (result.error) { issues.push({ type: "source_fetch_failed", severity: "error", - sourceName: source.name, - message: `Failed to fetch furnidata from "${source.name}"`, + sourceName: result.source.name, + message: `Failed to fetch furnidata from "${result.source.name}"`, }); + continue; + } + for (const entry of result.entries) { + if (!itemByName.has(entry.classname)) { + missingFromSources.push({ + sourceName: result.source.name, + sourceId: result.source.id, + classname: entry.classname, + itemName: entry.name, + }); + } } }