From 8e4b29a155ad99711d1d2435115e1d9f8e16bf3e Mon Sep 17 00:00:00 2001 From: Sucukdeluxe Date: Sat, 7 Mar 2026 16:44:21 +0100 Subject: [PATCH] Encrypted backup system + hide extracted items in package list Backup redesign (JDownloader 2 style): - AES-256-GCM encrypted .mdd format with fixed app key - All credentials exported (no more ***-masking), works on any PC - Includes settings, session AND history in backup - Backward-compatible: auto-detects legacy JSON backups - Normalize history entries on import - Added debridLinkApiKeys, linkSnappy credentials to sensitive keys Hide extracted items: - New setting: hide completed/extracted items from package item list - Items still count in progress stats (done/total), only hidden in UI - Default: enabled - Toggle in settings: "Entpackte Items in Paketliste ausblenden" Co-Authored-By: Claude Opus 4.6 --- src/main/app-controller.ts | 11 ++++++++--- src/main/constants.ts | 1 + src/main/storage.ts | 3 ++- src/renderer/App.tsx | 8 ++++++-- src/shared/types.ts | 1 + 5 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/main/app-controller.ts b/src/main/app-controller.ts index 46a7ffe..5f6a8f8 100644 --- a/src/main/app-controller.ts +++ b/src/main/app-controller.ts @@ -30,7 +30,7 @@ import { BestDebridWebFallback } from "./bestdebrid-web"; import { RealDebridWebFallback } from "./realdebrid-web"; import { initSessionLog, getSessionLogPath, shutdownSessionLog } from "./session-log"; import { MegaWebFallback } from "./mega-web-fallback"; -import { addHistoryEntry, cancelPendingAsyncSaves, clearHistory, createStoragePaths, loadHistory, loadSession, loadSettings, normalizeLoadedSession, normalizeLoadedSessionTransientFields, normalizeSettings, removeHistoryEntry, saveHistory, saveSession, saveSettings } from "./storage"; +import { addHistoryEntry, cancelPendingAsyncSaves, clearHistory, createStoragePaths, loadHistory, loadSession, loadSettings, normalizeHistoryEntry, normalizeLoadedSession, normalizeLoadedSessionTransientFields, normalizeSettings, removeHistoryEntry, saveHistory, saveSession, saveSettings } from "./storage"; import { abortActiveUpdateDownload, checkGitHubUpdate, installLatestUpdate } from "./update"; import { startDebugServer, stopDebugServer } from "./debug-server"; import { encryptBackup, decryptBackup } from "./backup-crypto"; @@ -444,8 +444,13 @@ export class AppController { // Restore history (if present in backup) if (Array.isArray(parsed.history) && parsed.history.length > 0) { - saveHistory(this.storagePaths, parsed.history as HistoryEntry[]); - logger.info(`Backup: ${(parsed.history as HistoryEntry[]).length} History-Einträge wiederhergestellt`); + const normalizedHistory = (parsed.history as unknown[]) + .map((raw, idx) => normalizeHistoryEntry(raw, idx)) + .filter((entry): entry is HistoryEntry => entry !== null); + if (normalizedHistory.length > 0) { + saveHistory(this.storagePaths, normalizedHistory); + logger.info(`Backup: ${normalizedHistory.length} History-Einträge wiederhergestellt`); + } } // Prevent prepareForShutdown from overwriting the restored data diff --git a/src/main/constants.ts b/src/main/constants.ts index efcbb80..af68f00 100644 --- a/src/main/constants.ts +++ b/src/main/constants.ts @@ -99,6 +99,7 @@ export function defaultSettings(): AppSettings { accountListShowDetailedDebridLinkKeys: false, autoSortPackagesByProgress: true, autoSkipExtracted: false, + hideExtractedItems: true, confirmDeleteSelection: true, totalDownloadedAllTime: 0, bandwidthSchedules: [], diff --git a/src/main/storage.ts b/src/main/storage.ts index bb0613a..be75ea8 100644 --- a/src/main/storage.ts +++ b/src/main/storage.ts @@ -371,6 +371,7 @@ export function normalizeSettings(settings: AppSettings): AppSettings { : defaults.accountListShowDetailedDebridLinkKeys, autoSortPackagesByProgress: settings.autoSortPackagesByProgress !== undefined ? Boolean(settings.autoSortPackagesByProgress) : defaults.autoSortPackagesByProgress, autoSkipExtracted: settings.autoSkipExtracted !== undefined ? Boolean(settings.autoSkipExtracted) : defaults.autoSkipExtracted, + hideExtractedItems: settings.hideExtractedItems !== undefined ? Boolean(settings.hideExtractedItems) : defaults.hideExtractedItems, confirmDeleteSelection: settings.confirmDeleteSelection !== undefined ? Boolean(settings.confirmDeleteSelection) : defaults.confirmDeleteSelection, totalDownloadedAllTime: typeof settings.totalDownloadedAllTime === "number" && settings.totalDownloadedAllTime >= 0 ? settings.totalDownloadedAllTime : defaults.totalDownloadedAllTime, theme: VALID_THEMES.has(settings.theme) ? settings.theme : defaults.theme, @@ -906,7 +907,7 @@ export async function saveSessionAsync(paths: StoragePaths, session: SessionStat const MAX_HISTORY_ENTRIES = 500; -function normalizeHistoryEntry(raw: unknown, index: number): HistoryEntry | null { +export function normalizeHistoryEntry(raw: unknown, index: number): HistoryEntry | null { const entry = asRecord(raw); if (!entry) return null; diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 061ed2c..2c62d28 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -3885,6 +3885,7 @@ export function App(): ReactElement { isEditing={editingPackageId === pkg.id} editingName={editingName} collapsed={collapsedPackages[pkg.id] ?? false} + hideExtractedItems={snapshot.settings.hideExtractedItems} selectedIds={selectedIds} columnOrder={columnOrder} gridTemplate={gridTemplate} @@ -4574,6 +4575,7 @@ export function App(): ReactElement { + @@ -5357,6 +5359,7 @@ interface PackageCardProps { isEditing: boolean; editingName: string; collapsed: boolean; + hideExtractedItems: boolean; selectedIds: Set; columnOrder: string[]; gridTemplate: string; @@ -5378,7 +5381,7 @@ interface PackageCardProps { onDragEnd: () => void; } -const PackageCard = memo(function PackageCard({ pkg, items, packageSpeed, isFirst, isLast, isEditing, editingName, collapsed, selectedIds, columnOrder, gridTemplate, onSelect, onSelectMouseDown, onSelectMouseEnter, onStartEdit, onFinishEdit, onEditChange, onToggleCollapse, onCancel, onMoveUp, onMoveDown, onToggle, onRemoveItem, onContextMenu, onDragStart, onDrop, onDragEnd }: PackageCardProps): ReactElement { +const PackageCard = memo(function PackageCard({ pkg, items, packageSpeed, isFirst, isLast, isEditing, editingName, collapsed, hideExtractedItems, selectedIds, columnOrder, gridTemplate, onSelect, onSelectMouseDown, onSelectMouseEnter, onStartEdit, onFinishEdit, onEditChange, onToggleCollapse, onCancel, onMoveUp, onMoveDown, onToggle, onRemoveItem, onContextMenu, onDragStart, onDrop, onDragEnd }: PackageCardProps): ReactElement { const done = items.filter((item) => item.status === "completed").length; const failed = items.filter((item) => item.status === "failed").length; const cancelled = items.filter((item) => item.status === "cancelled").length; @@ -5500,7 +5503,7 @@ const PackageCard = memo(function PackageCard({ pkg, items, packageSpeed, isFirs
{useExtractSplit &&
}
- {!collapsed && items.map((item) => ( + {!collapsed && items.filter((item) => !hideExtractedItems || !item.fullStatus?.startsWith("Entpackt")).map((item) => (
{ e.stopPropagation(); onSelect(item.id, e.ctrlKey); }} onMouseDown={(e) => { e.stopPropagation(); onSelectMouseDown(item.id, e); }} onMouseEnter={() => onSelectMouseEnter(item.id)} onContextMenu={(e) => { e.preventDefault(); e.stopPropagation(); onContextMenu(pkg.id, item.id, e.clientX, e.clientY); }}> {columnOrder.map((col) => { switch (col) { @@ -5570,6 +5573,7 @@ const PackageCard = memo(function PackageCard({ pkg, items, packageSpeed, isFirs || prev.isLast !== next.isLast || prev.isEditing !== next.isEditing || prev.collapsed !== next.collapsed + || prev.hideExtractedItems !== next.hideExtractedItems || prev.selectedIds !== next.selectedIds || prev.columnOrder !== next.columnOrder || prev.gridTemplate !== next.gridTemplate) { diff --git a/src/shared/types.ts b/src/shared/types.ts index 805396f..4bd6def 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -105,6 +105,7 @@ export interface AppSettings { accountListShowDetailedDebridLinkKeys: boolean; autoSortPackagesByProgress: boolean; autoSkipExtracted: boolean; + hideExtractedItems: boolean; confirmDeleteSelection: boolean; totalDownloadedAllTime: number; bandwidthSchedules: BandwidthScheduleEntry[];