From 1d38bb9084c06fdc9a62bbd42ffc00737537aa5b Mon Sep 17 00:00:00 2001 From: Sucukdeluxe <259325684+Sucukdeluxe@users.noreply.github.com> Date: Fri, 14 Aug 2026 18:35:12 +0200 Subject: [PATCH] feat(startup): provision managed tools in the background after install A fresh installation only provisioned Streamlink and FFmpeg when the first download started, which delayed that download by the full tool download and extraction time. Packaged builds now schedule a background provisioning pass fifteen seconds after startup so the tools are typically ready before the first download begins, reusing the existing ensure functions with their in-flight deduplication so a download that starts earlier simply joins the running installation. The timer is tracked and cleared during shutdown, and development and test launches keep the previous deferred behavior so isolated environments never trigger network provisioning. --- src/main-runtime.production-path.test.ts | 8 +++++++ src/main.ts | 28 +++++++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/main-runtime.production-path.test.ts b/src/main-runtime.production-path.test.ts index 6a95717..2ebc6e7 100644 --- a/src/main-runtime.production-path.test.ts +++ b/src/main-runtime.production-path.test.ts @@ -85,6 +85,14 @@ describe('main runtime safety production paths', () => { expect(closeHandler.indexOf('finish({ success: true, filename })')).toBeGreaterThan(closeHandler.indexOf('partialDownloadRegistry.commit')); }); + it('provisions managed tools in the background after a packaged startup', () => { + const source = mainSource(); + expect(source).toContain('let startupToolsProvisionTimer: NodeJS.Timeout | null = null;'); + expect(source).toContain('clearTimeout(startupToolsProvisionTimer)'); + expect(source).toMatch(/if \(app\.isPackaged\) \{\s*startupToolsProvisionTimer = setTimeout\([\s\S]*?appShutdownStarted[\s\S]*?startup-tools-provisioning-start[\s\S]*?ensureStreamlinkInstalled\(\)[\s\S]*?ensureFfmpegInstalled\(\)[\s\S]*?startup-tools-provisioning-finished/); + expect(source).toMatch(/\} else \{\s*appendDebugLog\('startup-tools-check-skipped', 'Deferred to first use'\);/); + }); + it('announces tool preparation before every download tool gate', () => { const source = mainSource(); const vod = source.slice(source.indexOf('async function downloadVOD'), source.indexOf('async function processDownloadMergeGroup')); diff --git a/src/main.ts b/src/main.ts index 82d3189..15823ad 100644 --- a/src/main.ts +++ b/src/main.ts @@ -171,6 +171,7 @@ const DEFAULT_FILENAME_TEMPLATE_CLIP = '{date}_{part}.mp4'; // ./main/domain/config-normalize (Single-Source-Of-Truth, vermeidet // Drift wenn man eine der Defaults aendert). const QUEUE_SAVE_DEBOUNCE_MS = 250; +const STARTUP_TOOLS_PROVISION_DELAY_MS = 15 * 1000; const MIN_FREE_DISK_BYTES = 128 * 1024 * 1024; const DEBUG_LOG_FLUSH_INTERVAL_MS = 1000; const DEBUG_LOG_BUFFER_FLUSH_LINES = 48; @@ -4970,6 +4971,14 @@ function runStorageCleanup(opts: { dryRun: boolean }): CleanupReport { let autoCleanupTimer: NodeJS.Timeout | null = null; let autoCleanupStartupTimer: NodeJS.Timeout | null = null; let lastAutoCleanupAt = 0; +let startupToolsProvisionTimer: NodeJS.Timeout | null = null; + +function stopStartupToolsProvisionTimer(): void { + if (startupToolsProvisionTimer) { + clearTimeout(startupToolsProvisionTimer); + startupToolsProvisionTimer = null; + } +} function stopAutoCleanupTimer(): void { if (autoCleanupTimer) { @@ -9052,7 +9061,23 @@ app.whenReady().then(() => { restartAutoCleanupTimer(); createWindow(); startDevelopmentReload(); - appendDebugLog('startup-tools-check-skipped', 'Deferred to first use'); + if (app.isPackaged) { + startupToolsProvisionTimer = setTimeout(() => { + startupToolsProvisionTimer = null; + if (appShutdownStarted) return; + appendDebugLog('startup-tools-provisioning-start'); + void (async () => { + const streamlinkReady = await ensureStreamlinkInstalled(); + if (appShutdownStarted) return; + const ffmpegReady = await ensureFfmpegInstalled(); + appendDebugLog('startup-tools-provisioning-finished', { streamlinkReady, ffmpegReady }); + })().catch((error) => { + appendDebugLog('startup-tools-provisioning-failed', String(error)); + }); + }, STARTUP_TOOLS_PROVISION_DELAY_MS); + } else { + appendDebugLog('startup-tools-check-skipped', 'Deferred to first use'); + } app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) { @@ -9110,6 +9135,7 @@ async function shutdownCleanup(reason: 'window-all-closed' | 'before-quit'): Pro ['auto-vod-poller', () => stopAutoVodPoller()], ['live-status-poller', () => stopLiveStatusPoller()], ['auto-cleanup-timer', () => stopAutoCleanupTimer()], + ['startup-tools-provision-timer', () => stopStartupToolsProvisionTimer()], ['queue-lifecycle', async () => { await queueRunLifecycle.shutdown( () => {