Commit iniziale
This commit is contained in:
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