From 4b901c96b5b826acf84454e52f79a92381d36e8d Mon Sep 17 00:00:00 2001 From: Jaime Idolpx Date: Wed, 17 Jun 2026 12:42:42 -0400 Subject: [PATCH] feat(SearchLocal, locate-db, webdav): implement scan cancellation with AbortController and enhance progress tracking --- src/app/components/SearchLocal.tsx | 27 +++++++++++++++++++++++---- src/app/locate-db.ts | 10 +++++++++- src/app/webdav.ts | 2 ++ 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/app/components/SearchLocal.tsx b/src/app/components/SearchLocal.tsx index 95d4331..f661330 100644 --- a/src/app/components/SearchLocal.tsx +++ b/src/app/components/SearchLocal.tsx @@ -166,7 +166,8 @@ function FilterChips({ } export default function SearchLocal({ config, setConfig, onClose, onOpenFolder }: SearchLocalProps) { - const scrollRef = useRef(null); + const scrollRef = useRef(null); + const scanAbortRef = useRef(null); const [query, setQuery] = useState(() => _store.query); const [isSearching, setIsSearching] = useState(false); const [isScanning, setIsScanning] = useState(false); @@ -214,6 +215,10 @@ export default function SearchLocal({ config, setConfig, onClose, onOpenFolder } } }, []); + useEffect(() => { + return () => { scanAbortRef.current?.abort(); }; + }, []); + const mountedPaths = useMemo(() => { const paths = new Set(); for (const d of Object.values(config?.devices?.iec ?? {})) { @@ -267,24 +272,32 @@ export default function SearchLocal({ config, setConfig, onClose, onOpenFolder } }; const handleScan = async () => { + const controller = new AbortController(); + scanAbortRef.current = controller; setIsScanning(true); setScanPhase('scanning'); setScanValue(0); try { - const { count, bytes } = await buildLocateDb((phase, value, path) => - flushSync(() => { setScanPhase(phase); setScanValue(value); if (path !== undefined) setScanPath(path); }) + const { count, bytes } = await buildLocateDb( + (phase, value, path) => + flushSync(() => { setScanPhase(phase); setScanValue(value); if (path !== undefined) setScanPath(path); }), + controller.signal, ); toast.success(`Indexed ${count.toLocaleString()} items · ${humanFileSize(bytes)}`); setDbPhase('ready'); } catch (e: any) { - toast.error(`Scan failed: ${e?.message ?? e}`); + if (e?.name === 'AbortError') toast.info('Scan stopped.'); + else toast.error(`Scan failed: ${e?.message ?? e}`); } finally { + scanAbortRef.current = null; setIsScanning(false); setScanPhase(null); setScanPath(null); } }; + const handleStopScan = () => scanAbortRef.current?.abort(); + const handleRefreshDb = () => { resetLocateDb(); setDbPhase('idle'); @@ -459,6 +472,12 @@ export default function SearchLocal({ config, setConfig, onClose, onOpenFolder } ) : (

Scanning /sd recursively…

)} + )} diff --git a/src/app/locate-db.ts b/src/app/locate-db.ts index e5ccffb..0570bf1 100644 --- a/src/app/locate-db.ts +++ b/src/app/locate-db.ts @@ -175,14 +175,18 @@ export type ScanPhase = 'scanning' | 'building' | 'saving' | 'compressing'; */ export async function buildLocateDb( onProgress?: (phase: ScanPhase, value: number, path?: string) => void, + signal?: AbortSignal, ): Promise<{ count: number; bytes: number }> { + signal?.throwIfAborted(); const sqlite3 = await getSqlite3(); // ── 1. Recursive PROPFIND on /sd ──────────────────────────────────────── const entries = await listDirectory( '/sd', true, bytes => onProgress?.('scanning', bytes), + signal, ); + signal?.throwIfAborted(); // ── 2. Build in-memory DB ─────────────────────────────────────────────── const db = new sqlite3.oo1.DB(':memory:', 'ct'); @@ -207,12 +211,16 @@ export async function buildLocateDb( e.lastModified ? Math.floor(e.lastModified.getTime() / 1000) : 0, e.type === 'folder' ? 1 : 0, ]).stepReset(); - if (i % SCAN_PROGRESS_INTERVAL === 0) onProgress?.('building', i, e.path); + if (i % SCAN_PROGRESS_INTERVAL === 0) { + signal?.throwIfAborted(); + onProgress?.('building', i, e.path); + } } } finally { stmt.finalize(); } db.exec('COMMIT'); + signal?.throwIfAborted(); onProgress?.('building', entries.length); // ── 3. Serialize to bytes ──────────────────────────────────────────────── diff --git a/src/app/webdav.ts b/src/app/webdav.ts index ff20c7e..5138d03 100644 --- a/src/app/webdav.ts +++ b/src/app/webdav.ts @@ -175,6 +175,7 @@ export async function listDirectory( path: string, recursive = false, onProgress?: (bytes: number) => void, + signal?: AbortSignal, ): Promise { const manager = getWebDAVClient(); const base = manager.client.baseUrl; @@ -190,6 +191,7 @@ export async function listDirectory( method: 'PROPFIND', headers: { 'Depth': String(depth), 'Content-Type': 'text/xml; charset=utf-8' }, body: PROPFIND_LIST_BODY, + signal, }); if (!response.body) throw new Error('No response body'); const reader = response.body.getReader();