Skip to content

Commit 8008904

Browse files
committed
Add concurrent unzip implementation
1 parent f736adc commit 8008904

4 files changed

Lines changed: 77 additions & 6 deletions

File tree

extensions/ql-vscode/src/codeql-cli/distribution.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import {
2525
showAndLogErrorMessage,
2626
showAndLogWarningMessage,
2727
} from "../common/logging";
28-
import { unzipToDirectory } from "../common/unzip";
28+
import { unzipToDirectoryConcurrently } from "../common/unzip-concurrently";
2929

3030
/**
3131
* distribution.ts
@@ -420,7 +420,10 @@ class ExtensionSpecificDistributionManager {
420420
void extLogger.log(
421421
`Extracting CodeQL CLI to ${this.getDistributionStoragePath()}`,
422422
);
423-
await unzipToDirectory(archivePath, this.getDistributionStoragePath());
423+
await unzipToDirectoryConcurrently(
424+
archivePath,
425+
this.getDistributionStoragePath(),
426+
);
424427
} finally {
425428
await remove(tmpDirectory);
426429
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { availableParallelism } from "os";
2+
import { dirname, join } from "path";
3+
import { createWriteStream, ensureDir } from "fs-extra";
4+
import {
5+
copyStream,
6+
openZip,
7+
openZipReadStream,
8+
readZipEntries,
9+
} from "./unzip";
10+
import PQueue from "p-queue";
11+
12+
export async function unzipToDirectoryConcurrently(
13+
archivePath: string,
14+
destinationPath: string,
15+
): Promise<void> {
16+
const zipFile = await openZip(archivePath, {
17+
autoClose: false,
18+
strictFileNames: true,
19+
lazyEntries: true,
20+
});
21+
22+
try {
23+
const entries = await readZipEntries(zipFile);
24+
25+
const queue = new PQueue({
26+
concurrency: availableParallelism(),
27+
});
28+
29+
await queue.addAll(
30+
entries.map((entry) => async () => {
31+
const path = join(destinationPath, entry.fileName);
32+
33+
if (/\/$/.test(entry.fileName)) {
34+
// Directory file names end with '/'
35+
36+
await ensureDir(path);
37+
} else {
38+
// Ensure the directory exists
39+
await ensureDir(dirname(path));
40+
41+
const readable = await openZipReadStream(zipFile, entry);
42+
43+
let mode: number | undefined = entry.externalFileAttributes >>> 16;
44+
if (mode <= 0) {
45+
mode = undefined;
46+
}
47+
48+
const writeStream = createWriteStream(path, {
49+
autoClose: true,
50+
mode,
51+
});
52+
53+
await copyStream(readable, writeStream);
54+
}
55+
}),
56+
);
57+
} finally {
58+
zipFile.close();
59+
}
60+
}

extensions/ql-vscode/src/common/unzip.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export function readZipEntries(zipFile: ZipFile): Promise<ZipEntry[]> {
5151
});
5252
}
5353

54-
function openZipReadStream(
54+
export function openZipReadStream(
5555
zipFile: ZipFile,
5656
entry: ZipEntry,
5757
): Promise<Readable> {
@@ -86,7 +86,7 @@ export async function openZipBuffer(
8686
});
8787
}
8888

89-
async function copyStream(
89+
export async function copyStream(
9090
readable: Readable,
9191
writeStream: WriteStream,
9292
): Promise<void> {
@@ -102,6 +102,14 @@ async function copyStream(
102102
});
103103
}
104104

105+
/**
106+
* Sequentially unzips all files from a zip archive. Please use
107+
* `unzipToDirectoryConcurrently` if you can. This function is only
108+
* provided because Jest cannot import `p-queue`.
109+
*
110+
* @param archivePath
111+
* @param destinationPath
112+
*/
105113
export async function unzipToDirectory(
106114
archivePath: string,
107115
destinationPath: string,

extensions/ql-vscode/src/variant-analysis/variant-analysis-results-manager.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
} from "./shared/variant-analysis";
1717
import { DisposableObject, DisposeHandler } from "../common/disposable-object";
1818
import { EventEmitter } from "vscode";
19-
import { unzipToDirectory } from "../common/unzip";
19+
import { unzipToDirectoryConcurrently } from "../common/unzip-concurrently";
2020
import { readRepoTask, writeRepoTask } from "./repo-tasks-store";
2121

2222
type CacheKey = `${number}/${string}`;
@@ -106,7 +106,7 @@ export class VariantAnalysisResultsManager extends DisposableObject {
106106
VariantAnalysisResultsManager.RESULTS_DIRECTORY,
107107
);
108108

109-
await unzipToDirectory(zipFilePath, unzippedFilesDirectory);
109+
await unzipToDirectoryConcurrently(zipFilePath, unzippedFilesDirectory);
110110

111111
this._onResultDownloaded.fire({
112112
variantAnalysisId,

0 commit comments

Comments
 (0)