From 164959acaff3488baddcc3fed3555b4cad68c601 Mon Sep 17 00:00:00 2001 From: Davide Grilli Date: Mon, 16 Mar 2026 10:05:13 +0100 Subject: [PATCH] feat(processing): smistamento ricorsivo cartelle + gestione unrouted per ZIP e folder --- renderer/renderer.js | 1 + services/folderProcessor.js | 51 +++++++++++++++++++++++++++-------- services/unrouted.js | 53 +++++++++++++++++++++++++++++++++++++ services/zipProcessor.js | 15 ++++++++--- 4 files changed, 106 insertions(+), 14 deletions(-) create mode 100644 services/unrouted.js diff --git a/renderer/renderer.js b/renderer/renderer.js index 2a8255a..6294442 100644 --- a/renderer/renderer.js +++ b/renderer/renderer.js @@ -33,6 +33,7 @@ function renderResult(title, result) { `scansionati: ${result.scanned ?? 0}`, `copiati: ${result.copied ?? 0}`, `saltati: ${result.skipped ?? 0}`, + `non smistati: ${result.unrouted ?? 0}`, '', 'dettagli (max 20):', detailsText, diff --git a/services/folderProcessor.js b/services/folderProcessor.js index a694950..12be5b5 100644 --- a/services/folderProcessor.js +++ b/services/folderProcessor.js @@ -2,23 +2,45 @@ const fs = require('fs-extra'); const path = require('path'); const { getDestinationDecision, isCadFile } = require('./router'); const { buildDestinationIndex } = require('./destinationIndex'); +const { getUnroutedTarget } = require('./unrouted'); + +async function collectFilesRecursively(rootDir) { + const files = []; + + async function walk(currentDir) { + const entries = await fs.readdir(currentDir, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = path.join(currentDir, entry.name); + + if (entry.isDirectory()) { + await walk(fullPath); + continue; + } + + if (entry.isFile()) { + files.push({ fullPath, fileName: entry.name }); + } + } + } + + await walk(rootDir); + return files; +} async function processFolder(folder, config) { - const entries = await fs.readdir(folder, { withFileTypes: true }); + const files = await collectFilesRecursively(folder); const destinationIndex = await buildDestinationIndex(config?.destination); const result = { scanned: 0, copied: 0, skipped: 0, + unrouted: 0, details: [], }; - for (const entry of entries) { - if (!entry.isFile()) { - continue; - } - - const file = entry.name; + for (const { fullPath, fileName } of files) { + const file = fileName; result.scanned += 1; if (!isCadFile(file)) { @@ -26,18 +48,25 @@ async function processFolder(folder, config) { continue; } - const src = path.join(folder, file); const decision = getDestinationDecision(file, config, destinationIndex); const destDir = decision.destination; if (!destDir) { - result.skipped += 1; - result.details.push({ file, reason: decision.reason || 'Nessuna regola trovata' }); + const unroutedTarget = await getUnroutedTarget(file); + await fs.copy(fullPath, unroutedTarget.destinationPath, { overwrite: false }); + + result.copied += 1; + result.unrouted += 1; + result.details.push({ + file, + destination: unroutedTarget.destinationDir, + reason: decision.reason || 'Nessuna regola trovata', + }); continue; } const dest = path.join(destDir, file); - await fs.copy(src, dest, { overwrite: true }); + await fs.copy(fullPath, dest, { overwrite: true }); result.copied += 1; result.details.push({ file, destination: destDir }); diff --git a/services/unrouted.js b/services/unrouted.js new file mode 100644 index 0000000..b8b558e --- /dev/null +++ b/services/unrouted.js @@ -0,0 +1,53 @@ +const fs = require('fs-extra'); +const path = require('path'); +const os = require('os'); + +const PRIMARY_UNROUTED_DIR = '/cadroute/__NON_SMISTATI'; +const HOME_UNROUTED_DIR = path.join(os.homedir(), '.cadroute', '__NON_SMISTATI'); + +let resolvedUnroutedDirPromise = null; + +async function resolveUnroutedDir() { + if (!resolvedUnroutedDirPromise) { + resolvedUnroutedDirPromise = (async () => { + try { + await fs.ensureDir(PRIMARY_UNROUTED_DIR); + return PRIMARY_UNROUTED_DIR; + } catch { + await fs.ensureDir(HOME_UNROUTED_DIR); + return HOME_UNROUTED_DIR; + } + })(); + } + + return resolvedUnroutedDirPromise; +} + +async function getUniquePath(destinationDir, fileName) { + const parsed = path.parse(fileName); + let candidate = path.join(destinationDir, fileName); + let counter = 1; + + while (await fs.pathExists(candidate)) { + candidate = path.join(destinationDir, `${parsed.name}__${counter}${parsed.ext}`); + counter += 1; + } + + return candidate; +} + +async function getUnroutedTarget(fileName) { + const destinationDir = await resolveUnroutedDir(); + const destinationPath = await getUniquePath(destinationDir, fileName); + + return { + destinationDir, + destinationPath, + }; +} + +module.exports = { + getUnroutedTarget, + PRIMARY_UNROUTED_DIR, + HOME_UNROUTED_DIR, +}; diff --git a/services/zipProcessor.js b/services/zipProcessor.js index a8c838e..0e23d08 100644 --- a/services/zipProcessor.js +++ b/services/zipProcessor.js @@ -4,6 +4,7 @@ const path = require('path'); const { pipeline } = require('stream/promises'); const { getDestinationDecision, isCadFile } = require('./router'); const { buildDestinationIndex } = require('./destinationIndex'); +const { getUnroutedTarget } = require('./unrouted'); async function processZip(zipPath, config) { const stream = fs.createReadStream(zipPath).pipe(unzipper.Parse({ forceStream: true })); @@ -12,6 +13,7 @@ async function processZip(zipPath, config) { scanned: 0, copied: 0, skipped: 0, + unrouted: 0, details: [], }; @@ -35,9 +37,16 @@ async function processZip(zipPath, config) { const destDir = decision.destination; if (!destDir) { - result.skipped += 1; - result.details.push({ file: baseName, reason: decision.reason || 'Nessuna regola trovata' }); - entry.autodrain(); + const unroutedTarget = await getUnroutedTarget(baseName); + await pipeline(entry, fs.createWriteStream(unroutedTarget.destinationPath)); + + result.copied += 1; + result.unrouted += 1; + result.details.push({ + file: baseName, + destination: unroutedTarget.destinationDir, + reason: decision.reason || 'Nessuna regola trovata', + }); continue; }