fix(library): instant Lightroom-style catalog restore with lazy tile fallback

This commit is contained in:
2026-10-05 08:05:09 +07:00
parent b30c7403e3
commit c4d904a207
4 changed files with 106 additions and 40 deletions
+75 -22
View File
@@ -1320,6 +1320,12 @@ export function clearLibraryCache(): void {
cached = null;
}
let backupTileFetcher: ((id: string) => Promise<Blob | null>) | null = null;
export function setBackupTileFetcher(fetcher: ((id: string) => Promise<Blob | null>) | null): void {
backupTileFetcher = fetcher;
}
// Null, and not an empty list: a read that failed is not a catalogue with
// nothing in it, and the screen that would draw an empty strip on one is the
// screen that loses the frames it was showing — under a scan of its own, where
@@ -1329,13 +1335,30 @@ export async function getPhotoThumb(id: string): Promise<Blob | null> {
if (cached) return cached;
try {
const db = await openDb();
if (!db.objectStoreNames.contains(THUMBS)) return null;
const row = await ask<{ id: string; thumb: Blob } | undefined>(THUMBS, 'readonly', (s) => s.get(id));
if (row?.thumb) {
cacheThumb(id, row.thumb);
return row.thumb;
if (db.objectStoreNames.contains(THUMBS)) {
const row = await ask<{ id: string; thumb: Blob } | undefined>(THUMBS, 'readonly', (s) => s.get(id));
if (row?.thumb) {
cacheThumb(id, row.thumb);
return row.thumb;
}
}
} catch {}
if (backupTileFetcher) {
try {
const blob = await backupTileFetcher(id);
if (blob) {
cacheThumb(id, blob);
try {
const db = await openDb();
if (db.objectStoreNames.contains(THUMBS)) {
const tx = db.transaction(THUMBS, 'readwrite');
tx.objectStore(THUMBS).put({ id, thumb: blob });
}
} catch {}
return blob;
}
} catch {}
}
return null;
}
@@ -1352,26 +1375,56 @@ export async function getPhotoThumbs(ids: string[]): Promise<Map<string, Blob>>
}
if (!missing.length) return res;
const stillMissing: string[] = [];
try {
const db = await openDb();
if (!db.objectStoreNames.contains(THUMBS)) return res;
await new Promise<void>((resolve, reject) => {
const tx = db.transaction(THUMBS, 'readonly');
const store = tx.objectStore(THUMBS);
for (const id of missing) {
const req = store.get(id);
req.onsuccess = () => {
const row = req.result as { id: string; thumb: Blob } | undefined;
if (row?.thumb) {
cacheThumb(id, row.thumb);
res.set(id, row.thumb);
if (db.objectStoreNames.contains(THUMBS)) {
await new Promise<void>((resolve, reject) => {
const tx = db.transaction(THUMBS, 'readonly');
const store = tx.objectStore(THUMBS);
for (const id of missing) {
const req = store.get(id);
req.onsuccess = () => {
const row = req.result as { id: string; thumb: Blob } | undefined;
if (row?.thumb) {
cacheThumb(id, row.thumb);
res.set(id, row.thumb);
} else {
stillMissing.push(id);
}
};
}
tx.oncomplete = () => resolve();
tx.onerror = () => reject(tx.error);
});
} else {
stillMissing.push(...missing);
}
} catch {
stillMissing.push(...missing);
}
if (stillMissing.length && backupTileFetcher) {
const fetcher = backupTileFetcher;
await Promise.all(
stillMissing.map(async (id) => {
try {
const blob = await fetcher(id);
if (blob) {
cacheThumb(id, blob);
res.set(id, blob);
try {
const db = await openDb();
if (db.objectStoreNames.contains(THUMBS)) {
const tx = db.transaction(THUMBS, 'readwrite');
tx.objectStore(THUMBS).put({ id, thumb: blob });
}
} catch {}
}
};
}
tx.oncomplete = () => resolve();
tx.onerror = () => reject(tx.error);
});
} catch {}
} catch {}
})
);
}
return res;
}