- 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:\
295 lines
7.1 KiB
JavaScript
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,
|
|
};
|