diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b89f19..04f477b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,16 @@ All notable changes to this project will be documented in this file. ### Added - archive: Support DEFLATE in Zipper where JS implementations support it in CompressionStream. + [Issue #40](https://github.com/codedread/bitjs/issues/40) +- archive: Support DEFLATE in Unzipper where JS implementations support it in DecompressionStream. + [Issue #38](https://github.com/codedread/bitjs/issues/38) - file: Added detection of GZIP files. - io: Added a skip() method to BitStream to match ByteStream. +### Fixed + +- Fixed a benign JS error in the Web Worker wrapper + ## [1.2.1] - 2024-01-19 ### Added diff --git a/README.md b/README.md index 8a46947..f248639 100644 --- a/README.md +++ b/README.md @@ -8,15 +8,13 @@ A set of dependency-free JavaScript modules to handle binary data in JS (using [Typed Arrays](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray)). Includes: - * bitjs/archive: Unarchiving files (unzip, unrar, untar) in JavaScript, - implemented as Web Workers where supported, and allowing progressive - unarchiving while streaming. - * bitjs/codecs: Get the codec info of media containers in a ISO RFC6381 - MIME type string + * bitjs/archive: Decompressing files (unzip, unrar, untar) in JavaScript, implemented as Web + Workers where supported, and allowing progressive unarchiving while streaming. + * bitjs/codecs: Get the codec info of media containers in a ISO RFC6381 MIME type string. * bitjs/file: Detect the type of file from its binary signature. * bitjs/image: Parsing GIF, JPEG, PNG. Conversion of WebP to PNG or JPEG. - * bitjs/io: Low-level classes for interpreting binary data (BitStream - ByteStream). For example, reading or peeking at N bits at a time. + * bitjs/io: Low-level classes for interpreting binary data (BitStream, ByteStream). For example, + reading or peeking at N bits at a time. ## Installation diff --git a/archive/decompress.js b/archive/decompress.js index c777e88..5613657 100644 --- a/archive/decompress.js +++ b/archive/decompress.js @@ -316,3 +316,22 @@ export function getUnarchiver(ab, options = {}) { } return unarchiver; } + +// import * as fs from 'node:fs'; +// async function main() { +// const nodeBuf = fs.readFileSync(`./action-1.cbz`); +// const ab = nodeBuf.buffer.slice(nodeBuf.byteOffset, nodeBuf.byteOffset + nodeBuf.length); +// const then = Date.now(); +// const zipper = new Unzipper(ab, {debug: true}); +// zipper.addEventListener('extract', evt => { +// const f = evt.unarchivedFile; +// fs.writeFileSync(f.filename, Buffer.from(f.fileData)); +// }); +// zipper.addEventListener('finish', evt => { +// console.dir(evt); +// console.log(`Took ${(Date.now() - then)}ms`); +// }); +// await zipper.start(); +// } + +// main(); diff --git a/archive/unzip.js b/archive/unzip.js index 346077b..f3e710d 100644 --- a/archive/unzip.js +++ b/archive/unzip.js @@ -178,7 +178,7 @@ class ZipLocalFile { } // determine what kind of compressed data we have and decompress - unzip() { + async unzip() { if (!this.fileData) { err('unzip() called on a file with out compressed file data'); } @@ -196,7 +196,7 @@ class ZipLocalFile { if (logToConsole) { info(`ZIP v2.0, DEFLATE: ${this.filename} (${this.compressedSize} bytes)`); } - this.fileData = inflate(this.fileData, this.uncompressedSize); + this.fileData = await inflate(this.fileData, this.uncompressedSize); } else { err(`UNSUPPORTED VERSION/FORMAT: ZIP v${this.version}, ` + @@ -483,9 +483,18 @@ function inflateBlockData(bstream, hcLiteralTable, hcDistanceTable, buffer) { * Compression method 8. Deflate: http://tools.ietf.org/html/rfc1951 * @param {Uint8Array} compressedData A Uint8Array of the compressed file data. * @param {number} numDecompressedBytes - * @returns {Uint8Array} The decompressed array. + * @returns {Promise} The decompressed array. */ -function inflate(compressedData, numDecompressedBytes) { +async function inflate(compressedData, numDecompressedBytes) { + // Try to use native implementation of DEFLATE if it exists. + try { + const blob = new Blob([compressedData.buffer]); + const decompressedStream = blob.stream().pipeThrough(new DecompressionStream('deflate-raw')); + return new Uint8Array(await new Response(decompressedStream).arrayBuffer()); + } catch (err) { + // Fall through to non-native implementation of DEFLATE. + } + // Bit stream representing the compressed data. /** @type {BitStream} */ const bstream = new BitStream(compressedData.buffer, @@ -596,7 +605,7 @@ function inflate(compressedData, numDecompressedBytes) { return buffer.data; } -function archiveUnzip() { +async function archiveUnzip() { let bstream = bytestream.tee(); // loop until we don't see any more local files or we find a data descriptor. @@ -619,7 +628,7 @@ function archiveUnzip() { currentBytesUnarchivedInFile = 0; // Actually do the unzipping. - oneLocalFile.unzip(); + await oneLocalFile.unzip(); if (oneLocalFile.fileData != null) { hostPort.postMessage({ type: 'extract', unarchivedFile: oneLocalFile }, [oneLocalFile.fileData.buffer]); @@ -724,7 +733,7 @@ function archiveUnzip() { // event.data.file has the first ArrayBuffer. // event.data.bytes has all subsequent ArrayBuffers. -const onmessage = function (event) { +const onmessage = async function (event) { const bytes = event.data.file || event.data.bytes; logToConsole = !!event.data.logToConsole; @@ -755,7 +764,7 @@ const onmessage = function (event) { if (unarchiveState === UnarchiveState.UNARCHIVING || unarchiveState === UnarchiveState.WAITING) { try { - archiveUnzip(); + await archiveUnzip(); } catch (e) { if (typeof e === 'string' && e.startsWith('Error! Overflowed')) { // Overrun the buffer.