diff --git a/docker/frontend/src/Library.tsx b/docker/frontend/src/Library.tsx index 85c0e19..0c42424 100644 --- a/docker/frontend/src/Library.tsx +++ b/docker/frontend/src/Library.tsx @@ -825,27 +825,23 @@ function Catalogue() { // either way. const synced = useRef(0); useEffect(() => { - const root = folders.find((f) => f.name === rootKey); - if (!loaded || !root || !ready.has(root.name)) return; + if (!loaded) return; // Another window may hold the reading — the app beside the browser, which // this window cannot see. Asking is a round trip, and the answer is what // keeps this one from reading the same roll a second time. const catchUp = () => { if (scanSession()) return; - // A folder of a hundred thousand frames is a hundred thousand stats on the - // disk, and this screen is raised every time the reader comes back from the - // studio — so the walk waits for the folder to have had a moment to change, - // which is a second of a roll the reader was not going to notice and not - // the several it took to walk it again. if (Date.now() - synced.current < CATCHUP_MS) return; synced.current = Date.now(); void scanBusy().then(async (busy) => { if (busy || scanSession()) return; - // A position left on the disk is a roll that stopped in the middle of - // itself and the only thing that finishes it is reading it again. This - // is the question, and it is answered by one small file opened and shut - // — not by the walk it decides on. - if (await unfinished(root.name)) void scan(root, true); + for (const folder of folders) { + if (!ready.has(folder.name)) continue; + if (await unfinished(folder.name)) { + void scan(folder, false); + break; + } + } }); }; catchUp(); @@ -854,9 +850,7 @@ function Catalogue() { }; document.addEventListener('visibilitychange', raised); return () => document.removeEventListener('visibilitychange', raised); - // `folders` and `ready` are read here, not watched: the two only ever gain a - // name or a permission, and neither is a reason to read the disk again. - }, [loaded, rootKey, scan]); + }, [loaded, folders, ready, scan]); const add = useCallback(async () => { setNote(null); @@ -1017,20 +1011,24 @@ function Catalogue() { const add = (folder: string, rel: string, counted: boolean) => { const normF = (folder || '').replace(/\\/g, '/'); const normRel = (rel || '').replace(/\\/g, '/'); - const set = foldersUnder.get(normF) ?? new Set(); - foldersUnder.set(normF, set); + const lowerF = normF.toLowerCase(); + const set = foldersUnder.get(lowerF) ?? new Set(); + foldersUnder.set(lowerF, set); let path = ''; for (const part of normRel.split('/')) { if (!part) continue; path = path ? `${path}/${part}` : part; set.add(path); - if (counted) hits.set(`${normF}/${path}`, (hits.get(`${normF}/${path}`) ?? 0) + 1); + if (counted) { + const key = `${lowerF}/${path.toLowerCase()}`; + hits.set(key, (hits.get(key) ?? 0) + 1); + } } }; for (const photo of photos) { const normF = (photo.folder || '').replace(/\\/g, '/'); const normDir = (photo.dir || '').replace(/\\/g, '/'); - hits.set(normF, (hits.get(normF) ?? 0) + 1); + hits.set(normF.toLowerCase(), (hits.get(normF.toLowerCase()) ?? 0) + 1); if (normDir) add(normF, normDir, true); } for (const dir of dirs) add(dir.folder, dir.rel, false); @@ -1039,26 +1037,28 @@ function Catalogue() { const nodes = useMemo(() => { const { hits, foldersUnder } = baseTreeInfo; - const counted = (key: string, hit: number | undefined) => { + const counted = (key: string) => { const normKey = (key || '').replace(/\\/g, '/'); const lowerKey = normKey.toLowerCase(); + const hitVal = hits.get(lowerKey) ?? 0; const readVal = read?.[normKey] ?? read?.[lowerKey] ?? read?.[key] ?? 0; - return Math.max(hit ?? 0, readVal); + return Math.max(hitVal, readVal); }; const out: Node[] = []; for (const folder of folders) { const normF = (folder.name || '').replace(/\\/g, '/'); - out.push({ key: normF, label: folder.label || folder.name, depth: 0, count: counted(normF, hits.get(normF)) }); + const lowerF = normF.toLowerCase(); + out.push({ key: normF, label: folder.label || folder.name, depth: 0, count: counted(normF) }); // Path order puts a folder before its own children, which is the order the // indented rows have to come in. - for (const dir of [...(foldersUnder.get(normF) ?? [])].sort((a, b) => a.localeCompare(b))) { + for (const dir of [...(foldersUnder.get(lowerF) ?? [])].sort((a, b) => a.localeCompare(b))) { const parts = dir.split('/'); const key = `${normF}/${dir}`; out.push({ key, label: parts[parts.length - 1], depth: parts.length, - count: counted(key, hits.get(key)), + count: counted(key), }); } } diff --git a/docker/frontend/src/engine/library.ts b/docker/frontend/src/engine/library.ts index 138ea29..8c36cc2 100644 --- a/docker/frontend/src/engine/library.ts +++ b/docker/frontend/src/engine/library.ts @@ -46,7 +46,7 @@ const LANES = 4; // How often the walk's position is written down, in the same seconds as the // catalogue is read back on: the position is the whole of what the walk has found // and not read, and writing it is a string of a few megabytes on a long roll. -const WALK_MS = 5000; +const WALK_MS = 1000; // How often the screen is told how far the reading has come: the column is // rebuilt out of the count, and a rebuild a frame is a reading that spends its // time drawing itself. Five times a second is a counter that moves to the eye @@ -872,12 +872,8 @@ export async function scanFolder( // again is skipped on its size and its time. let wroteAt = 0; const write = async () => { - // A reading kept to one folder has no position to write: the file it would - // go in is the whole roll's, and a fraction of the tree written there is a - // roll that comes back with the rest of itself missing. - if (bounded) return; const at = Date.now(); - if (at - wroteAt < WALK_MS) return; + if (wroteAt !== 0 && at - wroteAt < WALK_MS) return; wroteAt = at; const held = [...inHand, ...batch.map(pathOf)]; const savedEntries = entries.slice(0, 500).map((e) => e.rel); @@ -1093,7 +1089,7 @@ export async function scanFolder( // walks the roll from the top and skips what has not moved. A reading kept to // one folder never wrote one down, and the file it would clear is the whole // roll's — a position another reading is in the middle of. - if (!stop && !bounded) await clearWalk(folder.name); + if (!stop) await clearWalk(folder.name); // The last word, whatever the clock says: the frames the reading got to are // the frames the column is about to be told about one last time. publish(true); diff --git a/docker/recipescam-images.tar.gz.sha256 b/docker/recipescam-images.tar.gz.sha256 index 05b553e..6d19873 100644 --- a/docker/recipescam-images.tar.gz.sha256 +++ b/docker/recipescam-images.tar.gz.sha256 @@ -1 +1 @@ -d01beb3a629be66f3e70b82a325b26c92dd620e79a41de3ab73a578781f5bf45 recipescam-images.tar.gz +3c54c3f27787b7c1a660483aaed2a310eef61b466e8251767b4ef2d80b3d6505 recipescam-images.tar.gz