diff --git a/desktop/src/main/stream.ts b/desktop/src/main/stream.ts index d6768069f..12db786f5 100644 --- a/desktop/src/main/stream.ts +++ b/desktop/src/main/stream.ts @@ -44,33 +44,49 @@ export const registerStreamProtocol = () => { const path = decodeURIComponent(pathname); switch (host) { case "read": - try { - return net.fetch(pathToFileURL(path).toString()); - } catch (e) { - log.error(`Failed to read stream for ${url}`, e); - return new Response(`Failed to read stream: ${e.message}`, { - status: 500, - }); - } - + return handleRead(path); case "write": - try { - await writeStream(path, request.body); - return new Response("", { status: 200 }); - } catch (e) { - log.error(`Failed to write stream for ${url}`, e); - return new Response( - `Failed to write stream: ${e.message}`, - { status: 500 }, - ); - } - + return handleWrite(path, request); default: return new Response("", { status: 404 }); } }); }; +const handleRead = async (path: string) => { + try { + const res = await net.fetch(pathToFileURL(path).toString()); + if (res.ok) { + // net.fetch defaults to text/plain, which might be fine + // in practice, but as an extra precaution indicate that + // this is binary data. + res.headers.set("Content-Type", "application/octet-stream"); + + // Add the file's size as the Content-Length header. + const fileSize = (await fs.stat(path)).size; + res.headers.set("Content-Length", `${fileSize}`); + } + return res; + } catch (e) { + log.error(`Failed to read stream at ${path}`, e); + return new Response(`Failed to read stream: ${e.message}`, { + status: 500, + }); + } +}; + +const handleWrite = async (path: string, request: Request) => { + try { + await writeStream(path, request.body); + return new Response("", { status: 200 }); + } catch (e) { + log.error(`Failed to write stream to ${path}`, e); + return new Response(`Failed to write stream: ${e.message}`, { + status: 500, + }); + } +}; + /** * Write a (web) ReadableStream to a file at the given {@link filePath}. * diff --git a/web/apps/photos/src/utils/native-stream.ts b/web/apps/photos/src/utils/native-stream.ts index 742c00a61..b98e6ae1a 100644 --- a/web/apps/photos/src/utils/native-stream.ts +++ b/web/apps/photos/src/utils/native-stream.ts @@ -17,6 +17,8 @@ * @return A standard web {@link Response} object that contains the contents of * the file. In particular, `response.body` will be a {@link ReadableStream} * that can be used to read the files contents in a streaming, chunked, manner. + * Also, the response is guaranteed to have a "Content-Length" header indicating + * the size of the file that we'll be reading from disk. */ export const readStream = async (path: string) => { const req = new Request(`stream://read${path}`, {