Accesso al File System
Uno dei principali vantaggi di Electron è l’accesso completo al file system tramite le API di Node.js. Questo permette di creare editor di testo, file manager, IDE e qualsiasi applicazione che necessiti di interagire con i file dell’utente.
Percorsi dell’Applicazione
Electron fornisce percorsi predefiniti per le cartelle comuni del sistema:
// main.js
const { app } = require('electron');
app.getPath('home'); // Home dell'utente: C:\Users\Nome
app.getPath('appData'); // Dati app: C:\Users\Nome\AppData\Roaming
app.getPath('userData'); // Dati utente: appData + nome app
app.getPath('documents'); // Documenti: C:\Users\Nome\Documents
app.getPath('downloads'); // Download: C:\Users\Nome\Downloads
app.getPath('desktop'); // Desktop: C:\Users\Nome\Desktop
app.getPath('temp'); // File temporanei
app.getPath('exe'); // Eseguibile dell'app
Leggere File
// main.js
const fs = require('node:fs');
const fsPromises = require('node:fs/promises');
const { ipcMain } = require('electron');
// Lettura asincrona (consigliata)
ipcMain.handle('fs:readFile', async (_event, filePath) => {
try {
const content = await fsPromises.readFile(filePath, 'utf-8');
return { success: true, content };
} catch (error) {
return { success: false, error: error.message };
}
});
// Leggere file binari (immagini, etc.)
ipcMain.handle('fs:readBinaryFile', async (_event, filePath) => {
const buffer = await fsPromises.readFile(filePath);
return buffer.toString('base64');
});
// Leggere file JSON
ipcMain.handle('fs:readJSON', async (_event, filePath) => {
const content = await fsPromises.readFile(filePath, 'utf-8');
return JSON.parse(content);
});
Scrivere File
// main.js
ipcMain.handle('fs:writeFile', async (_event, filePath, content) => {
try {
await fsPromises.writeFile(filePath, content, 'utf-8');
return { success: true };
} catch (error) {
return { success: false, error: error.message };
}
});
// Aggiungere contenuto a un file
ipcMain.handle('fs:appendFile', async (_event, filePath, content) => {
await fsPromises.appendFile(filePath, content, 'utf-8');
return { success: true };
});
// Scrivere JSON formattato
ipcMain.handle('fs:writeJSON', async (_event, filePath, data) => {
const content = JSON.stringify(data, null, 2);
await fsPromises.writeFile(filePath, content, 'utf-8');
return { success: true };
});
Gestire Cartelle
// Leggere il contenuto di una cartella
ipcMain.handle('fs:readDir', async (_event, dirPath) => {
const entries = await fsPromises.readdir(dirPath, { withFileTypes: true });
return entries.map((entry) => ({
name: entry.name,
isDirectory: entry.isDirectory(),
isFile: entry.isFile(),
path: path.join(dirPath, entry.name),
}));
});
// Creare una cartella
ipcMain.handle('fs:mkdir', async (_event, dirPath) => {
await fsPromises.mkdir(dirPath, { recursive: true });
return { success: true };
});
// Eliminare una cartella (con contenuto)
ipcMain.handle('fs:rmdir', async (_event, dirPath) => {
await fsPromises.rm(dirPath, { recursive: true, force: true });
return { success: true };
});
Informazioni sui File
ipcMain.handle('fs:stat', async (_event, filePath) => {
const stats = await fsPromises.stat(filePath);
return {
size: stats.size,
isDirectory: stats.isDirectory(),
isFile: stats.isFile(),
created: stats.birthtime,
modified: stats.mtime,
accessed: stats.atime,
};
});
// Verificare se un file esiste
ipcMain.handle('fs:exists', async (_event, filePath) => {
try {
await fsPromises.access(filePath);
return true;
} catch {
return false;
}
});
Rinominare e Spostare
// Rinominare un file
ipcMain.handle('fs:rename', async (_event, oldPath, newPath) => {
await fsPromises.rename(oldPath, newPath);
return { success: true };
});
// Copiare un file
ipcMain.handle('fs:copy', async (_event, src, dest) => {
await fsPromises.copyFile(src, dest);
return { success: true };
});
// Eliminare un file
ipcMain.handle('fs:delete', async (_event, filePath) => {
await fsPromises.unlink(filePath);
return { success: true };
});
Monitorare i Cambiamenti (File Watcher)
const { watch } = require('node:fs');
ipcMain.handle('fs:watch', (_event, dirPath) => {
const watcher = watch(dirPath, { recursive: true }, (eventType, filename) => {
// Invia l'evento al renderer
mainWindow.webContents.send('fs:changed', {
type: eventType, // 'rename' o 'change'
filename: filename,
path: path.join(dirPath, filename),
});
});
// Salva il watcher per poterlo chiudere
return 'watching';
});
ipcMain.on('fs:unwatch', () => {
// Chiudi il watcher
});
Preload e Renderer
// preload.js
contextBridge.exposeInMainWorld('fileSystem', {
readFile: (path) => ipcRenderer.invoke('fs:readFile', path),
writeFile: (path, content) => ipcRenderer.invoke('fs:writeFile', path, content),
readDir: (path) => ipcRenderer.invoke('fs:readDir', path),
exists: (path) => ipcRenderer.invoke('fs:exists', path),
stat: (path) => ipcRenderer.invoke('fs:stat', path),
rename: (oldPath, newPath) => ipcRenderer.invoke('fs:rename', oldPath, newPath),
delete: (path) => ipcRenderer.invoke('fs:delete', path),
onFileChanged: (callback) => {
ipcRenderer.on('fs:changed', (_event, data) => callback(data));
},
});
// renderer.js
async function loadFile(filePath) {
const result = await window.fileSystem.readFile(filePath);
if (result.success) {
editor.value = result.content;
} else {
showError(result.error);
}
}
async function listFiles(dirPath) {
const entries = await window.fileSystem.readDir(dirPath);
entries.forEach((entry) => {
const icon = entry.isDirectory ? '📁' : '📄';
console.log(`${icon} ${entry.name}`);
});
}
Gestione dei Dati dell’Applicazione
Per salvare configurazioni e dati dell’applicazione, usa la cartella userData:
// main.js
const userDataPath = app.getPath('userData');
const configPath = path.join(userDataPath, 'config.json');
// Leggere la configurazione
function loadConfig() {
try {
const data = fs.readFileSync(configPath, 'utf-8');
return JSON.parse(data);
} catch {
return {}; // Config di default se il file non esiste
}
}
// Salvare la configurazione
function saveConfig(config) {
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
}
Best Practice
- Usa sempre percorsi assoluti — costruiscili con
path.join() - Gestisci gli errori — il file system può fallire per permessi, file inesistenti, disco pieno
- Preferisci le API asincrone — non bloccare il Main Process con operazioni sincrone
- Valida i percorsi — assicurati che i percorsi dal renderer non escano dalla cartella prevista
- Usa
app.getPath()per i percorsi di sistema — non costruire percorsi hardcoded