fix: atomic config writes to prevent data loss on update/crash
- All config writes now go through _atomicWrite() (write to .tmp, backup to .bak, rename .tmp to main config) - load() falls back to .bak if main config is empty or corrupt - Prevents 0KB config files caused by process termination during write Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
6945b42886
commit
153ea2b193
@ -82,10 +82,24 @@ class ConfigStore {
|
||||
} catch {}
|
||||
}
|
||||
|
||||
_readAndParse(filePath) {
|
||||
const raw = fs.readFileSync(filePath, 'utf-8');
|
||||
if (!raw || raw.trim().length < 2) return null;
|
||||
return JSON.parse(raw);
|
||||
}
|
||||
|
||||
load() {
|
||||
try {
|
||||
const raw = fs.readFileSync(this.filePath, 'utf-8');
|
||||
const data = JSON.parse(raw);
|
||||
let data = null;
|
||||
// Try main config
|
||||
try { data = this._readAndParse(this.filePath); } catch {}
|
||||
// Fallback to backup if main is empty/corrupt
|
||||
if (!data) {
|
||||
const backupPath = this.filePath + '.bak';
|
||||
try { data = this._readAndParse(backupPath); } catch {}
|
||||
}
|
||||
if (!data) return JSON.parse(JSON.stringify(DEFAULTS));
|
||||
|
||||
// Merge with defaults so new hosters are always present
|
||||
const hosters = { ...DEFAULTS.hosters };
|
||||
for (const [name, val] of Object.entries(data.hosters || {})) {
|
||||
@ -116,12 +130,7 @@ class ConfigStore {
|
||||
if (config.hosters) current.hosters = config.hosters;
|
||||
if (config.hosterSettings) current.hosterSettings = config.hosterSettings;
|
||||
if (config.globalSettings) current.globalSettings = config.globalSettings;
|
||||
const data = JSON.stringify(current, null, 2);
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.writeFile(this.filePath, data, 'utf-8', (err) => {
|
||||
if (err) reject(err); else resolve();
|
||||
});
|
||||
});
|
||||
return this._atomicWrite(JSON.stringify(current, null, 2));
|
||||
}
|
||||
|
||||
loadHistory() {
|
||||
@ -129,29 +138,39 @@ class ConfigStore {
|
||||
return config.history || [];
|
||||
}
|
||||
|
||||
_atomicWrite(data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const tmpPath = this.filePath + '.tmp';
|
||||
const backupPath = this.filePath + '.bak';
|
||||
fs.writeFile(tmpPath, data, 'utf-8', (err) => {
|
||||
if (err) return reject(err);
|
||||
try {
|
||||
if (fs.existsSync(this.filePath)) {
|
||||
const existing = fs.readFileSync(this.filePath, 'utf-8');
|
||||
if (existing && existing.trim().length > 2) {
|
||||
fs.writeFileSync(backupPath, existing, 'utf-8');
|
||||
}
|
||||
}
|
||||
fs.renameSync(tmpPath, this.filePath);
|
||||
} catch (e) { return reject(e); }
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
appendHistory(entry) {
|
||||
const config = this.load();
|
||||
config.history.push(entry);
|
||||
if (config.history.length > MAX_HISTORY) {
|
||||
config.history = config.history.slice(-MAX_HISTORY);
|
||||
}
|
||||
const data = JSON.stringify(config, null, 2);
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.writeFile(this.filePath, data, 'utf-8', (err) => {
|
||||
if (err) reject(err); else resolve();
|
||||
});
|
||||
});
|
||||
return this._atomicWrite(JSON.stringify(config, null, 2));
|
||||
}
|
||||
|
||||
clearHistory() {
|
||||
const config = this.load();
|
||||
config.history = [];
|
||||
const data = JSON.stringify(config, null, 2);
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.writeFile(this.filePath, data, 'utf-8', (err) => {
|
||||
if (err) reject(err); else resolve();
|
||||
});
|
||||
});
|
||||
return this._atomicWrite(JSON.stringify(config, null, 2));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "multi-hoster-uploader",
|
||||
"version": "1.6.7",
|
||||
"version": "1.6.8",
|
||||
"description": "Upload files to doodstream, voe, vidmoly, byse simultaneously",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user