diff --git a/main.js b/main.js index 957b81c..3c6fb5d 100644 --- a/main.js +++ b/main.js @@ -1750,12 +1750,19 @@ ipcMain.handle('export-backup', async () => { const { canceled, filePath } = await dialog.showSaveDialog(mainWindow, { title: 'Backup exportieren', 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 }; const config = configStore.load(); - const encrypted = backupCrypto.encrypt(config); - fs.writeFileSync(filePath, encrypted); + if (filePath.toLowerCase().endsWith('.json')) { + fs.writeFileSync(filePath, JSON.stringify(config, null, 2), 'utf-8'); + } else { + const encrypted = backupCrypto.encrypt(config); + fs.writeFileSync(filePath, encrypted); + } return { ok: true, path: filePath }; }); @@ -1767,7 +1774,11 @@ ipcMain.handle('import-backup', async (_event, legacyPassword) => { } else { const { canceled, filePaths } = await dialog.showOpenDialog(mainWindow, { 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'] }); if (canceled || !filePaths.length) return { ok: false, canceled: true }; @@ -1776,14 +1787,25 @@ ipcMain.handle('import-backup', async (_event, legacyPassword) => { _lastImportPath = sourcePath; } let imported; - try { - imported = backupCrypto.decrypt(buffer, legacyPassword); - } catch (err) { - if (err && err.needsPassword) { - return { ok: false, needsPassword: true }; + 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 { + imported = backupCrypto.decrypt(buffer, legacyPassword); + } catch (err) { + if (err && err.needsPassword) { + 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; + throw err; } - _lastImportPath = null; - throw err; } _lastImportPath = null; // Validate imported data has required structure diff --git a/renderer/app.js b/renderer/app.js index d28c0cd..a5d89bf 100644 --- a/renderer/app.js +++ b/renderer/app.js @@ -1443,7 +1443,7 @@ async function doBackupExport() { } } -function askLegacyBackupPassword() { +function askLegacyBackupPassword(hint) { return new Promise((resolve) => { const overlay = document.createElement('div'); overlay.className = 'modal-overlay'; @@ -1456,7 +1456,7 @@ function askLegacyBackupPassword() { const header = document.createElement('div'); header.className = 'modal-header'; const h3 = document.createElement('h3'); - h3.textContent = 'Passwort erforderlich'; + h3.textContent = 'Backup nicht entschlüsselbar'; header.appendChild(h3); const body = document.createElement('div'); @@ -1464,7 +1464,15 @@ function askLegacyBackupPassword() { const p = document.createElement('p'); p.style.margin = '0 0 10px'; 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'); input.type = 'password'; input.className = 'key-input'; @@ -1508,7 +1516,7 @@ async function doBackupImport(legacyPassword) { const result = await window.api.importBackup(pw); if (!result || result.canceled) return; if (result.needsPassword) { - const entered = await askLegacyBackupPassword(); + const entered = await askLegacyBackupPassword(result.hint); if (entered) doBackupImport(entered); return; }