Files
cad-data-router/services/destinationScanner.js
Davide Grilli a49d338741 perf(scanner): ottimizza scansione destinazione con pass unico e barra determinata
Sostituisce le due scansioni sequenziali (cartelle + file CAD) con un
unico passaggio parallelo in services/destinationScanner.js. La lettura
di primo livello fornisce il totale delle sezioni, rendendo la barra
di progresso determinata (N / totale) durante l'analisi della destinazione.
Il label mostra contemporaneamente sezioni completate e file CAD trovati
2026-03-16 15:16:27 +01:00

115 lines
3.3 KiB
JavaScript

const fs = require('fs-extra');
const path = require('path');
const { getCadInfo } = require('./router');
const CODE_FOLDER_REGEX = /^\d{3}$/;
const MAX_SCAN_DEPTH = 6;
function toCadKey(cadInfo) {
return String(cadInfo?.key || '').toLowerCase();
}
/**
* Scansiona la cartella di destinazione in un unico passaggio parallelo e
* costruisce contemporaneamente:
* - folderIndex: Map<group3digits, fullPath[]> (per il routing)
* - cadKeyIndex: Set<cadKey> (per il rilevamento duplicati)
*
* Legge prima le sottocartelle di primo livello (1 sola readdir) per ottenere
* il totale e mostrare una barra determinata durante la scansione.
*/
async function buildDestinationIndexes(destinationRoot, onProgress) {
const folderIndex = new Map();
const cadKeyIndex = new Set();
if (!destinationRoot || !(await fs.pathExists(destinationRoot))) {
return { folderIndex, cadKeyIndex };
}
// Lettura di primo livello: 1 sola chiamata per avere il totale
let topEntries;
try {
topEntries = await fs.readdir(destinationRoot, { withFileTypes: true });
} catch {
return { folderIndex, cadKeyIndex };
}
const topDirs = topEntries
.filter((e) => e.isDirectory())
.map((e) => ({ name: e.name, fullPath: path.join(destinationRoot, e.name) }));
const total = topDirs.length;
let completed = 0;
let scannedFiles = 0;
// Registra i file CAD al primo livello (non cartelle)
for (const entry of topEntries) {
if (!entry.isFile()) continue;
const cadInfo = getCadInfo(entry.name);
if (cadInfo) {
scannedFiles += 1;
cadKeyIndex.add(toCadKey(cadInfo));
}
}
// Walk ricorsivo del sottoalbero di una cartella di primo livello
async function walkSubtree(dirPath, depth) {
if (depth > MAX_SCAN_DEPTH) {
return;
}
let entries;
try {
entries = await fs.readdir(dirPath, { withFileTypes: true });
} catch {
return;
}
const subdirPromises = [];
for (const entry of entries) {
const fullPath = path.join(dirPath, entry.name);
if (entry.isDirectory()) {
if (CODE_FOLDER_REGEX.test(entry.name)) {
const rows = folderIndex.get(entry.name) || [];
rows.push(fullPath);
folderIndex.set(entry.name, rows);
}
subdirPromises.push(walkSubtree(fullPath, depth + 1));
} else if (entry.isFile()) {
const cadInfo = getCadInfo(entry.name);
if (cadInfo) {
scannedFiles += 1;
onProgress?.({ phase: 'index-dup', scanned: scannedFiles, file: entry.name });
cadKeyIndex.add(toCadKey(cadInfo));
}
}
}
await Promise.all(subdirPromises);
}
// Processa tutte le cartelle di primo livello in parallelo
await Promise.all(
topDirs.map(async ({ name, fullPath }) => {
onProgress?.({ phase: 'index-dest', current: completed, total, folder: name });
if (CODE_FOLDER_REGEX.test(name)) {
const rows = folderIndex.get(name) || [];
rows.push(fullPath);
folderIndex.set(name, rows);
}
await walkSubtree(fullPath, 1);
completed += 1;
onProgress?.({ phase: 'index-dest', current: completed, total, folder: name });
})
);
return { folderIndex, cadKeyIndex };
}
module.exports = { buildDestinationIndexes, toCadKey };