diff --git a/archive/archive.js b/archive/archive.js index cd88395..3a61807 100644 --- a/archive/archive.js +++ b/archive/archive.js @@ -2,6 +2,7 @@ * archive.js * * Provides base functionality for unarchiving. + * DEPRECATED: Use decompress.js instead. * * Licensed under the MIT License * @@ -12,7 +13,9 @@ import { UnarchiveAppendEvent, UnarchiveErrorEvent, UnarchiveEvent, UnarchiveEve UnarchiveExtractEvent, UnarchiveFinishEvent, UnarchiveInfoEvent, UnarchiveProgressEvent, UnarchiveStartEvent, Unarchiver, UnrarrerInternal, UntarrerInternal, UnzipperInternal, - getUnarchiverInternal } from './archive-internal.js'; + getUnarchiverInternal } from './decompress-internal.js'; + +console.warn(`Stop using archive.js and use decompress.js instead. This module will be removed.`); export { UnarchiveAppendEvent, diff --git a/archive/compress.js b/archive/compress.js index 1a72ee2..ab73397 100644 --- a/archive/compress.js +++ b/archive/compress.js @@ -1,6 +1,4 @@ -import { ByteBuffer } from '../io/bytebuffer.js'; - // NOTE: THIS IS A VERY HACKY WORK-IN-PROGRESS! THE API IS NOT FROZEN! USE AT YOUR OWN RISK! /** @@ -10,9 +8,35 @@ import { ByteBuffer } from '../io/bytebuffer.js'; * @property {ArrayBuffer} fileData The bytes of the file. */ +/** + * @readonly + * @enum {number} + */ +export const ZipCompressionMethod = { + STORE: 0, // Default. + // DEFLATE: 8, +}; + +// export const DeflateCompressionMethod = { +// NO_COMPRESSION: 0, +// COMPRESSION_FIXED_HUFFMAN: 1, +// COMPRESSION_DYNAMIC_HUFFMAN: 2, +// } + +/** + * Data elements are packed into bytes in order of increasing bit number within the byte, + i.e., starting with the least-significant bit of the byte. + * Data elements other than Huffman codes are packed starting with the least-significant bit of the + data element. + * Huffman codes are packed starting with the most-significant bit of the code. + */ + /** * @typedef CompressorOptions * @property {string} pathToBitJS A string indicating where the BitJS files are located. + * @property {ZipCompressionMethod} zipCompressionMethod + * @property {DeflateCompressionMethod=} deflateCompressionMethod Only present if + * zipCompressionMethod is set to DEFLATE. */ /** @@ -30,6 +54,7 @@ export const CompressStatus = { /** * A thing that zips files. * NOTE: THIS IS A VERY HACKY WORK-IN-PROGRESS! THE API IS NOT FROZEN! USE AT YOUR OWN RISK! + * TODO: Make a streaming / event-driven API. */ export class Zipper { /** @@ -43,6 +68,12 @@ export class Zipper { */ this.pathToBitJS = options.pathToBitJS || '/'; + /** + * @type {ZipCompressionMethod} + * @private + */ + this.zipCompressionMethod = options.zipCompressionMethod || ZipCompressionMethod.STORE; + /** * Private web worker initialized during start(). * @type {Worker} @@ -80,9 +111,13 @@ export class Zipper { } /** + * Send in a set of files to be compressed. Set isLastFile to true if no more files are to added + * at some future state. The Promise will not resolve until isLastFile is set to true either in + * this method or in appendFiles(). * @param {FileInfo[]} files * @param {boolean} isLastFile - * @returns {Promise} A Promise that contains the entire zipped archive. + * @returns {Promise} A Promise that will contain the entire zipped archive as an array + * of bytes. */ start(files, isLastFile) { return new Promise((resolve, reject) => { diff --git a/archive/archive-internal.js b/archive/decompress-internal.js similarity index 99% rename from archive/archive-internal.js rename to archive/decompress-internal.js index 063a9c3..37ab2ad 100644 --- a/archive/archive-internal.js +++ b/archive/decompress-internal.js @@ -207,11 +207,15 @@ export class UnarchiveExtractEvent extends UnarchiveEvent { */ this.pathToBitJS_ = options.pathToBitJS || '/'; - /** @orivate {boolean} */ + /** + * @orivate + * @type {boolean} + */ this.debugMode_ = !!(options.debug); /** * A map from event type to an array of listeners. + * @private * @type {Map.} */ this.listeners_ = {}; @@ -221,8 +225,8 @@ export class UnarchiveExtractEvent extends UnarchiveEvent { /** * Private web worker initialized during start(). - * @type {Worker} * @private + * @type {Worker} */ this.worker_ = null; } diff --git a/archive/decompress.js b/archive/decompress.js new file mode 100644 index 0000000..e5cc5e6 --- /dev/null +++ b/archive/decompress.js @@ -0,0 +1,84 @@ +/** + * decompress.js + * + * Provides base functionality for unarchiving/decompression. + * + * Licensed under the MIT License + * + * Copyright(c) 2021 Google Inc. + */ + + import { UnarchiveAppendEvent, UnarchiveErrorEvent, UnarchiveEvent, UnarchiveEventType, + UnarchiveExtractEvent, UnarchiveFinishEvent, UnarchiveInfoEvent, + UnarchiveProgressEvent, UnarchiveStartEvent, Unarchiver, + UnrarrerInternal, UntarrerInternal, UnzipperInternal, + getUnarchiverInternal } from './decompress-internal.js'; + +export { + UnarchiveAppendEvent, + UnarchiveErrorEvent, + UnarchiveEvent, + UnarchiveEventType, + UnarchiveExtractEvent, + UnarchiveFinishEvent, + UnarchiveInfoEvent, + UnarchiveProgressEvent, + UnarchiveStartEvent, + Unarchiver, +} + +/** +* All extracted files returned by an Unarchiver will implement +* the following interface: +*/ + +/** +* @typedef UnarchivedFile +* @property {string} filename +* @property {Uint8Array} fileData +*/ + +/** +* The goal is to make this testable - send getUnarchiver() an array buffer of +* an archive, call start on the unarchiver, expect the returned result. +* +* Problem: It relies on Web Workers, and that won't work in a nodejs context. +* Solution: Make archive.js very thin, have it feed web-specific things into +* an internal module that is isomorphic JavaScript. +* +* TODO: +* - write unit tests for archive-internal.js that use the nodejs Worker +* equivalent. +* - maybe use @pgriess/node-webworker or @audreyt/node-webworker-threads or +* just node's worker_threads ? +*/ + +const createWorkerFn = (scriptFilename) => new Worker(scriptFilename); + +// Thin wrappers of compressors for clients who want to construct a specific +// unarchiver themselves rather than use getUnarchiver(). +export class Unzipper extends UnzipperInternal { + constructor(ab, options) { super(ab, createWorkerFn, options); } +} + +export class Unrarrer extends UnrarrerInternal { + constructor(ab, options) { super(ab, createWorkerFn, options); } +} + +export class Untarrer extends UntarrerInternal { + constructor(ab, options) { super(ab, createWorkerFn, options); } +} + +/** +* Factory method that creates an unarchiver based on the byte signature found +* in the arrayBuffer. +* @param {ArrayBuffer} ab The ArrayBuffer to unarchive. Note that this ArrayBuffer +* must not be referenced after calling this method, as the ArrayBuffer is marked +* as Transferable and sent to a Worker thread once start() is called. +* @param {Object|string} options An optional object of options, or a string +* representing where the path to the unarchiver script files. +* @return {Unarchiver} +*/ +export function getUnarchiver(ab, options = {}) { + return getUnarchiverInternal(ab, createWorkerFn, options); +} diff --git a/archive/unzip.js b/archive/unzip.js index f0b3a12..473de5b 100644 --- a/archive/unzip.js +++ b/archive/unzip.js @@ -493,7 +493,7 @@ function inflate(compressedData, numDecompressedBytes) { // Bit stream representing the compressed data. /** @type {bitjs.io.BitStream} */ const bstream = new bitjs.io.BitStream(compressedData.buffer, - false /* rtl */, + false /* mtl */, compressedData.byteOffset, compressedData.byteLength); /** @type {bitjs.io.ByteBuffer} */ diff --git a/tests/archive-testfiles/README.md b/tests/archive-testfiles/README.md index f965951..38d1396 100644 --- a/tests/archive-testfiles/README.md +++ b/tests/archive-testfiles/README.md @@ -2,4 +2,4 @@ 1. Create a zip or rar file with just one file inside it. 2. Use test-uploader.html and choose the archived file and the unarchived file. - 3. Paste that jSON output into a test json file. + 3. Paste that JSON output into a test json file. diff --git a/tests/zipper-test.js b/tests/zipper-test.js index 2117602..e0437ff 100644 --- a/tests/zipper-test.js +++ b/tests/zipper-test.js @@ -1,5 +1,5 @@ -import { Zipper } from '../archive/compress.js'; +import { Zipper, ZipCompressionMethod } from '../archive/compress.js'; const result = document.querySelector('#result'); const fileInputEl = document.querySelector('#zip-tester'); @@ -36,7 +36,10 @@ async function getFiles(fileChangeEvt) { result.innerHTML = `Loaded files`; - const zipper = new Zipper({ pathToBitJS: '../' }); + const zipper = new Zipper({ + pathToBitJS: '../', + zipCompressionMethod: ZipCompressionMethod.DEFLATE, + }); byteArray = await zipper.start(fileInfos, true); result.innerHTML = `Zipping done`; saveButtonEl.style.display = '';