diff --git a/services/folderProcessor.js b/services/folderProcessor.js index b0c73a9..3927d5d 100644 --- a/services/folderProcessor.js +++ b/services/folderProcessor.js @@ -3,7 +3,44 @@ const path = require('path'); const { getDestinationDecision, getCadInfo } = require('./router'); const { buildDestinationIndex } = require('./destinationIndex'); const { buildExistingCadKeyIndex, toCadKey } = require('./duplicateIndex'); -const { getUnroutedTarget, getDuplicateTarget } = require('./unrouted'); +const { prepareUnroutedTarget, prepareDuplicateTarget } = require('./unrouted'); + +function parseNumericVersion(version) { + const rawVersion = String(version || '').trim(); + if (!rawVersion || !/^\d+$/.test(rawVersion)) { + return null; + } + + try { + return BigInt(rawVersion); + } catch { + return null; + } +} + +function buildHighestNumericVersionByCadKey(files) { + const index = new Map(); + + for (const row of files) { + const cadInfo = getCadInfo(row.fileName); + if (!cadInfo) { + continue; + } + + const version = parseNumericVersion(cadInfo.version); + if (version === null) { + continue; + } + + const key = toCadKey(cadInfo); + const current = index.get(key); + if (current === undefined || version > current) { + index.set(key, version); + } + } + + return index; +} async function collectFilesRecursively(rootDir) { const files = []; @@ -33,6 +70,7 @@ async function processFolder(folder, config) { const files = await collectFilesRecursively(folder); const destinationIndex = await buildDestinationIndex(config?.destination); const existingCadKeys = await buildExistingCadKeyIndex(config?.destination); + const sourceMaxVersions = buildHighestNumericVersionByCadKey(files); const result = { scanned: 0, copied: 0, @@ -53,7 +91,26 @@ async function processFolder(folder, config) { } if (existingCadKeys.has(toCadKey(cadInfo))) { - const duplicateTarget = await getDuplicateTarget(file); + const incomingVersion = parseNumericVersion(cadInfo.version); + const highestInSource = sourceMaxVersions.get(toCadKey(cadInfo)); + if (incomingVersion !== null && highestInSource !== undefined && incomingVersion < highestInSource) { + result.details.push({ + file, + reason: 'Versione piu alta presente nei file da smistare', + }); + continue; + } + + const duplicateTarget = await prepareDuplicateTarget(file); + if (!duplicateTarget.shouldCopy) { + result.details.push({ + file, + destination: duplicateTarget.destinationDir, + reason: duplicateTarget.reason || 'Versione piu alta gia presente', + }); + continue; + } + await fs.copy(fullPath, duplicateTarget.destinationPath, { overwrite: false }); result.copied += 1; @@ -70,7 +127,16 @@ async function processFolder(folder, config) { const destDir = decision.destination; if (!destDir) { - const unroutedTarget = await getUnroutedTarget(file); + const unroutedTarget = await prepareUnroutedTarget(file); + if (!unroutedTarget.shouldCopy) { + result.details.push({ + file, + destination: unroutedTarget.destinationDir, + reason: unroutedTarget.reason || 'Versione piu alta gia presente', + }); + continue; + } + await fs.copy(fullPath, unroutedTarget.destinationPath, { overwrite: false }); result.copied += 1; diff --git a/services/unrouted.js b/services/unrouted.js index 61d05e0..303de26 100644 --- a/services/unrouted.js +++ b/services/unrouted.js @@ -1,6 +1,7 @@ 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'); @@ -83,6 +84,100 @@ 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 listFilesRecursively(rootDir) { const files = []; @@ -156,6 +251,8 @@ async function clearDuplicateFiles() { module.exports = { getUnroutedTarget, getDuplicateTarget, + prepareUnroutedTarget, + prepareDuplicateTarget, resolveUnroutedDir, resolveDuplicatesDir, listUnroutedFiles, diff --git a/services/zipProcessor.js b/services/zipProcessor.js index 09d9aa8..eb87d73 100644 --- a/services/zipProcessor.js +++ b/services/zipProcessor.js @@ -5,12 +5,62 @@ const { pipeline } = require('stream/promises'); const { getDestinationDecision, getCadInfo } = require('./router'); const { buildDestinationIndex } = require('./destinationIndex'); const { buildExistingCadKeyIndex, toCadKey } = require('./duplicateIndex'); -const { getUnroutedTarget, getDuplicateTarget } = require('./unrouted'); +const { prepareUnroutedTarget, prepareDuplicateTarget } = require('./unrouted'); + +function parseNumericVersion(version) { + const rawVersion = String(version || '').trim(); + if (!rawVersion || !/^\d+$/.test(rawVersion)) { + return null; + } + + try { + return BigInt(rawVersion); + } catch { + return null; + } +} + +async function buildZipHighestNumericVersionByCadKey(zipPath) { + const index = new Map(); + let directory; + + try { + directory = await unzipper.Open.file(zipPath); + } catch { + return index; + } + + for (const row of directory.files || []) { + if (row.type !== 'File') { + continue; + } + + const baseName = path.basename(row.path || ''); + const cadInfo = getCadInfo(baseName); + if (!cadInfo) { + continue; + } + + const version = parseNumericVersion(cadInfo.version); + if (version === null) { + continue; + } + + const key = toCadKey(cadInfo); + const current = index.get(key); + if (current === undefined || version > current) { + index.set(key, version); + } + } + + return index; +} async function processZip(zipPath, config) { 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, @@ -38,7 +88,28 @@ async function processZip(zipPath, config) { } if (existingCadKeys.has(toCadKey(cadInfo))) { - const duplicateTarget = await getDuplicateTarget(baseName); + const incomingVersion = parseNumericVersion(cadInfo.version); + const highestInSource = sourceMaxVersions.get(toCadKey(cadInfo)); + if (incomingVersion !== null && highestInSource !== undefined && incomingVersion < highestInSource) { + result.details.push({ + file: baseName, + reason: 'Versione piu alta presente nei file da smistare', + }); + entry.autodrain(); + continue; + } + + const duplicateTarget = await prepareDuplicateTarget(baseName); + if (!duplicateTarget.shouldCopy) { + result.details.push({ + file: baseName, + destination: duplicateTarget.destinationDir, + reason: duplicateTarget.reason || 'Versione piu alta gia presente', + }); + entry.autodrain(); + continue; + } + await pipeline(entry, fs.createWriteStream(duplicateTarget.destinationPath)); result.copied += 1; @@ -55,7 +126,17 @@ async function processZip(zipPath, config) { const destDir = decision.destination; if (!destDir) { - const unroutedTarget = await getUnroutedTarget(baseName); + const unroutedTarget = await prepareUnroutedTarget(baseName); + if (!unroutedTarget.shouldCopy) { + result.details.push({ + file: baseName, + destination: unroutedTarget.destinationDir, + reason: unroutedTarget.reason || 'Versione piu alta gia presente', + }); + entry.autodrain(); + continue; + } + await pipeline(entry, fs.createWriteStream(unroutedTarget.destinationPath)); result.copied += 1;