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 {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_readAndParse(filePath) {
|
||||||
|
const raw = fs.readFileSync(filePath, 'utf-8');
|
||||||
|
if (!raw || raw.trim().length < 2) return null;
|
||||||
|
return JSON.parse(raw);
|
||||||
|
}
|
||||||
|
|
||||||
load() {
|
load() {
|
||||||
try {
|
try {
|
||||||
const raw = fs.readFileSync(this.filePath, 'utf-8');
|
let data = null;
|
||||||
const data = JSON.parse(raw);
|
// 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
|
// Merge with defaults so new hosters are always present
|
||||||
const hosters = { ...DEFAULTS.hosters };
|
const hosters = { ...DEFAULTS.hosters };
|
||||||
for (const [name, val] of Object.entries(data.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.hosters) current.hosters = config.hosters;
|
||||||
if (config.hosterSettings) current.hosterSettings = config.hosterSettings;
|
if (config.hosterSettings) current.hosterSettings = config.hosterSettings;
|
||||||
if (config.globalSettings) current.globalSettings = config.globalSettings;
|
if (config.globalSettings) current.globalSettings = config.globalSettings;
|
||||||
const data = JSON.stringify(current, null, 2);
|
return this._atomicWrite(JSON.stringify(current, null, 2));
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
fs.writeFile(this.filePath, data, 'utf-8', (err) => {
|
|
||||||
if (err) reject(err); else resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loadHistory() {
|
loadHistory() {
|
||||||
@ -129,29 +138,39 @@ class ConfigStore {
|
|||||||
return config.history || [];
|
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) {
|
appendHistory(entry) {
|
||||||
const config = this.load();
|
const config = this.load();
|
||||||
config.history.push(entry);
|
config.history.push(entry);
|
||||||
if (config.history.length > MAX_HISTORY) {
|
if (config.history.length > MAX_HISTORY) {
|
||||||
config.history = config.history.slice(-MAX_HISTORY);
|
config.history = config.history.slice(-MAX_HISTORY);
|
||||||
}
|
}
|
||||||
const data = JSON.stringify(config, null, 2);
|
return this._atomicWrite(JSON.stringify(config, null, 2));
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
fs.writeFile(this.filePath, data, 'utf-8', (err) => {
|
|
||||||
if (err) reject(err); else resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
clearHistory() {
|
clearHistory() {
|
||||||
const config = this.load();
|
const config = this.load();
|
||||||
config.history = [];
|
config.history = [];
|
||||||
const data = JSON.stringify(config, null, 2);
|
return this._atomicWrite(JSON.stringify(config, null, 2));
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
fs.writeFile(this.filePath, data, 'utf-8', (err) => {
|
|
||||||
if (err) reject(err); else resolve();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "multi-hoster-uploader",
|
"name": "multi-hoster-uploader",
|
||||||
"version": "1.6.7",
|
"version": "1.6.8",
|
||||||
"description": "Upload files to doodstream, voe, vidmoly, byse simultaneously",
|
"description": "Upload files to doodstream, voe, vidmoly, byse simultaneously",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user