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 = + '
Téléchargement de la mise à jour…
' + + '
' + + '
' + + '
' + + '
0%
'; + document.body.appendChild(overlay); + + window.api.onUpdateProgress(({ percent, status, error }) => { + const bar = document.getElementById('updateProgressBar'); + const pct = document.getElementById('updateOverlayPct'); + const txt = document.getElementById('updateOverlayText'); + if (status === 'error') { + overlay.style.display = 'none'; + return; + } + overlay.style.display = 'flex'; + bar.style.width = percent + '%'; + pct.textContent = percent + '%'; + txt.textContent = status === 'installing' ? 'Installation… l\'application va redémarrer.' : 'Téléchargement de la mise à jour…'; + }); + } + diff --git a/tools/release.sh b/tools/release.sh new file mode 100755 index 0000000..8860a00 --- /dev/null +++ b/tools/release.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")/.." + +# ---------- Token ---------- +TOKEN="${GITEA_TOKEN:-}" +if [ -z "$TOKEN" ] && [ -f ".gitea-token" ]; then + TOKEN=$(tr -d '[:space:]' < .gitea-token) +fi +if [ -z "$TOKEN" ]; then + read -rsp "Token Gitea : " TOKEN + echo +fi + +# ---------- Version ---------- +CURRENT=$(node -p "require('./package.json').version") +if [ -n "${1:-}" ]; then + VERSION="$1" +else + echo "Version actuelle : v$CURRENT" + read -rp "Nouvelle version [patch / minor / major / X.Y.Z] : " VERSION +fi + +npm version "$VERSION" --no-git-tag-version --silent +NEW=$(node -p "require('./package.json').version") +echo "→ v$NEW" + +# ---------- Build ---------- +echo "" +echo "Nettoyage des anciens binaires..." +find dist -maxdepth 1 \( -name "*.AppImage" -o -name "*.exe" -o -name "*.blockmap" \) -delete 2>/dev/null || true + +echo "Build Linux (AppImage)..." +npm run dist --silent + +if command -v wine &>/dev/null; then + echo "Build Windows (exe)..." + npm run dist:win --silent +else + echo "(Wine non trouvé — build Windows ignoré)" +fi + +# ---------- Release Gitea ---------- +echo "" +GITEA_TOKEN="$TOKEN" node tools/release.js + +# ---------- Git ---------- +git add package.json package-lock.json +git commit -m "chore: release v$NEW" --quiet +git remote set-url origin "https://shamiiow:${TOKEN}@git.shamiiow.com/shamiiow/farm-tracker-sao.git" +git push --quiet +git remote set-url origin "https://git.shamiiow.com/shamiiow/farm-tracker-sao.git" + +echo "" +echo "Release v$NEW publiée."