import { watch, type FSWatcher } from 'node:fs'; import { dirname } from 'node:path'; import { MUSIC_ROOT, reindexFolder } from './indexer'; // Recursive file watcher on ~/Music (chokidar-style). Any change — add / modify (mtime delta) / delete — // is logged and, after a short debounce, triggers a LOCALIZED reindex of just the affected folder(s) // (reindexFolder recomputes that album/artist's cache entry; its mtime-based signature decides whether a // rebuild is actually needed, so irrelevant touches are cheap no-ops). Full correctness is backstopped by // the nightly full reindex — a new folder whose files land after the debounce is caught by that. // // Notes: max_user_watches is ~483k here (>> the folder count), and Bun's recursive watch fires through // the ~/Music symlink and for new-dir creation (so reindexFolder reads the new folder's contents). const DEBOUNCE_MS = 3000; let watcher: FSWatcher | null = null; let flushTimer: ReturnType | null = null; const pending = new Set(); // music-relative folders to reindex function scheduleFlush(): void { if (flushTimer) clearTimeout(flushTimer); flushTimer = setTimeout(() => void flush(), DEBOUNCE_MS); } async function flush(): Promise { flushTimer = null; const folders = [...pending]; pending.clear(); console.log(`[music] watch: reindexing ${folders.length} changed folder(s)`); for (const rel of folders) { try { await reindexFolder(rel); } catch (err) { console.error(`[music] watch: reindex "${rel}" failed:`, err instanceof Error ? err.message : err); } } } export function startMusicWatcher(): void { try { watcher = watch(MUSIC_ROOT, { recursive: true, persistent: true }, (eventType, filename) => { if (!filename) return; const rel = filename.toString(); // path relative to MUSIC_ROOT (posix) console.log(`[music] watch: ${eventType} — ${rel}`); // Reindex the containing folder (for a file change) AND the path itself (in case it's a new/removed // dir). File paths handed to reindexFolder are cheap no-ops; container folders build a null entry. const folder = dirname(rel); pending.add(folder === '.' ? '' : folder); pending.add(rel); scheduleFlush(); }); console.log(`[music] watching ${MUSIC_ROOT} (recursive) for changes`); } catch (err) { console.error('[music] failed to start file watcher:', err instanceof Error ? err.message : err); } } export function stopMusicWatcher(): void { if (flushTimer) { clearTimeout(flushTimer); flushTimer = null; } if (watcher) { watcher.close(); watcher = null; } pending.clear(); }