| ... | @@ -186,9 +186,30 @@ export function storeFiles(changes) { | ... | @@ -186,9 +186,30 @@ export function storeFiles(changes) { |
| 186 | // `notebook::fs` under FOLDERS, their handles kept in IndexedDB to reach them again. | 186 | // `notebook::fs` under FOLDERS, their handles kept in IndexedDB to reach them again. |
| 187 | const FOLDERS = "/Folders"; | 187 | const FOLDERS = "/Folders"; |
| 188 | const folders = new Map(); | 188 | const folders = new Map(); |
| 189 | let writing = Promise.resolve(); | 189 | const pendingWrites = []; |
| 190 | const serially = (work) => | 190 | let writing; |
| 191 | (writing = writing.then(work).catch((error) => console.error("Writing to the folder", error))); | 191 | function writeFolders(through = pendingWrites.at(-1)) { |
| | 192 | if (!through) return writing ?? Promise.resolve(); |
| | 193 | if (writing) return writing.then(() => { |
| | 194 | if (pendingWrites.includes(through)) return writeFolders(through); |
| | 195 | }); |
| | 196 | return writing = Promise.resolve().then(async () => { |
| | 197 | try { |
| | 198 | while (pendingWrites.length) { |
| | 199 | const work = pendingWrites[0]; |
| | 200 | await work(); |
| | 201 | pendingWrites.shift(); |
| | 202 | if (work === through) return; |
| | 203 | } |
| | 204 | } finally { |
| | 205 | writing = undefined; |
| | 206 | } |
| | 207 | }); |
| | 208 | } |
| | 209 | const serially = (work) => { |
| | 210 | pendingWrites.push(work); |
| | 211 | writeFolders().catch((error) => console.error("Writing to the folder", error)); |
| | 212 | }; |
| 192 | const folderOf = (path) => | 213 | const folderOf = (path) => |
| 193 | [...folders.keys()].find((root) => path === root || path.startsWith(`${root}/`)); | 214 | [...folders.keys()].find((root) => path === root || path.startsWith(`${root}/`)); |
| 194 | const stamp = (file) => `${file.size}:${file.lastModified}`; | 215 | const stamp = (file) => `${file.size}:${file.lastModified}`; |
| ... | @@ -252,21 +273,27 @@ export async function loadFolders() { | ... | @@ -252,21 +273,27 @@ export async function loadFolders() { |
| 252 | } | 273 | } |
| 253 | } | 274 | } |
| 254 | // Other apps' writes reach the page once they land, as a watch would report them. | 275 | // Other apps' writes reach the page once they land, as a watch would report them. |
| 255 | setInterval(() => serially(look), 3000); | 276 | setInterval(() => { |
| | 277 | if (!pendingWrites.includes(look)) serially(look); |
| | 278 | }, 3000); |
| 256 | return out; | 279 | return out; |
| 257 | } | 280 | } |
| 258 | | 281 | |
| 259 | /** Hands the page what changed in each folder since it was last read. */ | 282 | /** Hands the page what changed in each folder since it was last read. */ |
| 260 | async function look() { | 283 | async function look() { |
| 261 | for (const [root, folder] of folders) { | 284 | try { |
| 262 | const seen = new Map(); | 285 | for (const [root, folder] of folders) { |
| 263 | for (const [path, file] of await walk(folder.handle, root)) { | 286 | const seen = new Map(); |
| 264 | seen.set(path, file ? stamp(file) : "folder"); | 287 | for (const [path, file] of await walk(folder.handle, root)) { |
| 265 | if (folder.known.get(path) !== seen.get(path)) | 288 | seen.set(path, file ? stamp(file) : "folder"); |
| 266 | wasm.refreshed(path, file ? new Uint8Array(await file.arrayBuffer()) : null, file?.lastModified ?? 0); | 289 | if (folder.known.get(path) !== seen.get(path)) |
| | 290 | wasm.refreshed(path, file ? new Uint8Array(await file.arrayBuffer()) : null, file?.lastModified ?? 0); |
| | 291 | } |
| | 292 | for (const path of folder.known.keys()) if (!seen.has(path)) wasm.refreshed(path, undefined, 0); |
| | 293 | folder.known = seen; |
| 267 | } | 294 | } |
| 268 | for (const path of folder.known.keys()) if (!seen.has(path)) wasm.refreshed(path, undefined, 0); | 295 | } catch (error) { |
| 269 | folder.known = seen; | 296 | console.error("Reading the folder", error); |
| 270 | } | 297 | } |
| 271 | } | 298 | } |
| 272 | | 299 | |
| ... | @@ -282,7 +309,11 @@ async function writeFolder([path, length, ranges, committed]) { | ... | @@ -282,7 +309,11 @@ async function writeFolder([path, length, ranges, committed]) { |
| 282 | if (folders.has(path)) return; | 309 | if (folders.has(path)) return; |
| 283 | const [dir, name, folder] = await locate(path); | 310 | const [dir, name, folder] = await locate(path); |
| 284 | if (length === undefined) { | 311 | if (length === undefined) { |
| 285 | await dir.removeEntry(name, { recursive: true }).catch(() => {}); | 312 | try { |
| | 313 | await dir.removeEntry(name, { recursive: true }); |
| | 314 | } catch (error) { |
| | 315 | if (error.name !== "NotFoundError") throw error; |
| | 316 | } |
| 286 | for (const known of [...folder.known.keys()]) | 317 | for (const known of [...folder.known.keys()]) |
| 287 | if (known === path || known.startsWith(`${path}/`)) folder.known.delete(known); | 318 | if (known === path || known.startsWith(`${path}/`)) folder.known.delete(known); |
| 288 | return; | 319 | return; |
| ... | @@ -354,7 +385,7 @@ function settled() { | ... | @@ -354,7 +385,7 @@ function settled() { |
| 354 | settling.push({ resolve, reject }); | 385 | settling.push({ resolve, reject }); |
| 355 | storage.postMessage({ kind: "settle" }); | 386 | storage.postMessage({ kind: "settle" }); |
| 356 | }); | 387 | }); |
| 357 | return Promise.all([kept, writing]); | 388 | return Promise.all([kept, writeFolders()]); |
| 358 | } | 389 | } |
| 359 | | 390 | |
| 360 | // A deployed build's module and JavaScript lie in b/HASH/, which names the build. | 391 | // A deployed build's module and JavaScript lie in b/HASH/, which names the build. |