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:
+127
-17
@@ -137,10 +137,7 @@ function batchRowCount(batch) {
|
||||
let n = 0;
|
||||
const files = (batch && batch.files) || [];
|
||||
for (const file of files) {
|
||||
for (const result of (file.results || [])) {
|
||||
if (result.status === 'aborted' || result.status === 'error') continue;
|
||||
n++;
|
||||
}
|
||||
n += (file.results || []).length;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
@@ -180,13 +177,19 @@ function applyHistoryRetention(history, retention, nowMs) {
|
||||
|
||||
class ConfigStore {
|
||||
constructor(app) {
|
||||
const dir = app && app.isPackaged
|
||||
const useUserDataDir = app && (
|
||||
app.isPackaged ||
|
||||
(app.commandLine && typeof app.commandLine.hasSwitch === 'function' && app.commandLine.hasSwitch('user-data-dir'))
|
||||
);
|
||||
const dir = useUserDataDir
|
||||
? app.getPath('userData')
|
||||
: path.join(__dirname, '..');
|
||||
this.filePath = path.join(dir, 'electron-config.json');
|
||||
this.historyPath = path.join(dir, 'electron-history.json');
|
||||
this._writeQueue = Promise.resolve(); // Serializes all writes to prevent race conditions
|
||||
this._historyWriteQueue = Promise.resolve();
|
||||
this._pendingWriteOperations = new Set();
|
||||
this._writesQuiesced = false;
|
||||
this._historyMigrated = false;
|
||||
this._cache = null;
|
||||
this._cacheKey = '';
|
||||
@@ -238,9 +241,26 @@ class ConfigStore {
|
||||
});
|
||||
}
|
||||
|
||||
_enqueueHistoryWrite(fn) {
|
||||
this._historyWriteQueue = this._historyWriteQueue.then(fn, fn);
|
||||
return this._historyWriteQueue;
|
||||
_quiescedWriteError() {
|
||||
const error = new Error('Die Anwendung wird gerade beendet');
|
||||
error.code = 'CONFIG_WRITES_QUIESCED';
|
||||
return error;
|
||||
}
|
||||
|
||||
setWritesQuiesced(quiesced) {
|
||||
this._writesQuiesced = !!quiesced;
|
||||
}
|
||||
|
||||
_enqueueHistoryWrite(fn, options = {}) {
|
||||
if (this._writesQuiesced && !options.allowDuringQuiesce) return Promise.reject(this._quiescedWriteError());
|
||||
const operation = this._historyWriteQueue.then(fn, fn);
|
||||
this._pendingWriteOperations.add(operation);
|
||||
this._historyWriteQueue = operation.then(() => undefined, () => undefined);
|
||||
operation.then(
|
||||
() => this._pendingWriteOperations.delete(operation),
|
||||
() => this._pendingWriteOperations.delete(operation)
|
||||
);
|
||||
return operation;
|
||||
}
|
||||
|
||||
_migrateHistory() {
|
||||
@@ -476,11 +496,31 @@ class ConfigStore {
|
||||
return this._atomicWrite(data);
|
||||
}
|
||||
|
||||
_enqueueWrite(fn) {
|
||||
_enqueueWrite(fn, options = {}) {
|
||||
if (this._writesQuiesced && !options.allowDuringQuiesce) return Promise.reject(this._quiescedWriteError());
|
||||
this._wqDepth++;
|
||||
const done = () => { this._wqDepth--; };
|
||||
this._writeQueue = this._writeQueue.then(fn, fn).then(done, done);
|
||||
return this._writeQueue;
|
||||
const operation = this._writeQueue.then(fn, fn);
|
||||
this._pendingWriteOperations.add(operation);
|
||||
this._writeQueue = operation.then(
|
||||
() => { this._wqDepth--; },
|
||||
() => {
|
||||
this._wqDepth--;
|
||||
}
|
||||
);
|
||||
operation.then(
|
||||
() => this._pendingWriteOperations.delete(operation),
|
||||
() => this._pendingWriteOperations.delete(operation)
|
||||
);
|
||||
return operation;
|
||||
}
|
||||
|
||||
async drainWrites() {
|
||||
while (this._pendingWriteOperations.size > 0) {
|
||||
const pending = Array.from(this._pendingWriteOperations);
|
||||
const results = await Promise.allSettled(pending);
|
||||
const failed = results.find(result => result.status === 'rejected');
|
||||
if (failed) throw failed.reason;
|
||||
}
|
||||
}
|
||||
|
||||
_anyHosters(cfg) {
|
||||
@@ -522,6 +562,56 @@ class ConfigStore {
|
||||
});
|
||||
}
|
||||
|
||||
savePendingQueue(pendingQueue, options = {}) {
|
||||
const snapshot = pendingQueue === null || pendingQueue === undefined ? null : this._clone(pendingQueue);
|
||||
return this._enqueueWrite(() => {
|
||||
const current = this.load();
|
||||
current.globalSettings = {
|
||||
...(current.globalSettings || {}),
|
||||
pendingQueue: snapshot
|
||||
};
|
||||
this._guardHosters(current, false);
|
||||
return this._commit(current);
|
||||
}, options);
|
||||
}
|
||||
|
||||
saveRendererGlobalSettings(globalSettings) {
|
||||
const snapshot = this._clone(globalSettings || {});
|
||||
return this._enqueueWrite(() => {
|
||||
const current = this.load();
|
||||
const currentGlobalSettings = current.globalSettings || {};
|
||||
const currentRemote = currentGlobalSettings.remote || {};
|
||||
const incomingRemote = snapshot.remote || {};
|
||||
current.globalSettings = {
|
||||
...snapshot,
|
||||
pendingQueue: currentGlobalSettings.pendingQueue ?? null,
|
||||
diagnostics: this._clone(currentGlobalSettings.diagnostics || {}),
|
||||
historyRetention: currentGlobalSettings.historyRetention || 'all',
|
||||
remote: {
|
||||
...incomingRemote,
|
||||
token: incomingRemote.token || currentRemote.token || ''
|
||||
}
|
||||
};
|
||||
this._guardHosters(current, false);
|
||||
return this._commit(current);
|
||||
});
|
||||
}
|
||||
|
||||
saveRemoteSettings(remoteSettings, createToken) {
|
||||
const incoming = this._clone(remoteSettings || {});
|
||||
return this._enqueueWrite(async () => {
|
||||
const current = this.load();
|
||||
const currentGlobalSettings = current.globalSettings || {};
|
||||
const currentRemote = currentGlobalSettings.remote || {};
|
||||
const token = incoming.token || currentRemote.token || (incoming.enabled && typeof createToken === 'function' ? createToken() : '');
|
||||
const canonical = { ...incoming, token };
|
||||
current.globalSettings = { ...currentGlobalSettings, remote: canonical };
|
||||
this._guardHosters(current, false);
|
||||
await this._commit(current);
|
||||
return this._clone(canonical);
|
||||
});
|
||||
}
|
||||
|
||||
replaceSettings(config) {
|
||||
return this._enqueueWrite(() => {
|
||||
const current = this.load();
|
||||
@@ -604,8 +694,12 @@ class ConfigStore {
|
||||
pruneHistory(retention, opts = {}) {
|
||||
const dryRun = !!opts.dryRun;
|
||||
if (this._historyMigrated) {
|
||||
return this._enqueueHistoryWrite(() => {
|
||||
const current = this._readHistoryFile() || [];
|
||||
return this._enqueueHistoryWrite(async () => {
|
||||
const storedHistory = this._readHistoryFile();
|
||||
if (storedHistory === null && fs.existsSync(this.historyPath)) {
|
||||
throw new Error('Die Verlaufsdatei ist beschädigt und wurde nicht verändert');
|
||||
}
|
||||
const current = storedHistory || [];
|
||||
const beforeBatches = current.length;
|
||||
const beforeRows = countHistoryRows(current);
|
||||
const pruned = applyHistoryRetention(current, retention, Date.now());
|
||||
@@ -616,9 +710,25 @@ class ConfigStore {
|
||||
keptRows: countHistoryRows(pruned)
|
||||
};
|
||||
if (dryRun) return result;
|
||||
return this._writeHistoryFileAtomic(pruned)
|
||||
.then(() => this.save({ globalSettings: { ...this.load().globalSettings, historyRetention: String(retention || 'all') } }))
|
||||
.then(() => result);
|
||||
return this._enqueueWrite(async () => {
|
||||
const config = this.load();
|
||||
const previousGlobalSettings = this._clone(config.globalSettings || {});
|
||||
config.globalSettings = { ...previousGlobalSettings, historyRetention: String(retention || 'all') };
|
||||
this._guardHosters(config, false);
|
||||
await this._commit(config);
|
||||
try {
|
||||
await this._writeHistoryFileAtomic(pruned);
|
||||
} catch (historyError) {
|
||||
config.globalSettings = previousGlobalSettings;
|
||||
try {
|
||||
await this._commit(config);
|
||||
} catch (rollbackError) {
|
||||
throw new AggregateError([historyError, rollbackError], 'Verlauf und Aufbewahrung konnten nicht konsistent gespeichert werden');
|
||||
}
|
||||
throw historyError;
|
||||
}
|
||||
return result;
|
||||
});
|
||||
});
|
||||
}
|
||||
return this._enqueueWrite(() => {
|
||||
|
||||
Reference in New Issue
Block a user