Phase 2.2: performance optimizations, debug tools, app close fix

This commit is contained in:
2026-01-31 20:31:53 +02:00
parent fb2b55caff
commit 26f8067c94
18 changed files with 1006 additions and 167 deletions

View File

@@ -1,5 +1,5 @@
const { app, BrowserWindow, ipcMain, dialog, shell } = require('electron');
const { spawn } = require('child_process');
const { spawn, execSync } = require('child_process');
const path = require('path');
const fs = require('fs');
const Store = require('electron-store');
@@ -179,7 +179,8 @@ async function startBackend() {
backendProcess = spawn(backendExe, [], {
cwd: backendDir,
env,
stdio: ['ignore', 'pipe', 'pipe']
stdio: ['ignore', 'pipe', 'pipe'],
detached: process.platform !== 'win32' // Unix: create process group for clean kill
});
}
@@ -305,6 +306,35 @@ function createMainWindow() {
});
}
// ── Backend cleanup ───────────────────────────────────────────────
function killBackend() {
if (!backendProcess) return;
const pid = backendProcess.pid;
log(`Killing backend (PID ${pid})...`);
try {
if (process.platform === 'win32') {
// Windows: taskkill with /T (tree) to kill child processes too
execSync(`taskkill /f /t /pid ${pid}`, { stdio: 'ignore' });
} else {
// Unix: kill process group
process.kill(-pid, 'SIGTERM');
}
} catch (e) {
// Fallback: try normal kill
try {
backendProcess.kill('SIGKILL');
} catch (_e2) {
// Already dead
}
}
backendProcess = null;
log('Backend killed');
}
// ── App lifecycle ──────────────────────────────────────────────────
app.whenReady().then(async () => {
@@ -335,10 +365,7 @@ app.whenReady().then(async () => {
});
app.on('window-all-closed', () => {
if (backendProcess) {
backendProcess.kill();
backendProcess = null;
}
killBackend();
if (process.platform !== 'darwin') {
app.quit();
@@ -352,11 +379,11 @@ app.on('activate', () => {
});
app.on('before-quit', () => {
if (backendProcess) {
backendProcess.kill();
}
killBackend();
if (backendLogStream) {
backendLogStream.end();
backendLogStream = null;
}
});
@@ -404,3 +431,74 @@ ipcMain.handle('set-setting', (_event, key, value) => store.set(key, value));
ipcMain.handle('open-external', (_event, url) => shell.openExternal(url));
ipcMain.handle('open-path', (_event, filePath) => shell.openPath(filePath));
// ── Import Region Data ────────────────────────────────────────────
ipcMain.handle('import-region-data', async () => {
const result = await dialog.showOpenDialog(mainWindow, {
title: 'Select folder with region data',
properties: ['openDirectory']
});
const srcDir = result.filePaths[0];
if (!srcDir) return { success: false, message: 'Cancelled' };
const dataPath = getDataPath();
let terrainCount = 0;
let osmCount = 0;
try {
// Copy terrain/*.hgt files
const terrainSrc = path.join(srcDir, 'terrain');
if (fs.existsSync(terrainSrc)) {
const terrainDest = path.join(dataPath, 'terrain');
if (!fs.existsSync(terrainDest)) {
fs.mkdirSync(terrainDest, { recursive: true });
}
const hgtFiles = fs.readdirSync(terrainSrc).filter(f => f.endsWith('.hgt'));
for (const file of hgtFiles) {
fs.copyFileSync(path.join(terrainSrc, file), path.join(terrainDest, file));
terrainCount++;
}
}
// Copy osm/**/*.json files
const osmSrc = path.join(srcDir, 'osm');
if (fs.existsSync(osmSrc)) {
const osmDest = path.join(dataPath, 'osm');
const subdirs = fs.readdirSync(osmSrc).filter(d =>
fs.statSync(path.join(osmSrc, d)).isDirectory()
);
for (const subdir of subdirs) {
const subSrc = path.join(osmSrc, subdir);
const subDest = path.join(osmDest, subdir);
if (!fs.existsSync(subDest)) {
fs.mkdirSync(subDest, { recursive: true });
}
const jsonFiles = fs.readdirSync(subSrc).filter(f => f.endsWith('.json'));
for (const file of jsonFiles) {
fs.copyFileSync(path.join(subSrc, file), path.join(subDest, file));
osmCount++;
}
}
}
if (terrainCount === 0 && osmCount === 0) {
return {
success: false,
message: 'No data files found. Expected terrain/*.hgt or osm/**/*.json'
};
}
log(`Imported ${terrainCount} terrain tiles, ${osmCount} OSM files from ${srcDir}`);
return {
success: true,
terrainCount,
osmCount,
message: `Imported ${terrainCount} terrain tiles and ${osmCount} OSM cache files`
};
} catch (e) {
log(`Import error: ${e.message}`);
return { success: false, message: `Import failed: ${e.message}` };
}
});

View File

@@ -21,6 +21,9 @@ contextBridge.exposeInMainWorld('rfcp', {
openExternal: (url) => ipcRenderer.invoke('open-external', url),
openPath: (path) => ipcRenderer.invoke('open-path', path),
// Region data import
importRegionData: () => ipcRenderer.invoke('import-region-data'),
// Platform info
platform: process.platform,
isDesktop: true,