Skip to content

Commit 4be3f1a

Browse files
committed
add default props
1 parent 69b7131 commit 4be3f1a

File tree

2 files changed

+92
-21
lines changed

2 files changed

+92
-21
lines changed

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

Lines changed: 87 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,45 @@
66
import { WritableStreamBuffer } from 'stream-buffers'
77
import crypto from 'crypto'
88
import { readFileAsString } from '../filesystemUtilities'
9-
// Use require instead of import since this package doesn't support commonjs
10-
const { ZipWriter, TextReader } = require('@zip.js/zip.js')
9+
// // Use require instead of import since this package doesn't support commonjs
10+
// const { ZipWriter, TextReader } = require('@zip.js/zip.js')
11+
// @ts-ignore
12+
import { ZipWriter, TextReader } from '@zip.js/zip.js'
13+
import { getLogger } from '../logger/logger'
1114

1215
export interface ZipStreamResult {
1316
sizeInBytes: number
14-
md5: string
17+
hash: string
1518
streamBuffer: WritableStreamBuffer
1619
}
1720

21+
export type ZipStreamProps = {
22+
hashAlgorithm: 'md5' | 'sha256'
23+
maxNumberOfFileStreams: number
24+
compressionLevel: number
25+
}
26+
27+
const defaultProps: ZipStreamProps = {
28+
hashAlgorithm: 'sha256',
29+
maxNumberOfFileStreams: 100,
30+
compressionLevel: 1,
31+
}
32+
1833
/**
1934
* Creates in-memory zip archives that output to a stream buffer.
2035
*
2136
* Example usage:
2237
* ```ts
23-
* const zipStream = new ZipStream()
38+
* const zipStream = new ZipStream({
39+
hashAlgorithm: 'sha256',
40+
maxNumberOfFileStreams: 150,
41+
compressionLevel: 1,
42+
memLevel: 9,
43+
})
2444
* zipStream.writeString('Hello World', 'file1.txt')
2545
* zipStream.writeFile('/path/to/some/file.txt', 'file2.txt')
26-
* const result = await zipStream.finalize()
27-
* console.log(result) // { sizeInBytes: ..., md5: ..., streamBuffer: ... }
46+
* const result = await zipStream.finalize([optional onProgress handler, called 1x per sec])
47+
* console.log(result) // { sizeInBytes: ..., hash: ..., streamBuffer: ... }
2848
* ```
2949
*/
3050
export class ZipStream {
@@ -33,35 +53,86 @@ export class ZipStream {
3353
private _zipWriter: ZipWriter<WritableStream>
3454
private _streamBuffer: WritableStreamBuffer
3555
private _hasher: crypto.Hash
56+
private _numberOfFilesToStream: number = 0
57+
private _numberOfFilesSucceeded: number = 0
58+
private _filesToZip: [string, string][] = []
59+
private _filesBeingZipped: number = 0
60+
private _maxNumberOfFileStreams: number
3661

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

4167
this._zipWriter = new ZipWriter(
4268
new WritableStream({
4369
write: chunk => {
4470
this._streamBuffer.write(chunk)
4571
this._hasher.update(chunk)
72+
this._numberOfFilesSucceeded++
73+
this._filesBeingZipped--
74+
75+
if (this._filesToZip.length > 0 && this._filesBeingZipped < maxNumberOfFileStreams) {
76+
this._filesBeingZipped++
77+
const [fileToZip, path] = this._filesToZip.shift()!
78+
void readFileAsString(fileToZip).then(content => {
79+
return this._zipWriter.add(path, new TextReader(content))
80+
})
81+
}
4682
},
47-
})
83+
}),
84+
{ level: compressionLevel }
4885
)
86+
this._maxNumberOfFileStreams = maxNumberOfFileStreams
87+
88+
this._streamBuffer = new WritableStreamBuffer()
89+
90+
this._hasher = crypto.createHash(hashAlgorithm)
4991
}
5092

51-
public async writeString(data: string, path: string) {
93+
public writeString(data: string, path: string) {
5294
return this._zipWriter.add(path, new TextReader(data))
5395
}
5496

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

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

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)