From d36fa782503ff1dc5c8876fca3e0e39673338896 Mon Sep 17 00:00:00 2001 From: locpham Date: Sat, 10 Oct 2026 20:02:27 +0700 Subject: [PATCH] fix(library): a drive is read again from the catalogue it is filed under --- docker/frontend/src/engine/library.ts | 33 +++++++++++++++++++++------ 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/docker/frontend/src/engine/library.ts b/docker/frontend/src/engine/library.ts index b140901..468f435 100644 --- a/docker/frontend/src/engine/library.ts +++ b/docker/frontend/src/engine/library.ts @@ -379,10 +379,17 @@ export async function reconnectPhotosForFolders( const folderTargets: { name: string; handle: FileSystemDirectoryHandle }[] = []; folderTargets.push({ name: newFolderHandle.name, handle: newFolderHandle }); - const knownFolderNames = new Set([ - ...existingFolders.filter((f) => !f.source).map((f) => f.name), - ...allPhotos.filter((p) => !p.remoteId).map((p) => p.folder), - ]); + const knownFolderNames = new Set( + [ + ...existingFolders.filter((f) => !f.source).map((f) => f.name), + ...allPhotos.filter((p) => !p.remoteId).map((p) => p.folder), + ] + // A row's folder is filed spelled the way `readPhotos` reads it, and the + // drive root's `\` spells as `''` — a name no folder can be opened at, and + // one that would leave a record for a folder with no name behind it. + .map(normPath) + .filter(Boolean) + ); for (const name of knownFolderNames) { if (folderTargets.some((t) => t.name === name)) continue; @@ -398,8 +405,12 @@ export async function reconnectPhotosForFolders( await new Promise((resolve, reject) => { const tx = db.transaction(FOLDERS, 'readwrite'); const store = tx.objectStore(FOLDERS); + // The record goes back with the label it was given: the tree draws `D:` where + // the disk's own name for its root is `\`, and a put without the label hands + // the reader back the name the browser could not make sense of. + const labels = new Map(existingFolders.map((f) => [f.name, f.label])); for (const target of folderTargets) { - store.put({ name: target.name, handle: target.handle }); + store.put({ name: target.name, label: labels.get(target.name), handle: target.handle }); reconnectedFolders++; } tx.oncomplete = () => resolve(); @@ -408,7 +419,9 @@ export async function reconnectPhotosForFolders( const photosToUpdate: LibraryPhoto[] = []; for (const target of folderTargets) { - const matchingPhotos = allPhotos.filter((p) => p.folder === target.name && !p.handle && !p.remoteId); + const matchingPhotos = allPhotos.filter( + (p) => normPath(p.folder) === normPath(target.name) && !p.handle && !p.remoteId + ); if (!matchingPhotos.length) continue; const dirCache = new Map(); @@ -2280,8 +2293,14 @@ export async function makeThumbs( export async function readPhotos(folder?: string): Promise { try { const db = await openDb(); + // Every row is filed under `normPath(folder.name)`, and a drive root is the + // case that shows why that matters: the picker names `D:` `\\`, so `normPath` + // spells it `''`. Asking the index for the raw name is asking for a spelling + // nothing is filed under — the catalogue comes back empty, every frame of the + // roll is read as new, and a second reading of a drive it already holds drags + // a handle for every frame of it into memory at once. Ask for what is filed. const rows = await ask(PHOTOS, 'readonly', (s) => - folder ? s.index('folder').getAll(folder) : s.getAll() + folder ? s.index('folder').getAll(normPath(folder)) : s.getAll() ); for (let i = 0; i < rows.length; i++) { if (rows[i].thumb) delete (rows[i] as { thumb?: Blob | null }).thumb;