Files
cad-data-router/services/unrouted.js
Davide Grilli 3fa7758fbe feat: smistamento file non-CAD, rinomina cartelle speciali, guida utente
- Aggiunge cartella __SALTATI: i file non-CAD vengono ora copiati in
  __SALTATI invece di essere ignorati (folderProcessor, zipProcessor)
- Rinomina cartella duplicati in __DUPLICATI per coerenza con le altre
  cartelle speciali (__NON_SMISTATI, __SALTATI)
- Aggiunge pulsante "Anteprima saltati" in UI con anteprima e pulizia
- Aggiunge guida utente HTML/CSS in renderer/docs/ con sidebar navigabile
- Aggiunge menu Help > Documentazione che apre la guida in una finestra
- Imposta DEFAULT_DESTINATION a X:\
2026-03-16 12:10:54 +01:00

295 lines
7.1 KiB
JavaScript

const fs = require('fs-extra');
const path = require('path');
const os = require('os');
const { getCadInfo } = require('./router');
const PRIMARY_UNROUTED_DIR = '/cadroute/__NON_SMISTATI';
const HOME_UNROUTED_DIR = path.join(os.homedir(), '.cadroute', '__NON_SMISTATI');
const PRIMARY_DUPLICATES_DIR = '/cadroute/__DUPLICATI';
const HOME_DUPLICATES_DIR = path.join(os.homedir(), '.cadroute', '__DUPLICATI');
const PRIMARY_SKIPPED_DIR = '/cadroute/__SALTATI';
const HOME_SKIPPED_DIR = path.join(os.homedir(), '.cadroute', '__SALTATI');
const SPECIAL_TARGETS = {
unrouted: {
primary: PRIMARY_UNROUTED_DIR,
fallback: HOME_UNROUTED_DIR,
},
duplicates: {
primary: PRIMARY_DUPLICATES_DIR,
fallback: HOME_DUPLICATES_DIR,
},
skipped: {
primary: PRIMARY_SKIPPED_DIR,
fallback: HOME_SKIPPED_DIR,
},
};
const resolvedDirs = new Map();
async function resolveTargetDir(kind) {
const target = SPECIAL_TARGETS[kind];
if (!target) {
throw new Error(`Target speciale non supportato: ${kind}`);
}
if (!resolvedDirs.has(kind)) {
resolvedDirs.set(
kind,
(async () => {
try {
await fs.ensureDir(target.primary);
return target.primary;
} catch {
await fs.ensureDir(target.fallback);
return target.fallback;
}
})()
);
}
return resolvedDirs.get(kind);
}
async function resolveUnroutedDir() {
return resolveTargetDir('unrouted');
}
async function resolveDuplicatesDir() {
return resolveTargetDir('duplicates');
}
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 getTarget(kind, fileName) {
const destinationDir = await resolveTargetDir(kind);
const destinationPath = await getUniquePath(destinationDir, fileName);
return {
destinationDir,
destinationPath,
};
}
async function getUnroutedTarget(fileName) {
return getTarget('unrouted', fileName);
}
async function getDuplicateTarget(fileName) {
return getTarget('duplicates', fileName);
}
function parseNumericVersion(version) {
const rawVersion = String(version || '').trim();
if (!rawVersion || !/^\d+$/.test(rawVersion)) {
return null;
}
try {
return BigInt(rawVersion);
} catch {
return null;
}
}
function normalizeCadKey(cadInfo) {
return String(cadInfo?.key || '').toLowerCase();
}
async function findComparableVersionFiles(destinationDir, incomingCadInfo) {
const incomingKey = normalizeCadKey(incomingCadInfo);
const entries = await fs.readdir(destinationDir, { withFileTypes: true }).catch(() => []);
const comparable = [];
for (const entry of entries) {
if (!entry.isFile()) {
continue;
}
const cadInfo = getCadInfo(entry.name);
if (!cadInfo || normalizeCadKey(cadInfo) !== incomingKey) {
continue;
}
const numericVersion = parseNumericVersion(cadInfo.version);
if (numericVersion === null) {
continue;
}
comparable.push({
path: path.join(destinationDir, entry.name),
version: numericVersion,
name: entry.name,
});
}
return comparable;
}
async function prepareSpecialTarget(kind, fileName) {
const destinationDir = await resolveTargetDir(kind);
const incomingCadInfo = getCadInfo(fileName);
if (!incomingCadInfo) {
const destinationPath = await getUniquePath(destinationDir, fileName);
return { shouldCopy: true, destinationDir, destinationPath };
}
const incomingVersion = parseNumericVersion(incomingCadInfo.version);
if (incomingVersion === null) {
const destinationPath = await getUniquePath(destinationDir, fileName);
return { shouldCopy: true, destinationDir, destinationPath };
}
const comparable = await findComparableVersionFiles(destinationDir, incomingCadInfo);
if (!comparable.length) {
const destinationPath = await getUniquePath(destinationDir, fileName);
return { shouldCopy: true, destinationDir, destinationPath };
}
const highest = comparable.reduce((max, row) => (row.version > max.version ? row : max));
if (incomingVersion <= highest.version) {
return {
shouldCopy: false,
destinationDir,
reason: `Versione piu alta gia presente (${highest.name})`,
};
}
await Promise.all(comparable.map((row) => fs.remove(row.path)));
const destinationPath = await getUniquePath(destinationDir, fileName);
return {
shouldCopy: true,
destinationDir,
destinationPath,
cleaned: comparable.length,
};
}
async function prepareUnroutedTarget(fileName) {
return prepareSpecialTarget('unrouted', fileName);
}
async function prepareDuplicateTarget(fileName) {
return prepareSpecialTarget('duplicates', fileName);
}
async function resolveSkippedDir() {
return resolveTargetDir('skipped');
}
async function getSkippedTarget(fileName) {
return getTarget('skipped', fileName);
}
async function listSkippedFiles() {
return listTargetFiles('skipped');
}
async function clearSkippedFiles() {
return clearTargetFiles('skipped');
}
async function listFilesRecursively(rootDir) {
const files = [];
async function walk(currentDir) {
let entries;
try {
entries = await fs.readdir(currentDir, { withFileTypes: true });
} catch {
return;
}
for (const entry of entries) {
const fullPath = path.join(currentDir, entry.name);
if (entry.isDirectory()) {
await walk(fullPath);
continue;
}
if (!entry.isFile()) {
continue;
}
const stats = await fs.stat(fullPath).catch(() => null);
if (!stats) {
continue;
}
files.push({
name: entry.name,
relativePath: path.relative(rootDir, fullPath),
size: stats.size,
updatedAt: stats.mtime.toISOString(),
});
}
}
await walk(rootDir);
files.sort((a, b) => a.relativePath.localeCompare(b.relativePath, 'it'));
return files;
}
async function listTargetFiles(kind) {
const directory = await resolveTargetDir(kind);
const files = await listFilesRecursively(directory);
return { directory, files };
}
async function clearTargetFiles(kind) {
const directory = await resolveTargetDir(kind);
await fs.emptyDir(directory);
return { directory };
}
async function listUnroutedFiles() {
return listTargetFiles('unrouted');
}
async function listDuplicateFiles() {
return listTargetFiles('duplicates');
}
async function clearUnroutedFiles() {
return clearTargetFiles('unrouted');
}
async function clearDuplicateFiles() {
return clearTargetFiles('duplicates');
}
module.exports = {
getUnroutedTarget,
getDuplicateTarget,
getSkippedTarget,
prepareUnroutedTarget,
prepareDuplicateTarget,
resolveUnroutedDir,
resolveDuplicatesDir,
resolveSkippedDir,
listUnroutedFiles,
listDuplicateFiles,
listSkippedFiles,
clearUnroutedFiles,
clearDuplicateFiles,
clearSkippedFiles,
PRIMARY_UNROUTED_DIR,
HOME_UNROUTED_DIR,
PRIMARY_DUPLICATES_DIR,
HOME_DUPLICATES_DIR,
PRIMARY_SKIPPED_DIR,
HOME_SKIPPED_DIR,
};