This commit is contained in:
Manav Rathi
2024-06-25 12:56:49 +05:30
parent 5c3cfb7403
commit 3220da556d
4 changed files with 54 additions and 13 deletions

View File

@@ -1,14 +1,13 @@
import StreamZip from "node-stream-zip";
import fs from "node:fs/promises";
import path from "node:path";
import { existsSync } from "original-fs";
import type { PendingUploads, ZipItem } from "../../types/ipc";
import log from "../log";
import { uploadStatusStore } from "../stores/upload-status";
import { clearOpenZipCache } from "./zip";
import { clearOpenZipCache, markClosableZip, openZip } from "./zip";
export const listZipItems = async (zipPath: string): Promise<ZipItem[]> => {
const zip = new StreamZip.async({ file: zipPath });
const zip = openZip(zipPath);
const entries = await zip.entries();
const entryNames: string[] = [];
@@ -22,7 +21,7 @@ export const listZipItems = async (zipPath: string): Promise<ZipItem[]> => {
}
}
await zip.close();
markClosableZip(zipPath);
return entryNames.map((entryName) => [zipPath, entryName]);
};
@@ -35,7 +34,7 @@ export const pathOrZipItemSize = async (
return stat.size;
} else {
const [zipPath, entryName] = pathOrZipItem;
const zip = new StreamZip.async({ file: zipPath });
const zip = openZip(zipPath);
const entry = await zip.entry(entryName);
if (!entry)
throw new Error(

View File

@@ -1,7 +1,27 @@
import { LRUCache } from "lru-cache";
import StreamZip from "node-stream-zip";
const _cache = new LRUCache<string, StreamZip.StreamZipAsync>({ max: 50 });
/** The cache. */
const _cache = new LRUCache<string, StreamZip.StreamZipAsync>({
max: 50,
disposeAfter: (zip, zipPath) => {
if (isInUse(zipPath)) {
// Add it back again. The `noDisposeOnSet` flag prevents dispose
// from being called again. From my understanding, it shouldn't
// matter in our case, but I've kept it here to match the
// recommended example:
// https://github.com/isaacs/node-lru-cache/issues/151#issuecomment-1033206697
_cache.set(zipPath, zip);
} else {
void zip.close();
}
},
});
/** Reference count. */
const _refCount = new Map<string, number>();
const isInUse = (zipPath: string) => (_refCount.get(zipPath) ?? 0) > 0;
/**
* Cached `StreamZip.async`s
@@ -29,10 +49,32 @@ export const openZip = (zipPath: string) => {
result = new StreamZip.async({ file: zipPath });
_cache.set(zipPath, result);
}
_refCount.set(zipPath, (_refCount.get(zipPath) ?? 0) + 1);
return result;
};
/**
* Indicate to our cache that an item we opened earlier using {@link openZip}
* can now be safely closed.
*
* @param zipPath The key that was used for opening this zip.
*/
export const markClosableZip = (zipPath: string) => {
const rc = _refCount.get(zipPath);
if (!rc) throw new Error(`Double close for ${zipPath}`);
if (rc == 1) _refCount.delete(zipPath);
else _refCount.set(zipPath, rc - 1);
};
/**
* Clear any entries previously cached by {@link openZip}.
*/
export const clearOpenZipCache = () => _cache.clear();
export const clearOpenZipCache = () => {
if (_refCount.size > 0) {
const keys = JSON.stringify([..._refCount.keys()]);
throw new Error(
`Attempting to clear zip file cache when some items are still in use: ${keys}`,
);
}
_cache.clear();
};

View File

@@ -2,7 +2,6 @@
* @file stream data to-from renderer using a custom protocol handler.
*/
import { net, protocol } from "electron/main";
import StreamZip from "node-stream-zip";
import { randomUUID } from "node:crypto";
import { createWriteStream, existsSync } from "node:fs";
import fs from "node:fs/promises";
@@ -11,6 +10,7 @@ import { ReadableStream } from "node:stream/web";
import { pathToFileURL } from "node:url";
import log from "./log";
import { ffmpegConvertToMP4 } from "./services/ffmpeg";
import { markClosableZip, openZip } from "./services/zip";
import { ensure } from "./utils/common";
import {
deleteTempFile,
@@ -113,7 +113,7 @@ const handleRead = async (path: string) => {
};
const handleReadZip = async (zipPath: string, entryName: string) => {
const zip = new StreamZip.async({ file: zipPath });
const zip = openZip(zipPath);
const entry = await zip.entry(entryName);
if (!entry) return new Response("", { status: 404 });
@@ -130,7 +130,7 @@ const handleReadZip = async (zipPath: string, entryName: string) => {
webReadableStreamAny as ReadableStream<Uint8Array>;
// Close the zip handle when the underlying stream closes.
stream.on("end", () => void zip.close());
stream.on("end", () => markClosableZip(zipPath));
// While it is documented that entry.time is the modification time,
// the units are not mentioned. By seeing the source code, we can

View File

@@ -1,10 +1,10 @@
import { app } from "electron/main";
import StreamZip from "node-stream-zip";
import { existsSync } from "node:fs";
import fs from "node:fs/promises";
import path from "node:path";
import type { ZipItem } from "../../types/ipc";
import log from "../log";
import { markClosableZip, openZip } from "../services/zip";
import { ensure } from "./common";
/**
@@ -128,9 +128,9 @@ export const makeFileForDataOrPathOrZipItem = async (
} else {
writeToTemporaryFile = async () => {
const [zipPath, entryName] = dataOrPathOrZipItem;
const zip = new StreamZip.async({ file: zipPath });
const zip = openZip(zipPath);
await zip.extract(entryName, path);
await zip.close();
markClosableZip(zipPath);
};
}
}