Permalink
Browse files

initial commit

  • Loading branch information...
1 parent d39af71 commit 1188cb22341788db903cf021221cea2009c6e8c3 John Hewson committed Aug 30, 2011
View
4 .gitignore
@@ -0,0 +1,4 @@
+build/
+test/temp*
+*.node
+.lock-wscript
View
34 README.md
@@ -23,12 +23,6 @@ You'll need to start with:
var deflate = require('deflate');
-### String
-
-Deflate a `String`, returning a `Buffer`:
-
- var gzip = deflate.deflate('some text which needs to be compressed');
-
### Buffer
Deflate a `Buffer` which contains all data to be compressed:
@@ -64,29 +58,43 @@ the low-level API. All low-level functions are synchronous.
## Options
### deflate(level=6)
-Use `gzip` format. `level` sets the compression level from `0` (uncompressed) to `9` (highly compressed),
+Deflates a gzip formatted `Buffer`. `level` sets the compression level from `0` (uncompressed) to `9` (highly compressed),
a smaller number is faster.
### deflate(format='gzip', level=6)
-`format` specifies the format and can be `gzip`, `zlib`, or `deflate`.
+Deflates a buffer.
+
+`format` can be `gzip`, `zlib`, or `deflate`.
`level` sets the compression level from `0` (uncompressed) to `9` (highly compressed),
a smaller number is faster.
----
+### createDeflateStream(readStream, level=6)
+Creates a `Stream` which wraps `readStream` to deflates gzip content.
+
+`level` sets the compression level from `0` (uncompressed) to `9`, a smaller number is faster.
-### createDeflateStream(level=6)
-Use `gzip` format. `level` sets the compression level from `0` (uncompressed) to `9`, a smaller number is faster.
+### createDeflateStream(readStream, format='gzip', level=6, bufferSize=131072)
+Creates a `Stream` which wraps `readStream` to deflate its content.
-### createDeflateStream(format='gzip', level=6, bufferSize=131072)
-`format` specifies the format and can be `gzip`, `zlib`, or `deflate`.
+`format` can be `gzip`, `zlib`, or `deflate`.
`level` sets the compression level from `0` (uncompressed) to `9` (highly compressed),
a smaller number is faster.
`bufferSize` is the size of the output buffer in bytes. Output data is buffered until it reaches
this size, with the exception of the final chunk. The default is 128K.
+### inflate(buffer, format='gzip')
+Inflates a `Buffer`.
+
+`format` can be `gzip`, `zlib`, or `deflate`.
+
+### createInflateStream(readStream, format='gzip')
+Creates a `Stream` which wraps `readStream` to inflate its content.
+
+`format` can be `gzip`, `zlib`, or `deflate`.
+
## Tests
Tests require [nodeunit](https://github.com/caolan/nodeunit), you can run them all with:
View
3 build.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+node-waf configure $*
+node-waf build
View
2 lib/deflate/.gitignore
@@ -0,0 +1,2 @@
+build/
+
View
102 lib/deflate/index.js
@@ -0,0 +1,102 @@
+var deflate = require('./deflate-bindings'),
+ Stream = require('stream').Stream,
+ util = require('util');
+
+module.exports = deflate;
+
+//////////////////////////////////////////////////////////
+// TODO: REFACTOR THIS TO MATCH MY LineReader STREAM ????
+//////////////////////////////////////////////////////////
+
+module.exports.createDeflateStream = function(readStream, format, level, bufferSize) {
+ if (!readStream) {
+ throw new Error('expected ReadableStream');
+ }
+ return new DeflateStream(readStream, format, level, bufferSize);
+};
+
+function DeflateStream(readStream, format, level, bufferSize) {
+ this.writable = true;
+ this.readable = true;
+ this.deflater = new deflate.Deflater(format, level, bufferSize);
+ this.readStream = readStream;
+
+ if (readStream) {
+ if (!readStream.readable) {
+ throw new Error('expected ReadableStream');
+ }
+ }
+}
+util.inherits(DeflateStream, Stream);
+
+DeflateStream.prototype.write = function(data) {
+ this.deflater.write(data);
+ while (this.deflater.deflate()) {
+ this.emit('data', this.deflater.read());
+ }
+};
+
+DeflateStream.prototype.end = function() {
+ while (this.deflater.flush()) {
+ this.emit('data', this.deflater.read());
+ }
+ this.emit('end');
+};
+
+DeflateStream.prototype.destroy = function() {
+ this.emit('close');
+};
+
+DeflateStream.prototype.pipe = function(src) {
+ Stream.prototype.pipe.call(this, src);
+ if (this.readStream) {
+ this.readStream.pipe(this);
+ }
+};
+
+//////////////////////////////////////////////////////////
+
+module.exports.createInflateStream = function(readStream, format, level, bufferSize) {
+ if (!readStream) {
+ throw new Error('expected ReadableStream');
+ }
+ return new InflateStream(readStream, format, level, bufferSize);
+};
+
+function InflateStream(readStream, format, level, bufferSize) {
+ this.writable = true;
+ this.readable = true;
+ this.inflater = new deflate.Inflater(format, level, bufferSize);
+ this.readStream = readStream;
+
+ if (readStream) {
+ if (!readStream.readable) {
+ throw new Error('expected ReadableStream');
+ }
+ }
+}
+util.inherits(InflateStream, Stream);
+
+InflateStream.prototype.write = function(data) {
+ this.inflater.write(data);
+ while (this.inflater.inflate()) {
+ this.emit('data', this.inflater.read());
+ }
+};
+
+InflateStream.prototype.end = function() {
+ this.emit('end');
+};
+
+InflateStream.prototype.destroy = function() {
+ this.emit('close');
+};
+
+InflateStream.prototype.pipe = function(src) {
+ Stream.prototype.pipe.call(this, src);
+ if (this.readStream) {
+ this.readStream.pipe(this);
+ }
+};
+
+//////////////////////////////////////////////////////////
View
17 package.json
@@ -0,0 +1,17 @@
+{
+ "name": "deflate",
+ "description": "super-simple streaming gzip, wrapping native zlib (deflate/inflate)",
+ "homepage" : "https://github.com/jahewson/node-deflate",
+ "bugs" : { "web" : "https://github.com/jahewson/node-deflate/issues" },
+ "version": "0.0.1",
+ "author": "John Hewson",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/jahewson/node-deflate"
+ },
+ "engine": [ "node >=0.4.0" ],
+ "main" : "./lib",
+ "scripts": {
+ "install": "./build.sh"
+ }
+}
View
785 src/deflate.cc
@@ -0,0 +1,785 @@
+// Copyright (C) 2011 John Hewson
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+
+#include <node.h>
+#include <node_buffer.h>
+#include <zlib.h>
+#include <string.h>
+
+#include <stdio.h>
+
+using namespace v8;
+using namespace node;
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+static Handle<Value> GetZError(int ret) {
+ const char* msg;
+
+ switch (ret) {
+ case Z_ERRNO:
+ msg = "Z_ERRNO";
+ break;
+ case Z_STREAM_ERROR:
+ msg = "Z_STREAM_ERROR";
+ break;
+ case Z_DATA_ERROR:
+ msg = "Z_DATA_ERROR";
+ break;
+ case Z_MEM_ERROR:
+ msg = "Z_MEM_ERROR";
+ break;
+ case Z_BUF_ERROR:
+ msg = "Z_BUF_ERROR";
+ break;
+ case Z_VERSION_ERROR:
+ msg = "Z_VERSION_ERROR";
+ break;
+ default:
+ msg = "Unknown ZLIB Error";
+ }
+
+ return ThrowException(Exception::Error(String::New(msg)));
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+class Deflater : ObjectWrap {
+
+ private:
+ z_stream strm;
+ char* input;
+ char* output;
+ int output_length;
+ int status;
+ bool finished;
+ int output_chunk_size;
+
+ public:
+
+ Deflater() {
+ input = NULL;
+ output_length = 0;
+ output = NULL;
+ finished = false;
+ }
+
+ int Init(int level, int windowBits, int memLevel, int strategy, int chunk_size) {
+ strm.zalloc = Z_NULL;
+ strm.zfree = Z_NULL;
+ strm.opaque = Z_NULL;
+ output_chunk_size = chunk_size;
+ return deflateInit2(&strm, level, Z_DEFLATED, windowBits, memLevel, strategy);
+ }
+
+ int SetInput(int in_length) {
+ strm.avail_in = in_length;
+ strm.next_in = (Bytef*) input;
+
+ if (output == NULL || output_length == output_chunk_size) {
+ strm.avail_out = output_chunk_size;
+ output = (char*) malloc(output_chunk_size);
+
+ if (output == NULL) {
+ return Z_MEM_ERROR;
+ }
+
+ strm.next_out = (Bytef*) output;
+ }
+
+ return Z_OK;
+ }
+
+ void ResetOutput() {
+ strm.avail_out = output_chunk_size;
+ strm.next_out = (Bytef*) output;
+ }
+
+ int Deflate() {
+ int ret = deflate(&strm, Z_NO_FLUSH);
+ status = ret;
+ output_length = output_chunk_size - strm.avail_out;
+ return ret;
+ }
+
+ int Finish() {
+ strm.avail_in = 0;
+ strm.next_in = NULL;
+
+ int ret = deflate(&strm, Z_FINISH);
+ status = ret;
+
+ output_length = output_chunk_size - strm.avail_out;
+
+ if (ret == Z_STREAM_END) {
+ deflateEnd(&strm);
+ finished = true;
+ }
+
+ return ret;
+ }
+
+ bool IsOutputBufferFull() {
+ return output_length == output_chunk_size;
+ }
+
+ bool HasFinished() {
+ return finished;
+ }
+
+ int GetOutputLength() {
+ return output_length;
+ }
+
+ char* GetOutput() {
+ return output;
+ }
+
+ // node.js wrapper //
+
+ static void Init(v8::Handle<v8::Object> target) {
+ Local<FunctionTemplate> t = FunctionTemplate::New(New);
+
+ t->InstanceTemplate()->SetInternalFieldCount(1);
+ t->SetClassName(String::NewSymbol("Deflater"));
+
+ NODE_SET_PROTOTYPE_METHOD(t, "write", SetInput);
+ NODE_SET_PROTOTYPE_METHOD(t, "deflate", Deflate);
+ NODE_SET_PROTOTYPE_METHOD(t, "read", GetOutput);
+ NODE_SET_PROTOTYPE_METHOD(t, "flush", Finish);
+
+ target->Set(String::NewSymbol("Deflater"), t->GetFunction());
+ }
+
+ static Handle<Value> New (const Arguments& args) {
+ HandleScope scope;
+
+ int level = Z_DEFAULT_COMPRESSION;
+ int windowBits = 16 + MAX_WBITS; // gzip
+ int memLevel = 8;
+ int strategy = Z_DEFAULT_STRATEGY;
+ int output_chunk_size = 131072; // 128K
+
+ int idx = 0;
+
+ if (args.Length() > 0) {
+ if(args[idx+0]->IsNumber()) {
+ level = args[idx+0]->Int32Value();
+ if (level < 0 || level > 9) {
+ Local<Value> exception = Exception::Error(String::New("level must be between 0 and 9"));
+ return ThrowException(exception);
+ }
+ }
+ else if (args[idx+0]->IsString()) {
+ char* strLevel = *String::AsciiValue(args[idx+0]->ToString());
+
+ if (strcmp(strLevel, "gzip") == 0) {
+ windowBits = 16 + MAX_WBITS;
+ }
+ else if (strcmp(strLevel, "zlib") == 0) {
+ windowBits = MAX_WBITS;
+ }
+ else if (strcmp(strLevel, "deflate") == 0) {
+ windowBits = -MAX_WBITS;
+ }
+ else {
+ Local<Value> exception = Exception::TypeError(String::New("bad deflate kind"));
+ return ThrowException(exception);
+ }
+ }
+ else if (args[idx+0]->IsUndefined()) {
+ }
+ else {
+ Local<Value> exception = Exception::TypeError(String::New("expected a Number or String"));
+ return ThrowException(exception);
+ }
+
+ if (args.Length() > 1) {
+ if(args[idx+1]->IsNumber()) {
+ level = args[idx+1]->Int32Value();
+ if (level < 0 || level > 9) {
+ Local<Value> exception = Exception::Error(String::New("level must be between 0 and 9"));
+ return ThrowException(exception);
+ }
+ }
+ else if (args[idx+1]->IsUndefined()) {
+ }
+ else {
+ Local<Value> exception = Exception::TypeError(String::New("expected a Number"));
+ return ThrowException(exception);
+ }
+ }
+
+ if (args.Length() > 2) {
+ if(args[idx+2]->IsNumber()) {
+ output_chunk_size = args[idx+2]->Int32Value();
+ if (output_chunk_size < 0) {
+ Local<Value> exception = Exception::Error(String::New("invalid buffer size"));
+ return ThrowException(exception);
+ }
+ }
+ else if (args[idx+2]->IsUndefined()) {
+ }
+ else {
+ Local<Value> exception = Exception::TypeError(String::New("buffer size must be a Number"));
+ return ThrowException(exception);
+ }
+ }
+ }
+
+ Deflater *deflater = new Deflater();
+ deflater->Wrap(args.This());
+
+ int r = deflater->Init(level, windowBits, memLevel, strategy, output_chunk_size);
+
+ if (r < 0) {
+ return GetZError(r);
+ }
+
+ return args.This();
+ }
+
+ static Handle<Value> SetInput(const Arguments& args) {
+ Deflater *deflater = ObjectWrap::Unwrap<Deflater>(args.This());
+ HandleScope scope;
+
+ Local<Object> in = args[0]->ToObject();
+ ssize_t length = Buffer::Length(in);
+
+ // copy the input buffer, because it can be kept for several deflate() calls
+ deflater->input = (char*)realloc(deflater->input, length);
+
+ if (deflater->input == NULL) {
+ return GetZError(Z_MEM_ERROR);
+ }
+
+ memcpy(deflater->input, Buffer::Data(in), length);
+
+ int r = deflater->SetInput(length);
+
+ if (r < 0) {
+ return GetZError(r);
+ }
+
+ return scope.Close(Undefined());
+ }
+
+ static Handle<Value> Deflate(const Arguments& args) {
+ Deflater *deflater = ObjectWrap::Unwrap<Deflater>(args.This());
+ HandleScope scope;
+
+ int r = deflater->Deflate();
+
+ if (r < 0) {
+ return GetZError(r);
+ }
+
+ return scope.Close(Boolean::New(deflater->IsOutputBufferFull()));
+ }
+
+ static Handle<Value> Finish(const Arguments& args) {
+ Deflater *deflater = ObjectWrap::Unwrap<Deflater>(args.This());
+ HandleScope scope;
+
+ if (deflater->HasFinished()) {
+ return scope.Close(False());
+ }
+
+ int r = deflater->Finish();
+
+ if (r < 0) {
+ return GetZError(r);
+ }
+
+ return scope.Close(True());
+ }
+
+ static Handle<Value> GetOutput(const Arguments& args) {
+ Deflater *deflater = ObjectWrap::Unwrap<Deflater>(args.This());
+ HandleScope scope;
+
+ Buffer* slowBuffer = Buffer::New(deflater->GetOutput(), deflater->GetOutputLength());
+ deflater->ResetOutput();
+
+ if (deflater->HasFinished()) {
+ free(deflater->GetOutput());
+ }
+
+ Local<Object> globalObj = Context::GetCurrent()->Global();
+ Local<Function> bufferConstructor = Local<Function>::Cast(globalObj->Get(String::New("Buffer")));
+ Handle<Value> constructorArgs[3] = { slowBuffer->handle_, Integer::New(deflater->GetOutputLength()), Integer::New(0) };
+ Local<Object> jsBuffer = bufferConstructor->NewInstance(3, constructorArgs);
+
+ return scope.Close(jsBuffer);
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+class Inflater : ObjectWrap {
+
+ private:
+ z_stream strm;
+ char* input;
+ char* output;
+ int output_length;
+ int status;
+ bool finished;
+ int output_chunk_size;
+
+ public:
+
+ Inflater() {
+ input = NULL;
+ output_length = 0;
+ output = NULL;
+ finished = false;
+ }
+
+ int Init(int windowBits, int chunk_size) {
+ strm.zalloc = Z_NULL;
+ strm.zfree = Z_NULL;
+ strm.opaque = Z_NULL;
+ strm.avail_in = 0;
+ strm.next_in = Z_NULL;
+ output_chunk_size = chunk_size;
+ return inflateInit2(&strm, windowBits);
+ }
+
+ int SetInput(int in_length) {
+ strm.avail_in = in_length;
+ strm.next_in = (Bytef*) input;
+
+ if (output == NULL || output_length == output_chunk_size) {
+ strm.avail_out = output_chunk_size;
+ output = (char*) malloc(output_chunk_size);
+
+ if (output == NULL) {
+ return Z_MEM_ERROR;
+ }
+
+ strm.next_out = (Bytef*) output;
+ }
+
+ return Z_OK;
+ }
+
+ void ResetOutput() {
+ strm.avail_out = output_chunk_size;
+ strm.next_out = (Bytef*) output;
+ }
+
+ int Inflate() {
+ int ret = inflate(&strm, Z_NO_FLUSH);
+ status = ret;
+ output_length = output_chunk_size - strm.avail_out;
+
+ if (ret == Z_STREAM_END) {
+ inflateEnd(&strm);
+ finished = true;
+ }
+
+ return ret;
+ }
+
+ bool IsOutputBufferFull() {
+ return output_length == output_chunk_size;
+ }
+
+ bool HasFinished() {
+ return finished;
+ }
+
+ int GetOutputLength() {
+ return output_length;
+ }
+
+ char* GetOutput() {
+ return output;
+ }
+
+ // node.js wrapper //
+
+ static void Init(v8::Handle<v8::Object> target) {
+ Local<FunctionTemplate> t = FunctionTemplate::New(New);
+
+ t->InstanceTemplate()->SetInternalFieldCount(1);
+ t->SetClassName(String::NewSymbol("Inflater"));
+
+ NODE_SET_PROTOTYPE_METHOD(t, "write", SetInput);
+ NODE_SET_PROTOTYPE_METHOD(t, "inflate", Inflate);
+ NODE_SET_PROTOTYPE_METHOD(t, "read", GetOutput);
+
+ target->Set(String::NewSymbol("Inflater"), t->GetFunction());
+ }
+
+ static Handle<Value> New (const Arguments& args) {
+ HandleScope scope;
+
+ int windowBits = 16 + MAX_WBITS; // gzip
+ int output_chunk_size = 65545; // <--- TODO: should have optional output buffer size
+
+ if (args.Length() > 0) {
+ if (args[0]->IsString()) {
+ char* strLevel = *String::AsciiValue(args[0]->ToString());
+
+ if (strcmp(strLevel, "gzip") == 0) {
+ windowBits = 16 + MAX_WBITS;
+ }
+ else if (strcmp(strLevel, "zlib") == 0) {
+ windowBits = MAX_WBITS;
+ }
+ else if (strcmp(strLevel, "deflate") == 0) {
+ windowBits = -MAX_WBITS;
+ }
+ else {
+ Local<Value> exception = Exception::TypeError(String::New("bad deflate kind"));
+ return ThrowException(exception);
+ }
+ }
+ else if (args[0]->IsUndefined()) {
+ }
+ else {
+ Local<Value> exception = Exception::TypeError(String::New("expected a Number or String"));
+ return ThrowException(exception);
+ }
+ }
+
+ Inflater *inflater = new Inflater();
+ inflater->Wrap(args.This());
+
+ int r = inflater->Init(windowBits, output_chunk_size);
+
+ if (r < 0) {
+ return GetZError(r);
+ }
+
+ return args.This();
+ }
+
+ static Handle<Value> SetInput(const Arguments& args) {
+ Inflater *inflater = ObjectWrap::Unwrap<Inflater>(args.This());
+ HandleScope scope;
+
+ Local<Object> in = args[0]->ToObject();
+ ssize_t length = Buffer::Length(in);
+
+ // copy the input buffer, because it can be kept for several deflate() calls
+ inflater->input = (char*)realloc(inflater->input, length);
+
+ if (inflater->input == NULL) {
+ return GetZError(Z_MEM_ERROR);
+ }
+
+ memcpy(inflater->input, Buffer::Data(in), length);
+
+ int r = inflater->SetInput(length);
+
+ if (r < 0) {
+ return GetZError(r);
+ }
+
+ return scope.Close(Undefined());
+ }
+
+ static Handle<Value> Inflate(const Arguments& args) {
+ Inflater *inflater = ObjectWrap::Unwrap<Inflater>(args.This());
+ HandleScope scope;
+
+ if (inflater->HasFinished()) {
+ return scope.Close(False());
+ }
+
+ int r = inflater->Inflate();
+
+ if (r < 0) {
+ return GetZError(r);
+ }
+
+ if (inflater->HasFinished()) {
+ return scope.Close(True());
+ }
+ else {
+ return scope.Close(Boolean::New(inflater->IsOutputBufferFull()));
+ }
+ }
+
+ static Handle<Value> GetOutput(const Arguments& args) {
+ Inflater *inflater = ObjectWrap::Unwrap<Inflater>(args.This());
+ HandleScope scope;
+
+ Buffer* slowBuffer = Buffer::New(inflater->GetOutput(), inflater->GetOutputLength());
+ inflater->ResetOutput();
+
+ if (inflater->HasFinished()) {
+ free(inflater->GetOutput());
+ }
+
+ Local<Object> globalObj = Context::GetCurrent()->Global();
+ Local<Function> bufferConstructor = Local<Function>::Cast(globalObj->Get(String::New("Buffer")));
+ Handle<Value> constructorArgs[3] = { slowBuffer->handle_, Integer::New(inflater->GetOutputLength()), Integer::New(0) };
+ Local<Object> jsBuffer = bufferConstructor->NewInstance(3, constructorArgs);
+
+ return scope.Close(jsBuffer);
+ }
+};
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+static Handle<Value> GetVersion(const Arguments &args) {
+ const char* version = zlibVersion();
+ return String::New(version);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+static Handle<Value> OnePassDeflate(const Arguments& args) {
+ HandleScope scope;
+
+ int level = Z_DEFAULT_COMPRESSION;
+ int windowBits = 16 + MAX_WBITS; // gzip
+ int memLevel = 8;
+ int strategy = Z_DEFAULT_STRATEGY;
+
+ int idx = 1;
+
+ if (args.Length() > 0) {
+ if(args[idx+0]->IsNumber()) {
+ level = args[idx+0]->Int32Value();
+ if (level < 0 || level > 9) {
+ Local<Value> exception = Exception::Error(String::New("level must be between 0 and 9"));
+ return ThrowException(exception);
+ }
+ }
+ else if (args[idx+0]->IsString()) {
+ char* strLevel = *String::AsciiValue(args[idx+0]->ToString());
+
+ if (strcmp(strLevel, "gzip") == 0) {
+ windowBits = 16 + MAX_WBITS;
+ }
+ else if (strcmp(strLevel, "zlib") == 0) {
+ windowBits = MAX_WBITS;
+ }
+ else if (strcmp(strLevel, "deflate") == 0) {
+ windowBits = -MAX_WBITS;
+ }
+ else {
+ Local<Value> exception = Exception::TypeError(String::New("bad deflate kind"));
+ return ThrowException(exception);
+ }
+ }
+ else if (args[idx+0]->IsUndefined()) {
+ }
+ else {
+ Local<Value> exception = Exception::TypeError(String::New("expected a Number or String"));
+ return ThrowException(exception);
+ }
+
+ if (args.Length() > 1) {
+ if(args[idx+1]->IsNumber()) {
+ level = args[idx+1]->Int32Value();
+ if (level < 0 || level > 9) {
+ Local<Value> exception = Exception::Error(String::New("level must be between 0 and 9"));
+ return ThrowException(exception);
+ }
+ }
+ else if (args[idx+1]->IsUndefined()) {
+ }
+ else {
+ Local<Value> exception = Exception::TypeError(String::New("expected a Number"));
+ return ThrowException(exception);
+ }
+ }
+ }
+
+ Local<Object> inBuff = args[0]->ToObject();
+ char* in = Buffer::Data(inBuff);
+ size_t in_length = Buffer::Length(inBuff);
+
+ z_stream strm;
+ strm.zalloc = Z_NULL;
+ strm.zfree = Z_NULL;
+ strm.opaque = Z_NULL;
+ int r = deflateInit2(&strm, level, Z_DEFLATED, windowBits, memLevel, strategy);
+
+ if (r < 0) {
+ return GetZError(r);
+ }
+
+ // deflate
+ strm.avail_in = in_length;
+ strm.next_in = (Bytef*) in;
+
+ uLong bound = deflateBound(&strm, strm.avail_in);
+
+ char* out = (char*) malloc(bound);
+
+ if (out == NULL) {
+ return GetZError(Z_MEM_ERROR);
+ }
+
+ strm.avail_out = bound;
+ strm.next_out = (Bytef*) out;
+
+ r = deflate(&strm, Z_FINISH);
+
+ int out_length = bound - strm.avail_out;
+
+ deflateEnd(&strm);
+
+ if (r < 0) {
+ return GetZError(r);
+ }
+
+ // output
+ Buffer* slowBuffer = Buffer::New(out, out_length);
+ free(out);
+
+ Local<Object> globalObj = Context::GetCurrent()->Global();
+ Local<Function> bufferConstructor = Local<Function>::Cast(globalObj->Get(String::New("Buffer")));
+ Handle<Value> constructorArgs[3] = { slowBuffer->handle_, Integer::New(out_length), Integer::New(0) };
+ Local<Object> jsBuffer = bufferConstructor->NewInstance(3, constructorArgs);
+
+ return scope.Close(jsBuffer);
+}
+
+static Handle<Value> OnePassInflate(const Arguments& args) {
+ HandleScope scope;
+
+ int windowBits = 16 + MAX_WBITS; // gzip
+
+ int idx = 1;
+
+ if (args.Length() > 0) {
+ if (args[idx+0]->IsString()) {
+ char* strLevel = *String::AsciiValue(args[idx+0]->ToString());
+
+ if (strcmp(strLevel, "gzip") == 0) {
+ windowBits = 16 + MAX_WBITS;
+ }
+ else if (strcmp(strLevel, "zlib") == 0) {
+ windowBits = MAX_WBITS;
+ }
+ else if (strcmp(strLevel, "deflate") == 0) {
+ windowBits = -MAX_WBITS;
+ }
+ else {
+ Local<Value> exception = Exception::TypeError(String::New("bad deflate kind"));
+ return ThrowException(exception);
+ }
+ }
+ else if (args[idx+0]->IsUndefined()) {
+ }
+ else {
+ Local<Value> exception = Exception::TypeError(String::New("expected a String"));
+ return ThrowException(exception);
+ }
+ }
+
+ Local<Object> inBuff = args[0]->ToObject();
+ char* in = Buffer::Data(inBuff);
+ size_t in_length = Buffer::Length(inBuff);
+
+ if (in_length == 0) {
+ Local<Value> exception = Exception::TypeError(String::New("Buffer length must be greater than zero"));
+ return ThrowException(exception);
+ }
+
+ z_stream strm;
+ strm.zalloc = Z_NULL;
+ strm.zfree = Z_NULL;
+ strm.opaque = Z_NULL;
+ strm.avail_in = 0;
+ strm.next_in = Z_NULL;
+ int r = inflateInit2(&strm, windowBits);
+
+ if (r < 0) {
+ return GetZError(r);
+ }
+
+ // deflate
+ strm.avail_in = in_length;
+ strm.next_in = (Bytef*) in;
+
+ // urgh, we don't know the buffer size (Q: is it in the gzip header?)
+ uLong bound = 131072; // 128K
+
+ char* out = (char*) malloc(bound);
+
+ if (out == NULL) {
+ return GetZError(Z_MEM_ERROR);
+ }
+
+ strm.avail_out = bound;
+ strm.next_out = (Bytef*) out;
+
+ r = inflate(&strm, Z_FINISH);
+
+ while (r == Z_BUF_ERROR) {
+ bound = bound * 2;
+ size_t len = (char*)strm.next_out - out;
+
+ out = (char*) realloc(out, bound);
+
+ if (out == NULL) {
+ return GetZError(Z_MEM_ERROR);
+ }
+
+ strm.avail_out = bound - len;
+ strm.next_out = (Bytef*) (out + len);
+
+ r = inflate(&strm, Z_FINISH);
+ }
+
+ if (r < 0) {
+ return GetZError(r);
+ }
+
+ int out_length = bound - strm.avail_out;
+
+ inflateEnd(&strm);
+
+ // output
+ Buffer* slowBuffer = Buffer::New(out, out_length);
+ free(out);
+
+ Local<Object> globalObj = Context::GetCurrent()->Global();
+ Local<Function> bufferConstructor = Local<Function>::Cast(globalObj->Get(String::New("Buffer")));
+ Handle<Value> constructorArgs[3] = { slowBuffer->handle_, Integer::New(out_length), Integer::New(0) };
+ Local<Object> jsBuffer = bufferConstructor->NewInstance(3, constructorArgs);
+
+ return scope.Close(jsBuffer);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+extern "C" {
+ void init (Handle<Object> target)
+ {
+ Deflater::Init(target);
+ Inflater::Init(target);
+
+ NODE_SET_METHOD(target, "version", GetVersion);
+ NODE_SET_METHOD(target, "deflate", OnePassDeflate);
+ NODE_SET_METHOD(target, "inflate", OnePassInflate);
+ }
+
+ NODE_MODULE(compress, init);
+}
View
BIN test/andromeda.bmp
Binary file not shown.
View
BIN test/andromeda.bmp.deflate
Binary file not shown.
View
BIN test/andromeda.bmp.gz
Binary file not shown.
View
BIN test/andromeda.bmp.z
Binary file not shown.
View
144 test/node-deflate-test.js
@@ -0,0 +1,144 @@
+var deflate = require('../lib/deflate'),
+ fs = require('fs'),
+ path = require('path');
+
+function testOnePassDeflate(format, extension, test) {
+ test.expect(1);
+
+ var inPath = path.join(__dirname, 'andromeda.bmp');
+ var outPath = path.join(__dirname, 'temp.' + extension);
+
+ fs.writeFileSync(outPath, deflate.deflate(fs.readFileSync(inPath), format)); // <-- todo: just use buffer!
+
+ validateDeflate(test, extension);
+ test.done();
+}
+
+function testStreamingDeflate(format, extension, test) {
+ test.expect(1);
+
+ var input = fs.createReadStream(path.join(__dirname, 'andromeda.bmp'));
+ var output = fs.createWriteStream(path.join(__dirname, 'temp.' + extension)); // <-- TODO: write to MemoryStream ???
+
+ var ds = deflate.createDeflateStream(input, format);
+ ds.pipe(output);
+
+ input.on('close', function() {
+ validateDeflate(test, extension);
+ test.done();
+ });
+}
+
+function validateDeflate(test, extension, inflate) {
+ var correct = fs.readFileSync(path.join(__dirname, 'andromeda.bmp.' + extension));
+ var deflated = fs.readFileSync(path.join(__dirname, 'temp.' + extension)); // <-- TODO!
+
+ var failed = false;
+
+ for (var i = 0; i < correct.length; i++) {
+ if (deflated[i] != correct[i]) {
+ test.equal(deflated[i], correct[i], 'at offset: ' + i);
+ failed = true;
+ break;
+ }
+ }
+
+ if (!failed) {
+ test.ok(true);
+ }
+}
+
+function testOnePassInflate(format, extension, test) {
+ test.expect(1);
+
+ var inPath = path.join(__dirname, 'andromeda.bmp.' + extension);
+ var outPath = path.join(__dirname, 'temp_' + extension + '.bmp');
+
+ fs.writeFileSync(outPath, deflate.inflate(fs.readFileSync(inPath), format)); // <-- todo: just use buffer!
+
+ validateInflate(test, extension);
+ test.done();
+}
+
+
+function testStreamingInflate(format, extension, test) {
+ test.expect(1);
+
+ var input = fs.createReadStream(path.join(__dirname, 'andromeda.bmp.' + extension));
+ var output = fs.createWriteStream(path.join(__dirname, 'temp_' + extension + '.bmp')); // <-- TODO: write to MemoryStream ???
+
+ var ds = deflate.createInflateStream(input, format);
+ ds.pipe(output);
+
+ input.on('close', function() {
+ validateInflate(test, extension);
+ test.done();
+ });
+}
+
+function validateInflate(test, extension, inflate) {
+ var correct = fs.readFileSync(path.join(__dirname, 'andromeda.bmp'));
+ var inflated = fs.readFileSync(path.join(__dirname, 'temp_' + extension + '.bmp')); // <-- TODO!
+
+ var failed = false;
+
+ for (var i = 0; i < correct.length; i++) {
+ if (inflated[i] != correct[i]) {
+ test.equal(inflated[i], correct[i], 'at offset: ' + i);
+ failed = true;
+ break;
+ }
+ }
+
+ if (!failed) {
+ test.ok(true);
+ }
+}
+
+exports['GZIP deflate'] = function(test) {
+ testOnePassDeflate('gzip', 'gz', test);
+};
+
+exports['ZLIB deflate'] = function(test) {
+ testOnePassDeflate('zlib', 'z', test);
+};
+
+exports['DEFLATE deflate'] = function(test) {
+ testOnePassDeflate('deflate', 'deflate', test);
+};
+
+exports['streaming GZIP deflate'] = function(test) {
+ testStreamingDeflate('gzip', 'gz', test);
+};
+
+exports['streaming ZLIB deflate'] = function(test) {
+ testStreamingDeflate('zlib', 'z', test);
+};
+
+exports['streaming DEFLATE deflate'] = function(test) {
+ testStreamingDeflate('deflate', 'deflate', test);
+};
+
+exports['GZIP inflate'] = function(test) {
+ testOnePassInflate('gzip', 'gz', test);
+};
+
+exports['ZLIB inflate'] = function(test) {
+ testOnePassInflate('zlib', 'z', test);
+};
+
+exports['DEFLATE inflate'] = function(test) {
+ testOnePassInflate('deflate', 'deflate', test);
+};
+
+exports['streaming GZIP inflate'] = function(test) {
+ testStreamingInflate('gzip', 'gz', test);
+};
+
+exports['streaming ZLIB inflate'] = function(test) {
+ testStreamingInflate('zlib', 'z', test);
+};
+
+exports['streaming DEFLATE inflate'] = function(test) {
+ testStreamingInflate('deflate', 'deflate', test);
+};
View
45 wscript
@@ -0,0 +1,45 @@
+import Options
+from os import unlink, symlink, popen
+from os.path import exists
+from shutil import copy2 as copy
+
+TARGET = 'deflate-bindings'
+TARGET_FILE = '%s.node' % TARGET
+built = 'build/default/%s' % TARGET_FILE
+dest = 'lib/deflate/%s' % TARGET_FILE
+
+def set_options(opt):
+ opt.tool_options("compiler_cxx")
+ opt.add_option('--debug', dest='debug', action='store_true', default=False)
+
+def configure(conf):
+ conf.check_tool("compiler_cxx")
+ conf.check_tool("node_addon")
+ conf.check(lib='z', libpath=['/usr/lib', '/usr/local/lib'], uselib_store='ZLIB', mandatory=True)
+
+ conf.env.DEFINES = []
+
+ if Options.options.debug:
+ conf.env.DEFINES += [ 'DEBUG' ]
+ conf.env.CXXFLAGS = [ '-O0', '-g3' ]
+ else:
+ conf.env.CXXFLAGS = [ '-O3' ]
+
+
+def build(bld):
+ obj = bld.new_task_gen("cxx", "shlib", "node_addon")
+ #obj.cxxflags = ["-D_FILE_OFFSET_BITS=64", "-D_LARGEFILE_SOURCE", "-Wall"]
+ obj.target = TARGET
+ obj.source = "src/deflate.cc"
+ obj.uselib = "ZLIB"
+ obj.defines = bld.env.DEFINES
+
+def shutdown():
+ if Options.commands['clean']:
+ if exists(TARGET_FILE):
+ unlink(TARGET_FILE)
+ if exists(dest):
+ unlink(dest)
+ else:
+ if exists(built):
+ copy(built, dest)

0 comments on commit 1188cb2

Please sign in to comment.