diff --git a/page/src/filesystem.ts b/page/src/filesystem.ts index 100a7a8f..1a7e4390 100644 --- a/page/src/filesystem.ts +++ b/page/src/filesystem.ts @@ -1,17 +1,64 @@ import { parseZipFile, ProgressHandler } from "./zip-file"; import idbfsModule, { MainModule } from "@irori/idbfs"; -function fetchFilesystemZip() { - return fetch("./root.zip?1", { +type DownloadProgressHandler = ( + receivedBytes: number, + totalBytes: number, +) => void; + +function fetchFilesystemZip(progressCallback: DownloadProgressHandler) { + return fetch("./root.zip", { method: "GET", headers: { "Content-Type": "application/octet-stream", }, - }).then((r) => r.arrayBuffer()); + }).then((response) => { + const maybeReader = response.body?.getReader(); + if (!maybeReader) { + throw new Error("Bad reader"); + } + + const reader = maybeReader; + + const contentLength = parseInt( + response.headers?.get("Content-Length") || "0", + ); + + let receivedLength = 0; + let chunks: Uint8Array[] = []; + + function processData( + res: ReadableStreamReadResult>, + ): Promise { + if (res.value) { + chunks.push(res.value); + receivedLength += res.value.length; + } + + progressCallback(receivedLength, contentLength); + + if (!res.done) { + return reader.read().then(processData); + } + const chunksAll = new Uint8Array(receivedLength); + let position = 0; + for (const chunk of chunks) { + chunksAll.set(new Uint8Array(chunk), position); + position += chunk.length; + } + + return Promise.resolve(chunksAll.buffer); + } + + return reader.read().then(processData); + }); } -async function fetchFilesystem(progressHandler: ProgressHandler) { - const filesys = await fetchFilesystemZip(); +async function fetchFilesystem( + progressHandler: ProgressHandler, + downloadProgressHandler: DownloadProgressHandler, +) { + const filesys = await fetchFilesystemZip(downloadProgressHandler); return await parseZipFile(filesys, progressHandler); } @@ -161,7 +208,10 @@ export class Filesystem { } } -export async function setupFilesystem(progressHandler: ProgressHandler) { +export async function setupFilesystem( + progressHandler: ProgressHandler, + downloadProgressHandler: DownloadProgressHandler, +) { const idbfs = await initializeIDBFS(); const fs = new Filesystem(idbfs); @@ -169,7 +219,10 @@ export async function setupFilesystem(progressHandler: ProgressHandler) { return fs; } - const filesystem = await fetchFilesystem(progressHandler); + const filesystem = await fetchFilesystem( + progressHandler, + downloadProgressHandler, + ); filesystem.forEach((e) => { if (idbfs.FS.analyzePath("/" + e.name, false).exists) { diff --git a/page/src/playground.tsx b/page/src/playground.tsx index 85336df8..27736755 100644 --- a/page/src/playground.tsx +++ b/page/src/playground.tsx @@ -40,6 +40,28 @@ interface PlaygroundState { drawerOpen: boolean; } +function makePercentHandler( + handler: (percent: number) => void, +): (current: number, total: number) => void { + const progress = { + tracked: 0, + }; + + return (current, total) => { + if (total == 0) { + return; + } + + const percent = Math.floor((current * 100) / total); + const sanePercent = Math.max(Math.min(percent, 100), 0); + + if (sanePercent + 1 > progress.tracked) { + progress.tracked = sanePercent + 1; + handler(sanePercent); + } + }; +} + export class Playground extends React.Component< PlaygroundProps, PlaygroundState @@ -90,9 +112,14 @@ export class Playground extends React.Component< const promise = new Promise((resolve) => { this.logLine("Loading filesystem..."); - setupFilesystem((current, total, file) => { - this.logLine(`Processing filesystem (${current}/${total}): ${file}`); - }).then(resolve); + setupFilesystem( + (current, total, file) => { + this.logLine(`Processing filesystem (${current}/${total}): ${file}`); + }, + makePercentHandler((percent) => { + this.logLine(`Downloading filesystem: ${percent}%`); + }), + ).then(resolve); }); promise.then((filesystem) => this.setState({ filesystem }));