Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d159ac484a | ||
|
|
f4b5fadc5f | ||
|
|
169817f707 | ||
|
|
1418c2bc17 | ||
|
|
8d33141294 | ||
|
|
35341b522a |
28
main.js
28
main.js
@ -1750,12 +1750,19 @@ ipcMain.handle('export-backup', async () => {
|
|||||||
const { canceled, filePath } = await dialog.showSaveDialog(mainWindow, {
|
const { canceled, filePath } = await dialog.showSaveDialog(mainWindow, {
|
||||||
title: 'Backup exportieren',
|
title: 'Backup exportieren',
|
||||||
defaultPath: `multi-hoster-backup-${new Date().toISOString().slice(0, 10)}.mhu`,
|
defaultPath: `multi-hoster-backup-${new Date().toISOString().slice(0, 10)}.mhu`,
|
||||||
filters: [{ name: 'Multi-Hoster Backup', extensions: ['mhu'] }]
|
filters: [
|
||||||
|
{ name: 'Multi-Hoster Backup (verschlüsselt)', extensions: ['mhu'] },
|
||||||
|
{ name: 'Multi-Hoster Backup (Klartext JSON)', extensions: ['json'] }
|
||||||
|
]
|
||||||
});
|
});
|
||||||
if (canceled || !filePath) return { ok: false, canceled: true };
|
if (canceled || !filePath) return { ok: false, canceled: true };
|
||||||
const config = configStore.load();
|
const config = configStore.load();
|
||||||
|
if (filePath.toLowerCase().endsWith('.json')) {
|
||||||
|
fs.writeFileSync(filePath, JSON.stringify(config, null, 2), 'utf-8');
|
||||||
|
} else {
|
||||||
const encrypted = backupCrypto.encrypt(config);
|
const encrypted = backupCrypto.encrypt(config);
|
||||||
fs.writeFileSync(filePath, encrypted);
|
fs.writeFileSync(filePath, encrypted);
|
||||||
|
}
|
||||||
return { ok: true, path: filePath };
|
return { ok: true, path: filePath };
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1767,7 +1774,11 @@ ipcMain.handle('import-backup', async (_event, legacyPassword) => {
|
|||||||
} else {
|
} else {
|
||||||
const { canceled, filePaths } = await dialog.showOpenDialog(mainWindow, {
|
const { canceled, filePaths } = await dialog.showOpenDialog(mainWindow, {
|
||||||
title: 'Backup importieren',
|
title: 'Backup importieren',
|
||||||
filters: [{ name: 'Multi-Hoster Backup', extensions: ['mhu'] }],
|
filters: [
|
||||||
|
{ name: 'Multi-Hoster Backup', extensions: ['mhu', 'json'] },
|
||||||
|
{ name: 'Verschlüsselt (.mhu)', extensions: ['mhu'] },
|
||||||
|
{ name: 'Klartext (.json)', extensions: ['json'] }
|
||||||
|
],
|
||||||
properties: ['openFile']
|
properties: ['openFile']
|
||||||
});
|
});
|
||||||
if (canceled || !filePaths.length) return { ok: false, canceled: true };
|
if (canceled || !filePaths.length) return { ok: false, canceled: true };
|
||||||
@ -1776,15 +1787,26 @@ ipcMain.handle('import-backup', async (_event, legacyPassword) => {
|
|||||||
_lastImportPath = sourcePath;
|
_lastImportPath = sourcePath;
|
||||||
}
|
}
|
||||||
let imported;
|
let imported;
|
||||||
|
const looksLikeJson = buffer.length >= 1 && (buffer[0] === 0x7B || buffer[0] === 0x20 || buffer[0] === 0x0A || buffer[0] === 0x0D || buffer[0] === 0x09 || buffer[0] === 0xEF);
|
||||||
|
if (looksLikeJson) {
|
||||||
|
try {
|
||||||
|
const text = buffer.toString('utf-8').replace(/^\uFEFF/, '');
|
||||||
|
imported = JSON.parse(text);
|
||||||
|
} catch (err) {
|
||||||
|
_lastImportPath = null;
|
||||||
|
return { ok: false, error: 'Klartext-Backup ist kein gültiges JSON: ' + (err.message || err) };
|
||||||
|
}
|
||||||
|
} else {
|
||||||
try {
|
try {
|
||||||
imported = backupCrypto.decrypt(buffer, legacyPassword);
|
imported = backupCrypto.decrypt(buffer, legacyPassword);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err && err.needsPassword) {
|
if (err && err.needsPassword) {
|
||||||
return { ok: false, needsPassword: true };
|
return { ok: false, needsPassword: true, hint: 'Falls dieses Backup mit der aktuellen Version erzeugt wurde, ist die Datei vermutlich beim Transfer beschädigt worden (z. B. FTP-Text-Modus). Versuch es mit einem Klartext-JSON-Export.' };
|
||||||
}
|
}
|
||||||
_lastImportPath = null;
|
_lastImportPath = null;
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
_lastImportPath = null;
|
_lastImportPath = null;
|
||||||
// Validate imported data has required structure
|
// Validate imported data has required structure
|
||||||
if (!imported || typeof imported !== 'object' || !imported.hosters || !imported.hosterSettings || !imported.globalSettings) {
|
if (!imported || typeof imported !== 'object' || !imported.hosters || !imported.hosterSettings || !imported.globalSettings) {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "multi-hoster-uploader",
|
"name": "multi-hoster-uploader",
|
||||||
"version": "3.3.48",
|
"version": "3.3.51",
|
||||||
"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": {
|
||||||
|
|||||||
@ -63,10 +63,12 @@ const queueSortState = { key: 'filename', direction: 'asc' };
|
|||||||
// History state
|
// History state
|
||||||
let historyRowsData = [];
|
let historyRowsData = [];
|
||||||
let historySortState = { key: 'date', direction: 'desc' };
|
let historySortState = { key: 'date', direction: 'desc' };
|
||||||
|
let _historySortClicked = false;
|
||||||
|
|
||||||
// Session-specific files for the "Files" panel (resets each session)
|
// Session-specific files for the "Files" panel (resets each session)
|
||||||
let sessionFilesData = [];
|
let sessionFilesData = [];
|
||||||
const recentSortState = { key: 'date', direction: 'desc' };
|
const recentSortState = { key: 'date', direction: 'desc' };
|
||||||
|
let _recentSortClicked = false;
|
||||||
const selectedRecentIds = new Set();
|
const selectedRecentIds = new Set();
|
||||||
// Maintained incrementally — avoids O(n) filter() scans every 250ms in the status bar.
|
// Maintained incrementally — avoids O(n) filter() scans every 250ms in the status bar.
|
||||||
let _sessionDoneCount = 0;
|
let _sessionDoneCount = 0;
|
||||||
@ -1443,7 +1445,7 @@ async function doBackupExport() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function askLegacyBackupPassword() {
|
function askLegacyBackupPassword(hint) {
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const overlay = document.createElement('div');
|
const overlay = document.createElement('div');
|
||||||
overlay.className = 'modal-overlay';
|
overlay.className = 'modal-overlay';
|
||||||
@ -1456,7 +1458,7 @@ function askLegacyBackupPassword() {
|
|||||||
const header = document.createElement('div');
|
const header = document.createElement('div');
|
||||||
header.className = 'modal-header';
|
header.className = 'modal-header';
|
||||||
const h3 = document.createElement('h3');
|
const h3 = document.createElement('h3');
|
||||||
h3.textContent = 'Passwort erforderlich';
|
h3.textContent = 'Backup nicht entschlüsselbar';
|
||||||
header.appendChild(h3);
|
header.appendChild(h3);
|
||||||
|
|
||||||
const body = document.createElement('div');
|
const body = document.createElement('div');
|
||||||
@ -1464,7 +1466,15 @@ function askLegacyBackupPassword() {
|
|||||||
const p = document.createElement('p');
|
const p = document.createElement('p');
|
||||||
p.style.margin = '0 0 10px';
|
p.style.margin = '0 0 10px';
|
||||||
p.style.fontSize = '13px';
|
p.style.fontSize = '13px';
|
||||||
p.textContent = 'Dieses Backup wurde mit einem Passwort verschlüsselt.';
|
p.textContent = 'Wenn das Backup mit der alten Passwort-Option (vor v3.0) erstellt wurde, hier eingeben.';
|
||||||
|
if (hint) {
|
||||||
|
const p2 = document.createElement('p');
|
||||||
|
p2.style.margin = '0 0 10px';
|
||||||
|
p2.style.fontSize = '12px';
|
||||||
|
p2.style.color = 'var(--text-dim)';
|
||||||
|
p2.textContent = hint;
|
||||||
|
body.appendChild(p2);
|
||||||
|
}
|
||||||
const input = document.createElement('input');
|
const input = document.createElement('input');
|
||||||
input.type = 'password';
|
input.type = 'password';
|
||||||
input.className = 'key-input';
|
input.className = 'key-input';
|
||||||
@ -1508,7 +1518,7 @@ async function doBackupImport(legacyPassword) {
|
|||||||
const result = await window.api.importBackup(pw);
|
const result = await window.api.importBackup(pw);
|
||||||
if (!result || result.canceled) return;
|
if (!result || result.canceled) return;
|
||||||
if (result.needsPassword) {
|
if (result.needsPassword) {
|
||||||
const entered = await askLegacyBackupPassword();
|
const entered = await askLegacyBackupPassword(result.hint);
|
||||||
if (entered) doBackupImport(entered);
|
if (entered) doBackupImport(entered);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -2522,8 +2532,10 @@ async function executeHealthCheck(hosters, _mode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function runHealthCheck(mode = 'manual', requestedHosters = null) {
|
async function runHealthCheck(mode = 'manual', requestedHosters = null) {
|
||||||
if (healthCheckRunning || (uploading && mode === 'manual')) return [];
|
if (healthCheckRunning) {
|
||||||
// Build check list: all enabled accounts with creds
|
if (mode === 'manual') showCopyToast('Account-Check läuft bereits.');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
let hosters;
|
let hosters;
|
||||||
if (Array.isArray(requestedHosters) && requestedHosters.length > 0) {
|
if (Array.isArray(requestedHosters) && requestedHosters.length > 0) {
|
||||||
hosters = requestedHosters;
|
hosters = requestedHosters;
|
||||||
@ -4140,8 +4152,14 @@ function renderHistoryTable(container) {
|
|||||||
const th = e.target.closest('th.sortable');
|
const th = e.target.closest('th.sortable');
|
||||||
if (th && container.contains(th)) {
|
if (th && container.contains(th)) {
|
||||||
const key = th.dataset.historySort;
|
const key = th.dataset.historySort;
|
||||||
if (historySortState.key === key) historySortState.direction = historySortState.direction === 'asc' ? 'desc' : 'asc';
|
const defaultDir = key === 'date' ? 'desc' : 'asc';
|
||||||
else { historySortState.key = key; historySortState.direction = key === 'date' ? 'desc' : 'asc'; }
|
if (!_historySortClicked || historySortState.key !== key) {
|
||||||
|
_historySortClicked = true;
|
||||||
|
historySortState.key = key;
|
||||||
|
historySortState.direction = defaultDir;
|
||||||
|
} else {
|
||||||
|
historySortState.direction = historySortState.direction === 'asc' ? 'desc' : 'asc';
|
||||||
|
}
|
||||||
renderHistoryTable(container);
|
renderHistoryTable(container);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -4197,11 +4215,13 @@ function setupListeners() {
|
|||||||
const th = e.target.closest('th[data-recent-sort]');
|
const th = e.target.closest('th[data-recent-sort]');
|
||||||
if (!th) return;
|
if (!th) return;
|
||||||
const key = th.dataset.recentSort;
|
const key = th.dataset.recentSort;
|
||||||
if (recentSortState.key === key) {
|
const defaultDir = key === 'date' ? 'desc' : 'asc';
|
||||||
recentSortState.direction = recentSortState.direction === 'desc' ? 'asc' : 'desc';
|
if (!_recentSortClicked || recentSortState.key !== key) {
|
||||||
} else {
|
_recentSortClicked = true;
|
||||||
recentSortState.key = key;
|
recentSortState.key = key;
|
||||||
recentSortState.direction = key === 'date' ? 'desc' : 'asc';
|
recentSortState.direction = defaultDir;
|
||||||
|
} else {
|
||||||
|
recentSortState.direction = recentSortState.direction === 'desc' ? 'asc' : 'desc';
|
||||||
}
|
}
|
||||||
renderRecentUploadsPanel();
|
renderRecentUploadsPanel();
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user