From 1afce943ae1d776159fb742eb8cb3b6f36c42d1f Mon Sep 17 00:00:00 2001 From: Sucukdeluxe Date: Mon, 9 Mar 2026 05:16:41 +0100 Subject: [PATCH] Fix history timing and retention controls --- src/main/app-controller.ts | 18 +++++++--- src/main/constants.ts | 1 + src/main/download-manager.ts | 40 +++++++++++++++++---- src/main/storage.ts | 26 +++++++++++++- src/renderer/App.tsx | 33 +++++++++++++---- src/renderer/styles.css | 14 ++++++++ src/shared/types.ts | 4 +++ tests/download-manager.test.ts | 63 ++++++++++++++++++++++++++++++++ tests/storage.test.ts | 65 +++++++++++++++++++++++++++++++++- 9 files changed, 246 insertions(+), 18 deletions(-) diff --git a/src/main/app-controller.ts b/src/main/app-controller.ts index 33f56b9..f9b8a72 100644 --- a/src/main/app-controller.ts +++ b/src/main/app-controller.ts @@ -32,7 +32,7 @@ import { getItemLogPath, initItemLogs, shutdownItemLogs } from "./item-log"; import { getPackageLogPath, initPackageLogs, shutdownPackageLogs } from "./package-log"; import { initSessionLog, getSessionLogPath, shutdownSessionLog } from "./session-log"; import { MegaWebFallback } from "./mega-web-fallback"; -import { addHistoryEntry, cancelPendingAsyncSaves, clearHistory, createStoragePaths, loadHistory, loadSession, loadSettings, normalizeHistoryEntry, normalizeLoadedSession, normalizeLoadedSessionTransientFields, normalizeSettings, removeHistoryEntry, saveHistory, saveSession, saveSettings } from "./storage"; +import { addHistoryEntryForRetention, cancelPendingAsyncSaves, clearHistory, createStoragePaths, loadHistoryForRetention, loadSession, loadSettings, normalizeHistoryEntry, normalizeLoadedSession, normalizeLoadedSessionTransientFields, normalizeSettings, removeHistoryEntry, resetHistoryForRetention, saveHistory, saveSession, saveSettings } from "./storage"; import { abortActiveUpdateDownload, checkGitHubUpdate, installLatestUpdate } from "./update"; import { rotateDebugToken, startDebugServer, stopDebugServer } from "./debug-server"; import { encryptBackup, decryptBackup } from "./backup-crypto"; @@ -87,6 +87,7 @@ export class AppController { initRenameLog(this.storagePaths.baseDir); initTraceLog(this.storagePaths.baseDir); this.settings = loadSettings(this.storagePaths); + resetHistoryForRetention(this.storagePaths, this.settings.historyRetentionMode); const session = loadSession(this.storagePaths); this.megaWebFallback = new MegaWebFallback(() => ({ login: this.settings.megaLogin, @@ -102,7 +103,7 @@ export class AppController { bestDebridWebUnrestrict: (link: string, signal?: AbortSignal) => this.bestDebridWebFallback.unrestrict(link, signal), invalidateMegaSession: () => this.megaWebFallback.invalidateSession(), onHistoryEntry: (entry: HistoryEntry) => { - addHistoryEntry(this.storagePaths, entry); + addHistoryEntryForRetention(this.storagePaths, this.settings.historyRetentionMode, entry); } }); this.manager.on("state", (snapshot: UiSnapshot) => { @@ -253,7 +254,11 @@ export class AppController { nextSettings.debridLinkApiKeyTotalUsageBytes = Object.fromEntries( Object.entries(liveSettings.debridLinkApiKeyTotalUsageBytes || {}).filter(([keyId]) => getDebridLinkApiKeyIds(nextSettings.debridLinkApiKeys).includes(keyId)) ); + const retentionChanged = previousSettings.historyRetentionMode !== nextSettings.historyRetentionMode; this.settings = nextSettings; + if (retentionChanged) { + resetHistoryForRetention(this.storagePaths, this.settings.historyRetentionMode); + } saveSettings(this.storagePaths, this.settings); this.manager.setSettings(this.settings); this.audit("INFO", "Einstellungen aktualisiert", { @@ -532,7 +537,7 @@ export class AppController { public exportBackup(): Buffer { const settings = { ...this.settings }; const session = this.manager.getSession(); - const history = loadHistory(this.storagePaths); + const history = loadHistoryForRetention(this.storagePaths, this.settings.historyRetentionMode); const payload = JSON.stringify({ version: 2, appVersion: APP_VERSION, @@ -622,6 +627,8 @@ export class AppController { } } + resetHistoryForRetention(this.storagePaths, this.settings.historyRetentionMode); + // Prevent prepareForShutdown from overwriting the restored data this.manager.skipShutdownPersist = true; this.manager.blockAllPersistence = true; @@ -664,11 +671,14 @@ export class AppController { this.audit("INFO", "App beendet"); shutdownTraceLog(); shutdownAuditLog(); + if (this.settings.historyRetentionMode === "session") { + clearHistory(this.storagePaths); + } logger.info("App beendet"); } public getHistory(): HistoryEntry[] { - return loadHistory(this.storagePaths); + return loadHistoryForRetention(this.storagePaths, this.settings.historyRetentionMode); } public clearHistory(): void { diff --git a/src/main/constants.ts b/src/main/constants.ts index d8ee91c..c2e17f7 100644 --- a/src/main/constants.ts +++ b/src/main/constants.ts @@ -97,6 +97,7 @@ export function defaultSettings(): AppSettings { minimizeToTray: false, theme: "dark" as const, collapseNewPackages: true, + historyRetentionMode: "permanent", accountListShowDetailedDebridLinkKeys: false, autoSortPackagesByProgress: true, autoSkipExtracted: false, diff --git a/src/main/download-manager.ts b/src/main/download-manager.ts index 74c5b21..ee5bb28 100644 --- a/src/main/download-manager.ts +++ b/src/main/download-manager.ts @@ -2129,6 +2129,8 @@ export class DownloadManager extends EventEmitter { cancelled: false, enabled: true, priority: "normal", + downloadStartedAt: 0, + downloadCompletedAt: 0, createdAt: nowMs(), updatedAt: nowMs() }; @@ -4958,8 +4960,10 @@ export class DownloadManager extends EventEmitter { } item.progressPercent = 100; item.speedBps = 0; - item.updatedAt = nowMs(); - pkg.updatedAt = nowMs(); + const finalizedAt = nowMs(); + item.updatedAt = finalizedAt; + this.notePackageDownloadCompleted(pkg, finalizedAt); + pkg.updatedAt = finalizedAt; this.recordRunOutcome(item.id, "completed"); if (this.session.running) { @@ -5732,6 +5736,27 @@ export class DownloadManager extends EventEmitter { void this.runPackagePostProcessing(packageId).catch((err) => logger.warn(`runPackagePostProcessing Fehler (extractNow): ${compactErrorText(err)}`)); } + private notePackageDownloadStarted(pkg: PackageEntry, startedAt = nowMs()): void { + if ((pkg.downloadStartedAt || 0) <= 0) { + pkg.downloadStartedAt = startedAt; + } + } + + private notePackageDownloadCompleted(pkg: PackageEntry, completedAt = nowMs()): void { + this.notePackageDownloadStarted(pkg, completedAt); + pkg.downloadCompletedAt = Math.max(pkg.downloadCompletedAt || 0, completedAt); + } + + private getPackageHistoryDurationSeconds(pkg: PackageEntry): number { + const startedAt = pkg.downloadStartedAt > 0 ? pkg.downloadStartedAt : pkg.createdAt; + const finishedAtCandidate = pkg.downloadCompletedAt > 0 ? pkg.downloadCompletedAt : nowMs(); + const finishedAt = Math.max(startedAt || 0, finishedAtCandidate || 0); + if (startedAt <= 0 || finishedAt <= 0) { + return 1; + } + return Math.max(1, Math.floor((finishedAt - startedAt) / 1000)); + } + private recordPackageHistory(packageId: string, pkg: PackageEntry, items: DownloadItem[]): void { if (!this.onHistoryEntryCallback || this.historyRecordedPackages.has(packageId)) { return; @@ -5742,7 +5767,7 @@ export class DownloadManager extends EventEmitter { } this.historyRecordedPackages.add(packageId); const totalBytes = completedItems.reduce((sum, item) => sum + (item.downloadedBytes || 0), 0); - const durationSeconds = pkg.createdAt > 0 ? Math.max(1, Math.floor((nowMs() - pkg.createdAt) / 1000)) : 1; + const durationSeconds = this.getPackageHistoryDurationSeconds(pkg); const providers = new Set(completedItems.map(item => item.provider).filter(Boolean)); const provider = providers.size === 1 ? [...providers][0] : null; const entry: HistoryEntry = { @@ -5776,7 +5801,7 @@ export class DownloadManager extends EventEmitter { const completedCount = completedItems.length; if (completedCount > 0) { const totalBytes = completedItems.reduce((sum, item) => sum + (item.downloadedBytes || 0), 0); - const durationSeconds = pkg.createdAt > 0 ? Math.max(1, Math.floor((nowMs() - pkg.createdAt) / 1000)) : 1; + const durationSeconds = this.getPackageHistoryDurationSeconds(pkg); const providers = new Set(completedItems.map(item => item.provider).filter(Boolean)); const provider = providers.size === 1 ? [...providers][0] : null; const entry: HistoryEntry = { @@ -6759,6 +6784,7 @@ export class DownloadManager extends EventEmitter { return; } + this.notePackageDownloadStarted(pkg); item.status = "validating"; item.fullStatus = "Link wird umgewandelt"; item.speedBps = 0; @@ -7077,14 +7103,16 @@ export class DownloadManager extends EventEmitter { throw new Error(`aborted:${active.abortReason}`); } + const completedAt = nowMs(); item.status = "completed"; item.fullStatus = this.settings.autoExtract ? "Entpacken - Ausstehend" : `Fertig (${humanSize(item.downloadedBytes)})`; item.progressPercent = 100; item.speedBps = 0; - item.updatedAt = nowMs(); - pkg.updatedAt = nowMs(); + item.updatedAt = completedAt; + this.notePackageDownloadCompleted(pkg, completedAt); + pkg.updatedAt = completedAt; this.recordRunOutcome(item.id, "completed"); logger.info(`Download fertig: ${item.fileName} (${humanSize(item.downloadedBytes)}), pkg=${pkg.name}`); this.logPackageForItem(item, "INFO", "Download abgeschlossen", { diff --git a/src/main/storage.ts b/src/main/storage.ts index 37fc10b..a4e8c1b 100644 --- a/src/main/storage.ts +++ b/src/main/storage.ts @@ -2,7 +2,7 @@ import fs from "node:fs"; import fsp from "node:fs/promises"; import path from "node:path"; import { getDebridLinkApiKeyIds } from "../shared/debrid-link-keys"; -import { AppSettings, BandwidthScheduleEntry, DebridFallbackProvider, DebridProvider, DownloadItem, DownloadStatus, HistoryEntry, PackageEntry, PackagePriority, SessionState } from "../shared/types"; +import { AppSettings, BandwidthScheduleEntry, DebridFallbackProvider, DebridProvider, DownloadItem, DownloadStatus, HistoryEntry, HistoryRetentionMode, PackageEntry, PackagePriority, SessionState } from "../shared/types"; import { getProviderUsageDayKey } from "../shared/provider-daily-limits"; import { defaultSettings } from "./constants"; import { logger } from "./logger"; @@ -15,6 +15,7 @@ const VALID_FINISHED_POLICIES = new Set(["never", "immediate", "on_start", "pack const VALID_SPEED_MODES = new Set(["global", "per_download"]); const VALID_THEMES = new Set(["dark", "light"]); const VALID_EXTRACT_CPU_PRIORITIES = new Set(["high", "middle", "low"]); +const VALID_HISTORY_RETENTION_MODES = new Set(["never", "session", "permanent"]); const VALID_PACKAGE_PRIORITIES = new Set(["high", "normal", "low"]); const VALID_DOWNLOAD_STATUSES = new Set([ "queued", "validating", "downloading", "paused", "reconnect_wait", "extracting", "integrity_check", "completed", "failed", "cancelled" @@ -375,6 +376,9 @@ export function normalizeSettings(settings: AppSettings): AppSettings { clipboardWatch: Boolean(settings.clipboardWatch), minimizeToTray: Boolean(settings.minimizeToTray), collapseNewPackages: settings.collapseNewPackages !== undefined ? Boolean(settings.collapseNewPackages) : defaults.collapseNewPackages, + historyRetentionMode: VALID_HISTORY_RETENTION_MODES.has(settings.historyRetentionMode) + ? settings.historyRetentionMode + : defaults.historyRetentionMode, accountListShowDetailedDebridLinkKeys: settings.accountListShowDetailedDebridLinkKeys !== undefined ? Boolean(settings.accountListShowDetailedDebridLinkKeys) : defaults.accountListShowDetailedDebridLinkKeys, @@ -582,6 +586,8 @@ export function normalizeLoadedSession(raw: unknown): SessionState { cancelled: Boolean(pkg.cancelled), enabled: pkg.enabled === undefined ? true : Boolean(pkg.enabled), priority: VALID_PACKAGE_PRIORITIES.has(asText(pkg.priority)) ? asText(pkg.priority) as PackagePriority : "normal", + downloadStartedAt: clampNumber(pkg.downloadStartedAt, 0, 0, Number.MAX_SAFE_INTEGER), + downloadCompletedAt: clampNumber(pkg.downloadCompletedAt, 0, 0, Number.MAX_SAFE_INTEGER), createdAt: clampNumber(pkg.createdAt, now, 0, Number.MAX_SAFE_INTEGER), updatedAt: clampNumber(pkg.updatedAt, now, 0, Number.MAX_SAFE_INTEGER) }; @@ -1013,6 +1019,24 @@ export function addHistoryEntry(paths: StoragePaths, entry: HistoryEntry): Histo return updated; } +export function loadHistoryForRetention(paths: StoragePaths, retentionMode: HistoryRetentionMode): HistoryEntry[] { + return retentionMode === "never" ? [] : loadHistory(paths); +} + +export function addHistoryEntryForRetention(paths: StoragePaths, retentionMode: HistoryRetentionMode, entry: HistoryEntry): HistoryEntry[] { + if (retentionMode === "never") { + return []; + } + return addHistoryEntry(paths, entry); +} + +export function resetHistoryForRetention(paths: StoragePaths, retentionMode: HistoryRetentionMode): void { + if (retentionMode === "permanent") { + return; + } + clearHistory(paths); +} + export function removeHistoryEntry(paths: StoragePaths, entryId: string): HistoryEntry[] { const existing = loadHistory(paths); const updated = existing.filter(e => e.id !== entryId); diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 10909bf..09fc65a 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -785,7 +785,7 @@ const emptySnapshot = (): UiSnapshot => ({ autoReconnect: false, reconnectWaitSeconds: 45, completedCleanupPolicy: "never", maxParallel: 4, maxParallelExtract: 2, extractCpuPriority: "high", retryLimit: 0, speedLimitEnabled: false, speedLimitKbps: 0, speedLimitMode: "global", updateRepo: "", autoUpdateCheck: true, clipboardWatch: false, minimizeToTray: false, - theme: "dark", collapseNewPackages: true, autoSortPackagesByProgress: true, autoSkipExtracted: false, confirmDeleteSelection: true, + theme: "dark", collapseNewPackages: true, historyRetentionMode: "permanent", autoSortPackagesByProgress: true, autoSkipExtracted: false, hideExtractedItems: true, confirmDeleteSelection: true, accountListShowDetailedDebridLinkKeys: false, bandwidthSchedules: [], totalDownloadedAllTime: 0, totalCompletedFilesAllTime: 0, totalRuntimeAllTimeMs: 0, columnOrder: ["name", "size", "progress", "hoster", "account", "prio", "status", "speed"], @@ -814,6 +814,12 @@ const cleanupLabels: Record = { never: "Nie", immediate: "Sofort", on_start: "Beim App-Start", package_done: "Sobald Paket fertig ist" }; +const historyRetentionLabels: Record = { + never: "Nie", + session: "Nur aktuelle Session", + permanent: "Dauerhaft" +}; + const AUTO_RENDER_PACKAGE_LIMIT = 260; const providerLabels: Record = { @@ -981,6 +987,10 @@ function formatRuntimeDuration(durationMs: number): string { return `${formatUnit(months, "Monat", "Monate")}, ${formatUnit(weeks, "Woche", "Wochen")}, ${formatUnit(days, "Tag", "Tage")}, ${formatUnit(hours, "Stunde", "Stunden")}, ${formatUnit(minutes, "Minute", "Minuten")}`; } +function formatHistoryDuration(durationSeconds: number): string { + return formatRuntimeDuration(Math.max(0, durationSeconds || 0) * 1000); +} + function formatAllDebridSourceLabel(source: AllDebridHostInfo["source"]): string { return source === "web" ? "Web-Login" : "API-Key"; } @@ -4293,7 +4303,8 @@ export function App(): ReactElement { ? `${selectedHistoryIds.size} von ${historyEntries.length} ausgewählt` : `${historyEntries.length} Paket${historyEntries.length !== 1 ? "e" : ""} im Verlauf`} - {selectedHistoryIds.size > 0 && ( +
+ {selectedHistoryIds.size > 0 && ( - )} - {historyEntries.length > 0 && ( + )} + {historyEntries.length > 0 && ( - )} + )} +
{historyEntries.length === 0 &&
Noch keine abgeschlossenen Pakete im Verlauf.
} {historyEntries.map((entry) => { @@ -4381,7 +4393,7 @@ export function App(): ReactElement { Heruntergeladen {humanSize(entry.downloadedBytes)} Dauer - {entry.durationSeconds >= 3600 ? `${Math.floor(entry.durationSeconds / 3600)}h ${Math.floor((entry.durationSeconds % 3600) / 60)}min` : entry.durationSeconds >= 60 ? `${Math.floor(entry.durationSeconds / 60)}min ${entry.durationSeconds % 60}s` : `${entry.durationSeconds}s`} + {formatHistoryDuration(entry.durationSeconds)} Durchschnitt {entry.durationSeconds > 0 ? formatSpeedMbps(Math.round(entry.downloadedBytes / entry.durationSeconds)) : ""} Provider @@ -4551,6 +4563,15 @@ export function App(): ReactElement { +
+ + +
Nie = kein Verlauf. Nur aktuelle Session = wird beim Beenden gelöscht. Dauerhaft = bleibt wie bisher gespeichert.
+
diff --git a/src/renderer/styles.css b/src/renderer/styles.css index 9b251ba..8c1f3d0 100644 --- a/src/renderer/styles.css +++ b/src/renderer/styles.css @@ -2243,12 +2243,18 @@ body, display: flex; flex-direction: column; gap: 6px; + height: 100%; + min-height: 0; + overflow: auto; + padding-right: 2px; } .history-toolbar { display: flex; align-items: center; + flex-wrap: wrap; justify-content: space-between; + gap: 12px; padding: 5px 12px; background: var(--card); border: 1px solid var(--border); @@ -2258,6 +2264,14 @@ body, color: var(--muted); } +.history-toolbar-actions { + display: flex; + align-items: center; + justify-content: flex-end; + gap: 8px; + margin-left: auto; +} + .history-card { cursor: default; } diff --git a/src/shared/types.ts b/src/shared/types.ts index 7eaffc3..4d16386 100644 --- a/src/shared/types.ts +++ b/src/shared/types.ts @@ -29,6 +29,7 @@ export type DebridFallbackProvider = DebridProvider | "none"; export type AppTheme = "dark" | "light"; export type PackagePriority = "high" | "normal" | "low"; export type ExtractCpuPriority = "high" | "middle" | "low"; +export type HistoryRetentionMode = "never" | "session" | "permanent"; export interface BandwidthScheduleEntry { id: string; @@ -107,6 +108,7 @@ export interface AppSettings { minimizeToTray: boolean; theme: AppTheme; collapseNewPackages: boolean; + historyRetentionMode: HistoryRetentionMode; accountListShowDetailedDebridLinkKeys: boolean; autoSortPackagesByProgress: boolean; autoSkipExtracted: boolean; @@ -167,6 +169,8 @@ export interface PackageEntry { enabled: boolean; priority: PackagePriority; postProcessLabel?: string; + downloadStartedAt?: number; + downloadCompletedAt?: number; createdAt: number; updatedAt: number; } diff --git a/tests/download-manager.test.ts b/tests/download-manager.test.ts index 60340b0..5250753 100644 --- a/tests/download-manager.test.ts +++ b/tests/download-manager.test.ts @@ -66,6 +66,69 @@ afterEach(async () => { }); describe("download manager", () => { + it("records history duration from the first actual package start", () => { + const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-history-")); + tempDirs.push(root); + const historyEntries: Array<{ durationSeconds: number; downloadedBytes: number }> = []; + const manager = new DownloadManager( + defaultSettings(), + emptySession(), + createStoragePaths(path.join(root, "state")), + { + onHistoryEntry: (entry) => { + historyEntries.push({ + durationSeconds: entry.durationSeconds, + downloadedBytes: entry.downloadedBytes + }); + } + } + ); + + const packageId = "history-pkg"; + const itemId = "history-item"; + const pkg = { + id: packageId, + name: "History Test", + outputDir: path.join(root, "downloads", "History Test"), + extractDir: path.join(root, "extract", "History Test"), + status: "completed", + itemIds: [itemId], + cancelled: false, + enabled: true, + priority: "normal", + createdAt: 1_000, + updatedAt: 61_000, + downloadStartedAt: 15_000, + downloadCompletedAt: 60_000 + }; + const item = { + id: itemId, + packageId, + url: "https://example.com/history.rar", + provider: "realdebrid", + status: "completed", + retries: 0, + speedBps: 0, + downloadedBytes: 90 * 1024 * 1024, + totalBytes: 90 * 1024 * 1024, + progressPercent: 100, + fileName: "history.rar", + targetPath: path.join(pkg.outputDir, "history.rar"), + resumable: true, + attempts: 1, + lastError: "", + fullStatus: "Fertig", + createdAt: 15_000, + updatedAt: 60_000 + }; + + (manager as any).recordPackageHistory(packageId, pkg, [item]); + + expect(historyEntries).toHaveLength(1); + expect(historyEntries[0]?.durationSeconds).toBe(45); + expect(historyEntries[0]?.downloadedBytes).toBe(90 * 1024 * 1024); + }); + function createCompletedArchiveSession(root: string, packageName: string, extractedFileName: string): { session: ReturnType; packageId: string; diff --git a/tests/storage.test.ts b/tests/storage.test.ts index 893a7a3..2447670 100644 --- a/tests/storage.test.ts +++ b/tests/storage.test.ts @@ -6,7 +6,7 @@ import { parseDebridLinkApiKeys } from "../src/shared/debrid-link-keys"; import { getProviderUsageDayKey } from "../src/shared/provider-daily-limits"; import { AppSettings } from "../src/shared/types"; import { defaultSettings } from "../src/main/constants"; -import { createStoragePaths, emptySession, loadSession, loadSettings, normalizeSettings, saveSession, saveSessionAsync, saveSettings } from "../src/main/storage"; +import { addHistoryEntryForRetention, createStoragePaths, emptySession, loadHistory, loadHistoryForRetention, loadSession, loadSettings, normalizeSettings, resetHistoryForRetention, saveHistory, saveSession, saveSessionAsync, saveSettings } from "../src/main/storage"; const tempDirs: string[] = []; @@ -273,6 +273,65 @@ describe("settings storage", () => { expect(normalizedDisabled.allDebridUseWebLogin).toBe(false); }); + it("defaults history retention to permanent and normalizes invalid values", () => { + expect(defaultSettings().historyRetentionMode).toBe("permanent"); + + const normalized = normalizeSettings({ + ...defaultSettings(), + historyRetentionMode: "broken" as unknown as AppSettings["historyRetentionMode"] + }); + + expect(normalized.historyRetentionMode).toBe("permanent"); + }); + + it("skips adding persisted history entries when history retention is never", () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "rd-store-")); + tempDirs.push(dir); + const paths = createStoragePaths(dir); + + const result = addHistoryEntryForRetention(paths, "never", { + id: "hist-1", + name: "ignored", + totalBytes: 1024, + downloadedBytes: 1024, + fileCount: 1, + provider: "realdebrid", + completedAt: Date.now(), + durationSeconds: 12, + status: "completed", + outputDir: path.join(dir, "out"), + urls: ["https://example.com/file.rar"] + }); + + expect(result).toEqual([]); + expect(loadHistory(paths)).toEqual([]); + expect(loadHistoryForRetention(paths, "never")).toEqual([]); + }); + + it("clears persisted history for session retention mode", () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "rd-store-")); + tempDirs.push(dir); + const paths = createStoragePaths(dir); + + saveHistory(paths, [{ + id: "hist-2", + name: "kept", + totalBytes: 2048, + downloadedBytes: 2048, + fileCount: 1, + provider: "realdebrid", + completedAt: Date.now(), + durationSeconds: 20, + status: "completed", + outputDir: path.join(dir, "out"), + urls: ["https://example.com/file2.rar"] + }]); + + resetHistoryForRetention(paths, "session"); + + expect(loadHistory(paths)).toEqual([]); + }); + it("assigns and preserves bandwidth schedule ids", () => { const normalized = normalizeSettings({ ...defaultSettings(), @@ -305,6 +364,8 @@ describe("settings storage", () => { itemIds: ["item1", "item2", "item3", "item4"], cancelled: false, enabled: true, + downloadStartedAt: 0, + downloadCompletedAt: 0, createdAt: Date.now(), updatedAt: Date.now() }; @@ -438,6 +499,8 @@ describe("settings storage", () => { itemIds: ["item-backup"], cancelled: false, enabled: true, + downloadStartedAt: 0, + downloadCompletedAt: 0, createdAt: Date.now(), updatedAt: Date.now() };