zlib: add sync versions for convenience methods
authorNikolai Vavilov <vvnicholas@gmail.com>
Tue, 28 Jan 2014 17:35:51 +0000 (19:35 +0200)
committerFedor Indutny <fedor.indutny@gmail.com>
Fri, 31 Jan 2014 23:45:45 +0000 (03:45 +0400)
doc/api/zlib.markdown
lib/zlib.js
src/node_zlib.cc
test/simple/test-zlib-convenience-methods.js

index 59d7355..8ade5a8 100644 (file)
@@ -195,34 +195,36 @@ the header.
 <!--type=misc-->
 
 All of these take a string or buffer as the first argument, an optional second
-argument to supply options to the zlib classes and will call the supplied
-callback with `callback(error, result)`.
+argument to supply options to the zlib classes and an optional callback. If a
+callback is supplied, they will call it asynchronously with
+`callback(error, result)`, otherwise they will return the result or throw the
+error synchronously.
 
-## zlib.deflate(buf, [options], callback)
+## zlib.deflate(buf, [options], [callback])
 
 Compress a string with Deflate.
 
-## zlib.deflateRaw(buf, [options], callback)
+## zlib.deflateRaw(buf, [options], [callback])
 
 Compress a string with DeflateRaw.
 
-## zlib.gzip(buf, [options], callback)
+## zlib.gzip(buf, [options], [callback])
 
 Compress a string with Gzip.
 
-## zlib.gunzip(buf, [options], callback)
+## zlib.gunzip(buf, [options], [callback])
 
 Decompress a raw Buffer with Gunzip.
 
-## zlib.inflate(buf, [options], callback)
+## zlib.inflate(buf, [options], [callback])
 
 Decompress a raw Buffer with Inflate.
 
-## zlib.inflateRaw(buf, [options], callback)
+## zlib.inflateRaw(buf, [options], [callback])
 
 Decompress a raw Buffer with InflateRaw.
 
-## zlib.unzip(buf, [options], callback)
+## zlib.unzip(buf, [options], [callback])
 
 Decompress a raw Buffer with Unzip.
 
index 3341588..ae37fcf 100644 (file)
@@ -112,7 +112,7 @@ exports.deflate = function(buffer, opts, callback) {
     callback = opts;
     opts = {};
   }
-  zlibBuffer(new Deflate(opts), buffer, callback);
+  return zlibBuffer(new Deflate(opts), buffer, callback);
 };
 
 exports.gzip = function(buffer, opts, callback) {
@@ -120,7 +120,7 @@ exports.gzip = function(buffer, opts, callback) {
     callback = opts;
     opts = {};
   }
-  zlibBuffer(new Gzip(opts), buffer, callback);
+  return zlibBuffer(new Gzip(opts), buffer, callback);
 };
 
 exports.deflateRaw = function(buffer, opts, callback) {
@@ -128,7 +128,7 @@ exports.deflateRaw = function(buffer, opts, callback) {
     callback = opts;
     opts = {};
   }
-  zlibBuffer(new DeflateRaw(opts), buffer, callback);
+  return zlibBuffer(new DeflateRaw(opts), buffer, callback);
 };
 
 exports.unzip = function(buffer, opts, callback) {
@@ -136,7 +136,7 @@ exports.unzip = function(buffer, opts, callback) {
     callback = opts;
     opts = {};
   }
-  zlibBuffer(new Unzip(opts), buffer, callback);
+  return zlibBuffer(new Unzip(opts), buffer, callback);
 };
 
 exports.inflate = function(buffer, opts, callback) {
@@ -144,7 +144,7 @@ exports.inflate = function(buffer, opts, callback) {
     callback = opts;
     opts = {};
   }
-  zlibBuffer(new Inflate(opts), buffer, callback);
+  return zlibBuffer(new Inflate(opts), buffer, callback);
 };
 
 exports.gunzip = function(buffer, opts, callback) {
@@ -152,7 +152,7 @@ exports.gunzip = function(buffer, opts, callback) {
     callback = opts;
     opts = {};
   }
-  zlibBuffer(new Gunzip(opts), buffer, callback);
+  return zlibBuffer(new Gunzip(opts), buffer, callback);
 };
 
 exports.inflateRaw = function(buffer, opts, callback) {
@@ -160,10 +160,14 @@ exports.inflateRaw = function(buffer, opts, callback) {
     callback = opts;
     opts = {};
   }
-  zlibBuffer(new InflateRaw(opts), buffer, callback);
+  return zlibBuffer(new InflateRaw(opts), buffer, callback);
 };
 
 function zlibBuffer(engine, buffer, callback) {
+  if (!util.isFunction(callback)) {
+    return zlibBufferSync(engine, buffer, callback);
+  }
+
   var buffers = [];
   var nread = 0;
 
@@ -196,6 +200,16 @@ function zlibBuffer(engine, buffer, callback) {
   }
 }
 
+function zlibBufferSync(engine, buffer, callback) {
+  if (util.isString(buffer))
+    buffer = new Buffer(buffer);
+  if (!util.isBuffer(buffer))
+    throw new TypeError('Not a string or buffer');
+
+  var flushFlag = binding.Z_FINISH;
+
+  return engine._processChunk(buffer, flushFlag);
+}
 
 // generic zlib
 // minimal 2-byte header
@@ -453,10 +467,48 @@ Zlib.prototype._transform = function(chunk, encoding, cb) {
     }
   }
 
+  var self = this;
+  this._processChunk(chunk, flushFlag, cb);
+};
+
+Zlib.prototype._processChunk = function(chunk, flushFlag, cb) {
   var availInBefore = chunk && chunk.length;
   var availOutBefore = this._chunkSize - this._offset;
   var inOff = 0;
 
+  var self = this;
+
+  var async = util.isFunction(cb);
+
+  if (!async) {
+    var buffers = [];
+    var nread = 0;
+
+    var error;
+    this.on('error', function(er) {
+      error = er;
+    });
+
+    do {
+      var res = this._binding.writeSync(flushFlag,
+                                        chunk, // in
+                                        inOff, // in_off
+                                        availInBefore, // in_len
+                                        this._buffer, // out
+                                        this._offset, //out_off
+                                        availOutBefore); // out_len
+    } while (!this._hadError && callback(res[0], res[1]));
+
+    if (this._hadError) {
+      throw error;
+    }
+
+    var buf = Buffer.concat(buffers, nread);
+    this.close();
+
+    return buf;
+  }
+
   var req = this._binding.write(flushFlag,
                                 chunk, // in
                                 inOff, // in_off
@@ -468,7 +520,6 @@ Zlib.prototype._transform = function(chunk, encoding, cb) {
   req.buffer = chunk;
   req.callback = callback;
 
-  var self = this;
   function callback(availInAfter, availOutAfter) {
     if (self._hadError)
       return;
@@ -480,7 +531,12 @@ Zlib.prototype._transform = function(chunk, encoding, cb) {
       var out = self._buffer.slice(self._offset, self._offset + have);
       self._offset += have;
       // serve some output to the consumer.
-      self.push(out);
+      if (async) {
+        self.push(out);
+      } else {
+        buffers.push(out);
+        nread += out.length;
+      }
     }
 
     // exhausted the output buffer, or used all the input create a new one.
@@ -498,6 +554,9 @@ Zlib.prototype._transform = function(chunk, encoding, cb) {
       inOff += (availInBefore - availInAfter);
       availInBefore = availInAfter;
 
+      if (!async)
+        return true;
+
       var newReq = self._binding.write(flushFlag,
                                        chunk,
                                        inOff,
@@ -510,6 +569,9 @@ Zlib.prototype._transform = function(chunk, encoding, cb) {
       return;
     }
 
+    if (!async)
+      return false;
+
     // finished with the chunk.
     cb();
   }
index 16372bd..8a2125f 100644 (file)
@@ -39,6 +39,7 @@
 
 namespace node {
 
+using v8::Array;
 using v8::Context;
 using v8::FunctionCallbackInfo;
 using v8::FunctionTemplate;
@@ -125,6 +126,7 @@ class ZCtx : public AsyncWrap {
 
 
   // write(flush, in, in_off, in_len, out, out_off, out_len)
+  template <bool async>
   static void Write(const FunctionCallbackInfo<Value>& args) {
     HandleScope scope(node_isolate);
     assert(args.Length() == 7);
@@ -190,6 +192,15 @@ class ZCtx : public AsyncWrap {
     // set this so that later on, I can easily tell how much was written.
     ctx->chunk_size_ = out_len;
 
+    if (!async) {
+      // sync version
+      Process(work_req);
+      if (CheckError(ctx))
+        AfterSync(ctx, args);
+      return;
+    }
+
+    // async version
     uv_queue_work(ctx->env()->event_loop(),
                   work_req,
                   ZCtx::Process,
@@ -199,6 +210,21 @@ class ZCtx : public AsyncWrap {
   }
 
 
+  static void AfterSync(ZCtx* ctx, const FunctionCallbackInfo<Value>& args) {
+    Local<Integer> avail_out = Integer::New(ctx->strm_.avail_out, node_isolate);
+    Local<Integer> avail_in = Integer::New(ctx->strm_.avail_in, node_isolate);
+
+    ctx->write_in_progress_ = false;
+
+    Local<Array> result = Array::New(2);
+    result->Set(0, avail_in);
+    result->Set(1, avail_out);
+    args.GetReturnValue().Set(result);
+
+    ctx->Unref();
+  }
+
+
   // thread pool!
   // This function may be called multiple times on the uv_work pool
   // for a single write() call, until all of the input bytes have
@@ -249,6 +275,31 @@ class ZCtx : public AsyncWrap {
     // or shift the queue and call Process.
   }
 
+
+  static bool CheckError(ZCtx* ctx) {
+    // Acceptable error states depend on the type of zlib stream.
+    switch (ctx->err_) {
+    case Z_OK:
+    case Z_STREAM_END:
+    case Z_BUF_ERROR:
+      // normal statuses, not fatal
+      break;
+    case Z_NEED_DICT:
+      if (ctx->dictionary_ == NULL)
+        ZCtx::Error(ctx, "Missing dictionary");
+      else
+        ZCtx::Error(ctx, "Bad dictionary");
+      return false;
+    default:
+      // something else.
+      ZCtx::Error(ctx, "Zlib error");
+      return false;
+    }
+
+    return true;
+  }
+
+
   // v8 land!
   static void After(uv_work_t* work_req, int status) {
     assert(status == 0);
@@ -259,25 +310,8 @@ class ZCtx : public AsyncWrap {
     HandleScope handle_scope(env->isolate());
     Context::Scope context_scope(env->context());
 
-    // Acceptable error states depend on the type of zlib stream.
-    switch (ctx->err_) {
-      case Z_OK:
-      case Z_STREAM_END:
-      case Z_BUF_ERROR:
-        // normal statuses, not fatal
-        break;
-      case Z_NEED_DICT:
-        if (ctx->dictionary_ == NULL) {
-          ZCtx::Error(ctx, "Missing dictionary");
-        } else {
-          ZCtx::Error(ctx, "Bad dictionary");
-        }
-        return;
-      default:
-        // something else.
-        ZCtx::Error(ctx, "Zlib error");
-        return;
-    }
+    if (!CheckError(ctx))
+      return;
 
     Local<Integer> avail_out = Integer::New(ctx->strm_.avail_out, node_isolate);
     Local<Integer> avail_in = Integer::New(ctx->strm_.avail_in, node_isolate);
@@ -556,7 +590,8 @@ void InitZlib(Handle<Object> target,
 
   z->InstanceTemplate()->SetInternalFieldCount(1);
 
-  NODE_SET_PROTOTYPE_METHOD(z, "write", ZCtx::Write);
+  NODE_SET_PROTOTYPE_METHOD(z, "write", ZCtx::Write<true>);
+  NODE_SET_PROTOTYPE_METHOD(z, "writeSync", ZCtx::Write<false>);
   NODE_SET_PROTOTYPE_METHOD(z, "init", ZCtx::Init);
   NODE_SET_PROTOTYPE_METHOD(z, "close", ZCtx::Close);
   NODE_SET_PROTOTYPE_METHOD(z, "params", ZCtx::Params);
index 43794fb..43b68d1 100644 (file)
@@ -58,8 +58,22 @@ var opts = {
     });
   });
 
+  var result = zlib[method[0]](expect, opts);
+  result = zlib[method[1]](result, opts);
+  assert.equal(result, expect,
+    'Should get original string after ' +
+    method[0] + '/' + method[1] + ' with options.');
+  hadRun++;
+
+  result = zlib[method[0]](expect);
+  result = zlib[method[1]](result);
+  assert.equal(result, expect,
+    'Should get original string after ' +
+    method[0] + '/' + method[1] + ' without options.');
+  hadRun++;
+
 });
 
 process.on('exit', function() {
-  assert.equal(hadRun, 8, 'expect 8 compressions');
+  assert.equal(hadRun, 16, 'expect 16 compressions');
 });