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 <noreply@anthropic.com>
This commit is contained in:
parent
0edd8f6be5
commit
8e4b29a155
@ -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
|
||||
|
||||
@ -99,6 +99,7 @@ export function defaultSettings(): AppSettings {
|
||||
accountListShowDetailedDebridLinkKeys: false,
|
||||
autoSortPackagesByProgress: true,
|
||||
autoSkipExtracted: false,
|
||||
hideExtractedItems: true,
|
||||
confirmDeleteSelection: true,
|
||||
totalDownloadedAllTime: 0,
|
||||
bandwidthSchedules: [],
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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 {
|
||||
</div>
|
||||
<label className="toggle-line"><input type="checkbox" checked={settingsDraft.autoExtract} onChange={(e) => setBool("autoExtract", e.target.checked)} /> Auto-Extract</label>
|
||||
<label className="toggle-line"><input type="checkbox" checked={settingsDraft.autoSkipExtracted} onChange={(e) => setBool("autoSkipExtracted", e.target.checked)} /> Bereits Entpacktes beim Start überspringen</label>
|
||||
<label className="toggle-line"><input type="checkbox" checked={settingsDraft.hideExtractedItems} onChange={(e) => setBool("hideExtractedItems", e.target.checked)} /> Entpackte Items in Paketliste ausblenden</label>
|
||||
<label className="toggle-line"><input type="checkbox" checked={settingsDraft.autoRename4sf4sj} onChange={(e) => setBool("autoRename4sf4sj", e.target.checked)} /> Auto-Rename (Beta)</label>
|
||||
<label className="toggle-line"><input type="checkbox" checked={settingsDraft.createExtractSubfolder} onChange={(e) => setBool("createExtractSubfolder", e.target.checked)} /> Entpackte Dateien in Paket-Unterordner speichern</label>
|
||||
<label className="toggle-line"><input type="checkbox" checked={settingsDraft.hybridExtract} onChange={(e) => setBool("hybridExtract", e.target.checked)} /> Hybrid-Extract</label>
|
||||
@ -5357,6 +5359,7 @@ interface PackageCardProps {
|
||||
isEditing: boolean;
|
||||
editingName: string;
|
||||
collapsed: boolean;
|
||||
hideExtractedItems: boolean;
|
||||
selectedIds: Set<string>;
|
||||
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
|
||||
<div className="progress-dl" style={{ width: `${dlProgress}%` }} />
|
||||
{useExtractSplit && <div className="progress-ex" style={{ width: `${exProgress}%` }} />}
|
||||
</div>
|
||||
{!collapsed && items.map((item) => (
|
||||
{!collapsed && items.filter((item) => !hideExtractedItems || !item.fullStatus?.startsWith("Entpackt")).map((item) => (
|
||||
<div key={item.id} className={`item-row${selectedIds.has(item.id) ? " item-selected" : ""}`} style={{ gridTemplateColumns: gridTemplate }} onClick={(e) => { 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) {
|
||||
|
||||
@ -105,6 +105,7 @@ export interface AppSettings {
|
||||
accountListShowDetailedDebridLinkKeys: boolean;
|
||||
autoSortPackagesByProgress: boolean;
|
||||
autoSkipExtracted: boolean;
|
||||
hideExtractedItems: boolean;
|
||||
confirmDeleteSelection: boolean;
|
||||
totalDownloadedAllTime: number;
|
||||
bandwidthSchedules: BandwidthScheduleEntry[];
|
||||
|
||||
Loading…
Reference in New Issue
Block a user