|
@@ -2,6 +2,7 @@
|
|
|
* @file stream data to-from renderer using a custom protocol handler.
|
|
|
*/
|
|
|
import { net, protocol } from "electron/main";
|
|
|
+import StreamZip from "node-stream-zip";
|
|
|
import { createWriteStream, existsSync } from "node:fs";
|
|
|
import fs from "node:fs/promises";
|
|
|
import { Readable } from "node:stream";
|
|
@@ -34,17 +35,23 @@ export const registerStreamProtocol = () => {
|
|
|
protocol.handle("stream", async (request: Request) => {
|
|
|
const url = request.url;
|
|
|
// The request URL contains the command to run as the host, and the
|
|
|
- // pathname of the file as the path. For example,
|
|
|
+ // pathname of the file as the path. An additional path can be specified
|
|
|
+ // as the URL hash.
|
|
|
//
|
|
|
- // stream://write/path/to/file
|
|
|
- // host-pathname-----
|
|
|
+ // For example,
|
|
|
//
|
|
|
- const { host, pathname } = new URL(url);
|
|
|
+ // stream://write/path/to/file#/path/to/another/file
|
|
|
+ // host[pathname----] [pathname-2---------]
|
|
|
+ //
|
|
|
+ const { host, pathname, hash } = new URL(url);
|
|
|
// Convert e.g. "%20" to spaces.
|
|
|
const path = decodeURIComponent(pathname);
|
|
|
+ const hashPath = decodeURIComponent(hash);
|
|
|
switch (host) {
|
|
|
case "read":
|
|
|
return handleRead(path);
|
|
|
+ case "read-zip":
|
|
|
+ return handleReadZip(path, hashPath);
|
|
|
case "write":
|
|
|
return handleWrite(path, request);
|
|
|
default:
|
|
@@ -88,6 +95,36 @@ const handleRead = async (path: string) => {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
+const handleReadZip = async (zipPath: string, zipEntryPath: string) => {
|
|
|
+ try {
|
|
|
+ const zip = new StreamZip.async({
|
|
|
+ file: zipPath,
|
|
|
+ });
|
|
|
+ const entry = await zip.entry(zipEntryPath);
|
|
|
+ const stream = await zip.stream(entry);
|
|
|
+
|
|
|
+ return new Response(Readable.toWeb(new Readable(stream)), {
|
|
|
+ headers: {
|
|
|
+ // We don't know the exact type, but it doesn't really matter,
|
|
|
+ // just set it to a generic binary content-type so that the
|
|
|
+ // browser doesn't tinker with it thinking of it as text.
|
|
|
+ "Content-Type": "application/octet-stream",
|
|
|
+ "Content-Length": `${entry.size}`,
|
|
|
+ // !!TODO(MR): Is this ms
|
|
|
+ "X-Last-Modified-Ms": `${entry.time}`,
|
|
|
+ },
|
|
|
+ });
|
|
|
+ } catch (e) {
|
|
|
+ log.error(
|
|
|
+ `Failed to read entry ${zipEntryPath} from zip file at ${zipPath}`,
|
|
|
+ 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);
|