Skip to content

Commit de527a2

Browse files
committed
pr feedback
1 parent 80b70d0 commit de527a2

1 file changed

Lines changed: 83 additions & 73 deletions

File tree

apps/zipsync/src/unpack.ts

Lines changed: 83 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import * as zlib from 'node:zlib';
88
import { type IReadonlyPathTrieNode, LookupByPath } from '@rushstack/lookup-by-path/lib/LookupByPath';
99
import type { ITerminal } from '@rushstack/terminal';
1010

11-
import { getDisposableFileHandle, unlinkSync, type IDisposableFileHandle } from './fs';
11+
import { getDisposableFileHandle, rmdirSync, unlinkSync, type IDisposableFileHandle } from './fs';
1212
import { type IIncrementalZlib, createIncrementalZlib } from './compress';
1313
import { markStart, markEnd, getDuration, emitSummary, formatDuration } from './perf';
1414
import {
@@ -55,6 +55,80 @@ export interface IZipSyncUnpackResult {
5555
otherEntriesDeleted: number;
5656
}
5757

58+
const bufferSize: number = 1 << 25; // 32 MiB
59+
const outputBuffer: Buffer<ArrayBuffer> = Buffer.allocUnsafeSlow(bufferSize);
60+
function extractFileFromZip(
61+
terminal: ITerminal,
62+
targetPath: string,
63+
zipBuffer: Buffer,
64+
entry: ICentralDirectoryHeaderParseResult
65+
): void {
66+
terminal.writeDebugLine(`Extracting file: ${entry.filename}`);
67+
const fileZipBuffer: Buffer = getFileFromZip(zipBuffer, entry);
68+
let fileData: Buffer;
69+
using fileHandle: IDisposableFileHandle = getDisposableFileHandle(targetPath, 'w');
70+
if (entry.header.compressionMethod === STORE_COMPRESSION) {
71+
fileData = fileZipBuffer;
72+
let writeOffset: number = 0;
73+
while (writeOffset < fileData.length && !isNaN(fileHandle.fd)) {
74+
const written: number = fs.writeSync(
75+
fileHandle.fd,
76+
fileData,
77+
writeOffset,
78+
fileData.length - writeOffset
79+
);
80+
writeOffset += written;
81+
}
82+
} else if (entry.header.compressionMethod === DEFLATE_COMPRESSION) {
83+
using inflateIncremental: IIncrementalZlib = createIncrementalZlib(
84+
outputBuffer,
85+
(chunk, lengthBytes) => {
86+
let writeOffset: number = 0;
87+
while (lengthBytes > 0 && writeOffset < chunk.byteLength) {
88+
const written: number = fs.writeSync(fileHandle.fd, chunk, writeOffset, lengthBytes);
89+
lengthBytes -= written;
90+
writeOffset += written;
91+
}
92+
},
93+
'inflate'
94+
);
95+
inflateIncremental.update(fileZipBuffer);
96+
inflateIncremental.update(Buffer.alloc(0));
97+
} else {
98+
throw new Error(
99+
`Unsupported compression method: ${entry.header.compressionMethod} for ${entry.filename}`
100+
);
101+
}
102+
}
103+
104+
function shouldExtract(
105+
terminal: ITerminal,
106+
targetPath: string,
107+
entry: ICentralDirectoryHeaderParseResult,
108+
metadata: IMetadata | undefined
109+
): boolean {
110+
if (metadata) {
111+
const metadataFile: { size: number; sha1Hash: string } | undefined = metadata.files[entry.filename];
112+
113+
if (metadataFile) {
114+
try {
115+
using existingFile: IDisposableFileHandle = getDisposableFileHandle(targetPath, 'r');
116+
const existingHash: string | false = computeFileHash(existingFile.fd);
117+
if (existingHash === metadataFile.sha1Hash) {
118+
return false;
119+
}
120+
} catch (e) {
121+
if ((e as NodeJS.ErrnoException).code === 'ENOENT') {
122+
terminal.writeDebugLine(`File does not exist, will extract: ${entry.filename}`);
123+
} else {
124+
throw e;
125+
}
126+
}
127+
}
128+
}
129+
return true;
130+
}
131+
58132
export function unpack({
59133
archivePath,
60134
targetDirectories: rawTargetDirectories,
@@ -210,22 +284,15 @@ export function unpack({
210284

211285
for (const dir of dirsToCleanup) {
212286
// Try to remove the directory. If it is not empty, this will throw and we can ignore the error.
213-
try {
214-
fs.rmdirSync(dir);
215-
terminal.writeDebugLine(`Deleted empty directory: ${dir}`);
216-
deletedFoldersCount++;
217-
} catch (e) {
218-
// Probably not empty
219-
terminal.writeDebugLine(`Directory not empty, skipping: ${dir}`);
220-
}
287+
rmdirSync(dir);
288+
terminal.writeDebugLine(`Deleted empty directory: ${dir}`);
289+
deletedFoldersCount++;
221290
}
222291

223292
terminal.writeDebugLine(`Existing entries tracked: ${scanCount}`);
224293
markEnd('unpack.scan.existing');
225294

226295
markStart('unpack.extract.loop');
227-
const bufferSize: number = 1 << 25; // 32 MiB
228-
const outputBuffer: Buffer<ArrayBuffer> = Buffer.allocUnsafeSlow(bufferSize);
229296

230297
const dirsCreated: Set<string> = new Set<string>();
231298

@@ -241,69 +308,12 @@ export function unpack({
241308
dirsCreated.add(targetDir);
242309
}
243310

244-
let shouldExtract: boolean = true;
245-
if (metadata) {
246-
const metadataFile: { size: number; sha1Hash: string } | undefined = metadata.files[entry.filename];
247-
248-
if (metadataFile) {
249-
try {
250-
using existingFile: IDisposableFileHandle = getDisposableFileHandle(targetPath, 'r');
251-
const existingHash: string | false = computeFileHash(existingFile.fd);
252-
if (existingHash === metadataFile.sha1Hash) {
253-
shouldExtract = false;
254-
skippedCount++;
255-
terminal.writeDebugLine(`Skip unchanged file: ${entry.filename}`);
256-
}
257-
} catch (e) {
258-
if ((e as NodeJS.ErrnoException).code === 'ENOENT') {
259-
// File does not exist, will extract
260-
} else {
261-
throw e;
262-
}
263-
}
264-
}
265-
}
266-
267-
if (shouldExtract) {
268-
terminal.writeDebugLine(`Extracting file: ${entry.filename}`);
269-
const fileZipBuffer: Buffer = getFileFromZip(zipBuffer, entry);
270-
let fileData: Buffer;
271-
using fileHandle: IDisposableFileHandle = getDisposableFileHandle(targetPath, 'w');
272-
if (entry.header.compressionMethod === STORE_COMPRESSION) {
273-
fileData = fileZipBuffer;
274-
let writeOffset: number = 0;
275-
while (writeOffset < fileData.length && !isNaN(fileHandle.fd)) {
276-
const written: number = fs.writeSync(
277-
fileHandle.fd,
278-
fileData,
279-
writeOffset,
280-
fileData.length - writeOffset
281-
);
282-
writeOffset += written;
283-
}
284-
} else if (entry.header.compressionMethod === DEFLATE_COMPRESSION) {
285-
using inflateIncremental: IIncrementalZlib = createIncrementalZlib(
286-
outputBuffer,
287-
(chunk, lengthBytes) => {
288-
let writeOffset: number = 0;
289-
while (lengthBytes > 0 && writeOffset < chunk.byteLength) {
290-
const written: number = fs.writeSync(fileHandle.fd, chunk, writeOffset, lengthBytes);
291-
lengthBytes -= written;
292-
writeOffset += written;
293-
}
294-
},
295-
'inflate'
296-
);
297-
inflateIncremental.update(fileZipBuffer);
298-
inflateIncremental.update(Buffer.alloc(0));
299-
} else {
300-
throw new Error(
301-
`Unsupported compression method: ${entry.header.compressionMethod} for ${entry.filename}`
302-
);
303-
}
304-
305-
// If data descriptor was used we rely on central directory values already consumed.
311+
if (shouldExtract(terminal, targetPath, entry, metadata)) {
312+
extractFileFromZip(terminal, targetPath, zipBuffer, entry);
306313
extractedCount++;
314+
} else {
315+
skippedCount++;
316+
terminal.writeDebugLine(`Skip unchanged file: ${entry.filename}`);
307317
}
308318
}
309319
markEnd('unpack.extract.loop');

0 commit comments

Comments
 (0)