const { app, BrowserWindow, dialog, ipcMain, shell, Menu } = require('electron'); const path = require('path'); const fs = require('fs-extra'); const { processFolder } = require('./services/folderProcessor'); const { processZip } = require('./services/zipProcessor'); const { resolveUnroutedDir, listUnroutedFiles, listDuplicateFiles, listSkippedFiles, clearUnroutedFiles, clearDuplicateFiles, clearSkippedFiles, } = require('./services/unrouted'); const CAD_EXTENSIONS = ['prt', 'asm', 'drw']; const DEFAULT_DESTINATION = 'X:\\'; const SETTINGS_FILENAME = 'cad-router-settings.json'; const LINUX_RUNTIME_DIR = '.cadroute'; function buildConfig(destination) { const resolvedDestination = String(destination || '').trim() || DEFAULT_DESTINATION; return { destination: resolvedDestination, rules: CAD_EXTENSIONS.map((ext) => ({ ext, destination: resolvedDestination })), }; } function getSettingsPath() { return path.join(getRuntimeDirectory(), SETTINGS_FILENAME); } function getRuntimeDirectory() { if (process.platform === 'linux') { return path.join(app.getPath('home'), LINUX_RUNTIME_DIR); } return app.getPath('userData'); } async function ensureRuntimeDirectory() { await fs.ensureDir(getRuntimeDirectory()); } async function loadPersistedDestination() { try { const settingsPath = getSettingsPath(); if (!(await fs.pathExists(settingsPath))) { return DEFAULT_DESTINATION; } const settings = await fs.readJson(settingsPath); const destination = String(settings?.destination || '').trim(); return destination || DEFAULT_DESTINATION; } catch { return DEFAULT_DESTINATION; } } async function persistDestination(destination) { const settingsPath = getSettingsPath(); await fs.ensureDir(path.dirname(settingsPath)); await fs.writeJson(settingsPath, { destination }, { spaces: 2 }); } let config = buildConfig(DEFAULT_DESTINATION); function createWindow() { const win = new BrowserWindow({ width: 1000, height: 800, icon: path.join(__dirname, 'build', 'icon.png'), webPreferences: { preload: path.join(__dirname, 'preload.js'), contextIsolation: true, nodeIntegration: false, }, }); win.loadFile(path.join(__dirname, 'renderer', 'index.html')); } function createDocsWindow() { const docs = new BrowserWindow({ width: 900, height: 700, title: 'CadRoute — Documentazione', icon: path.join(__dirname, 'build', 'icon.png'), webPreferences: { contextIsolation: true, nodeIntegration: false }, }); docs.loadFile(path.join(__dirname, 'renderer', 'docs', 'index.html')); docs.setMenuBarVisibility(false); } function showAboutDialog() { dialog.showMessageBox({ type: 'info', title: 'Informazioni su CadRoute', message: 'CadRoute', detail: [ `Versione: ${app.getVersion()}`, `Autore: Davide Grilli`, `Azienda: Cevolani Italia s.r.l.`, `Licenza: MIT`, ``, `Smistatore automatico di file CAD (Creo).`, ``, `Electron: ${process.versions.electron}`, `Node: ${process.versions.node}`, ].join('\n'), buttons: ['OK'], noLink: true, }); } Menu.setApplicationMenu(Menu.buildFromTemplate([ { label: 'Informazioni', submenu: [{ label: 'Informazioni su CadRoute...', click: () => showAboutDialog() }], }, { label: 'Help', submenu: [{ label: 'Documentazione', click: () => createDocsWindow() }], }, ])); app.whenReady().then(async () => { await ensureRuntimeDirectory(); const persistedDestination = await loadPersistedDestination(); config = buildConfig(persistedDestination); 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 (event) => { const result = await dialog.showOpenDialog({ properties: ['openDirectory'], }); if (result.canceled || !result.filePaths[0]) { return { canceled: true }; } 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 (event) => { const result = await dialog.showOpenDialog({ properties: ['openFile'], filters: [{ name: 'Zip', extensions: ['zip'] }], }); if (result.canceled || !result.filePaths[0]) { return { canceled: true }; } 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) => { const droppedPath = String(payload?.path || '').trim(); if (!droppedPath) { throw new Error('Percorso non valido'); } const stats = await fs.stat(droppedPath).catch(() => null); if (!stats) { throw new Error('Percorso non trovato'); } const onProgress = (data) => event.sender.send('progress', data); if (stats.isDirectory()) { 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, onProgress); return { canceled: false, sourceType: 'zip', ...routingResult }; } throw new Error('Trascina una cartella o un file .zip'); }); ipcMain.handle('get-destination', async () => ({ destination: config.destination || DEFAULT_DESTINATION, })); ipcMain.handle('select-destination-folder', async () => { const result = await dialog.showOpenDialog({ properties: ['openDirectory'], }); if (result.canceled || !result.filePaths[0]) { return { canceled: true }; } return { canceled: false, path: result.filePaths[0], }; }); ipcMain.handle('update-destination', async (_event, payload) => { const destination = String(payload?.destination || '').trim(); if (!destination) { throw new Error('La destinazione non puo essere vuota'); } config = buildConfig(destination); await persistDestination(config.destination); return { destination: config.destination }; }); ipcMain.handle('open-unrouted-folder', async () => { const unroutedDir = await resolveUnroutedDir(); const openResult = await shell.openPath(unroutedDir); if (openResult) { throw new Error(`Impossibile aprire la cartella: ${openResult}`); } return { path: unroutedDir }; }); ipcMain.handle('list-unrouted-files', async () => listUnroutedFiles()); ipcMain.handle('list-duplicates-files', async () => listDuplicateFiles()); ipcMain.handle('list-skipped-files', async () => listSkippedFiles()); ipcMain.handle('clear-unrouted-files', async () => clearUnroutedFiles()); ipcMain.handle('clear-duplicates-files', async () => clearDuplicateFiles()); ipcMain.handle('clear-skipped-files', async () => clearSkippedFiles());