release: v2.0.6
Redesign the desktop workspace with task sidebars, live filters, clearer settings, and an accessible update dialog. Harden encrypted backup imports, configuration persistence, history retention, queue snapshots, shutdown recovery, and update installation ordering. Publish verified Windows artifacts and refreshed English documentation.
This commit is contained in:
+36
-33
@@ -14,6 +14,7 @@ let cachedCheckTs = 0;
|
||||
const CACHE_TTL = 10 * 60 * 1000; // 10 min
|
||||
|
||||
let activeAbort = null;
|
||||
const launchedInstallerPaths = new Set();
|
||||
|
||||
function getCurrentVersion() {
|
||||
return app.getVersion();
|
||||
@@ -131,10 +132,10 @@ async function checkForUpdate() {
|
||||
return cachedCheck;
|
||||
}
|
||||
|
||||
async function parseLatestYml(url) {
|
||||
async function parseLatestYml(url, fetchImpl = fetch) {
|
||||
if (!url) return null;
|
||||
try {
|
||||
const res = await fetch(url, { redirect: 'follow' });
|
||||
const res = await fetchImpl(url, { redirect: 'follow' });
|
||||
const text = await res.text();
|
||||
// Extract sha512 from latest.yml
|
||||
const match = text.match(/sha512:\s*([A-Za-z0-9+/=]+)/);
|
||||
@@ -150,17 +151,18 @@ function verifyExeHeader(buf) {
|
||||
return buf[0] === 0x4D && buf[1] === 0x5A; // 'MZ'
|
||||
}
|
||||
|
||||
async function installUpdate(onProgress) {
|
||||
async function prepareUpdate(onProgress, options = {}) {
|
||||
if (activeAbort) activeAbort.abort();
|
||||
activeAbort = new AbortController();
|
||||
const signal = activeAbort.signal;
|
||||
const fetchImpl = options.fetchImpl || fetch;
|
||||
|
||||
try {
|
||||
// Stage: starting
|
||||
if (onProgress) onProgress({ stage: 'starting', percent: 0 });
|
||||
|
||||
// Check or use cached
|
||||
let check = cachedCheck;
|
||||
let check = options.checkResult || cachedCheck;
|
||||
if (!check || !check.available) {
|
||||
check = await checkForUpdate();
|
||||
}
|
||||
@@ -172,10 +174,10 @@ async function installUpdate(onProgress) {
|
||||
}
|
||||
|
||||
// Stage: downloading
|
||||
const tmpDir = app.getPath('temp');
|
||||
const tmpDir = options.tempDir || app.getPath('temp');
|
||||
const installerPath = path.join(tmpDir, check.assetName);
|
||||
|
||||
const res = await fetch(check.assetUrl, {
|
||||
const res = await fetchImpl(check.assetUrl, {
|
||||
method: 'GET',
|
||||
signal,
|
||||
redirect: 'follow'
|
||||
@@ -233,7 +235,7 @@ async function installUpdate(onProgress) {
|
||||
}
|
||||
|
||||
// Optional SHA-512 verification from latest.yml
|
||||
const expectedSha = await parseLatestYml(check.latestYmlUrl);
|
||||
const expectedSha = await parseLatestYml(check.latestYmlUrl, fetchImpl);
|
||||
if (expectedSha) {
|
||||
const actualSha = crypto.createHash('sha512').update(fileBuffer).digest('base64');
|
||||
if (actualSha !== expectedSha) {
|
||||
@@ -248,32 +250,14 @@ async function installUpdate(onProgress) {
|
||||
// Write to disk
|
||||
fs.writeFileSync(installerPath, fileBuffer);
|
||||
|
||||
// Stage: launching
|
||||
if (onProgress) onProgress({ stage: 'launching', percent: 100 });
|
||||
|
||||
const { spawn } = require('child_process');
|
||||
spawn(installerPath, ['/S', '--updated', '--force-run'], {
|
||||
detached: true,
|
||||
stdio: 'ignore'
|
||||
}).unref();
|
||||
|
||||
// Stage: done
|
||||
if (onProgress) onProgress({ stage: 'done', percent: 100 });
|
||||
|
||||
const _doQuit = () => setTimeout(() => app.quit(), 900);
|
||||
const _getActive = () => {
|
||||
try { return globalThis._mhuUploadManagerRef && globalThis._mhuUploadManagerRef.getActiveJobCount ? globalThis._mhuUploadManagerRef.getActiveJobCount() : 0; }
|
||||
catch { return 0; }
|
||||
const prepared = {
|
||||
installerPath,
|
||||
assetName: check.assetName,
|
||||
remoteVersion: check.remoteVersion || '',
|
||||
transportTag: check.transportTag || ''
|
||||
};
|
||||
if (_getActive() > 0) {
|
||||
const POLL_MS = 3000;
|
||||
const poller = setInterval(() => {
|
||||
if (_getActive() === 0) { clearInterval(poller); _doQuit(); }
|
||||
}, POLL_MS);
|
||||
setTimeout(() => { try { clearInterval(poller); } catch {} _doQuit(); }, 30 * 60 * 1000);
|
||||
} else {
|
||||
_doQuit();
|
||||
}
|
||||
if (onProgress) onProgress({ stage: 'prepared', percent: 100 });
|
||||
return prepared;
|
||||
|
||||
} catch (err) {
|
||||
if (onProgress) onProgress({ stage: 'error', error: err.message });
|
||||
@@ -283,6 +267,25 @@ async function installUpdate(onProgress) {
|
||||
}
|
||||
}
|
||||
|
||||
function launchPreparedUpdate(prepared, options = {}) {
|
||||
const installerPath = prepared && typeof prepared.installerPath === 'string' ? prepared.installerPath : '';
|
||||
if (!installerPath) throw new Error('Vorbereitetes Update ist unvollständig');
|
||||
const key = path.resolve(installerPath).toLowerCase();
|
||||
if (launchedInstallerPaths.has(key)) return false;
|
||||
const spawnImpl = options.spawnImpl || require('child_process').spawn;
|
||||
launchedInstallerPaths.add(key);
|
||||
try {
|
||||
spawnImpl(installerPath, ['/S', '--updated', '--force-run'], {
|
||||
detached: true,
|
||||
stdio: 'ignore'
|
||||
}).unref();
|
||||
return true;
|
||||
} catch (error) {
|
||||
launchedInstallerPaths.delete(key);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function abortUpdate() {
|
||||
if (activeAbort) {
|
||||
activeAbort.abort();
|
||||
@@ -290,4 +293,4 @@ function abortUpdate() {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { checkForUpdate, installUpdate, abortUpdate, isNewer, resolveReleaseVersion };
|
||||
module.exports = { checkForUpdate, prepareUpdate, launchPreparedUpdate, abortUpdate, isNewer, resolveReleaseVersion };
|
||||
|
||||
Reference in New Issue
Block a user