Files
cad-data-router/sop.md
2026-03-05 14:45:06 +01:00

6.0 KiB
Raw Blame History

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