From 87e16ca28e293255d32d216cf566e54e4917f4a3 Mon Sep 17 00:00:00 2001 From: debont80 Date: Thu, 9 Jul 2026 15:45:35 -0400 Subject: [PATCH] fix(sync): run watched sync when a scheduled --sync hits the running instance The Windows daily-sync task launches `AeroFetch.exe --sync`, but the single- instance lock routes that relaunch to the `second-instance` handler whenever AeroFetch is already open. The handler only focused the window and looked for a link -- it never checked `isSyncLaunch(argv)` -- so the sync never ran. The daily auto-download of new watched-source uploads therefore did nothing for minimize- to-tray / always-on users (and the relaunch stole window focus each day). Add a `runWatchedSync` main->renderer push: on a `--sync` relaunch the handler now nudges the renderer to run syncWatched() instead of focusing, so the daily sync fires whether AeroFetch was closed or already running. Co-Authored-By: Claude Opus 4.8 --- src/main/index.ts | 9 +++++++++ src/preload/index.ts | 8 ++++++++ src/renderer/src/mockApi.ts | 1 + src/renderer/src/store/sources.ts | 7 +++++-- src/shared/ipc.ts | 3 +++ 5 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/main/index.ts b/src/main/index.ts index aba8894..143c6e1 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -260,6 +260,15 @@ if (isPrimaryInstance) { // link it carried and hand it to the window we already have. app.on('second-instance', (_e, argv) => { if (!mainWindow) return + // A scheduled `--sync` relaunch (Task Scheduler) hit the already-running instance: + // run the watched-source sync in this window instead of no-opping. Without this the + // daily auto-download never fires while AeroFetch is open — e.g. minimize-to-tray + // users. Deliberately does NOT focus the window, so the daily sync stays unobtrusive + // (mirrors the fresh-launch `--sync` showInactive path in createWindow). + if (isSyncLaunch(argv)) { + mainWindow.webContents.send(IpcChannels.runWatchedSync) + return + } focusWindow(mainWindow) const incomingUrl = extractIncomingUrl(argv) if (incomingUrl) mainWindow.webContents.send(IpcChannels.externalUrl, incomingUrl) diff --git a/src/preload/index.ts b/src/preload/index.ts index 51b978e..216ef99 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -279,6 +279,14 @@ const api = { return () => ipcRenderer.removeListener(IpcChannels.indexProgress, listener) }, + /** Subscribe to the "run watched-source sync now" nudge a scheduled `--sync` relaunch + * sends when it hits the already-running instance. Returns an unsubscribe function. */ + onRunWatchedSync: (cb: () => void): (() => void) => { + const listener = (): void => cb() + ipcRenderer.on(IpcChannels.runWatchedSync, listener) + return () => ipcRenderer.removeListener(IpcChannels.runWatchedSync, listener) + }, + // --- Built-in yt-dlp terminal (Phase N) --- /** Run the bundled yt-dlp with raw args; output streams via onTerminalOutput. */ diff --git a/src/renderer/src/mockApi.ts b/src/renderer/src/mockApi.ts index 7a4407f..8d5ae21 100644 --- a/src/renderer/src/mockApi.ts +++ b/src/renderer/src/mockApi.ts @@ -271,6 +271,7 @@ export const mockApi: Api = { onSystemThemeUpdate: () => () => {}, openHighContrastSettings: async () => {}, onExternalUrl: () => () => {}, + onRunWatchedSync: () => () => {}, // Media-manager sources — a seeded channel so the (Phase H) Library view has // demo data in this browser-only preview. listSources: async () => [ diff --git a/src/renderer/src/store/sources.ts b/src/renderer/src/store/sources.ts index cd7d16c..6096825 100644 --- a/src/renderer/src/store/sources.ts +++ b/src/renderer/src/store/sources.ts @@ -485,13 +485,16 @@ if (!PREVIEW) { .listSources() .then((sources) => { useSources.setState({ sources }) - // If any source is watched, kick off a sync shortly after launch (covers the - // scheduled `--sync` launch and a normal launch alike). + // If any source is watched, kick off a sync shortly after launch (covers a fresh + // `--sync` launch and a normal launch alike). if (sources.some((s) => s.watched)) { setTimeout(() => useSources.getState().syncWatched(), 1500) } }) .catch(logError('startup listSources')) + // A scheduled `--sync` relaunch that hit the already-running instance nudges us here, + // so the daily sync fires even when AeroFetch was already open (e.g. tray mode). + window.api.onRunWatchedSync?.(() => useSources.getState().syncWatched()) window.api.onIndexProgress((p: IndexProgress) => { useSources.setState({ indexing: { diff --git a/src/shared/ipc.ts b/src/shared/ipc.ts index 39f7a0c..24870e8 100644 --- a/src/shared/ipc.ts +++ b/src/shared/ipc.ts @@ -105,6 +105,9 @@ export const IpcChannels = { scheduledSyncSet: 'sources:scheduled-sync-set', /** main → renderer push channel for live indexing progress */ indexProgress: 'sources:index-progress', + /** main → renderer: a scheduled `--sync` relaunch hit the running instance — run the + * watched-source sync here instead of no-opping (Phase J) */ + runWatchedSync: 'sources:run-sync', // --- Built-in yt-dlp terminal (Phase N) --- terminalRun: 'terminal:run', terminalCancel: 'terminal:cancel', -- 2.47.3