Auto-Updater: Doppelstart verhindern + Download-Status sichtbar

- State-Tracking im Main-Prozess (idle/checking/downloading/ready)
- Manueller Check gibt aktuellen Status zurück statt Squirrel-Doppelstart
- Auto-Check nur wenn idle (kein Konflikt mit laufendem Download)
- Frontend synchronisiert Status beim Mount und Modal-Öffnen
- getUpdateStatus IPC für synchrone Status-Abfrage

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Daniel 2026-03-08 16:27:29 +01:00
parent 2c6ac0745d
commit d909909591
3 changed files with 61 additions and 13 deletions

View file

@ -33,43 +33,75 @@ function setupAutoUpdater() {
return; return;
} }
// State-Tracking: verhindert Doppelstarts und bewahrt Status
let updateState = 'idle'; // idle | checking | downloading | ready
autoUpdater.on('checking-for-update', () => {
console.log('[AutoUpdater] Checking for updates...');
updateState = 'checking';
});
autoUpdater.on('update-available', () => { autoUpdater.on('update-available', () => {
console.log('[AutoUpdater] Update available, downloading...'); console.log('[AutoUpdater] Update available, downloading...');
updateState = 'downloading';
if (mainWindow) mainWindow.webContents.send('update-available'); if (mainWindow) mainWindow.webContents.send('update-available');
}); });
autoUpdater.on('update-downloaded', (_event, releaseNotes, releaseName) => { autoUpdater.on('update-downloaded', (_event, releaseNotes, releaseName) => {
console.log('[AutoUpdater] Update downloaded:', releaseName || 'new version'); console.log('[AutoUpdater] Update downloaded:', releaseName || 'new version');
updateState = 'ready';
if (mainWindow) mainWindow.webContents.send('update-ready'); if (mainWindow) mainWindow.webContents.send('update-ready');
}); });
autoUpdater.on('update-not-available', () => {
console.log('[AutoUpdater] App is up to date.');
updateState = 'idle';
if (mainWindow) mainWindow.webContents.send('update-not-available');
});
autoUpdater.on('error', (err) => {
console.error('[AutoUpdater] Error:', err.message);
updateState = 'idle';
if (mainWindow) mainWindow.webContents.send('update-error', err.message);
});
// Handle install-update request from renderer // Handle install-update request from renderer
ipcMain.on('install-update', () => { ipcMain.on('install-update', () => {
autoUpdater.quitAndInstall(); autoUpdater.quitAndInstall();
}); });
autoUpdater.on('update-not-available', () => { // Manual check from renderer — gibt aktuellen Status zurück statt Doppelstart
console.log('[AutoUpdater] App is up to date.');
if (mainWindow) mainWindow.webContents.send('update-not-available');
});
// Manual check from renderer
ipcMain.on('check-for-updates', () => { ipcMain.on('check-for-updates', () => {
if (updateState === 'downloading') {
if (mainWindow) mainWindow.webContents.send('update-available');
return;
}
if (updateState === 'ready') {
if (mainWindow) mainWindow.webContents.send('update-ready');
return;
}
if (updateState === 'checking') {
return; // bereits am Prüfen
}
try { autoUpdater.checkForUpdates(); } catch (e) { console.error('[AutoUpdater]', e.message); } try { autoUpdater.checkForUpdates(); } catch (e) { console.error('[AutoUpdater]', e.message); }
}); });
autoUpdater.on('error', (err) => { // Sync-Abfrage: Frontend kann aktuellen Status beim Modal-Öffnen abfragen
console.error('[AutoUpdater] Error:', err.message); ipcMain.on('get-update-status', (event) => {
if (mainWindow) mainWindow.webContents.send('update-error', err.message); event.returnValue = updateState;
}); });
// Check for updates after 5 seconds, then every 30 minutes // Auto-Check nach 5 Sek, dann alle 30 Min (nur wenn idle)
setTimeout(() => { setTimeout(() => {
try { autoUpdater.checkForUpdates(); } catch (e) { console.error('[AutoUpdater]', e.message); } if (updateState === 'idle') {
try { autoUpdater.checkForUpdates(); } catch (e) { console.error('[AutoUpdater]', e.message); }
}
}, 5000); }, 5000);
setInterval(() => { setInterval(() => {
try { autoUpdater.checkForUpdates(); } catch (e) { console.error('[AutoUpdater]', e.message); } if (updateState === 'idle') {
try { autoUpdater.checkForUpdates(); } catch (e) { console.error('[AutoUpdater]', e.message); }
}
}, 30 * 60 * 1000); }, 30 * 60 * 1000);
} }

View file

@ -9,6 +9,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
onUpdateNotAvailable: (callback) => ipcRenderer.on('update-not-available', callback), onUpdateNotAvailable: (callback) => ipcRenderer.on('update-not-available', callback),
onUpdateError: (callback) => ipcRenderer.on('update-error', (_e, msg) => callback(msg)), onUpdateError: (callback) => ipcRenderer.on('update-error', (_e, msg) => callback(msg)),
checkForUpdates: () => ipcRenderer.send('check-for-updates'), checkForUpdates: () => ipcRenderer.send('check-for-updates'),
getUpdateStatus: () => ipcRenderer.sendSync('get-update-status'),
installUpdate: () => ipcRenderer.send('install-update'), installUpdate: () => ipcRenderer.send('install-update'),
setStreaming: (active) => ipcRenderer.send('streaming-status', active), setStreaming: (active) => ipcRenderer.send('streaming-status', active),
showNotification: (title, body) => ipcRenderer.send('show-notification', title, body), showNotification: (title, body) => ipcRenderer.send('show-notification', title, body),

View file

@ -65,6 +65,11 @@ export default function App() {
setUpdateStatus('error'); setUpdateStatus('error');
setUpdateError(msg || 'Unbekannter Fehler'); setUpdateError(msg || 'Unbekannter Fehler');
}); });
// Sync initial status (Hintergrund-Download könnte bereits laufen)
const currentState = api.getUpdateStatus?.();
if (currentState === 'downloading') setUpdateStatus('downloading');
else if (currentState === 'ready') setUpdateStatus('ready');
else if (currentState === 'checking') setUpdateStatus('checking');
}, [isElectron]); }, [isElectron]);
// Fetch plugin list // Fetch plugin list
@ -185,7 +190,17 @@ export default function App() {
)} )}
<span <span
className="hub-version hub-version-clickable" className="hub-version hub-version-clickable"
onClick={() => setShowVersionModal(true)} onClick={() => {
// Status vom Main-Prozess synchronisieren bevor Modal öffnet
if (isElectron) {
const api = (window as any).electronAPI;
const s = api.getUpdateStatus?.();
if (s === 'downloading') setUpdateStatus('downloading');
else if (s === 'ready') setUpdateStatus('ready');
else if (s === 'checking') setUpdateStatus('checking');
}
setShowVersionModal(true);
}}
title="Versionsinformationen" title="Versionsinformationen"
> >
v{version} v{version}