Upload file updater.js via GUI

This commit is contained in:
2026-02-02 20:00:53 +01:00
parent 6895a042d1
commit d88eade5a0

164
updater.js Normal file
View File

@@ -0,0 +1,164 @@
// updater.js - Moderner Auto-Updater für Git Manager GUI
const { app, shell } = require('electron');
const https = require('https');
const fs = require('fs');
const path = require('path');
const { spawn } = require('child_process');
const GITEA_API_URL = 'https://git.viper.ipv64.net/api/v1/repos/M_Viper/Git-Manager-Gui/releases';
class Updater {
constructor(mainWindow) {
this.mainWindow = mainWindow;
this.checkingForUpdates = false;
}
/**
* Hauptfunktion zur Prüfung auf Updates
*/
async checkForUpdates(silent = false) {
if (this.checkingForUpdates) return;
this.checkingForUpdates = true;
try {
const latestRelease = await this.getLatestRelease();
if (!latestRelease) return;
// Versionen säubern (nur Zahlen und Punkte)
const serverVer = latestRelease.tag_name.replace(/[^\d.]/g, '');
const localVer = app.getVersion().replace(/[^\d.]/g, '');
console.log(`[Updater] Version-Check: Server(${serverVer}) vs Lokal(${localVer})`);
if (this.compareVersions(serverVer, localVer) > 0) {
console.log("[Updater] Update verfügbar. Sende Daten an Renderer...");
this.mainWindow.webContents.send('update-available', {
version: serverVer,
body: latestRelease.body,
url: latestRelease.html_url,
asset: this.findAsset(latestRelease.assets)
});
} else {
console.log("[Updater] Anwendung ist auf dem neuesten Stand.");
}
} catch (error) {
console.error('[Updater] Fehler beim Update-Check:', error);
} finally {
this.checkingForUpdates = false;
}
}
async getLatestRelease() {
return new Promise((resolve, reject) => {
const options = { headers: { 'User-Agent': 'GitManager-GUI-Updater' } };
https.get(GITEA_API_URL, options, (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
try {
const releases = JSON.parse(data);
resolve(Array.isArray(releases) ? releases[0] : null);
} catch (e) { reject(e); }
});
}).on('error', reject);
});
}
compareVersions(v1, v2) {
const a = v1.split('.').map(Number);
const b = v2.split('.').map(Number);
for (let i = 0; i < 3; i++) {
if ((a[i] || 0) > (b[i] || 0)) return 1;
if ((a[i] || 0) < (b[i] || 0)) return -1;
}
return 0;
}
findAsset(assets) {
if (!assets) return null;
const ext = process.platform === 'win32' ? '.exe' : '.AppImage';
return assets.find(a => a.name.toLowerCase().endsWith(ext));
}
/**
* Startet den Download und führt die Installation aus
*/
async startDownload(asset) {
if (!asset || !asset.browser_download_url) {
console.error("[Updater] Kein gültiges Asset gefunden!");
return;
}
const tempPath = path.join(app.getPath('temp'), asset.name);
console.log(`[Updater] Download gestartet: ${asset.name} -> ${tempPath}`);
const file = fs.createWriteStream(tempPath);
const download = (url) => {
https.get(url, { headers: { 'User-Agent': 'Electron-Updater' } }, (res) => {
// Handle Redirects
if (res.statusCode === 301 || res.statusCode === 302) {
return download(res.headers.location);
}
if (res.statusCode !== 200) {
console.error(`[Updater] Download-Fehler: Status ${res.statusCode}`);
return;
}
res.pipe(file);
file.on('finish', () => {
file.close();
console.log("[Updater] Download abgeschlossen. Initialisiere entkoppelten Installer...");
this.installAndQuit(tempPath);
});
}).on('error', (err) => {
fs.unlink(tempPath, () => {});
console.error("[Updater] Netzwerkfehler beim Download:", err);
});
};
download(asset.browser_download_url);
}
/**
* Führt den Installer entkoppelt aus und schließt die App mit Verzögerung
*/
installAndQuit(filePath) {
console.log(`[Updater] Bereite Installation vor: ${filePath}`);
if (process.platform === 'win32') {
// Wir nutzen spawn mit detached: true, damit der Installer weiterläuft,
// wenn der Hauptprozess (Electron) beendet wird.
try {
const child = spawn('cmd.exe', ['/c', 'start', '""', filePath], {
detached: true,
stdio: 'ignore'
});
child.unref(); // Trennt die Referenz zum Installer
console.log("[Updater] Installer-Prozess entkoppelt gestartet. App schließt in 2 Sek...");
// WICHTIG: Wir geben Windows Zeit, den Installer stabil zu laden,
// bevor wir die App schließen und die Dateisperren aufheben.
setTimeout(() => {
app.quit();
}, 2000);
} catch (err) {
console.error("[Updater] Kritischer Fehler beim Installer-Start:", err);
// Notfall-Versuch über shell
shell.openPath(filePath).then(() => app.quit());
}
} else {
// Linux/AppImage
shell.openPath(filePath).then(() => {
setTimeout(() => app.quit(), 1000);
});
}
}
}
module.exports = Updater;