diff --git a/main.js b/main.js index 1c7cdef..d891b29 100644 --- a/main.js +++ b/main.js @@ -103,6 +103,70 @@ function resolveAssetUrl(release) { return asset ? asset.browser_download_url : null; } +function sendUpdateProgress(percent, status, error) { + if (mainWindow && !mainWindow.isDestroyed()) + mainWindow.webContents.send('update-progress', { percent, status, error }); +} + +function downloadFileWithProgress(url, destPath, onProgress) { + return new Promise((resolve, reject) => { + const follow = (currentUrl, redirects) => { + if (redirects > 5) return reject(new Error('Trop de redirections')); + const mod = currentUrl.startsWith('https') ? require('https') : require('http'); + const req = mod.get(currentUrl, { timeout: 120000, headers: { 'User-Agent': 'farm-tracker-updater' } }, (res) => { + if ([301, 302, 307, 308].includes(res.statusCode)) + return follow(res.headers.location, redirects + 1); + if (res.statusCode !== 200) + return reject(new Error(`HTTP ${res.statusCode}`)); + const total = parseInt(res.headers['content-length'] || '0', 10); + let downloaded = 0; + const file = fs.createWriteStream(destPath); + res.on('data', chunk => { + downloaded += chunk.length; + file.write(chunk); + if (total > 0 && onProgress) onProgress(Math.round(downloaded / total * 100)); + }); + res.on('end', () => file.close(() => resolve(destPath))); + res.on('error', err => { file.close(); try { fs.unlinkSync(destPath); } catch (_) {} reject(err); }); + }); + req.on('error', reject); + req.on('timeout', () => { req.destroy(); reject(new Error('Timeout')); }); + }; + follow(url, 0); + }); +} + +async function downloadAndInstall(downloadUrl, version) { + if (!app.isPackaged) { + dialog.showMessageBox(mainWindow, { + type: 'info', title: 'Mode développement', + message: 'Auto-install non disponible en mode dev.', + buttons: ['OK'] + }); + return; + } + const ext = process.platform === 'win32' ? '.exe' : '.AppImage'; + const tmpPath = path.join(app.getPath('temp'), `farm-tracker-${version}${ext}`); + try { + sendUpdateProgress(0, 'downloading'); + await downloadFileWithProgress(downloadUrl, tmpPath, pct => sendUpdateProgress(pct, 'downloading')); + sendUpdateProgress(100, 'installing'); + if (process.platform === 'linux') { + fs.chmodSync(tmpPath, 0o755); + const current = process.env.APPIMAGE; + if (current) { fs.copyFileSync(tmpPath, current); fs.unlinkSync(tmpPath); app.relaunch(); } + else app.relaunch({ execPath: tmpPath }); + app.exit(0); + } else if (process.platform === 'win32') { + const { spawn } = require('child_process'); + spawn(tmpPath, ['/S'], { detached: true, stdio: 'ignore' }).unref(); + app.quit(); + } + } catch (e) { + sendUpdateProgress(0, 'error', e.message); + } +} + async function showUpdateDialog(version, downloadUrl, ignoreSnooze = false) { const current = app.getVersion(); if (!isNewerVersion(version, current)) return false; @@ -117,14 +181,14 @@ async function showUpdateDialog(version, downloadUrl, ignoreSnooze = false) { type: 'info', title: 'Mise à jour disponible', message: `Version ${version} disponible`, - detail: `Version actuelle : ${current}`, - buttons: ['Télécharger', 'Me rappeler dans...'], + detail: `Version actuelle : ${current}\nL'application se relancera automatiquement après la mise à jour.`, + buttons: ['Installer maintenant', 'Me rappeler dans...'], defaultId: 0, cancelId: 1 }); if (response === 0) { - shell.openExternal(downloadUrl); + downloadAndInstall(downloadUrl, version); return true; } diff --git a/preload.js b/preload.js index 2d58ff2..57f1c36 100644 --- a/preload.js +++ b/preload.js @@ -24,5 +24,6 @@ contextBridge.exposeInMainWorld('api', { loadSessionHistory: () => ipcRenderer.invoke('load-session-history'), saveSessionHistory: (obj) => ipcRenderer.invoke('save-session-history', obj), - checkUpdateManual: () => ipcRenderer.invoke('check-update-manual') + checkUpdateManual: () => ipcRenderer.invoke('check-update-manual'), + onUpdateProgress: (cb) => ipcRenderer.on('update-progress', (_, data) => cb(data)) }); diff --git a/renderer/index.html b/renderer/index.html index 4b4b830..c85ff6e 100644 --- a/renderer/index.html +++ b/renderer/index.html @@ -2002,6 +2002,35 @@ })(); })(); + + // ---------- Progression de mise à jour ---------- + if (window.api && window.api.onUpdateProgress) { + const overlay = document.createElement('div'); + overlay.id = 'updateOverlay'; + overlay.style.cssText = 'position:fixed;inset:0;background:rgba(11,15,12,0.94);z-index:9999;display:none;align-items:center;justify-content:center;flex-direction:column;gap:18px;'; + overlay.innerHTML = + '