Compare commits
5 Commits
fea0699ff1
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 9aec9d3a50 | |||
| 65b1777a60 | |||
| a49d338741 | |||
| b0bcea2f10 | |||
| 57f8b230b2 |
@@ -7,6 +7,7 @@ Analizza una cartella o un archivio `.zip`, riconosce i file CAD (`.prt`, `.asm`
|
||||
## Funzionalità
|
||||
|
||||
- Smistamento da cartella o archivio ZIP (con drag & drop)
|
||||
- Riscansione completa della destinazione a ogni processo (senza cache)
|
||||
- Routing automatico basato sul gruppo di cifre nel nome file
|
||||
- Gestione versioni: mantiene solo la versione più alta
|
||||
- Cartelle speciali: `__NON_SMISTATI`, `__DUPLICATI`, `__SALTATI`
|
||||
@@ -47,8 +48,8 @@ Gli artefatti vengono generati in `dist/`.
|
||||
| `__DUPLICATI` | File CAD già presenti in destinazione |
|
||||
| `__SALTATI` | File non CAD (PDF, immagini, ecc.) |
|
||||
|
||||
- **Windows:** `C:\cadroute\<cartella>\`
|
||||
- **Linux:** `~/.cadroute/<cartella>/`
|
||||
- **Windows:** `%APPDATA%\CadRoute\<cartella>\`
|
||||
|
||||
## Autore
|
||||
|
||||
|
||||
19
main.js
19
main.js
@@ -142,7 +142,7 @@ app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') app.quit();
|
||||
});
|
||||
|
||||
ipcMain.handle('select-folder', async () => {
|
||||
ipcMain.handle('select-folder', async (event) => {
|
||||
const result = await dialog.showOpenDialog({
|
||||
properties: ['openDirectory'],
|
||||
});
|
||||
@@ -151,11 +151,12 @@ ipcMain.handle('select-folder', async () => {
|
||||
return { canceled: true };
|
||||
}
|
||||
|
||||
const routingResult = await processFolder(result.filePaths[0], config);
|
||||
const onProgress = (data) => event.sender.send('progress', data);
|
||||
const routingResult = await processFolder(result.filePaths[0], config, onProgress);
|
||||
return { canceled: false, ...routingResult };
|
||||
});
|
||||
|
||||
ipcMain.handle('select-zip', async () => {
|
||||
ipcMain.handle('select-zip', async (event) => {
|
||||
const result = await dialog.showOpenDialog({
|
||||
properties: ['openFile'],
|
||||
filters: [{ name: 'Zip', extensions: ['zip'] }],
|
||||
@@ -165,11 +166,12 @@ ipcMain.handle('select-zip', async () => {
|
||||
return { canceled: true };
|
||||
}
|
||||
|
||||
const routingResult = await processZip(result.filePaths[0], config);
|
||||
const onProgress = (data) => event.sender.send('progress', data);
|
||||
const routingResult = await processZip(result.filePaths[0], config, onProgress);
|
||||
return { canceled: false, ...routingResult };
|
||||
});
|
||||
|
||||
ipcMain.handle('process-dropped-path', async (_event, payload) => {
|
||||
ipcMain.handle('process-dropped-path', async (event, payload) => {
|
||||
const droppedPath = String(payload?.path || '').trim();
|
||||
if (!droppedPath) {
|
||||
throw new Error('Percorso non valido');
|
||||
@@ -180,13 +182,15 @@ ipcMain.handle('process-dropped-path', async (_event, payload) => {
|
||||
throw new Error('Percorso non trovato');
|
||||
}
|
||||
|
||||
const onProgress = (data) => event.sender.send('progress', data);
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
const routingResult = await processFolder(droppedPath, config);
|
||||
const routingResult = await processFolder(droppedPath, config, onProgress);
|
||||
return { canceled: false, sourceType: 'folder', ...routingResult };
|
||||
}
|
||||
|
||||
if (stats.isFile() && path.extname(droppedPath).toLowerCase() === '.zip') {
|
||||
const routingResult = await processZip(droppedPath, config);
|
||||
const routingResult = await processZip(droppedPath, config, onProgress);
|
||||
return { canceled: false, sourceType: 'zip', ...routingResult };
|
||||
}
|
||||
|
||||
@@ -223,6 +227,7 @@ ipcMain.handle('update-destination', async (_event, payload) => {
|
||||
return { destination: config.destination };
|
||||
});
|
||||
|
||||
|
||||
ipcMain.handle('open-unrouted-folder', async () => {
|
||||
const unroutedDir = await resolveUnroutedDir();
|
||||
const openResult = await shell.openPath(unroutedDir);
|
||||
|
||||
@@ -15,4 +15,6 @@ contextBridge.exposeInMainWorld('api', {
|
||||
clearUnroutedFiles: () => ipcRenderer.invoke('clear-unrouted-files'),
|
||||
clearDuplicatesFiles: () => ipcRenderer.invoke('clear-duplicates-files'),
|
||||
clearSkippedFiles: () => ipcRenderer.invoke('clear-skipped-files'),
|
||||
onProgress: (cb) => ipcRenderer.on('progress', (_e, data) => cb(data)),
|
||||
offProgress: () => ipcRenderer.removeAllListeners('progress'),
|
||||
});
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
<a href="#nomi">Convenzione nomi file</a>
|
||||
<a href="#nonsmistati">File non smistati</a>
|
||||
<a href="#duplicati">File duplicati</a>
|
||||
<a href="#saltati">File saltati</a>
|
||||
</nav>
|
||||
|
||||
<main>
|
||||
@@ -62,7 +63,7 @@
|
||||
<tr>
|
||||
<td><span class="badge skip">altri</span></td>
|
||||
<td>—</td>
|
||||
<td>Ignorati (PDF, immagini, documenti, ecc.)</td>
|
||||
<td>Copiati in <code>__SALTATI</code> (PDF, immagini, documenti, ecc.)</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -100,7 +101,7 @@
|
||||
<ol class="steps">
|
||||
<li><span>Clicca <strong>Process Folder</strong> per selezionare una cartella, oppure <strong>Process ZIP</strong> per selezionare un archivio. In alternativa, trascina la cartella o il file <code>.zip</code> nell'area tratteggiata.</span></li>
|
||||
<li><span>L'app scansiona tutti i file e identifica automaticamente quelli CAD.</span></li>
|
||||
<li><span>I file CAD vengono copiati nella sottocartella di destinazione corrispondente. I file non CAD vengono saltati.</span></li>
|
||||
<li><span>I file CAD vengono copiati nella sottocartella di destinazione corrispondente. I file non CAD vengono copiati in <code>__SALTATI</code>.</span></li>
|
||||
<li><span>Al termine vengono mostrate le statistiche nell'area di output.</span></li>
|
||||
</ol>
|
||||
|
||||
@@ -127,7 +128,7 @@
|
||||
</div>
|
||||
<div class="stat-item muted">
|
||||
<div class="stat-label">Saltati</div>
|
||||
<div class="stat-desc">File ignorati perché non CAD (PDF, immagini, ecc.).</div>
|
||||
<div class="stat-desc">File non CAD copiati in <code>__SALTATI</code> (PDF, immagini, ecc.).</div>
|
||||
</div>
|
||||
<div class="stat-item warn">
|
||||
<div class="stat-label">Non smistati</div>
|
||||
@@ -172,7 +173,7 @@ Esempi:
|
||||
</tr>
|
||||
<tr>
|
||||
<td>File già presente in destinazione</td>
|
||||
<td>Copiato in <code>duplicati/</code> per revisione</td>
|
||||
<td>Copiato in <code>__DUPLICATI</code> per revisione</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Versione inferiore presente nella sorgente</td>
|
||||
@@ -210,8 +211,8 @@ Esempi:
|
||||
<h3>Dove si trovano</h3>
|
||||
<p>I file non smistati vengono copiati nella cartella <code>__NON_SMISTATI</code>:</p>
|
||||
<ul style="margin: 8px 0 12px 20px; color: var(--muted);">
|
||||
<li><strong>Windows:</strong> <code>C:\cadroute\__NON_SMISTATI\</code></li>
|
||||
<li><strong>Linux:</strong> <code>~/.cadroute/__NON_SMISTATI/</code></li>
|
||||
<li><strong>Windows:</strong> <code>%APPDATA%\CadRoute\__NON_SMISTATI\</code></li>
|
||||
</ul>
|
||||
|
||||
<h3>Visualizzare e pulire</h3>
|
||||
@@ -231,16 +232,16 @@ Esempi:
|
||||
<h2>File duplicati</h2>
|
||||
<p>Un file CAD viene classificato come <strong>duplicato</strong> quando la sua chiave (<code>CODICE.tipo</code>, senza versione) è già presente nella cartella di destinazione al momento dello smistamento.</p>
|
||||
|
||||
<p>I duplicati non sovrascrivono i file già presenti in destinazione: vengono messi da parte nella cartella <code>duplicati/</code> per permettere una revisione manuale.</p>
|
||||
<p>I duplicati non sovrascrivono i file già presenti in destinazione: vengono messi da parte nella cartella <code>__DUPLICATI</code> per permettere una revisione manuale.</p>
|
||||
|
||||
<h3>Dove si trovano</h3>
|
||||
<ul style="margin: 8px 0 12px 20px; color: var(--muted);">
|
||||
<li><strong>Linux:</strong> <code>~/.cadroute/duplicati/</code></li>
|
||||
<li><strong>Windows:</strong> <code>%APPDATA%\CadRoute\duplicati\</code></li>
|
||||
<li><strong>Windows:</strong> <code>C:\cadroute\__DUPLICATI\</code></li>
|
||||
<li><strong>Linux:</strong> <code>~/.cadroute/__DUPLICATI/</code></li>
|
||||
</ul>
|
||||
|
||||
<h3>Gestione versioni nei duplicati</h3>
|
||||
<p>Anche all'interno della cartella <code>duplicati/</code> viene applicata la logica delle versioni: se arriva una versione più alta di un file già presente nei duplicati, la versione vecchia viene eliminata e sostituita con quella nuova.</p>
|
||||
<p>Anche all'interno della cartella <code>__DUPLICATI</code> viene applicata la logica delle versioni: se arriva una versione più alta di un file già presente nei duplicati, la versione vecchia viene eliminata e sostituita con quella nuova.</p>
|
||||
|
||||
<h3>Visualizzare e pulire</h3>
|
||||
<ol class="steps">
|
||||
@@ -254,6 +255,31 @@ Esempi:
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ── SALTATI ────────────────────────────── -->
|
||||
<section id="saltati">
|
||||
<h2>File saltati</h2>
|
||||
<p>Un file viene classificato come <strong>saltato</strong> quando non è riconosciuto come file CAD, cioè non ha estensione <code>.prt</code>, <code>.asm</code> o <code>.drw</code>. Rientrano in questa categoria PDF, immagini, documenti Word e qualsiasi altro formato non CAD.</p>
|
||||
|
||||
<p>I file saltati non vengono ignorati completamente: vengono copiati nella cartella <code>__SALTATI</code> per permettere una verifica manuale.</p>
|
||||
|
||||
<h3>Dove si trovano</h3>
|
||||
<ul style="margin: 8px 0 12px 20px; color: var(--muted);">
|
||||
<li><strong>Windows:</strong> <code>C:\cadroute\__SALTATI\</code></li>
|
||||
<li><strong>Linux:</strong> <code>~/.cadroute/__SALTATI/</code></li>
|
||||
</ul>
|
||||
|
||||
<h3>Visualizzare e pulire</h3>
|
||||
<ol class="steps">
|
||||
<li><span>Clicca <strong>Anteprima saltati</strong> per aprire la lista dei file presenti.</span></li>
|
||||
<li><span>Verifica che nessun file CAD sia finito qui per errore di denominazione.</span></li>
|
||||
<li><span>Se vuoi svuotare la cartella, clicca <strong>Pulisci cartella</strong> (rosso) e conferma.</span></li>
|
||||
</ol>
|
||||
|
||||
<div class="callout info">
|
||||
<strong>Nota:</strong> la cartella <code>__SALTATI</code> è utile per individuare file CAD rinominati erroneamente o con estensione mancante.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
<script>
|
||||
|
||||
@@ -254,6 +254,53 @@
|
||||
overflow: auto;
|
||||
min-height: 180px;
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
margin-top: 18px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.progress-label {
|
||||
font-size: 13px;
|
||||
color: var(--muted);
|
||||
margin-bottom: 6px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
progress {
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
background: var(--border);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
progress::-webkit-progress-bar {
|
||||
background: var(--border);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
progress::-webkit-progress-value {
|
||||
background: var(--accent);
|
||||
border-radius: 4px;
|
||||
transition: width 80ms ease;
|
||||
}
|
||||
|
||||
progress:indeterminate {
|
||||
background: linear-gradient(90deg, var(--border) 25%, var(--accent) 50%, var(--border) 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: progress-indeterminate 1.2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes progress-indeterminate {
|
||||
0% { background-position: 100% 0; }
|
||||
100% { background-position: -100% 0; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -270,6 +317,11 @@
|
||||
Trascina qui una cartella o un file .zip
|
||||
</section>
|
||||
|
||||
<div class="progress-container" id="progressContainer">
|
||||
<div class="progress-label" id="progressLabel">In corso...</div>
|
||||
<progress id="progressBar"></progress>
|
||||
</div>
|
||||
|
||||
<section class="rules">
|
||||
<h2>Destinazione file CAD</h2>
|
||||
<div class="rule-row single-row">
|
||||
|
||||
@@ -15,10 +15,52 @@ const clearPreviewBtn = document.getElementById('clearPreviewBtn');
|
||||
const previewTitle = document.getElementById('previewTitle');
|
||||
const previewMeta = document.getElementById('previewMeta');
|
||||
const previewList = document.getElementById('previewList');
|
||||
const progressContainer = document.getElementById('progressContainer');
|
||||
const progressBar = document.getElementById('progressBar');
|
||||
const progressLabel = document.getElementById('progressLabel');
|
||||
const defaultDropZoneText = 'Trascina qui una cartella o un file .zip';
|
||||
let isProcessing = false;
|
||||
let currentPreviewKind = null;
|
||||
|
||||
function showProgress() {
|
||||
progressContainer.style.display = 'block';
|
||||
progressBar.removeAttribute('value');
|
||||
progressLabel.textContent = 'In corso...';
|
||||
}
|
||||
|
||||
function hideProgress() {
|
||||
progressContainer.style.display = 'none';
|
||||
}
|
||||
|
||||
let _indexFilesFound = 0;
|
||||
|
||||
function updateProgress({ phase, current, total, file, scanned, folder }) {
|
||||
if (phase === 'scan') {
|
||||
_indexFilesFound = 0;
|
||||
progressLabel.textContent = 'Scansione file sorgente...';
|
||||
progressBar.removeAttribute('value');
|
||||
} else if (phase === 'index-dest') {
|
||||
progressBar.max = total;
|
||||
progressBar.value = current;
|
||||
const filesInfo = _indexFilesFound > 0 ? ` — ${_indexFilesFound} file CAD trovati` : '';
|
||||
progressLabel.textContent = `Analisi destinazione: ${current} / ${total}${folder ? ` — ${folder}` : ''}${filesInfo}`;
|
||||
} else if (phase === 'index-dup') {
|
||||
if (current !== undefined && total !== undefined) {
|
||||
progressBar.max = total;
|
||||
progressBar.value = current;
|
||||
progressLabel.textContent = `Analisi destinazione: ${current} / ${total} cartelle`;
|
||||
} else {
|
||||
_indexFilesFound = scanned;
|
||||
const dirInfo = progressBar.max > 0 ? `${progressBar.value} / ${progressBar.max} — ` : '';
|
||||
progressLabel.textContent = `Analisi destinazione: ${dirInfo}${scanned} file CAD trovati`;
|
||||
}
|
||||
} else if (phase === 'copy') {
|
||||
progressBar.max = total;
|
||||
progressBar.value = current;
|
||||
progressLabel.textContent = `Smistamento: ${current} / ${total}${file ? ` — ${file}` : ''}`;
|
||||
}
|
||||
}
|
||||
|
||||
function setLoading(isLoading) {
|
||||
folderBtn.disabled = isLoading;
|
||||
zipBtn.disabled = isLoading;
|
||||
@@ -101,6 +143,8 @@ async function handleAction(actionName, actionFn) {
|
||||
|
||||
isProcessing = true;
|
||||
setLoading(true);
|
||||
showProgress();
|
||||
window.api.onProgress(updateProgress);
|
||||
output.textContent = `${actionName} in corso...`;
|
||||
|
||||
try {
|
||||
@@ -115,6 +159,8 @@ async function handleAction(actionName, actionFn) {
|
||||
} catch (error) {
|
||||
output.textContent = `${actionName} fallito:\n${error.message}`;
|
||||
} finally {
|
||||
window.api.offProgress();
|
||||
hideProgress();
|
||||
setLoading(false);
|
||||
isProcessing = false;
|
||||
}
|
||||
@@ -309,6 +355,7 @@ clearPreviewBtn.addEventListener('click', async () => {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
closePreviewBtn.addEventListener('click', hidePreview);
|
||||
previewOverlay.addEventListener('click', (event) => {
|
||||
if (event.target === previewOverlay) {
|
||||
|
||||
@@ -4,13 +4,15 @@ const path = require('path');
|
||||
const CODE_FOLDER_REGEX = /^\d{3}$/;
|
||||
const MAX_SCAN_DEPTH = 6;
|
||||
|
||||
async function buildDestinationIndex(destinationRoot) {
|
||||
async function buildDestinationIndex(destinationRoot, onProgress) {
|
||||
const index = new Map();
|
||||
|
||||
if (!destinationRoot || !(await fs.pathExists(destinationRoot))) {
|
||||
return index;
|
||||
}
|
||||
|
||||
let scanned = 0;
|
||||
|
||||
async function walk(currentDir, depth) {
|
||||
if (depth > MAX_SCAN_DEPTH) {
|
||||
return;
|
||||
@@ -29,6 +31,9 @@ async function buildDestinationIndex(destinationRoot) {
|
||||
}
|
||||
|
||||
const fullPath = path.join(currentDir, entry.name);
|
||||
scanned += 1;
|
||||
onProgress?.({ phase: 'index-dest', scanned, folder: entry.name });
|
||||
|
||||
if (CODE_FOLDER_REGEX.test(entry.name)) {
|
||||
const rows = index.get(entry.name) || [];
|
||||
rows.push(fullPath);
|
||||
|
||||
99
services/destinationScanner.js
Normal file
99
services/destinationScanner.js
Normal file
@@ -0,0 +1,99 @@
|
||||
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();
|
||||
}
|
||||
|
||||
async function buildDestinationIndexes(destinationRoot, onProgress) {
|
||||
const folderIndex = new Map();
|
||||
const cadKeyIndex = new Set();
|
||||
|
||||
if (!destinationRoot || !(await fs.pathExists(destinationRoot))) {
|
||||
return { folderIndex, cadKeyIndex };
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
for (const entry of topEntries) {
|
||||
if (!entry.isFile()) continue;
|
||||
const cadInfo = getCadInfo(entry.name);
|
||||
if (cadInfo) {
|
||||
scannedFiles += 1;
|
||||
cadKeyIndex.add(toCadKey(cadInfo));
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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 };
|
||||
@@ -8,13 +8,15 @@ function toCadKey(cadInfo) {
|
||||
return String(cadInfo?.key || '').toLowerCase();
|
||||
}
|
||||
|
||||
async function buildExistingCadKeyIndex(destinationRoot) {
|
||||
async function buildExistingCadKeyIndex(destinationRoot, onProgress) {
|
||||
const keys = new Set();
|
||||
|
||||
if (!destinationRoot || !(await fs.pathExists(destinationRoot))) {
|
||||
return keys;
|
||||
}
|
||||
|
||||
let scanned = 0;
|
||||
|
||||
async function walk(currentDir, depth) {
|
||||
if (depth > MAX_SCAN_DEPTH) {
|
||||
return;
|
||||
@@ -44,6 +46,8 @@ async function buildExistingCadKeyIndex(destinationRoot) {
|
||||
continue;
|
||||
}
|
||||
|
||||
scanned += 1;
|
||||
onProgress?.({ phase: 'index-dup', scanned, file: entry.name });
|
||||
keys.add(toCadKey(cadInfo));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const { getDestinationDecision, getCadInfo } = require('./router');
|
||||
const { buildDestinationIndex } = require('./destinationIndex');
|
||||
const { buildExistingCadKeyIndex, toCadKey } = require('./duplicateIndex');
|
||||
const { buildDestinationIndexes, toCadKey } = require('./destinationScanner');
|
||||
const { prepareUnroutedTarget, prepareDuplicateTarget, getSkippedTarget } = require('./unrouted');
|
||||
|
||||
function parseNumericVersion(version) {
|
||||
@@ -66,10 +65,11 @@ async function collectFilesRecursively(rootDir) {
|
||||
return files;
|
||||
}
|
||||
|
||||
async function processFolder(folder, config) {
|
||||
async function processFolder(folder, config, onProgress) {
|
||||
onProgress?.({ phase: 'scan' });
|
||||
const files = await collectFilesRecursively(folder);
|
||||
const destinationIndex = await buildDestinationIndex(config?.destination);
|
||||
const existingCadKeys = await buildExistingCadKeyIndex(config?.destination);
|
||||
|
||||
const { folderIndex: destinationIndex, cadKeyIndex: existingCadKeys } = await buildDestinationIndexes(config?.destination, onProgress);
|
||||
const sourceMaxVersions = buildHighestNumericVersionByCadKey(files);
|
||||
const result = {
|
||||
scanned: 0,
|
||||
@@ -80,9 +80,13 @@ async function processFolder(folder, config) {
|
||||
details: [],
|
||||
};
|
||||
|
||||
for (const { fullPath, fileName } of files) {
|
||||
const total = files.length;
|
||||
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const { fullPath, fileName } = files[i];
|
||||
const file = fileName;
|
||||
result.scanned += 1;
|
||||
onProgress?.({ phase: 'copy', current: i + 1, total, file });
|
||||
|
||||
const cadInfo = getCadInfo(file);
|
||||
if (!cadInfo) {
|
||||
|
||||
@@ -16,6 +16,9 @@ function getCadInfo(filename) {
|
||||
const version = match[3] || '';
|
||||
const digits = rawCode.replace(/\D/g, '');
|
||||
const routingGroup = digits.length >= 5 ? digits.slice(2, 5) : null;
|
||||
const subfolder = digits.length >= 2
|
||||
? (digits[1] === '1' ? 'A' : digits[1] === '2' ? 'M_B' : null)
|
||||
: null;
|
||||
|
||||
return {
|
||||
code: rawCode,
|
||||
@@ -24,6 +27,7 @@ function getCadInfo(filename) {
|
||||
version,
|
||||
key: `${rawCode}.${type}`,
|
||||
routingGroup,
|
||||
subfolder,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -57,7 +61,14 @@ function getDestinationDecision(filename, config, destinationIndex) {
|
||||
};
|
||||
}
|
||||
|
||||
return { destination: candidates[0], reason: null };
|
||||
if (!cadInfo.subfolder) {
|
||||
return {
|
||||
destination: null,
|
||||
reason: `Prefisso non riconosciuto (${cadInfo.digits[1]}): atteso 91 (→ A) o 92 (→ M_B)`,
|
||||
};
|
||||
}
|
||||
|
||||
return { destination: path.join(candidates[0], cadInfo.subfolder), reason: null };
|
||||
}
|
||||
|
||||
function findDestination(filename, config, destinationIndex) {
|
||||
|
||||
@@ -3,8 +3,7 @@ const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const { pipeline } = require('stream/promises');
|
||||
const { getDestinationDecision, getCadInfo } = require('./router');
|
||||
const { buildDestinationIndex } = require('./destinationIndex');
|
||||
const { buildExistingCadKeyIndex, toCadKey } = require('./duplicateIndex');
|
||||
const { buildDestinationIndexes, toCadKey } = require('./destinationScanner');
|
||||
const { prepareUnroutedTarget, prepareDuplicateTarget, getSkippedTarget } = require('./unrouted');
|
||||
|
||||
function parseNumericVersion(version) {
|
||||
@@ -27,7 +26,7 @@ async function buildZipHighestNumericVersionByCadKey(zipPath) {
|
||||
try {
|
||||
directory = await unzipper.Open.file(zipPath);
|
||||
} catch {
|
||||
return index;
|
||||
return { index, total: 0 };
|
||||
}
|
||||
|
||||
for (const row of directory.files || []) {
|
||||
@@ -53,14 +52,16 @@ async function buildZipHighestNumericVersionByCadKey(zipPath) {
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
return { index, total: (directory.files || []).filter((f) => f.type === 'File').length };
|
||||
}
|
||||
|
||||
async function processZip(zipPath, config) {
|
||||
async function processZip(zipPath, config, onProgress) {
|
||||
onProgress?.({ phase: 'scan' });
|
||||
const { index: sourceMaxVersions, total } = await buildZipHighestNumericVersionByCadKey(zipPath);
|
||||
|
||||
const { folderIndex: destinationIndex, cadKeyIndex: existingCadKeys } = await buildDestinationIndexes(config?.destination, onProgress);
|
||||
|
||||
const stream = fs.createReadStream(zipPath).pipe(unzipper.Parse({ forceStream: true }));
|
||||
const destinationIndex = await buildDestinationIndex(config?.destination);
|
||||
const existingCadKeys = await buildExistingCadKeyIndex(config?.destination);
|
||||
const sourceMaxVersions = await buildZipHighestNumericVersionByCadKey(zipPath);
|
||||
const result = {
|
||||
scanned: 0,
|
||||
copied: 0,
|
||||
@@ -70,6 +71,8 @@ async function processZip(zipPath, config) {
|
||||
details: [],
|
||||
};
|
||||
|
||||
let current = 0;
|
||||
|
||||
for await (const entry of stream) {
|
||||
if (entry.type !== 'File') {
|
||||
entry.autodrain();
|
||||
@@ -78,7 +81,9 @@ async function processZip(zipPath, config) {
|
||||
|
||||
const file = entry.path;
|
||||
const baseName = path.basename(file);
|
||||
current += 1;
|
||||
result.scanned += 1;
|
||||
onProgress?.({ phase: 'copy', current, total, file: baseName });
|
||||
|
||||
const cadInfo = getCadInfo(baseName);
|
||||
if (!cadInfo) {
|
||||
|
||||
Reference in New Issue
Block a user