import fs from 'node:fs'; import { enrichFiles, walkUploadDir } from './lib/fs-tree.mjs'; export function createDriveRuntime(ctx) { const state = { ctx, listCache: { files: [], dirs: [], ts: 0, added: [], removed: [] }, sseClients: new Set(), watchTimer: null, watcher: null, }; async function refreshFileList() { const { files, dirs } = await walkUploadDir(ctx, ctx.uploadDir); const prevNames = new Set(state.listCache.files.map((f) => f.name)); const added = files.filter((f) => !prevNames.has(f.name)).map((f) => f.name); const nextNames = new Set(files.map((f) => f.name)); const removed = state.listCache.files.filter((f) => !nextNames.has(f.name)).map((f) => f.name); const enriched = await enrichFiles(ctx, files); state.listCache = { files: enriched, dirs, ts: Date.now(), added, removed, delta: added.length > 0 || removed.length > 0, }; return state.listCache; } function broadcastList(payload) { const body = `data: ${JSON.stringify(payload)}\n\n`; for (const client of state.sseClients) { try { client.write(body); } catch { state.sseClients.delete(client); } } } async function notifyChange() { const payload = await refreshFileList(); broadcastList(payload); return payload; } function scheduleNotify() { clearTimeout(state.watchTimer); state.watchTimer = setTimeout(() => { notifyChange().catch(console.error); }, 250); } function initWatcher() { if (state.watcher) return; try { state.watcher = fs.watch(ctx.uploadDir, { recursive: true }, scheduleNotify); } catch (err) { console.warn('[oa-drive] fs.watch unavailable:', err.message); } } async function bootstrap() { await refreshFileList(); initWatcher(); } function dispose() { clearTimeout(state.watchTimer); if (state.watcher) { state.watcher.close(); state.watcher = null; } state.sseClients.clear(); } return { ctx, getListCache: () => state.listCache, refreshFileList, notifyChange, addSseClient(res) { state.sseClients.add(res); return () => state.sseClients.delete(res); }, bootstrap, dispose, }; }