fix(SearchLocal, locate-db): enhance database download progress tracking and UI feedback
This commit is contained in:
parent
3b94d3d956
commit
9d7034e7d2
|
|
@ -175,7 +175,7 @@ export default function SearchLocal({ config, setConfig, onClose, onOpenFolder }
|
|||
const [mountEntry, setMountEntry] = useState<SearchResult | null>(null);
|
||||
const [actionEntry, setActionEntry] = useState<SearchResult | null>(null);
|
||||
const [searchError, setSearchError] = useState<string | null>(null);
|
||||
const [dbBytes, setDbBytes] = useState<number | null>(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<string | null>(() => _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 && (
|
||||
<div className="flex flex-col items-center justify-center py-16 gap-3">
|
||||
<div className="flex flex-col items-center justify-center py-16 gap-3 w-full px-8">
|
||||
<Loader2 className="w-8 h-8 text-blue-500 animate-spin" />
|
||||
<p className="text-sm text-neutral-500">{loadingLabel}</p>
|
||||
<p className="text-sm text-neutral-500 text-center">{loadingLabel}</p>
|
||||
{dbPhase === 'downloading' && (
|
||||
<div className="w-full max-w-xs">
|
||||
<div className="h-1.5 bg-neutral-200 rounded-full overflow-hidden">
|
||||
<div
|
||||
className={`h-full bg-blue-500 transition-all duration-150 ease-out ${downloadPct === null ? 'animate-pulse w-1/3' : ''}`}
|
||||
style={downloadPct !== null ? { width: `${downloadPct}%` } : undefined}
|
||||
/>
|
||||
</div>
|
||||
{downloadPct !== null && (
|
||||
<p className="text-xs text-neutral-400 text-center mt-1.5">{downloadPct}%</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<any> | null = null;
|
||||
function getSqlite3(): Promise<any> {
|
||||
|
|
@ -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<void> {
|
||||
export async function openLocateDb(
|
||||
onProgress?: (progress: { received: number; total: number | null }) => void,
|
||||
): Promise<void> {
|
||||
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.
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user