Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added stream-like read API and write API to node-zipfile #16

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
56 changes: 47 additions & 9 deletions README.md
@@ -1,20 +1,58 @@

# Node-Zipfile

Bindings to [libzip](http://nih.at/libzip/libzip.html) for handling zipfiles in [node](http://nodejs.org).


# Example

> var zipfile = require('zipfile')
> var zf = new zipfile.ZipFile('./data/world_merc.zip')
> zf
{ names: [ 'world_merc.dbf', 'world_merc.prj', 'world_merc.shp', 'world_merc.shx' ],
var zipfile = require('zipfile');
var fs = require('fs');

// Open:
var zf = new zipfile.ZipFile('./data/world_merc.zip');
// { names: [ 'world_merc.dbf', 'world_merc.prj', 'world_merc.shp', 'world_merc.shx' ],
count: 4 }
> var buffer = zf.readFileSync('world_merc.prj')
> buffer.toString()
'PROJCS["Google Maps Global Mercator",GEOGCS .... '

// Synchronous read:
var buffer = zf.readFileSync('world_merc.prj');
// 'PROJCS["Google Maps Global Mercator",GEOGCS .... '

// Asynchronous read:
var stream = zf.createReadStream('world_merc.prj');
var fstream = fs.createWriteStream('/tmp/world_merc.prj');
stream.pipe(fstream);

// Write:
// Add:
zf.addDirectory('foo');
zf.addFile('foo/world_merc.prj', '/tmp/world_merc.prj');
zf.save(function (err) {
if (err)
console.log("Error :" + err)
else
console.log("Done")
});
// Done

// Replace:
zf.replaceFile('foo/world_merc.prj', '/tmp/world_merc.prj');
zf.save(...);


# Note:

* libzip is not thread-safe. Thus, there can only be one of the following operation at a given time:
* `zf.createReadStream`
* `zf.readFileSync`
* `zf.addFile`
* `zf.replaceFile`
* `zf.save`

Call to `zf.readFileSync`, `zf.addFile` and `zf.replaceFile` are synchronous. Upon opening a read stream,
the zip archive can be used again with another of the operations above once the `'close'` event has been emitted
by the stream.
* Adding and replacing files in the archive returns immediatly. Files are actually written
when calling `zf.save`.

## Depends

Expand Down Expand Up @@ -84,4 +122,4 @@

## License

BSD, see LICENSE.txt
BSD, see LICENSE.txt
140 changes: 135 additions & 5 deletions lib/zipfile.js
@@ -1,8 +1,138 @@
var assert = require('assert');
var Stream = require('stream').Stream;
var util = require('util')
var zipfile = require('./_zipfile');

/* assert ABI compatibility */
assert.ok(zipfile.versions.node === process.versions.node, 'The running node version "' + process.versions.node + '" does not match the node version that node-zipfile was compiled against: "' + zipfile.versions.node + '"');
var pool;
var kMinPoolSpace = 128;
var kPoolSize = 40 * 1024;

// push all C++ symbols into js module
for (var k in zipfile) { exports[k] = zipfile[k]; }
function allocNewPool() {
pool = new Buffer(kPoolSize);
pool.used = 0;
}

zipfile.ZipFile.prototype.createReadStream = function (file) {
return new ReadStream(this, file);
}

var ReadStream = function(zf, file) {
if (!(this instanceof ReadStream)) return new ReadStream(zip, file);

Stream.call(this);

this.readable = true;
this.paused = false;
this.bufferSize = 64 * 1024;

if (this.encoding) this.setEncoding(this.encoding);

this.zf = zf;

zf.open(file);
this._read();
}
util.inherits(ReadStream, Stream);

ReadStream.prototype.setEncoding = function(encoding) {
var StringDecoder = require('string_decoder').StringDecoder; // lazy load
this._decoder = new StringDecoder(encoding);
};

ReadStream.prototype._read = function() {
var self = this;
if (!self.readable || self.paused || self.reading) return;

self.reading = true;

if (!pool || pool.length - pool.used < kMinPoolSpace) {
// discard the old pool. Can't add to the free list because
// users might have refernces to slices on it.
pool = null;
allocNewPool();
}

// Grab another reference to the pool in the case that while we're in the
// thread pool another read() finishes up the pool, and allocates a new
// one.
var thisPool = pool;
var toRead = Math.min(pool.length - pool.used, this.bufferSize);
var start = pool.used;

if (this.pos !== undefined) {
toRead = Math.min(this.end - this.pos + 1, toRead);
}

function afterRead(err, bytesRead) {
self.reading = false;
if (err) {
self.emit('error', err);
self.readable = false;
return;
}

if (bytesRead === 0) {
self.emit('end');
self.destroy();
return;
}

var b = thisPool.slice(start, start + bytesRead);

// Possible optimizition here?
// Reclaim some bytes if bytesRead < toRead?
// Would need to ensure that pool === thisPool.

// do not emit events if the stream is paused
if (self.paused) {
self.buffer = b;
return;
}

// do not emit events anymore after we declared the stream unreadable
if (!self.readable) return;

self._emitData(b);
self._read();
}

self.zf.read(pool, pool.used, toRead, afterRead);

if (self.pos !== undefined) {
self.pos += toRead;
}
pool.used += toRead;
};

ReadStream.prototype._emitData = function(d) {
if (this._decoder) {
var string = this._decoder.write(d);
if (string.length) this.emit('data', string);
} else {
this.emit('data', d);
}
};


ReadStream.prototype.destroy = function() {
this.readable = false;
this.zf.close();
this.emit('close');
};


ReadStream.prototype.pause = function() {
this.paused = true;
};

ReadStream.prototype.resume = function() {
this.paused = false;

if (this.buffer) {
this._emitData(this.buffer);
this.buffer = null;
}

this._read();
};

exports.ZipFile = zipfile.ZipFile;
2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name" : "zipfile",
"version" : "0.2.3",
"version" : "0.3.0",
"main" : "./lib/index.js",
"description" : "C++ library for handling zipfiles in node",
"keywords" : ["zipfile", "uncompress", "unzip", "zlib"],
Expand Down