Skip to content

Commit

Permalink
feat(compression): Integrate fflate library
Browse files Browse the repository at this point in the history
  • Loading branch information
ibgreen committed May 21, 2023
1 parent 25b4b40 commit 0aefaea
Show file tree
Hide file tree
Showing 9 changed files with 493 additions and 78 deletions.
108 changes: 108 additions & 0 deletions modules/compression/src/lib/deflate-compression-pako.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
// loaders.gl, MIT license
import type {CompressionOptions} from './compression';
import {Compression} from './compression';
import {getPakoError} from './utils/pako-utils';
import pako from 'pako'; // https://bundlephobia.com/package/pako

export type DeflateCompressionOptions = CompressionOptions & {
deflate?: pako.InflateOptions & pako.DeflateOptions & {useZlib?: boolean};
};

/**
* DEFLATE compression / decompression
* Using PAKO library
*/
export class DeflateCompression extends Compression {
readonly name: string = 'deflate';
readonly extensions: string[] = [];
readonly contentEncodings = ['deflate'];
readonly isSupported = true;

readonly options: DeflateCompressionOptions;

private _chunks: ArrayBuffer[] = [];

constructor(options: DeflateCompressionOptions = {}) {
super(options);
this.options = options;
}

async compress(input: ArrayBuffer): Promise<ArrayBuffer> {
return this.compressSync(input);
}

async decompress(input: ArrayBuffer): Promise<ArrayBuffer> {
return this.decompressSync(input);
}

compressSync(input: ArrayBuffer): ArrayBuffer {
const pakoOptions: pako.DeflateOptions = this.options?.deflate || {};
const inputArray = new Uint8Array(input);
return pako.deflate(inputArray, pakoOptions).buffer;
}

decompressSync(input: ArrayBuffer): ArrayBuffer {
const pakoOptions: pako.InflateOptions = this.options?.deflate || {};
const inputArray = new Uint8Array(input);
return pako.inflate(inputArray, pakoOptions).buffer;
}

async *compressBatches(
asyncIterator: AsyncIterable<ArrayBuffer> | Iterable<ArrayBuffer>
): AsyncIterable<ArrayBuffer> {
const pakoOptions: pako.DeflateOptions = this.options?.deflate || {};
const pakoProcessor = new pako.Deflate(pakoOptions);
yield* this.transformBatches(pakoProcessor, asyncIterator);
}

async *decompressBatches(
asyncIterator: AsyncIterable<ArrayBuffer> | Iterable<ArrayBuffer>
): AsyncIterable<ArrayBuffer> {
const pakoOptions: pako.InflateOptions = this.options?.deflate || {};
const pakoProcessor = new pako.Inflate(pakoOptions);
yield* this.transformBatches(pakoProcessor, asyncIterator);
}

async *transformBatches(
pakoProcessor: pako.Inflate | pako.Deflate,
asyncIterator: AsyncIterable<ArrayBuffer> | Iterable<ArrayBuffer>
): AsyncIterable<ArrayBuffer> {
pakoProcessor.onData = this._onData.bind(this);
pakoProcessor.onEnd = this._onEnd.bind(this);
for await (const chunk of asyncIterator) {
const uint8Array = new Uint8Array(chunk);
const ok = pakoProcessor.push(uint8Array, false); // false -> not last chunk
if (!ok) {
throw new Error(`${getPakoError()}write`);
}
const chunks = this._getChunks();
yield* chunks;
}

// End
const emptyChunk = new Uint8Array(0);
const ok = pakoProcessor.push(emptyChunk, true); // true -> last chunk
if (!ok) {
// For some reason we get error but it still works???
// throw new Error(getPakoError() + 'end');
}
const chunks = this._getChunks();
yield* chunks;
}

_onData(chunk) {
this._chunks.push(chunk);
}

_onEnd(status) {
if (status !== 0) {
throw new Error(getPakoError(status) + this._chunks.length);
}
}

_getChunks(): ArrayBuffer[] {
const chunks = this._chunks;
this._chunks = [];
return chunks;
}
}
52 changes: 52 additions & 0 deletions modules/compression/src/lib/deflate-compression-zlib.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// loaders.gl, MIT license
import {isBrowser, toArrayBuffer} from '@loaders.gl/loader-utils';
import {promisify2} from '@loaders.gl/loader-utils';
import type {CompressionOptions} from './compression';
import {Compression} from './compression';
import * as zlib from 'zlib';
import type {ZlibOptions} from 'zlib';

export type DeflateCompressionOptions = CompressionOptions & {
deflate?: ZlibOptions;
};

/**
* DEFLATE compression / decompression
* Using Node.js zlib library (works under Node only)
*/
export class DeflateCompression extends Compression {
readonly name: string = 'deflate';
readonly extensions: string[] = [];
readonly contentEncodings = ['deflate'];
readonly isSupported = isBrowser;

readonly options: DeflateCompressionOptions;

constructor(options: DeflateCompressionOptions = {}) {
super(options);
this.options = options;
if (!isBrowser) {
throw new Error('zlib only available under Node.js');
}
}

async compress(input: ArrayBuffer): Promise<ArrayBuffer> {
const buffer = await promisify2(zlib.deflate)(input, this.options.deflate || {});
return toArrayBuffer(buffer);
}

async decompress(input: ArrayBuffer): Promise<ArrayBuffer> {
const buffer = await promisify2(zlib.inflate)(input, this.options.deflate || {});
return toArrayBuffer(buffer);
}

compressSync(input: ArrayBuffer): ArrayBuffer {
const buffer = zlib.deflateSync(input, this.options.deflate || {});
return toArrayBuffer(buffer);
}

decompressSync(input: ArrayBuffer): ArrayBuffer {
const buffer = zlib.inflateSync(input, this.options.deflate || {});
return toArrayBuffer(buffer);
}
}
89 changes: 22 additions & 67 deletions modules/compression/src/lib/deflate-compression.ts
Original file line number Diff line number Diff line change
@@ -1,89 +1,68 @@
// DEFLATE
// loaders.gl, MIT license
import {promisify1} from '@loaders.gl/loader-utils';
import type {CompressionOptions} from './compression';
import {Compression} from './compression';
import {isBrowser, toArrayBuffer} from '@loaders.gl/loader-utils';
import pako from 'pako'; // https://bundlephobia.com/package/pako
import zlib from 'zlib';
import {promisify1} from '@loaders.gl/loader-utils';

import {deflate, inflate, deflateSync, inflateSync} from 'fflate';
import type {DeflateOptions} from 'fflate'; // https://bundlephobia.com/package/pako

export type DeflateCompressionOptions = CompressionOptions & {
deflate?: pako.InflateOptions & pako.DeflateOptions & {useZlib?: boolean};
deflate?: DeflateOptions;
};

/**
* DEFLATE compression / decompression
*/
export class DeflateCompression extends Compression {
readonly name: string = 'deflate';
readonly name: string = 'fflate';
readonly extensions: string[] = [];
readonly contentEncodings = ['deflate'];
readonly contentEncodings = ['fflate', 'gzip, zlib'];
readonly isSupported = true;

readonly options: DeflateCompressionOptions;

private _chunks: ArrayBuffer[] = [];

constructor(options: DeflateCompressionOptions = {}) {
super(options);
this.options = options;
}

async compress(input: ArrayBuffer): Promise<ArrayBuffer> {
// On Node.js we can use built-in zlib
if (!isBrowser && this.options.deflate?.useZlib) {
const buffer = this.options.deflate?.gzip
? await promisify1(zlib.gzip)(input)
: await promisify1(zlib.deflate)(input);
return toArrayBuffer(buffer);
}
return this.compressSync(input);
// const options = this.options?.gzip || {};
const inputArray = new Uint8Array(input);
const outputArray = await promisify1(deflate)(inputArray); // options - overload pick
return outputArray.buffer;
}

async decompress(input: ArrayBuffer): Promise<ArrayBuffer> {
// On Node.js we can use built-in zlib
if (!isBrowser && this.options.deflate?.useZlib) {
const buffer = this.options.deflate?.gzip
? await promisify1(zlib.gunzip)(input)
: await promisify1(zlib.inflate)(input);
return toArrayBuffer(buffer);
}
return this.decompressSync(input);
const inputArray = new Uint8Array(input);
const outputArray = await promisify1(inflate)(inputArray);
return outputArray.buffer;
}

compressSync(input: ArrayBuffer): ArrayBuffer {
// On Node.js we can use built-in zlib
if (!isBrowser && this.options.deflate?.useZlib) {
const buffer = this.options.deflate?.gzip ? zlib.gzipSync(input) : zlib.deflateSync(input);
return toArrayBuffer(buffer);
}
const pakoOptions: pako.DeflateOptions = this.options?.deflate || {};
const options = this.options?.deflate || {};
const inputArray = new Uint8Array(input);
return pako.deflate(inputArray, pakoOptions).buffer;
return deflateSync(inputArray, options).buffer;
}

decompressSync(input: ArrayBuffer): ArrayBuffer {
// On Node.js we can use built-in zlib
if (!isBrowser && this.options.deflate?.useZlib) {
const buffer = this.options.deflate?.gzip ? zlib.gunzipSync(input) : zlib.inflateSync(input);
return toArrayBuffer(buffer);
}
const pakoOptions: pako.InflateOptions = this.options?.deflate || {};
const inputArray = new Uint8Array(input);
return pako.inflate(inputArray, pakoOptions).buffer;
return inflateSync(inputArray).buffer;
}

/*
async *compressBatches(
asyncIterator: AsyncIterable<ArrayBuffer> | Iterable<ArrayBuffer>
): AsyncIterable<ArrayBuffer> {
const pakoOptions: pako.DeflateOptions = this.options?.deflate || {};
const pakoOptions: pako.DeflateOptions = this.options?.fflate || {};
const pakoProcessor = new pako.Deflate(pakoOptions);
yield* this.transformBatches(pakoProcessor, asyncIterator);
}
async *decompressBatches(
asyncIterator: AsyncIterable<ArrayBuffer> | Iterable<ArrayBuffer>
): AsyncIterable<ArrayBuffer> {
const pakoOptions: pako.InflateOptions = this.options?.deflate || {};
const pakoOptions: pako.InflateOptions = this.options?.fflate || {};
const pakoProcessor = new pako.Inflate(pakoOptions);
yield* this.transformBatches(pakoProcessor, asyncIterator);
}
Expand Down Expand Up @@ -130,29 +109,5 @@ export class DeflateCompression extends Compression {
this._chunks = [];
return chunks;
}

// TODO - For some reason we don't get the error message from pako in _onEnd?
_getError(code: number = 0): string {
const MESSAGES = {
/* Z_NEED_DICT 2 */
2: 'need dictionary',
/* Z_STREAM_END 1 */
1: 'stream end',
/* Z_OK 0 */
0: '',
/* Z_ERRNO (-1) */
'-1': 'file error',
/* Z_STREAM_ERROR (-2) */
'-2': 'stream error',
/* Z_DATA_ERROR (-3) */
'-3': 'data error',
/* Z_MEM_ERROR (-4) */
'-4': 'insufficient memory',
/* Z_BUF_ERROR (-5) */
'-5': 'buffer error',
/* Z_VERSION_ERROR (-6) */
'-6': 'incompatible version'
};
return `${this.name}: ${MESSAGES[code]}`;
}
*/
}
Loading

0 comments on commit 0aefaea

Please sign in to comment.