diff --git a/docker/frontend/src/engine/library.ts b/docker/frontend/src/engine/library.ts index 79f3ef1..128b74f 100644 --- a/docker/frontend/src/engine/library.ts +++ b/docker/frontend/src/engine/library.ts @@ -1017,7 +1017,10 @@ async function clearWalk(folder: string): Promise { async function framesAt(root: FileSystemDirectoryHandle, rels: string[]): Promise { const dirs = new Map(); const out: WalkFile[] = []; - for (const rel of rels) { + for (const raw of rels) { + // A walk file written on Windows spells its frame `2017\2017-02-PHUNINH\_DSF1.RAF`; + // normalising once here means everything below sees the same `/` as the rest of the catalogue. + const rel = normPath(raw); const cut = rel.lastIndexOf('/'); const at = cut < 0 ? '' : rel.slice(0, cut); let dir = dirs.get(at); diff --git a/docker/frontend/src/engine/libraryBackup.ts b/docker/frontend/src/engine/libraryBackup.ts index 1ba0d78..1941fca 100644 --- a/docker/frontend/src/engine/libraryBackup.ts +++ b/docker/frontend/src/engine/libraryBackup.ts @@ -28,6 +28,7 @@ import { listEdits, listFolders, listPhotos, + normPath, renameFolder, restoreCatalogue, setBackupTileFetcher, @@ -53,7 +54,9 @@ export async function readBackupTile(id: string): Promise { try { const root = await folderHandle(); if (!root) return null; - const parts = id.split('/'); + // A frame id is `folder/dir/name`; a Windows-written one can say `\`, so it is + // spelled the same way here as everywhere else before it is cut. + const parts = normPath(id).split('/'); const name = parts.pop() as string; const dir = await at(root, [THUMBS, ...parts].join('/')); const file = await dir.getFileHandle(spell(name)); @@ -274,7 +277,8 @@ async function at(root: FileSystemDirectoryHandle, rel: string, create = false): if (!cache) opened.set(root, (cache = new Map())); let node = root; let path = ''; - for (const part of rel.split('/')) { + // Both separators: a path handed in from a Windows-written id may say `\`. + for (const part of rel.split(/[\\/]/)) { if (!part) continue; const name = spell(part); path = path ? `${path}/${name}` : name; @@ -345,7 +349,7 @@ export async function backupNow(onProgress?: (p: BackupProgress) => void): Promi const blob = photo.thumb ?? (await getPhotoThumb(photo.id)); if (!blob) continue; if (had.get(photo.id) === blob.size) continue; - const parts = photo.id.split('/'); + const parts = normPath(photo.id).split('/'); parts.pop(); const rel = [THUMBS, ...parts, photo.name].join('/'); try { diff --git a/docker/frontend/src/engine/rollWalk.ts b/docker/frontend/src/engine/rollWalk.ts index 4da1055..0ebbaac 100644 --- a/docker/frontend/src/engine/rollWalk.ts +++ b/docker/frontend/src/engine/rollWalk.ts @@ -52,7 +52,9 @@ export const SYSTEM_DIR = /^[.@]|^\$RECYCLE\.BIN$|^RECYCLER$|^System Volume Info export async function openAt(root: FileSystemDirectoryHandle, rel: string): Promise { let dir = root; try { - for (const part of rel.split('/').filter(Boolean)) dir = await dir.getDirectoryHandle(part); + // ponytail: a path written on Windows can arrive with `\`, so both separators + // split it; a POSIX name holding a literal `\` is the price (normPath pays it already). + for (const part of rel.split(/[\\/]/).filter(Boolean)) dir = await dir.getDirectoryHandle(part); } catch { return null; }