diff --git a/src/app/components/SearchLocal.tsx b/src/app/components/SearchLocal.tsx index 7e6dba4..8b1a0e2 100644 --- a/src/app/components/SearchLocal.tsx +++ b/src/app/components/SearchLocal.tsx @@ -175,7 +175,7 @@ export default function SearchLocal({ config, setConfig, onClose, onOpenFolder } const [mountEntry, setMountEntry] = useState(null); const [actionEntry, setActionEntry] = useState(null); const [searchError, setSearchError] = useState(null); - const [dbBytes, setDbBytes] = useState(null); + const [dbProgress, setDbProgress] = useState<{ received: number; total: number | null }>({ received: 0, total: null }); const [dbPhase, setDbPhase] = useState<'idle' | 'downloading' | 'ready'>('idle'); const [showFilter, setShowFilter] = useState(() => _store.showFilter); const [filterSystem, setFilterSystem] = useState(() => _store.filterSystem); @@ -229,8 +229,8 @@ export default function SearchLocal({ config, setConfig, onClose, onOpenFolder } try { if (!isLocateDbLoaded()) { setDbPhase('downloading'); - setDbBytes(null); - await openLocateDb(bytes => flushSync(() => setDbBytes(bytes))); + setDbProgress({ received: 0, total: null }); + await openLocateDb(p => flushSync(() => setDbProgress(p))); setDbPhase('ready'); } const needle = query.trim(); @@ -297,9 +297,17 @@ export default function SearchLocal({ config, setConfig, onClose, onOpenFolder } }; const loadingLabel = dbPhase === 'downloading' - ? (dbBytes === null ? 'Loading database…' : `Loading database… ${humanFileSize(dbBytes)}`) + ? (dbProgress.received === 0 + ? 'Loading database…' + : dbProgress.total === null + ? `Loading database… ${humanFileSize(dbProgress.received)}` + : `Loading database… ${humanFileSize(dbProgress.received)} / ${humanFileSize(dbProgress.total)}`) : 'Searching…'; + const downloadPct = dbProgress.total && dbProgress.total > 0 + ? Math.min(100, Math.round((dbProgress.received / dbProgress.total) * 100)) + : null; + const busy = isSearching || isScanning; const facets = useMemo(() => { @@ -439,9 +447,22 @@ export default function SearchLocal({ config, setConfig, onClose, onOpenFolder } )} {isSearching && !hasSearched && ( -
+
-

{loadingLabel}

+

{loadingLabel}

+ {dbPhase === 'downloading' && ( +
+
+
+
+ {downloadPct !== null && ( +

{downloadPct}%

+ )} +
+ )}
)} diff --git a/src/app/locate-db.ts b/src/app/locate-db.ts index 32743de..c9befc8 100644 --- a/src/app/locate-db.ts +++ b/src/app/locate-db.ts @@ -3,6 +3,12 @@ import { getWebDAVBaseUrl, basename, listDirectory, putFileContents, deletePath const LOCATE_PATH = '/sd/.locate'; +function parseContentLength(header: string | null): number | null { + if (!header) return null; + const n = Number(header); + return Number.isFinite(n) && n > 0 ? n : null; +} + // Memoize the module init — loading the WASM binary is expensive. let _sqlite3Promise: Promise | null = null; function getSqlite3(): Promise { @@ -26,9 +32,13 @@ export function resetLocateDb(): void { /** * Fetch /sd/.locate and open it as an in-memory SQLite database. * Calling again when already loaded is a no-op unless you call resetLocateDb() first. - * onProgress receives raw bytes received so far during the download. + * onProgress receives { received, total } for each chunk; total is the value + * of the Content-Length response header, or null if the server didn't send + * one (chunked transfer, etc.). */ -export async function openLocateDb(onProgress?: (bytes: number) => void): Promise { +export async function openLocateDb( + onProgress?: (progress: { received: number; total: number | null }) => void, +): Promise { if (_db) return; const sqlite3 = await getSqlite3(); @@ -37,6 +47,8 @@ export async function openLocateDb(onProgress?: (bytes: number) => void): Promis const response = await fetch(url); if (!response.ok) throw new Error(`Cannot fetch locate database: ${response.status} ${response.statusText}`); + const total = parseContentLength(response.headers.get('content-length')); + // Stream the response body so onProgress gets called per chunk. let bytes: Uint8Array; if (response.body) { @@ -48,7 +60,7 @@ export async function openLocateDb(onProgress?: (bytes: number) => void): Promis if (done) break; chunks.push(value); received += value.byteLength; - onProgress?.(received); + onProgress?.({ received, total }); } const combined = new Uint8Array(received); let offset = 0; @@ -56,6 +68,7 @@ export async function openLocateDb(onProgress?: (bytes: number) => void): Promis bytes = combined; } else { bytes = new Uint8Array(await response.arrayBuffer()); + onProgress?.({ received: bytes.byteLength, total: total ?? bytes.byteLength }); } // Allocate the bytes in WASM heap, then deserialize into a fresh in-memory DB.