Multi-Hoster-Upload/lib/config-store.js
Administrator f00dc36a41 fix: migrate config from old paths on first launch
Checks alternate AppData folder names and portable exe directory
to find existing config when current path has no config file.
Prevents losing accounts, settings, and queue after updates.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 04:00:25 +01:00

159 lines
5.0 KiB
JavaScript

const fs = require('fs');
const path = require('path');
const HOSTER_SETTINGS_DEFAULTS = {
retries: 3,
maxSpeedKbs: 0, // 0 = unlimited
parallelCount: 2, // 1-100
restartBelowKbs: 0, // 0 = off
timeIntervalSec: 0, // delay between jobs
maxSizeMb: 0 // 0 = unlimited
};
const DEFAULTS = {
hosters: {
'doodstream.com': { enabled: true, apiKey: '', username: '', password: '' },
'voe.sx': { enabled: true, apiKey: '' },
'vidmoly.me': { enabled: true, authType: 'login', username: '', password: '' },
'byse.sx': { enabled: true, apiKey: '' }
},
hosterSettings: {
'doodstream.com': { ...HOSTER_SETTINGS_DEFAULTS },
'voe.sx': { ...HOSTER_SETTINGS_DEFAULTS },
'vidmoly.me': { ...HOSTER_SETTINGS_DEFAULTS },
'byse.sx': { ...HOSTER_SETTINGS_DEFAULTS }
},
globalSettings: {
alwaysOnTop: false,
shutdownAfterFinish: 'nothing', // nothing | sleep | shutdown | restart
logFilePath: '',
resumeQueueOnLaunch: true,
parallelUploadCount: 0, // 0 = use per-hoster limits only
scaleParallelUploads: false,
removeFromQueueOnDone: false,
globalMaxSpeedKbs: 0, // 0 = unlimited global speed
pendingQueue: null,
scramble: {
active: false,
prefix: '',
suffix: '',
chars: 'both', // 'letters' | 'numbers' | 'both'
length: 0 // 0 = same as original basename length
}
},
history: []
};
const MAX_HISTORY = 100;
class ConfigStore {
constructor(app) {
const dir = app && app.isPackaged
? app.getPath('userData')
: path.join(__dirname, '..');
this.filePath = path.join(dir, 'electron-config.json');
// Migrate config from old location if current doesn't exist
if (!fs.existsSync(this.filePath) && app && app.isPackaged) {
this._migrateFromOldPath(app);
}
}
_migrateFromOldPath(app) {
try {
const appDataDir = path.dirname(app.getPath('userData'));
// Check alternate folder names that may have been used
const candidates = ['multi-hoster-uploader', 'Multi-Hoster-Upload'];
for (const name of candidates) {
const oldPath = path.join(appDataDir, name, 'electron-config.json');
if (oldPath !== this.filePath && fs.existsSync(oldPath)) {
fs.mkdirSync(path.dirname(this.filePath), { recursive: true });
fs.copyFileSync(oldPath, this.filePath);
return;
}
}
// Also check next to the executable (portable mode previous location)
const exeDir = path.dirname(app.getPath('exe'));
const portablePath = path.join(exeDir, 'electron-config.json');
if (portablePath !== this.filePath && fs.existsSync(portablePath)) {
fs.mkdirSync(path.dirname(this.filePath), { recursive: true });
fs.copyFileSync(portablePath, this.filePath);
}
} catch {}
}
load() {
try {
const raw = fs.readFileSync(this.filePath, 'utf-8');
const data = JSON.parse(raw);
// Merge with defaults so new hosters are always present
const hosters = { ...DEFAULTS.hosters };
for (const [name, val] of Object.entries(data.hosters || {})) {
if (hosters[name]) {
hosters[name] = { ...hosters[name], ...val };
}
}
// Merge hoster settings with defaults
const hosterSettings = {};
for (const name of Object.keys(DEFAULTS.hosterSettings)) {
hosterSettings[name] = {
...HOSTER_SETTINGS_DEFAULTS,
...(data.hosterSettings && data.hosterSettings[name] || {})
};
}
const globalSettings = {
...DEFAULTS.globalSettings,
...(data.globalSettings || {})
};
return { hosters, hosterSettings, globalSettings, history: data.history || [] };
} catch {
return JSON.parse(JSON.stringify(DEFAULTS));
}
}
save(config) {
const current = this.load();
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();
});
});
}
loadHistory() {
const config = this.load();
return config.history || [];
}
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();
});
});
}
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();
});
});
}
}
module.exports = ConfigStore;