Commit iniziale
This commit is contained in:
40
.gitignore
vendored
Normal file
40
.gitignore
vendored
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# Dependencies
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Build output
|
||||||
|
dist/
|
||||||
|
out/
|
||||||
|
release/
|
||||||
|
*.exe
|
||||||
|
*.dmg
|
||||||
|
*.AppImage
|
||||||
|
*.deb
|
||||||
|
*.rpm
|
||||||
|
*.snap
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
logs/
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
|
||||||
|
# Environment files
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
|
||||||
|
# Temporary files
|
||||||
|
*.tmp
|
||||||
|
*.temp
|
||||||
|
|
||||||
|
# OS files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# IDE/editor
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
22
config/defaultConfig.json
Normal file
22
config/defaultConfig.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"ext": "prt",
|
||||||
|
"pattern": "^1",
|
||||||
|
"destination": "./output/clienti1/PRT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ext": "prt",
|
||||||
|
"pattern": "^6",
|
||||||
|
"destination": "./output/clienti2/PRT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ext": "asm",
|
||||||
|
"destination": "./output/assembly"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ext": "drw",
|
||||||
|
"destination": "./output/drawing"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
72
main.js
Normal file
72
main.js
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
const { app, BrowserWindow, dialog, ipcMain } = require('electron');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const { processFolder } = require('./services/folderProcessor');
|
||||||
|
const { processZip } = require('./services/zipProcessor');
|
||||||
|
const { loadConfig } = require('./services/configService');
|
||||||
|
|
||||||
|
let config;
|
||||||
|
|
||||||
|
function createWindow() {
|
||||||
|
const win = new BrowserWindow({
|
||||||
|
width: 900,
|
||||||
|
height: 640,
|
||||||
|
webPreferences: {
|
||||||
|
preload: path.join(__dirname, 'preload.js'),
|
||||||
|
contextIsolation: true,
|
||||||
|
nodeIntegration: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
win.loadFile(path.join(__dirname, 'renderer', 'index.html'));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function initConfig() {
|
||||||
|
config = await loadConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
app.whenReady().then(async () => {
|
||||||
|
await initConfig();
|
||||||
|
createWindow();
|
||||||
|
|
||||||
|
app.on('activate', () => {
|
||||||
|
if (BrowserWindow.getAllWindows().length === 0) {
|
||||||
|
createWindow();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.on('window-all-closed', () => {
|
||||||
|
if (process.platform !== 'darwin') app.quit();
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('select-folder', async () => {
|
||||||
|
const result = await dialog.showOpenDialog({
|
||||||
|
properties: ['openDirectory'],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.canceled || !result.filePaths[0]) {
|
||||||
|
return { canceled: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
const routingResult = await processFolder(result.filePaths[0], config);
|
||||||
|
return { canceled: false, ...routingResult };
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('select-zip', async () => {
|
||||||
|
const result = await dialog.showOpenDialog({
|
||||||
|
properties: ['openFile'],
|
||||||
|
filters: [{ name: 'Zip', extensions: ['zip'] }],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.canceled || !result.filePaths[0]) {
|
||||||
|
return { canceled: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
const routingResult = await processZip(result.filePaths[0], config);
|
||||||
|
return { canceled: false, ...routingResult };
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('get-config-path', async () => ({
|
||||||
|
configPath: loadConfig.getConfigPath(),
|
||||||
|
}));
|
||||||
4279
package-lock.json
generated
Normal file
4279
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
28
package.json
Normal file
28
package.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"name": "progetto",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "CAD File Router MVP",
|
||||||
|
"main": "main.js",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "env -u ELECTRON_RUN_AS_NODE electron .",
|
||||||
|
"start": "env -u ELECTRON_RUN_AS_NODE electron .",
|
||||||
|
"build": "electron-builder"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"build": {
|
||||||
|
"appId": "com.cad.router",
|
||||||
|
"win": {
|
||||||
|
"target": "nsis"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"electron": "^40.7.0",
|
||||||
|
"fs-extra": "^11.3.4",
|
||||||
|
"unzipper": "^0.12.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"electron-builder": "^26.8.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
7
preload.js
Normal file
7
preload.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
const { contextBridge, ipcRenderer } = require('electron');
|
||||||
|
|
||||||
|
contextBridge.exposeInMainWorld('api', {
|
||||||
|
selectFolder: () => ipcRenderer.invoke('select-folder'),
|
||||||
|
selectZip: () => ipcRenderer.invoke('select-zip'),
|
||||||
|
getConfigPath: () => ipcRenderer.invoke('get-config-path'),
|
||||||
|
});
|
||||||
115
renderer/index.html
Normal file
115
renderer/index.html
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="it">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>CAD File Router</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
color-scheme: light;
|
||||||
|
--bg: #f6f8fb;
|
||||||
|
--card: #ffffff;
|
||||||
|
--text: #0f172a;
|
||||||
|
--muted: #475569;
|
||||||
|
--accent: #0b5fff;
|
||||||
|
--border: #dbe2ea;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
background: linear-gradient(130deg, #eaf0ff 0%, var(--bg) 45%, #f9fbff 100%);
|
||||||
|
color: var(--text);
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page {
|
||||||
|
max-width: 820px;
|
||||||
|
margin: 40px auto;
|
||||||
|
background: var(--card);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border: 1px solid transparent;
|
||||||
|
background: var(--accent);
|
||||||
|
color: white;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 11px 16px;
|
||||||
|
font-size: 15px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.secondary {
|
||||||
|
background: #1f2937;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
opacity: 0.92;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: wait;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info {
|
||||||
|
margin-top: 18px;
|
||||||
|
padding: 14px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: #f8fafc;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
margin-top: 18px;
|
||||||
|
padding: 14px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: #0f172a;
|
||||||
|
color: #e2e8f0;
|
||||||
|
overflow: auto;
|
||||||
|
min-height: 180px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main class="page">
|
||||||
|
<h1>CAD File Router MVP</h1>
|
||||||
|
<p>Seleziona una cartella o uno ZIP. I file .prt/.asm/.drw verranno copiati in base alle regole.</p>
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<button id="folderBtn">Process Folder</button>
|
||||||
|
<button id="zipBtn" class="secondary">Process ZIP</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info" id="configInfo">Caricamento configurazione...</div>
|
||||||
|
<pre id="output">Pronto.</pre>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script src="./renderer.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
55
renderer/renderer.js
Normal file
55
renderer/renderer.js
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
const folderBtn = document.getElementById('folderBtn');
|
||||||
|
const zipBtn = document.getElementById('zipBtn');
|
||||||
|
const output = document.getElementById('output');
|
||||||
|
const configInfo = document.getElementById('configInfo');
|
||||||
|
|
||||||
|
function setLoading(isLoading) {
|
||||||
|
folderBtn.disabled = isLoading;
|
||||||
|
zipBtn.disabled = isLoading;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderResult(title, result) {
|
||||||
|
const details = (result.details || []).slice(0, 20);
|
||||||
|
const detailsText = details.length
|
||||||
|
? details
|
||||||
|
.map((d) => (d.destination ? `- ${d.file} -> ${d.destination}` : `- ${d.file}: ${d.reason}`))
|
||||||
|
.join('\n')
|
||||||
|
: '- nessun dettaglio';
|
||||||
|
|
||||||
|
output.textContent = [
|
||||||
|
title,
|
||||||
|
`scansionati: ${result.scanned ?? 0}`,
|
||||||
|
`copiati: ${result.copied ?? 0}`,
|
||||||
|
`saltati: ${result.skipped ?? 0}`,
|
||||||
|
'',
|
||||||
|
'dettagli (max 20):',
|
||||||
|
detailsText,
|
||||||
|
].join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleAction(actionName, actionFn) {
|
||||||
|
setLoading(true);
|
||||||
|
output.textContent = `${actionName} in corso...`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await actionFn();
|
||||||
|
|
||||||
|
if (!result || result.canceled) {
|
||||||
|
output.textContent = `${actionName} annullato.`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderResult(actionName, result);
|
||||||
|
} catch (error) {
|
||||||
|
output.textContent = `${actionName} fallito:\n${error.message}`;
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
folderBtn.addEventListener('click', () => handleAction('Process Folder', window.api.selectFolder));
|
||||||
|
zipBtn.addEventListener('click', () => handleAction('Process ZIP', window.api.selectZip));
|
||||||
|
|
||||||
|
window.api.getConfigPath().then(({ configPath }) => {
|
||||||
|
configInfo.textContent = `Config utente: ${configPath}`;
|
||||||
|
});
|
||||||
20
services/configService.js
Normal file
20
services/configService.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
const fs = require('fs-extra');
|
||||||
|
const path = require('path');
|
||||||
|
const os = require('os');
|
||||||
|
|
||||||
|
const configPath = path.join(os.homedir(), '.cad-router-config.json');
|
||||||
|
const defaultConfigPath = path.join(__dirname, '..', 'config', 'defaultConfig.json');
|
||||||
|
|
||||||
|
async function loadConfig() {
|
||||||
|
if (await fs.pathExists(configPath)) {
|
||||||
|
return fs.readJson(configPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultConfig = await fs.readJson(defaultConfigPath);
|
||||||
|
await fs.writeJson(configPath, defaultConfig, { spaces: 2 });
|
||||||
|
return defaultConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadConfig.getConfigPath = () => configPath;
|
||||||
|
|
||||||
|
module.exports = { loadConfig };
|
||||||
47
services/folderProcessor.js
Normal file
47
services/folderProcessor.js
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
const fs = require('fs-extra');
|
||||||
|
const path = require('path');
|
||||||
|
const { findDestination, isCadFile } = require('./router');
|
||||||
|
|
||||||
|
async function processFolder(folder, config) {
|
||||||
|
const entries = await fs.readdir(folder, { withFileTypes: true });
|
||||||
|
const result = {
|
||||||
|
scanned: 0,
|
||||||
|
copied: 0,
|
||||||
|
skipped: 0,
|
||||||
|
details: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const entry of entries) {
|
||||||
|
if (!entry.isFile()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const file = entry.name;
|
||||||
|
result.scanned += 1;
|
||||||
|
|
||||||
|
if (!isCadFile(file)) {
|
||||||
|
result.skipped += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const src = path.join(folder, file);
|
||||||
|
const destDir = findDestination(file, config);
|
||||||
|
|
||||||
|
if (!destDir) {
|
||||||
|
result.skipped += 1;
|
||||||
|
result.details.push({ file, reason: 'Nessuna regola trovata' });
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
await fs.ensureDir(destDir);
|
||||||
|
const dest = path.join(destDir, file);
|
||||||
|
await fs.copy(src, dest, { overwrite: true });
|
||||||
|
|
||||||
|
result.copied += 1;
|
||||||
|
result.details.push({ file, destination: destDir });
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { processFolder };
|
||||||
29
services/router.js
Normal file
29
services/router.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
function findDestination(filename, config) {
|
||||||
|
const ext = path.extname(filename).slice(1).toLowerCase();
|
||||||
|
|
||||||
|
for (const rule of config.rules || []) {
|
||||||
|
if ((rule.ext || '').toLowerCase() !== ext) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rule.pattern) {
|
||||||
|
return rule.destination;
|
||||||
|
}
|
||||||
|
|
||||||
|
const regex = new RegExp(rule.pattern);
|
||||||
|
if (regex.test(path.basename(filename))) {
|
||||||
|
return rule.destination;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isCadFile(filename) {
|
||||||
|
const ext = path.extname(filename).slice(1).toLowerCase();
|
||||||
|
return ['prt', 'asm', 'drw'].includes(ext);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { findDestination, isCadFile };
|
||||||
52
services/zipProcessor.js
Normal file
52
services/zipProcessor.js
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
const unzipper = require('unzipper');
|
||||||
|
const fs = require('fs-extra');
|
||||||
|
const path = require('path');
|
||||||
|
const { pipeline } = require('stream/promises');
|
||||||
|
const { findDestination, isCadFile } = require('./router');
|
||||||
|
|
||||||
|
async function processZip(zipPath, config) {
|
||||||
|
const stream = fs.createReadStream(zipPath).pipe(unzipper.Parse({ forceStream: true }));
|
||||||
|
const result = {
|
||||||
|
scanned: 0,
|
||||||
|
copied: 0,
|
||||||
|
skipped: 0,
|
||||||
|
details: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
for await (const entry of stream) {
|
||||||
|
if (entry.type !== 'File') {
|
||||||
|
entry.autodrain();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const file = entry.path;
|
||||||
|
const baseName = path.basename(file);
|
||||||
|
result.scanned += 1;
|
||||||
|
|
||||||
|
if (!isCadFile(baseName)) {
|
||||||
|
result.skipped += 1;
|
||||||
|
entry.autodrain();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const destDir = findDestination(baseName, config);
|
||||||
|
|
||||||
|
if (!destDir) {
|
||||||
|
result.skipped += 1;
|
||||||
|
result.details.push({ file: baseName, reason: 'Nessuna regola trovata' });
|
||||||
|
entry.autodrain();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
await fs.ensureDir(destDir);
|
||||||
|
const dest = path.join(destDir, baseName);
|
||||||
|
await pipeline(entry, fs.createWriteStream(dest));
|
||||||
|
|
||||||
|
result.copied += 1;
|
||||||
|
result.details.push({ file: baseName, destination: destDir });
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { processZip };
|
||||||
396
sop.md
Normal file
396
sop.md
Normal file
@@ -0,0 +1,396 @@
|
|||||||
|
# SOP – CAD File Router MVP
|
||||||
|
|
||||||
|
## Obiettivo
|
||||||
|
Realizzare un'app desktop (.exe) che:
|
||||||
|
- accetti una cartella o uno ZIP
|
||||||
|
- legga file CAD (.prt .asm .drw)
|
||||||
|
- analizzi nome file ed estensione
|
||||||
|
- applichi regole di routing
|
||||||
|
- copi i file verso destinazioni configurate (anche share di rete)
|
||||||
|
|
||||||
|
Tecnologie:
|
||||||
|
- Node.js
|
||||||
|
- Electron
|
||||||
|
- unzipper
|
||||||
|
- fs-extra
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Prerequisiti
|
||||||
|
|
||||||
|
Installare:
|
||||||
|
- Node.js LTS
|
||||||
|
- Git
|
||||||
|
- Visual Studio Code / VSCodium
|
||||||
|
|
||||||
|
Verifica:
|
||||||
|
|
||||||
|
```
|
||||||
|
node -v
|
||||||
|
npm -v
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Creazione progetto
|
||||||
|
|
||||||
|
```
|
||||||
|
mkdir cad-file-router
|
||||||
|
cd cad-file-router
|
||||||
|
npm init -y
|
||||||
|
```
|
||||||
|
|
||||||
|
Installare dipendenze:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install electron unzipper fs-extra
|
||||||
|
npm install electron-builder --save-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Struttura progetto
|
||||||
|
|
||||||
|
```
|
||||||
|
cad-file-router
|
||||||
|
│
|
||||||
|
├── package.json
|
||||||
|
├── main.js
|
||||||
|
├── preload.js
|
||||||
|
│
|
||||||
|
├── renderer
|
||||||
|
│ ├── index.html
|
||||||
|
│ └── renderer.js
|
||||||
|
│
|
||||||
|
├── services
|
||||||
|
│ ├── router.js
|
||||||
|
│ ├── zipProcessor.js
|
||||||
|
│ ├── folderProcessor.js
|
||||||
|
│ └── configService.js
|
||||||
|
│
|
||||||
|
└── config
|
||||||
|
└── defaultConfig.json
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Configurazione iniziale
|
||||||
|
|
||||||
|
`config/defaultConfig.json`
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"rules": [
|
||||||
|
{
|
||||||
|
"ext": "prt",
|
||||||
|
"pattern": "^1",
|
||||||
|
"destination": "\\\\SERVER-CAD\\clienti1\\PRT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ext": "prt",
|
||||||
|
"pattern": "^6",
|
||||||
|
"destination": "\\\\SERVER-CAD\\clienti2\\PRT"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ext": "asm",
|
||||||
|
"destination": "\\\\SERVER-CAD\\assembly"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ext": "drw",
|
||||||
|
"destination": "\\\\SERVER-CAD\\drawing"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Config Service
|
||||||
|
|
||||||
|
`services/configService.js`
|
||||||
|
|
||||||
|
```
|
||||||
|
const fs = require("fs-extra")
|
||||||
|
const path = require("path")
|
||||||
|
const os = require("os")
|
||||||
|
|
||||||
|
const configPath = path.join(os.homedir(), ".cad-router-config.json")
|
||||||
|
|
||||||
|
function loadConfig(){
|
||||||
|
|
||||||
|
if(fs.existsSync(configPath)){
|
||||||
|
return fs.readJsonSync(configPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultConfig = fs.readJsonSync(
|
||||||
|
path.join(__dirname,"../config/defaultConfig.json")
|
||||||
|
)
|
||||||
|
|
||||||
|
fs.writeJsonSync(configPath, defaultConfig)
|
||||||
|
|
||||||
|
return defaultConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { loadConfig }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Motore di routing
|
||||||
|
|
||||||
|
`services/router.js`
|
||||||
|
|
||||||
|
```
|
||||||
|
const path = require("path")
|
||||||
|
|
||||||
|
function findDestination(filename, config){
|
||||||
|
|
||||||
|
const ext = path.extname(filename).slice(1)
|
||||||
|
|
||||||
|
for(const rule of config.rules){
|
||||||
|
|
||||||
|
if(rule.ext !== ext)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if(!rule.pattern)
|
||||||
|
return rule.destination
|
||||||
|
|
||||||
|
const regex = new RegExp(rule.pattern)
|
||||||
|
|
||||||
|
if(regex.test(filename))
|
||||||
|
return rule.destination
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { findDestination }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Processare una cartella
|
||||||
|
|
||||||
|
`services/folderProcessor.js`
|
||||||
|
|
||||||
|
```
|
||||||
|
const fs = require("fs-extra")
|
||||||
|
const path = require("path")
|
||||||
|
const { findDestination } = require("./router")
|
||||||
|
|
||||||
|
async function processFolder(folder, config){
|
||||||
|
|
||||||
|
const files = await fs.readdir(folder)
|
||||||
|
|
||||||
|
for(const file of files){
|
||||||
|
|
||||||
|
const src = path.join(folder,file)
|
||||||
|
|
||||||
|
const destDir = findDestination(file, config)
|
||||||
|
|
||||||
|
if(!destDir) continue
|
||||||
|
|
||||||
|
const dest = path.join(destDir,file)
|
||||||
|
|
||||||
|
await fs.copy(src,dest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { processFolder }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Processare ZIP
|
||||||
|
|
||||||
|
`services/zipProcessor.js`
|
||||||
|
|
||||||
|
```
|
||||||
|
const unzipper = require("unzipper")
|
||||||
|
const fs = require("fs-extra")
|
||||||
|
const path = require("path")
|
||||||
|
const { findDestination } = require("./router")
|
||||||
|
|
||||||
|
async function processZip(zipPath, config){
|
||||||
|
|
||||||
|
const stream = fs.createReadStream(zipPath)
|
||||||
|
.pipe(unzipper.Parse())
|
||||||
|
|
||||||
|
for await (const entry of stream){
|
||||||
|
|
||||||
|
const file = entry.path
|
||||||
|
|
||||||
|
const destDir = findDestination(file, config)
|
||||||
|
|
||||||
|
if(!destDir){
|
||||||
|
entry.autodrain()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const dest = path.join(destDir,path.basename(file))
|
||||||
|
|
||||||
|
entry.pipe(fs.createWriteStream(dest))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { processZip }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Main Electron
|
||||||
|
|
||||||
|
`main.js`
|
||||||
|
|
||||||
|
```
|
||||||
|
const { app, BrowserWindow, dialog, ipcMain } = require("electron")
|
||||||
|
const path = require("path")
|
||||||
|
|
||||||
|
const { processFolder } = require("./services/folderProcessor")
|
||||||
|
const { processZip } = require("./services/zipProcessor")
|
||||||
|
const { loadConfig } = require("./services/configService")
|
||||||
|
|
||||||
|
let config = loadConfig()
|
||||||
|
|
||||||
|
function createWindow(){
|
||||||
|
|
||||||
|
const win = new BrowserWindow({
|
||||||
|
width:800,
|
||||||
|
height:600,
|
||||||
|
webPreferences:{
|
||||||
|
preload: path.join(__dirname,"preload.js")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
win.loadFile("renderer/index.html")
|
||||||
|
}
|
||||||
|
|
||||||
|
app.whenReady().then(createWindow)
|
||||||
|
|
||||||
|
ipcMain.handle("select-folder", async () => {
|
||||||
|
|
||||||
|
const result = await dialog.showOpenDialog({
|
||||||
|
properties:["openDirectory"]
|
||||||
|
})
|
||||||
|
|
||||||
|
if(result.canceled) return
|
||||||
|
|
||||||
|
await processFolder(result.filePaths[0], config)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.handle("select-zip", async () => {
|
||||||
|
|
||||||
|
const result = await dialog.showOpenDialog({
|
||||||
|
filters:[{ name:"Zip", extensions:["zip"] }]
|
||||||
|
})
|
||||||
|
|
||||||
|
if(result.canceled) return
|
||||||
|
|
||||||
|
await processZip(result.filePaths[0], config)
|
||||||
|
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Preload
|
||||||
|
|
||||||
|
`preload.js`
|
||||||
|
|
||||||
|
```
|
||||||
|
const { contextBridge, ipcRenderer } = require("electron")
|
||||||
|
|
||||||
|
contextBridge.exposeInMainWorld("api", {
|
||||||
|
|
||||||
|
selectFolder: () => ipcRenderer.invoke("select-folder"),
|
||||||
|
|
||||||
|
selectZip: () => ipcRenderer.invoke("select-zip")
|
||||||
|
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. UI minima
|
||||||
|
|
||||||
|
`renderer/index.html`
|
||||||
|
|
||||||
|
```
|
||||||
|
<h2>CAD File Router</h2>
|
||||||
|
|
||||||
|
<button onclick="window.api.selectFolder()">
|
||||||
|
Process Folder
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button onclick="window.api.selectZip()">
|
||||||
|
Process ZIP
|
||||||
|
</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Avvio applicazione
|
||||||
|
|
||||||
|
Nel package.json aggiungere:
|
||||||
|
|
||||||
|
```
|
||||||
|
"scripts": {
|
||||||
|
"start": "electron ."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Avviare:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. Build .exe
|
||||||
|
|
||||||
|
Aggiungere nel package.json:
|
||||||
|
|
||||||
|
```
|
||||||
|
"build": {
|
||||||
|
"appId": "com.cad.router",
|
||||||
|
"win": {
|
||||||
|
"target": "nsis"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Build:
|
||||||
|
|
||||||
|
```
|
||||||
|
npx electron-builder
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```
|
||||||
|
dist/
|
||||||
|
cad-file-router Setup.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## MVP completato quando
|
||||||
|
|
||||||
|
- l'app si avvia
|
||||||
|
- si seleziona ZIP o cartella
|
||||||
|
- i file .prt .asm .drw vengono analizzati
|
||||||
|
- i file vengono copiati nelle destinazioni configurate
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Migliorie future
|
||||||
|
|
||||||
|
- progress bar
|
||||||
|
- drag and drop ZIP
|
||||||
|
- modifica regole da UI
|
||||||
|
- logging operazioni
|
||||||
|
- parallelismo file
|
||||||
|
- watch folder automatico
|
||||||
|
|
||||||
Reference in New Issue
Block a user