From 1f21768c4c7f8417df21cc139f6877cdd894ff67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Padez?= Date: Mon, 27 Jul 2026 16:30:42 +0000 Subject: [PATCH] music: nightly 3am full reindex (staged + atomic swap) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Self-scheduling timer in the sidecar (fresh setTimeout each night, so it always fires at 3am local regardless of drift) runs reindexFull() — builds into a fresh slot and swaps atomically only on success, never disrupting the live index. Started at boot, cleared on shutdown. Logs the next scheduled time + each run. Co-Authored-By: Claude Opus 4.8 --- src/servers/sidecar/music/index.ts | 5 +++ src/servers/sidecar/music/nightly-reindex.ts | 43 ++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 src/servers/sidecar/music/nightly-reindex.ts diff --git a/src/servers/sidecar/music/index.ts b/src/servers/sidecar/music/index.ts index 00eb1d54..3e832e00 100644 --- a/src/servers/sidecar/music/index.ts +++ b/src/servers/sidecar/music/index.ts @@ -3,6 +3,7 @@ import { join } from 'node:path'; import type { SidecarCommand, SidecarEvent } from '../protocol'; import { createSidecarConnector } from '../connect'; import { streamAudioFile } from './stream-audio'; +import { startNightlyReindex, stopNightlyReindex } from './nightly-reindex'; import { reindexNow, ensureCacheSetup, @@ -74,6 +75,9 @@ const port = getFreePort(); // Ensure the cache is a symlink-to-slot before serving/building, so full reindexes can swap atomically. await ensureCacheSetup(); +// Nightly full reindex at 3am (staged + atomic swap). +startNightlyReindex(); + const server = Bun.serve({ port, hostname: '127.0.0.1', @@ -267,6 +271,7 @@ const connection = createSidecarConnector({ function shutdown(signal: string) { console.log(`[music] ${signal} received, shutting down...`); + stopNightlyReindex(); try { server.stop(true); } catch { diff --git a/src/servers/sidecar/music/nightly-reindex.ts b/src/servers/sidecar/music/nightly-reindex.ts new file mode 100644 index 00000000..178416e4 --- /dev/null +++ b/src/servers/sidecar/music/nightly-reindex.ts @@ -0,0 +1,43 @@ +import { reindexFull } from './indexer'; + +// Nightly full-from-scratch reindex at 3am (server-local time). Uses reindexFull, so it builds into a +// fresh slot and atomically swaps it in only on success — the live index is never disrupted mid-build. +// Self-scheduling (a fresh setTimeout each night) rather than setInterval, so it always fires at 3am +// regardless of drift. + +const REINDEX_HOUR = 3; + +let timer: ReturnType | null = null; + +function msUntilNextHour(hour: number): number { + const now = new Date(); + const next = new Date(now); + next.setHours(hour, 0, 0, 0); + if (next <= now) next.setDate(next.getDate() + 1); + return next.getTime() - now.getTime(); +} + +export function startNightlyReindex(): void { + const schedule = () => { + const ms = msUntilNextHour(REINDEX_HOUR); + const at = new Date(Date.now() + ms); + console.log(`[music] nightly full reindex scheduled for ${at.toLocaleString()} (in ${(ms / 3_600_000).toFixed(1)}h)`); + timer = setTimeout(async () => { + console.log('[music] nightly full reindex starting'); + try { + await reindexFull(); + } catch (err) { + console.error('[music] nightly full reindex error:', err instanceof Error ? err.message : err); + } + schedule(); // reschedule for the following night + }, ms); + }; + schedule(); +} + +export function stopNightlyReindex(): void { + if (timer) { + clearTimeout(timer); + timer = null; + } +}