From df17afeb413d5cdfa2e3411c463e37612891edda Mon Sep 17 00:00:00 2001 From: Arne Baeumler Date: Sat, 13 Jun 2026 08:28:34 +0200 Subject: [PATCH] refactor: streamline client import pipeline --- src/client/app.ts | 81 +++++++++++++++++++++++++++++++---------------- 1 file changed, 53 insertions(+), 28 deletions(-) diff --git a/src/client/app.ts b/src/client/app.ts index ec0c9f4..9fc82bb 100644 --- a/src/client/app.ts +++ b/src/client/app.ts @@ -26,6 +26,7 @@ const state = { }; let activeImportController = null; const IMPORT_RENDER_INTERVAL_MS = 180; +const IMPORT_CONCURRENCY = 2; let scheduledRenderFrame = null; let scheduledRenderTimer = null; let scheduledRenderOptions = { fitMap: false }; @@ -908,14 +909,19 @@ async function readRemoteImage(entry, shareUrlValue, signal) { const capturedAt = normalizeExifDate(tags?.DateTimeOriginal?.value ?? tags?.DateTimeOriginal) || parseDateOrNull(entry.lastModified); + + if (gps?.latitude === undefined || gps?.longitude === undefined) { + return null; + } + const thumbUrl = await createThumbnailObjectUrl(blob); state.objectUrls.push(thumbUrl); return { id: entry.href, name: entry.name, - latitude: gps?.latitude ?? null, - longitude: gps?.longitude ?? null, + latitude: gps.latitude, + longitude: gps.longitude, capturedAt, thumbUrl, fullUrl, @@ -923,6 +929,50 @@ async function readRemoteImage(entry, shareUrlValue, signal) { }; } +async function processImportEntries(listing, shareUrlValue, signal) { + let loaded = 0; + let skipped = 0; + let processed = 0; + let nextIndex = 0; + + async function importWorker() { + while (nextIndex < listing.length) { + if (signal.aborted) { + throw signal.reason ?? new Error("Import canceled"); + } + + const entry = listing[nextIndex]; + nextIndex += 1; + + try { + const photo = await readRemoteImage(entry, shareUrlValue, signal); + if (photo) { + appendPhoto(photo); + loaded += 1; + } else { + skipped += 1; + } + } catch (error) { + if (signal.aborted) { + throw signal.reason ?? error; + } + skipped += 1; + console.error(error); + } + + processed += 1; + setProgress(processed, listing.length, "processed " + processed + " / " + listing.length); + setImportStatus("Importing: " + loaded + " shown, " + skipped + " skipped."); + await new Promise((resolve) => setTimeout(resolve, 0)); + } + } + + const workerCount = Math.min(IMPORT_CONCURRENCY, listing.length); + await Promise.all(Array.from({ length: workerCount }, () => importWorker())); + + return { loaded, skipped }; +} + async function importFromShare() { if (activeImportController) { return; @@ -945,32 +995,7 @@ async function importFromShare() { setProgress(0, listing.length, "checking files"); - let loaded = 0; - let skipped = 0; - let processed = 0; - - for (const entry of listing) { - if (controller.signal.aborted) { - throw controller.signal.reason ?? new Error("Import canceled"); - } - - try { - const photo = await readRemoteImage(entry, importUrlInput.value, controller.signal); - if (photo.latitude !== null && photo.longitude !== null) { - appendPhoto(photo); - loaded += 1; - } else { - skipped += 1; - } - } catch (error) { - skipped += 1; - console.error(error); - } - - processed += 1; - setProgress(processed, listing.length, "processed " + processed + " / " + listing.length); - setImportStatus("Importing: " + loaded + " shown, " + skipped + " skipped."); - } + const { loaded, skipped } = await processImportEntries(listing, importUrlInput.value, controller.signal); if (!loaded) { throw new Error("No images with GPS data found.");