import { useEffect, useRef } from 'react' import { useQuery, useQueryClient } from '@tanstack/react-query' import { library, WorkerStatus } from '../services/api' interface ScanStatus { is_scanning: boolean current_folder?: string processed_files: number total_files: number errors: string[] } type Phase = 'idle' | 'scanning' | 'processing' | 'done' /** * Headless background-activity orchestrator. Polls scan + worker status * and invalidates affected query caches when a scan/processing pass * completes. The visible status indicator now lives inline in the * LeftSidebar (small spinner next to the FOLDERS header / specific * folder rows) — see useScanActivity. */ export function ScanProgress() { const queryClient = useQueryClient() const wasScanningRef = useRef(false) const wasProcessingRef = useRef(false) const { data: scanStatus } = useQuery({ queryKey: ['scan-status'], queryFn: () => library.scanStatus(), refetchInterval: (query) => query.state.data?.is_scanning ? 2000 : 30000, enabled: true, }) const isScanning = scanStatus?.is_scanning ?? false // Poll worker status to track vision queue activity. // Fast polling (3s) while processing, slow (30s) otherwise. const { data: workerStatus } = useQuery({ queryKey: ['worker-status-progress'], queryFn: () => library.maintenance.workerStatus(), refetchInterval: (query) => { const q = totalQueued(query.state.data) return q > 0 ? 3000 : 30000 }, enabled: true, }) const totalActive = totalQueued(workerStatus) const phase: Phase = isScanning ? 'scanning' : totalActive > 0 ? 'processing' : 'idle' useEffect(() => { if (phase === 'scanning') { wasScanningRef.current = true wasProcessingRef.current = false } else if (phase === 'processing') { wasProcessingRef.current = true if (wasScanningRef.current) { wasScanningRef.current = false queryClient.invalidateQueries({ queryKey: ['photos'] }) queryClient.invalidateQueries({ queryKey: ['folders'] }) queryClient.invalidateQueries({ queryKey: ['folders', 'tree'] }) queryClient.invalidateQueries({ queryKey: ['heaps'] }) queryClient.invalidateQueries({ queryKey: ['tags'] }) queryClient.invalidateQueries({ queryKey: ['library', 'stats'] }) } } else if (phase === 'idle') { if (wasScanningRef.current) { wasScanningRef.current = false queryClient.invalidateQueries({ queryKey: ['photos'] }) queryClient.invalidateQueries({ queryKey: ['folders'] }) queryClient.invalidateQueries({ queryKey: ['folders', 'tree'] }) queryClient.invalidateQueries({ queryKey: ['heaps'] }) queryClient.invalidateQueries({ queryKey: ['tags'] }) queryClient.invalidateQueries({ queryKey: ['library', 'stats'] }) } if (wasProcessingRef.current) { wasProcessingRef.current = false queryClient.invalidateQueries({ queryKey: ['tags'] }) queryClient.invalidateQueries({ queryKey: ['photos'] }) queryClient.invalidateQueries({ queryKey: ['library', 'stats'] }) } } }, [phase, queryClient]) return null } function totalQueued(ws: WorkerStatus | undefined): number { if (!ws) return 0 const queued = Object.values(ws.queues ?? {}).reduce((a, b) => a + b, 0) const active = ws.workers?.reduce( (sum, w) => sum + (w.active ?? 0) + (w.reserved ?? 0), 0 ) ?? 0 return queued + active }