Skip to content

Commit 7532d82

Browse files
committed
add default props
1 parent 69b7131 commit 7532d82

File tree

2 files changed

+88
-19
lines changed

2 files changed

+88
-19
lines changed

packages/core/src/shared/utilities/zipStream.ts

Lines changed: 83 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,41 @@ import crypto from 'crypto'
88
import { readFileAsString } from '../filesystemUtilities'
99
// Use require instead of import since this package doesn't support commonjs
1010
const { ZipWriter, TextReader } = require('@zip.js/zip.js')
11+
import { getLogger } from '../logger/logger'
1112

1213
export interface ZipStreamResult {
1314
sizeInBytes: number
14-
md5: string
15+
hash: string
1516
streamBuffer: WritableStreamBuffer
1617
}
1718

19+
export type ZipStreamProps = {
20+
hashAlgorithm: 'md5' | 'sha256'
21+
maxNumberOfFileStreams: number
22+
compressionLevel: number
23+
}
24+
25+
const defaultProps: ZipStreamProps = {
26+
hashAlgorithm: 'sha256',
27+
maxNumberOfFileStreams: 100,
28+
compressionLevel: 1,
29+
}
30+
1831
/**
1932
* Creates in-memory zip archives that output to a stream buffer.
2033
*
2134
* Example usage:
2235
* ```ts
23-
* const zipStream = new ZipStream()
36+
* const zipStream = new ZipStream({
37+
hashAlgorithm: 'sha256',
38+
maxNumberOfFileStreams: 150,
39+
compressionLevel: 1,
40+
memLevel: 9,
41+
})
2442
* zipStream.writeString('Hello World', 'file1.txt')
2543
* zipStream.writeFile('/path/to/some/file.txt', 'file2.txt')
26-
* const result = await zipStream.finalize()
27-
* console.log(result) // { sizeInBytes: ..., md5: ..., streamBuffer: ... }
44+
* const result = await zipStream.finalize([optional onProgress handler, called 1x per sec])
45+
* console.log(result) // { sizeInBytes: ..., hash: ..., streamBuffer: ... }
2846
* ```
2947
*/
3048
export class ZipStream {
@@ -33,35 +51,86 @@ export class ZipStream {
3351
private _zipWriter: ZipWriter<WritableStream>
3452
private _streamBuffer: WritableStreamBuffer
3553
private _hasher: crypto.Hash
54+
private _numberOfFilesToStream: number = 0
55+
private _numberOfFilesSucceeded: number = 0
56+
private _filesToZip: [string, string][] = []
57+
private _filesBeingZipped: number = 0
58+
private _maxNumberOfFileStreams: number
3659

37-
constructor() {
38-
this._streamBuffer = new WritableStreamBuffer()
39-
this._hasher = crypto.createHash('md5')
60+
constructor(props: Partial<ZipStreamProps> = {}) {
61+
// Allow any user-provided values to override default values
62+
const mergedProps = { ...defaultProps, ...props }
63+
const { hashAlgorithm, compressionLevel, maxNumberOfFileStreams } = mergedProps
4064

4165
this._zipWriter = new ZipWriter(
4266
new WritableStream({
4367
write: chunk => {
4468
this._streamBuffer.write(chunk)
4569
this._hasher.update(chunk)
70+
this._numberOfFilesSucceeded++
71+
this._filesBeingZipped--
72+
73+
if (this._filesToZip.length > 0 && this._filesBeingZipped < maxNumberOfFileStreams) {
74+
this._filesBeingZipped++
75+
const [fileToZip, path] = this._filesToZip.shift()!
76+
void readFileAsString(fileToZip).then(content => {
77+
return this._zipWriter.add(path, new TextReader(content))
78+
})
79+
}
4680
},
47-
})
81+
}),
82+
{ level: compressionLevel }
4883
)
84+
this._maxNumberOfFileStreams = maxNumberOfFileStreams
85+
86+
this._streamBuffer = new WritableStreamBuffer()
87+
88+
this._hasher = crypto.createHash(hashAlgorithm)
4989
}
5090

51-
public async writeString(data: string, path: string) {
91+
public writeString(data: string, path: string) {
5292
return this._zipWriter.add(path, new TextReader(data))
5393
}
5494

55-
public async writeFile(file: string, path: string) {
56-
const content = await readFileAsString(file)
57-
return this._zipWriter.add(path, new TextReader(content))
95+
public writeFile(file: string, path: string) {
96+
// We use _numberOfFilesToStream to make sure we don't finalize too soon
97+
// (before the progress event has been fired for the last file)
98+
// The problem is that we can't rely on progress.entries.total,
99+
// because files can be added to the queue faster
100+
// than the progress event is fired
101+
this._numberOfFilesToStream++
102+
// We only start zipping another file if we're under our limit
103+
// of concurrent file streams
104+
if (this._filesBeingZipped < this._maxNumberOfFileStreams) {
105+
this._filesBeingZipped++
106+
void readFileAsString(file).then(content => {
107+
return this._zipWriter.add(path, new TextReader(content))
108+
})
109+
} else {
110+
// Queue it for later (see "write" event)
111+
this._filesToZip.push([file, path])
112+
}
58113
}
59114

60-
public async finalize(): Promise<ZipStreamResult> {
115+
public async finalize(onProgress?: (percentComplete: number) => void): Promise<ZipStreamResult> {
116+
let finished = false
117+
// We need to poll to check for all the file streams to be completely processed
118+
// -- we are keeping track of this via the "progress" event handler
119+
while (!finished) {
120+
finished = await new Promise(resolve => {
121+
setTimeout(() => {
122+
getLogger().verbose('success is', this._numberOfFilesSucceeded, '/', this._numberOfFilesToStream)
123+
onProgress?.(Math.floor((100 * this._numberOfFilesSucceeded) / this._numberOfFilesToStream))
124+
resolve(this._numberOfFilesToStream <= this._numberOfFilesSucceeded)
125+
}, 1000)
126+
})
127+
}
128+
// We're done streaming all files, so we can close the zip stream
129+
61130
await this._zipWriter.close()
62131
return {
63132
sizeInBytes: this._streamBuffer.size(),
64-
md5: this._hasher.digest('base64'),
133+
hash: this._hasher.digest('base64'),
65134
streamBuffer: this._streamBuffer,
66135
}
67136
}

packages/core/src/test/shared/utilities/zipStream.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ describe('zipStream', function () {
2323
})
2424

2525
it('Should create a zip stream from text content', async function () {
26-
const zipStream = new ZipStream()
26+
const zipStream = new ZipStream({ hashAlgorithm: 'md5' })
2727
await zipStream.writeString('foo bar', 'file.txt')
2828
const result = await zipStream.finalize()
2929

@@ -36,16 +36,16 @@ describe('zipStream', function () {
3636
.createHash('md5')
3737
.update(await fsCommon.readFile(zipPath))
3838
.digest('base64')
39-
assert.strictEqual(result.md5, expectedMd5)
39+
assert.strictEqual(result.hash, expectedMd5)
4040
assert.strictEqual(result.sizeInBytes, (await fsCommon.stat(zipPath)).size)
4141
})
4242

4343
it('Should create a zip stream from file', async function () {
4444
const testFilePath = path.join(tmpDir, 'test.txt')
4545
await fsCommon.writeFile(testFilePath, 'foo bar')
4646

47-
const zipStream = new ZipStream()
48-
await zipStream.writeFile(testFilePath, 'file.txt')
47+
const zipStream = new ZipStream({ hashAlgorithm: 'md5' })
48+
zipStream.writeFile(testFilePath, 'file.txt')
4949
const result = await zipStream.finalize()
5050

5151
const zipPath = path.join(tmpDir, 'test.zip')
@@ -58,7 +58,7 @@ describe('zipStream', function () {
5858
.createHash('md5')
5959
.update(await fsCommon.readFile(zipPath))
6060
.digest('base64')
61-
assert.strictEqual(result.md5, expectedMd5)
61+
assert.strictEqual(result.hash, expectedMd5)
6262
assert.strictEqual(result.sizeInBytes, (await fsCommon.stat(zipPath)).size)
6363
})
6464
})

0 commit comments

Comments
 (0)