2024-05-24 17:26:38 +02:00
|
|
|
import fs from "fs/promises";
|
|
|
|
import fsSync from "fs";
|
|
|
|
import yauzl from "yauzl";
|
|
|
|
import stream from "stream";
|
|
|
|
import { Deferred } from "evt/tools/Deferred";
|
|
|
|
import { dirname as pathDirname, sep as pathSep } from "path";
|
2024-05-27 18:33:16 +02:00
|
|
|
import { existsAsync } from "./fs.existsAsync";
|
2024-05-24 17:26:38 +02:00
|
|
|
|
|
|
|
export async function extractArchive(params: {
|
|
|
|
archiveFilePath: string;
|
|
|
|
onArchiveFile: (params: {
|
|
|
|
relativeFilePathInArchive: string;
|
|
|
|
readFile: () => Promise<Buffer>;
|
2024-05-27 18:33:16 +02:00
|
|
|
/** NOTE: Will create the directory if it does not exist */
|
2024-05-24 17:26:38 +02:00
|
|
|
writeFile: (params: { filePath: string; modifiedData?: Buffer }) => Promise<void>;
|
2024-05-26 19:29:12 +02:00
|
|
|
earlyExit: () => void;
|
2024-05-24 17:26:38 +02:00
|
|
|
}) => Promise<void>;
|
|
|
|
}) {
|
|
|
|
const { archiveFilePath, onArchiveFile } = params;
|
|
|
|
|
|
|
|
const zipFile = await new Promise<yauzl.ZipFile>((resolve, reject) => {
|
|
|
|
yauzl.open(archiveFilePath, { lazyEntries: true }, async (error, zipFile) => {
|
2024-05-25 11:02:22 +02:00
|
|
|
if (error) {
|
2024-05-24 17:26:38 +02:00
|
|
|
reject(error);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
resolve(zipFile);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
const dDone = new Deferred<void>();
|
|
|
|
|
|
|
|
zipFile.once("end", () => {
|
|
|
|
zipFile.close();
|
|
|
|
dDone.resolve();
|
|
|
|
});
|
|
|
|
|
2024-05-25 12:30:21 +02:00
|
|
|
const writeFile = async (
|
|
|
|
entry: yauzl.Entry,
|
|
|
|
params: {
|
2024-05-24 17:26:38 +02:00
|
|
|
filePath: string;
|
|
|
|
modifiedData?: Buffer;
|
2024-05-25 12:30:21 +02:00
|
|
|
}
|
|
|
|
): Promise<void> => {
|
|
|
|
const { filePath, modifiedData } = params;
|
|
|
|
|
2024-05-27 18:33:16 +02:00
|
|
|
{
|
|
|
|
const dirPath = pathDirname(filePath);
|
|
|
|
|
|
|
|
if (!(await existsAsync(dirPath))) {
|
|
|
|
await fs.mkdir(dirPath, { recursive: true });
|
|
|
|
}
|
|
|
|
}
|
2024-05-25 12:30:21 +02:00
|
|
|
|
|
|
|
if (modifiedData !== undefined) {
|
|
|
|
await fs.writeFile(filePath, modifiedData);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const readStream = await new Promise<stream.Readable>(resolve =>
|
|
|
|
zipFile.openReadStream(entry, async (error, readStream) => {
|
|
|
|
if (error) {
|
|
|
|
dDone.reject(error);
|
|
|
|
return;
|
|
|
|
}
|
2024-05-24 17:26:38 +02:00
|
|
|
|
2024-05-25 12:30:21 +02:00
|
|
|
resolve(readStream);
|
|
|
|
})
|
|
|
|
);
|
2024-05-24 17:26:38 +02:00
|
|
|
|
2024-05-25 12:30:21 +02:00
|
|
|
const dDoneWithFile = new Deferred<void>();
|
|
|
|
|
|
|
|
stream.pipeline(readStream, fsSync.createWriteStream(filePath), error => {
|
|
|
|
if (error) {
|
|
|
|
dDone.reject(error);
|
2024-05-24 17:26:38 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-05-25 12:30:21 +02:00
|
|
|
dDoneWithFile.resolve();
|
|
|
|
});
|
2024-05-24 17:26:38 +02:00
|
|
|
|
2024-05-25 12:30:21 +02:00
|
|
|
await dDoneWithFile.pr;
|
|
|
|
};
|
2024-05-24 17:26:38 +02:00
|
|
|
|
2024-05-25 12:30:21 +02:00
|
|
|
const readFile = (entry: yauzl.Entry) =>
|
|
|
|
new Promise<Buffer>(resolve =>
|
|
|
|
zipFile.openReadStream(entry, async (error, readStream) => {
|
2024-05-25 11:02:22 +02:00
|
|
|
if (error) {
|
2024-05-24 17:26:38 +02:00
|
|
|
dDone.reject(error);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2024-05-25 12:30:21 +02:00
|
|
|
const chunks: Buffer[] = [];
|
2024-05-24 17:26:38 +02:00
|
|
|
|
2024-05-25 12:30:21 +02:00
|
|
|
readStream.on("data", chunk => {
|
|
|
|
chunks.push(chunk);
|
|
|
|
});
|
2024-05-24 17:26:38 +02:00
|
|
|
|
2024-05-25 12:30:21 +02:00
|
|
|
readStream.on("end", () => {
|
|
|
|
resolve(Buffer.concat(chunks));
|
|
|
|
});
|
|
|
|
|
|
|
|
readStream.on("error", error => {
|
|
|
|
dDone.reject(error);
|
|
|
|
});
|
|
|
|
})
|
|
|
|
);
|
2024-05-24 17:26:38 +02:00
|
|
|
|
|
|
|
zipFile.on("entry", async (entry: yauzl.Entry) => {
|
|
|
|
handle_file: {
|
|
|
|
// NOTE: Skip directories
|
|
|
|
if (entry.fileName.endsWith(pathSep)) {
|
|
|
|
break handle_file;
|
|
|
|
}
|
|
|
|
|
2024-05-26 19:50:25 +02:00
|
|
|
let hasEarlyExitBeenCalled = false;
|
|
|
|
|
2024-05-24 17:26:38 +02:00
|
|
|
await onArchiveFile({
|
|
|
|
relativeFilePathInArchive: entry.fileName.split("/").join(pathSep),
|
2024-05-25 12:30:21 +02:00
|
|
|
readFile: () => readFile(entry),
|
2024-05-26 19:29:12 +02:00
|
|
|
writeFile: params => writeFile(entry, params),
|
|
|
|
earlyExit: () => {
|
2024-05-26 19:50:25 +02:00
|
|
|
hasEarlyExitBeenCalled = true;
|
2024-05-26 19:29:12 +02:00
|
|
|
}
|
2024-05-24 17:26:38 +02:00
|
|
|
});
|
2024-05-26 19:50:25 +02:00
|
|
|
|
|
|
|
if (hasEarlyExitBeenCalled) {
|
|
|
|
zipFile.close();
|
|
|
|
dDone.resolve();
|
|
|
|
return;
|
|
|
|
}
|
2024-05-24 17:26:38 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
zipFile.readEntry();
|
|
|
|
});
|
|
|
|
|
|
|
|
zipFile.readEntry();
|
|
|
|
|
|
|
|
await dDone.pr;
|
|
|
|
}
|