1313
1414import { ByteBuffer } from '../io/bytebuffer.js' ;
1515import { CENTRAL_FILE_HEADER_SIG , CRC32_MAGIC_NUMBER , END_OF_CENTRAL_DIR_SIG ,
16- LOCAL_FILE_HEADER_SIG } from './common.js' ;
16+ LOCAL_FILE_HEADER_SIG , ZipCompressionMethod } from './common.js' ;
1717
1818/** @typedef {import('./common.js').FileInfo } FileInfo */
1919
@@ -37,9 +37,10 @@ let hostPort;
3737 * @typedef CompressFilesMessage A message the client sends to the implementation.
3838 * @property {FileInfo[] } files A set of files to add to the zip file.
3939 * @property {boolean } isLastFile Indicates this is the last set of files to add to the zip file.
40+ * @property {ZipCompressionMethod= } compressionMethod The compression method to use. Ignored except
41+ * for the first message sent.
4042 */
4143
42- // TODO: Support DEFLATE.
4344// TODO: Support options that can let client choose levels of compression/performance.
4445
4546/**
@@ -54,6 +55,9 @@ let hostPort;
5455 * @property {number } byteOffset (4 bytes)
5556 */
5657
58+ /** @type {ZipCompressionMethod } */
59+ let compressionMethod = ZipCompressionMethod . STORE ;
60+
5761/** @type {FileInfo[] } */
5862let filesCompressed = [ ] ;
5963
@@ -138,30 +142,39 @@ function dateToDosTime(jsDate) {
138142
139143/**
140144 * @param {FileInfo } file
141- * @returns {ByteBuffer }
145+ * @returns {Promise< ByteBuffer> }
142146 */
143- function zipOneFile ( file ) {
147+ async function zipOneFile ( file ) {
148+ /** @type {Uint8Array } */
149+ let compressedBytes ;
150+ if ( compressionMethod === ZipCompressionMethod . STORE ) {
151+ compressedBytes = file . fileData ;
152+ } else if ( compressionMethod === ZipCompressionMethod . DEFLATE ) {
153+ const blob = new Blob ( [ file . fileData . buffer ] ) ;
154+ const compressedStream = blob . stream ( ) . pipeThrough ( new CompressionStream ( 'deflate-raw' ) ) ;
155+ compressedBytes = new Uint8Array ( await new Response ( compressedStream ) . arrayBuffer ( ) ) ;
156+ }
157+
144158 // Zip Local File Header has 30 bytes and then the filename and extrafields.
145159 const fileHeaderSize = 30 + file . fileName . length ;
146160
147161 /** @type {ByteBuffer } */
148- const buffer = new ByteBuffer ( fileHeaderSize + file . fileData . byteLength ) ;
162+ const buffer = new ByteBuffer ( fileHeaderSize + compressedBytes . byteLength ) ;
149163
150164 buffer . writeNumber ( LOCAL_FILE_HEADER_SIG , 4 ) ; // Magic number.
151165 buffer . writeNumber ( 0x0A , 2 ) ; // Version.
152166 buffer . writeNumber ( 0 , 2 ) ; // General Purpose Flags.
153- buffer . writeNumber ( 0 , 2 ) ; // Compression Method. 0 = Store only .
167+ buffer . writeNumber ( compressionMethod , 2 ) ; // Compression Method.
154168
155169 const jsDate = new Date ( file . lastModTime ) ;
156170
157171 /** @type {CentralDirectoryFileHeaderInfo } */
158172 const centralDirectoryInfo = {
159- compressionMethod : 0 ,
173+ compressionMethod,
160174 lastModFileTime : dateToDosTime ( jsDate ) ,
161175 lastModFileDate : dateToDosDate ( jsDate ) ,
162176 crc32 : calculateCRC32 ( 0 , file . fileData ) ,
163- // TODO: For now, this is easy. Later when we do DEFLATE, we will have to calculate.
164- compressedSize : file . fileData . byteLength ,
177+ compressedSize : compressedBytes . byteLength ,
165178 uncompressedSize : file . fileData . byteLength ,
166179 fileName : file . fileName ,
167180 byteOffset : numBytesWritten ,
@@ -176,7 +189,7 @@ function zipOneFile(file) {
176189 buffer . writeNumber ( centralDirectoryInfo . fileName . length , 2 ) ; // Filename length.
177190 buffer . writeNumber ( 0 , 2 ) ; // Extra field length.
178191 buffer . writeASCIIString ( centralDirectoryInfo . fileName ) ; // Filename. Assumes ASCII.
179- buffer . insertBytes ( file . fileData ) ; // File data.
192+ buffer . insertBytes ( compressedBytes ) ;
180193
181194 return buffer ;
182195}
@@ -195,7 +208,7 @@ function writeCentralFileDirectory() {
195208 buffer . writeNumber ( 0 , 2 ) ; // Version made by. // 0x31e
196209 buffer . writeNumber ( 0 , 2 ) ; // Version needed to extract (minimum). // 0x14
197210 buffer . writeNumber ( 0 , 2 ) ; // General purpose bit flag
198- buffer . writeNumber ( 0 , 2 ) ; // Compression method.
211+ buffer . writeNumber ( compressionMethod , 2 ) ; // Compression method.
199212 buffer . writeNumber ( cdInfo . lastModFileTime , 2 ) ; // Last Mod File Time.
200213 buffer . writeNumber ( cdInfo . lastModFileDate , 2 ) ; // Last Mod Date.
201214 buffer . writeNumber ( cdInfo . crc32 , 4 ) ; // crc32.
@@ -228,7 +241,7 @@ function writeCentralFileDirectory() {
228241 * @param {{data: CompressFilesMessage} } evt The event for the implementation to process. It is an
229242 * error to send any more events after a previous event had isLastFile is set to true.
230243 */
231- const onmessage = function ( evt ) {
244+ const onmessage = async function ( evt ) {
232245 if ( state === CompressorState . FINISHED ) {
233246 throw `The zip implementation was sent a message after last file received.` ;
234247 }
@@ -239,11 +252,19 @@ const onmessage = function(evt) {
239252
240253 state = CompressorState . COMPRESSING ;
241254
255+ if ( filesCompressed . length === 0 && evt . data . compressionMethod !== undefined ) {
256+ if ( ! Object . values ( ZipCompressionMethod ) . includes ( evt . data . compressionMethod ) ) {
257+ throw `Do not support compression method ${ evt . data . compressionMethod } ` ;
258+ }
259+
260+ compressionMethod = evt . data . compressionMethod ;
261+ }
262+
242263 const msg = evt . data ;
243264 const filesToCompress = msg . files ;
244265 while ( filesToCompress . length > 0 ) {
245266 const fileInfo = filesToCompress . shift ( ) ;
246- const fileBuffer = zipOneFile ( fileInfo ) ;
267+ const fileBuffer = await zipOneFile ( fileInfo ) ;
247268 filesCompressed . push ( fileInfo ) ;
248269 numBytesWritten += fileBuffer . data . byteLength ;
249270 hostPort . postMessage ( { type : 'compress' , bytes : fileBuffer . data } , [ fileBuffer . data . buffer ] ) ;
0 commit comments