|
1 | 1 | import { EventSpewer } from 'detritus-utils';
|
2 | 2 |
|
3 |
| -import { InflateError } from './errors'; |
| 3 | +import { CompressTypes, ZLIB_SUFFIX } from './constants'; |
| 4 | +import { ZlibDecompressor, ZstdDecompressor } from './decompressors'; |
4 | 5 |
|
5 |
| -const DependencyTypes = Object.freeze({ |
6 |
| - PAKO: 'pako', |
7 |
| - ZLIB: 'zlib', |
8 |
| -}); |
9 | 6 |
|
10 |
| -const ErrorCodes = Object.freeze({ |
11 |
| - ERR_ZLIB_BINDING_CLOSED: 'ERR_ZLIB_BINDING_CLOSED', |
12 |
| -}); |
13 |
| - |
14 |
| -const Inflate = { |
15 |
| - flushCode: 0, |
16 |
| - module: require(DependencyTypes.ZLIB), |
17 |
| - type: DependencyTypes.ZLIB, |
18 |
| -}; |
19 |
| - |
20 |
| -Inflate.flushCode = Inflate.module.constants.Z_SYNC_FLUSH; |
21 |
| - |
22 |
| -try { |
23 |
| - Inflate.module = require(DependencyTypes.PAKO); |
24 |
| - Inflate.type = DependencyTypes.PAKO; |
25 |
| -} catch(e) {} |
| 7 | +export interface DecompresserOptions { |
| 8 | + type: string, |
| 9 | +} |
26 | 10 |
|
27 | 11 | export class Decompressor extends EventSpewer {
|
28 |
| - dataChunks: Array<Buffer>; |
29 |
| - chunks: Array<Buffer>; |
30 |
| - chunkSize: number; |
31 |
| - closed: boolean; |
32 |
| - flushing: boolean; |
33 |
| - inflate: any; |
34 |
| - suffix: Buffer; |
| 12 | + closed: boolean = false; |
| 13 | + decompressor!: ZlibDecompressor | ZstdDecompressor; |
| 14 | + type: string; |
35 | 15 |
|
36 |
| - constructor( |
37 |
| - suffix: Buffer, |
38 |
| - chunkSize: number = 64 * 1024, |
39 |
| - ) { |
| 16 | + constructor(options: DecompresserOptions) { |
40 | 17 | super();
|
41 | 18 |
|
42 |
| - this.dataChunks = []; |
43 |
| - this.chunks = []; |
44 |
| - this.chunkSize = chunkSize; |
45 |
| - this.closed = false; |
46 |
| - this.flushing = false; |
47 |
| - this.inflate = null; |
48 |
| - this.suffix = suffix; |
49 |
| - this.initialize(); |
50 |
| - } |
51 |
| - |
52 |
| - feed(chunk: Buffer): void { |
53 |
| - if (!this.closed && this.inflate) { |
54 |
| - this.chunks.push(chunk); |
55 |
| - this.write(); |
56 |
| - } |
57 |
| - } |
58 |
| - |
59 |
| - close(): void { |
60 |
| - this.closed = true; |
61 |
| - this.chunks.length = 0; |
62 |
| - this.dataChunks.length = 0; |
63 |
| - this.flushing = false; |
64 |
| - switch (Inflate.type) { |
65 |
| - case DependencyTypes.ZLIB: { |
66 |
| - this.inflate.close(); |
67 |
| - this.inflate.removeAllListeners('data'); |
| 19 | + this.type = options.type; |
| 20 | + switch (this.type) { |
| 21 | + case CompressTypes.ZLIB: { |
| 22 | + this.decompressor = new ZlibDecompressor(Buffer.from(ZLIB_SUFFIX)); |
| 23 | + this.decompressor.on('data', (data) => this.emit('data', data)); |
| 24 | + this.decompressor.on('error', (error) => this.emit('error', error)); |
68 | 25 | }; break;
|
69 |
| - } |
70 |
| - this.inflate = null; |
71 |
| - } |
72 |
| - |
73 |
| - initialize(): void { |
74 |
| - switch (Inflate.type) { |
75 |
| - case DependencyTypes.PAKO: { |
76 |
| - this.inflate = new Inflate.module.Inflate({ |
77 |
| - chunkSize: this.chunkSize, |
78 |
| - }); |
79 |
| - }; break; |
80 |
| - case DependencyTypes.ZLIB: { |
81 |
| - this.inflate = Inflate.module.createInflate({ |
82 |
| - chunkSize: this.chunkSize, |
83 |
| - flush: Inflate.flushCode, |
84 |
| - }); |
85 |
| - this.inflate.on('data', this.onData.bind(this)); |
86 |
| - this.inflate.on('error', this.onError.bind(this)); |
| 26 | + case CompressTypes.ZSTD: { |
| 27 | + this.decompressor = new ZstdDecompressor(); |
| 28 | + this.decompressor.on('data', (data) => this.emit('data', data)); |
| 29 | + this.decompressor.on('error', (error) => this.emit('error', error)); |
87 | 30 | }; break;
|
88 | 31 | default: {
|
89 |
| - throw new Error(`Unable to use any ${JSON.stringify(Object.values(DependencyTypes))}`); |
| 32 | + throw new Error(`Invalid Compress Type: ${this.type}`); |
90 | 33 | };
|
91 | 34 | }
|
92 |
| - |
93 |
| - this.dataChunks.length = 0; |
94 |
| - this.chunks.length = 0; |
95 |
| - this.flushing = false; |
96 |
| - this.closed = false; |
97 | 35 | }
|
98 | 36 |
|
99 |
| - reset(): void { |
100 |
| - this.close(); |
101 |
| - this.initialize(); |
102 |
| - } |
103 |
| - |
104 |
| - write(): void { |
105 |
| - if ( |
106 |
| - (this.closed) || |
107 |
| - (!this.inflate) || |
108 |
| - (!this.chunks.length) || |
109 |
| - (this.flushing) |
110 |
| - ) { |
111 |
| - return; |
| 37 | + close(): void { |
| 38 | + if (!this.closed) { |
| 39 | + this.closed = true; |
| 40 | + this.decompressor.close(); |
| 41 | + this.decompressor.removeAllListeners(); |
| 42 | + this.removeAllListeners(); |
112 | 43 | }
|
| 44 | + } |
113 | 45 |
|
114 |
| - const chunk = <Buffer> this.chunks.shift(); |
115 |
| - const isEnd = ( |
116 |
| - (this.suffix.length <= chunk.length) && |
117 |
| - (chunk.slice(-this.suffix.length).equals(this.suffix)) |
118 |
| - ); |
119 |
| - |
120 |
| - switch (Inflate.type) { |
121 |
| - case DependencyTypes.PAKO: { |
122 |
| - this.inflate.push(chunk, isEnd && Inflate.flushCode); |
123 |
| - if (isEnd) { |
124 |
| - if (this.inflate.err) { |
125 |
| - const error = new InflateError(this.inflate.msg, this.inflate.err); |
126 |
| - this.onError(error); |
127 |
| - } else { |
128 |
| - this.onData(this.inflate.result); |
129 |
| - } |
130 |
| - } |
131 |
| - }; break; |
132 |
| - case DependencyTypes.ZLIB: { |
133 |
| - this.inflate.write(chunk); |
134 |
| - if (isEnd) { |
135 |
| - this.flushing = true; |
136 |
| - this.inflate.flush(Inflate.flushCode, this.onFlush.bind(this)); |
137 |
| - return; |
138 |
| - } |
139 |
| - }; break; |
140 |
| - } |
141 |
| - this.write(); |
| 46 | + feed(data: Buffer): void { |
| 47 | + this.decompressor.feed(data); |
142 | 48 | }
|
143 | 49 |
|
144 |
| - onData( |
145 |
| - data: any, |
146 |
| - ): void { |
147 |
| - switch (Inflate.type) { |
148 |
| - case DependencyTypes.PAKO: { |
149 |
| - this.emit('data', Buffer.from(data)); |
150 |
| - }; break |
151 |
| - case DependencyTypes.ZLIB: { |
152 |
| - this.dataChunks.push(<Buffer> data); |
153 |
| - }; break; |
154 |
| - } |
| 50 | + reset(): void { |
| 51 | + this.decompressor.reset(); |
155 | 52 | }
|
156 | 53 |
|
157 |
| - onError( |
158 |
| - error: any, |
159 |
| - ): void { |
160 |
| - if (error.code === ErrorCodes.ERR_ZLIB_BINDING_CLOSED) { |
161 |
| - // zlib was flushing when we called .close on it |
162 |
| - return; |
163 |
| - } |
164 |
| - this.emit('error', error); |
| 54 | + on(event: string | symbol, listener: (...args: any[]) => void): this; |
| 55 | + on(event: 'data', listener: (data: Buffer) => any): this; |
| 56 | + on(event: 'error', listener: (error: Error) => any): this; |
| 57 | + on(event: string | symbol, listener: (...args: any[]) => void): this { |
| 58 | + super.on(event, listener); |
| 59 | + return this; |
165 | 60 | }
|
166 | 61 |
|
167 |
| - onFlush( |
168 |
| - error: any, |
169 |
| - ): void { |
170 |
| - if (error) { |
171 |
| - return; |
172 |
| - } |
173 |
| - if (this.dataChunks.length) { |
174 |
| - const chunk = (this.dataChunks.length === 1) ? this.dataChunks.shift() : Buffer.concat(this.dataChunks); |
175 |
| - this.dataChunks.length = 0; |
176 |
| - this.emit('data', chunk); |
| 62 | + static supported(): Array<string> { |
| 63 | + const supported: Array<string> = [CompressTypes.ZLIB]; |
| 64 | + if (ZstdDecompressor.isSupported()) { |
| 65 | + supported.unshift(CompressTypes.ZSTD); |
177 | 66 | }
|
178 |
| - this.flushing = false; |
179 |
| - this.write(); |
| 67 | + return supported; |
180 | 68 | }
|
181 | 69 | }
|
0 commit comments