Complete Phase C (custom commands & yt-dlp updater) and Phase D (library, UX & notifications)
Phase C had been implemented in the working tree but never committed: named custom-command templates (CRUD + per-download override), command preview, and the in-app yt-dlp self-updater. Phase D adds: history search/kind-filter/multi-select/bulk-delete/ re-download; native OS notifications on completion/failure; a private/ incognito download mode that skips history; settings+template backup/ restore via JSON; and a persistent error log surfaced as a Diagnostics report in Settings. See ROADMAP.md for the full per-item breakdown. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
import { dialog, type BrowserWindow } from 'electron'
|
||||
import { readFileSync, writeFileSync } from 'fs'
|
||||
import { getSettings, setSettings } from './settings'
|
||||
import { listTemplates, replaceTemplates } from './templates'
|
||||
import type {
|
||||
BackupExportResult,
|
||||
BackupImportResult,
|
||||
Settings,
|
||||
CommandTemplate
|
||||
} from '@shared/ipc'
|
||||
|
||||
interface BackupFile {
|
||||
version: 1
|
||||
settings: Settings
|
||||
templates: CommandTemplate[]
|
||||
}
|
||||
|
||||
export async function exportBackup(win: BrowserWindow | undefined): Promise<BackupExportResult> {
|
||||
const res = await dialog.showSaveDialog(win!, {
|
||||
defaultPath: 'aerofetch-backup.json',
|
||||
filters: [{ name: 'JSON', extensions: ['json'] }]
|
||||
})
|
||||
if (res.canceled || !res.filePath) return { ok: false }
|
||||
const payload: BackupFile = { version: 1, settings: getSettings(), templates: listTemplates() }
|
||||
try {
|
||||
writeFileSync(res.filePath, JSON.stringify(payload, null, 2))
|
||||
return { ok: true, path: res.filePath }
|
||||
} catch (e) {
|
||||
return { ok: false, error: (e as Error).message }
|
||||
}
|
||||
}
|
||||
|
||||
export async function importBackup(win: BrowserWindow | undefined): Promise<BackupImportResult> {
|
||||
const res = await dialog.showOpenDialog(win!, {
|
||||
properties: ['openFile'],
|
||||
filters: [{ name: 'JSON', extensions: ['json'] }]
|
||||
})
|
||||
if (res.canceled || !res.filePaths[0]) return { ok: false }
|
||||
|
||||
let parsed: unknown
|
||||
try {
|
||||
parsed = JSON.parse(readFileSync(res.filePaths[0], 'utf8'))
|
||||
} catch (e) {
|
||||
return { ok: false, error: `Could not read backup file: ${(e as Error).message}` }
|
||||
}
|
||||
if (!parsed || typeof parsed !== 'object') {
|
||||
return { ok: false, error: 'Not a valid AeroFetch backup file.' }
|
||||
}
|
||||
|
||||
// setSettings/replaceTemplates validate and sanitize every field, so a
|
||||
// hand-edited or partial backup file degrades gracefully rather than
|
||||
// corrupting the store.
|
||||
const file = parsed as Partial<BackupFile>
|
||||
if (file.settings && typeof file.settings === 'object') setSettings(file.settings)
|
||||
if (Array.isArray(file.templates)) replaceTemplates(file.templates)
|
||||
return { ok: true }
|
||||
}
|
||||
Reference in New Issue
Block a user