Multi-Hoster-Upload/lib/config-store.js
Administrator 0851bb09fc feat: floating drop target window and English column labels
- Small always-on-top drop target window (toggle in Settings > Allgemein)
- Files dropped on it get added to the queue with hoster modal
- Auto-shows on app start if previously enabled
- Column headers now in English (Filename, Uploaded/Size, Progress)
- Statusbar labels in English (Connections, Total)

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

199 lines
6.4 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: '',
sessionLog: false,
resumeQueueOnLaunch: true,
parallelUploadCount: 0, // 0 = use per-hoster limits only
scaleParallelUploads: false,
removeFromQueueOnDone: false,
showDropTarget: 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
},
folderMonitor: {
enabled: false,
folderPath: '',
recursive: false,
filterMode: 'include', // 'include' | 'exclude'
extensions: '', // comma-separated: 'mp4,mkv,avi'
skipDuplicates: true,
delaySec: 3,
autoStart: true,
hosters: [] // pre-selected hosters, empty = ask via modal
}
},
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 {}
}
_readAndParse(filePath) {
const raw = fs.readFileSync(filePath, 'utf-8');
if (!raw || raw.trim().length < 2) return null;
return JSON.parse(raw);
}
load() {
try {
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 || {})) {
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 savedGlobal = data.globalSettings || {};
const globalSettings = {
...DEFAULTS.globalSettings,
...savedGlobal
};
// Deep-merge nested objects so new keys are always present
for (const key of Object.keys(DEFAULTS.globalSettings)) {
const def = DEFAULTS.globalSettings[key];
if (def && typeof def === 'object' && !Array.isArray(def)) {
globalSettings[key] = { ...def, ...(savedGlobal[key] || {}) };
}
}
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;
return this._atomicWrite(JSON.stringify(current, null, 2));
}
loadHistory() {
const config = this.load();
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);
}
return this._atomicWrite(JSON.stringify(config, null, 2));
}
clearHistory() {
const config = this.load();
config.history = [];
return this._atomicWrite(JSON.stringify(config, null, 2));
}
}
module.exports = ConfigStore;