diff --git a/src/main/download-manager.ts b/src/main/download-manager.ts index 829ddae..57203b1 100644 --- a/src/main/download-manager.ts +++ b/src/main/download-manager.ts @@ -163,8 +163,9 @@ const ARCHIVE_SETTLE_MAX_WAIT_MS = 5000; const MAX_SAME_DIRECT_URL_ATTEMPTS = 3; -const MAX_HTTP416_FRESH_RESTARTS = 2; -const HTTP416_FRESH_RESTART_DELAY_MS = 8000; +const MAX_HTTP416_FRESH_RESTARTS = 2; +const HTTP416_FRESH_RESTART_DELAY_MS = 8000; +const DOWNLOAD_LIVE_UPDATE_INTERVAL_MS = 750; function getHttp416FreshRestartDelayMs(): number { const fromEnv = Number(process.env.RD_HTTP416_FRESH_RESTART_DELAY_MS ?? NaN); @@ -6561,14 +6562,7 @@ export class DownloadManager extends EventEmitter { if (this.stateEmitTimer) { return; } - const itemCount = this.itemCount; - const emitDelay = this.session.running - ? itemCount >= 1500 - ? 700 - : itemCount >= 250 - ? 500 - : 150 - : 200; + const emitDelay = this.session.running ? DOWNLOAD_LIVE_UPDATE_INTERVAL_MS : 200; this.stateEmitTimer = setTimeout(() => { this.stateEmitTimer = null; this.lastStateEmitAt = nowMs(); @@ -10579,12 +10573,7 @@ export class DownloadManager extends EventEmitter { let windowStarted = nowMs(); let lastPackageLogAt = 0; let lastLoggedPercent = -1; - const itemCount = this.itemCount; - const uiUpdateIntervalMs = itemCount >= 1500 - ? 700 - : itemCount >= 250 - ? 500 - : 120; + const uiUpdateIntervalMs = DOWNLOAD_LIVE_UPDATE_INTERVAL_MS; let lastUiEmitAt = 0; const stallTimeoutMs = getDownloadStallTimeoutMs(); const drainTimeoutMs = Math.max(30000, Math.min(300000, stallTimeoutMs > 0 ? stallTimeoutMs * 12 : 120000)); diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 2ba13f7..563f2fb 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -880,7 +880,7 @@ const historyRetentionLabels: Record= 250 ? 0 : 100; + let delay = running && activeTab === "downloads" ? 0 : itemCount >= 250 ? 0 : 100; if (!running) delay = Math.min(delay, 200); if (activeTab !== "downloads") delay = Math.max(delay, 800); return delay; @@ -1272,7 +1272,7 @@ const BandwidthChart = memo(function BandwidthChart({ running, paused, speedHist const reducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches; const interval = setInterval(() => { drawChart(); - }, reducedMotion ? 1000 : 500); + }, reducedMotion ? 1000 : 750); return () => clearInterval(interval); }, [drawChart, running, paused]); @@ -1374,7 +1374,7 @@ const DownloadSpeedSparkline = memo(function DownloadSpeedSparkline({ speedBps, draw(); }; - const id = window.setInterval(tick, 500); + const id = window.setInterval(tick, 750); return () => window.clearInterval(id); }, []); diff --git a/tests/app-shell.test.tsx b/tests/app-shell.test.tsx index 1aa99b7..6719d7a 100644 --- a/tests/app-shell.test.tsx +++ b/tests/app-shell.test.tsx @@ -50,10 +50,21 @@ describe("desktop shell", () => { expect(getSnapshotRenderDelay(2_470, true, "statistics")).toBe(800); }); - it("renders medium download queues immediately after their calm half-second snapshot", () => { + it("renders medium download queues immediately after their calm 750 ms snapshot", () => { expect(getSnapshotRenderDelay(661, true, "downloads")).toBe(0); }); + it("does not add renderer delay after the 750 ms manager cadence for a small active queue", () => { + expect(getSnapshotRenderDelay(69, true, "downloads")).toBe(0); + }); + + it("redraws the header speed sparkline on the same 750 ms cadence", () => { + const source = readFileSync(new URL("../src/renderer/App.tsx", import.meta.url), "utf8"); + const sparklineBlock = source.slice(source.indexOf("const DownloadSpeedSparkline"), source.indexOf("const initialCollectorTabs")); + + expect(sparklineBlock).toContain("window.setInterval(tick, 750)"); + }); + it("keeps application menus mounted for animated opening and closing", () => { const source = readFileSync(new URL("../src/renderer/App.tsx", import.meta.url), "utf8"); const css = readFileSync(new URL("../src/renderer/styles.css", import.meta.url), "utf8"); diff --git a/tests/download-manager.test.ts b/tests/download-manager.test.ts index b71e5d8..d6b4b7d 100644 --- a/tests/download-manager.test.ts +++ b/tests/download-manager.test.ts @@ -45,6 +45,44 @@ describe("runWithLimitedConcurrency", () => { }); }); +describe("download live update cadence", () => { + it.each([69, 661, 2_470])("emits a running queue snapshot no sooner than 750 ms for %i items", async (itemCount) => { + vi.useFakeTimers(); + try { + const root = fs.mkdtempSync(path.join(os.tmpdir(), "rd-live-cadence-")); + tempDirs.push(root); + const session = emptySession(); + session.running = true; + const manager = new DownloadManager(defaultSettings(), session, createStoragePaths(path.join(root, "state"))); + const internal = manager as unknown as { + emitState: () => void; + itemCount: number; + session: typeof session; + stateEmitTimer: NodeJS.Timeout | null; + }; + internal.itemCount = itemCount; + internal.session.running = true; + if (internal.stateEmitTimer) { + clearTimeout(internal.stateEmitTimer); + internal.stateEmitTimer = null; + } + let emitted = 0; + manager.on("state", () => { + emitted += 1; + }); + + internal.emitState(); + await vi.advanceTimersByTimeAsync(749); + expect(emitted).toBe(0); + + await vi.advanceTimersByTimeAsync(1); + expect(emitted).toBe(1); + } finally { + vi.useRealTimers(); + } + }); +}); + describe("resolveUnrestrictTimeoutBudgetMs", () => { it("covers the complete provider plan and each later Real-Debrid account attempt", () => { const settings = { diff --git a/tests/statistics-view.test.tsx b/tests/statistics-view.test.tsx index 6b864ca..33c51cf 100644 --- a/tests/statistics-view.test.tsx +++ b/tests/statistics-view.test.tsx @@ -507,7 +507,7 @@ describe("bandwidth chart palette", () => { expect(chartBlock).toContain('role="img"'); expect(chartBlock).toContain('aria-label="Bandbreitenverlauf der letzten 60 Sekunden"'); expect(chartBlock).toContain('window.matchMedia("(prefers-reduced-motion: reduce)")'); - expect(chartBlock).toContain("reducedMotion ? 1000 : 500"); + expect(chartBlock).toContain("reducedMotion ? 1000 : 750"); }); it("asks for confirmation before deleting all saved download statistics", () => {