├── .gitignore ├── .jshintrc ├── .travis.yml ├── Gruntfile.coffee ├── LICENSE.txt ├── Makefile ├── README.md ├── binding.gyp ├── deps ├── build-libzip.sh └── libzip-0.10.tar.bz2 ├── examples └── test.js ├── lib ├── index.js └── zipper.js ├── package.json ├── src ├── _zipper.cc ├── zipper.cpp └── zipper.hpp └── test ├── addfile_test.coffee ├── common.coffee └── sample-files └── test.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .*.swp 2 | build 3 | .lock-wscript 4 | .DS_Store 5 | *.o 6 | *.a 7 | lib/_zipper.node 8 | deps/libzip-*/ 9 | tmp 10 | node_modules 11 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "es5": true, 4 | "esnext": true, 5 | "bitwise": true, 6 | "curly": true, 7 | "eqeqeq": true, 8 | "eqnull": true, 9 | "immed": true, 10 | "latedef": true, 11 | "newcap": true, 12 | "noarg": true, 13 | "undef": true, 14 | "strict": false, 15 | "trailing": true, 16 | "smarttabs": true 17 | } 18 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.8" 4 | - "0.10" 5 | - "0.11" 6 | before_install: 7 | - sudo apt-get update -qq 8 | - sudo apt-get install -qq libzip-dev 9 | - npm install -g grunt-cli 10 | -------------------------------------------------------------------------------- /Gruntfile.coffee: -------------------------------------------------------------------------------- 1 | module.exports = (grunt) -> 2 | @loadNpmTasks('grunt-contrib-clean') 3 | @loadNpmTasks('grunt-contrib-jshint') 4 | @loadNpmTasks('grunt-contrib-watch') 5 | @loadNpmTasks('grunt-mocha-cli') 6 | @loadNpmTasks('grunt-mkdir') 7 | @loadNpmTasks('grunt-release') 8 | 9 | @initConfig 10 | jshint: 11 | all: ["lib/*.js"] 12 | options: 13 | jshintrc: ".jshintrc" 14 | 15 | clean: 16 | all: ['tmp'] 17 | 18 | mkdir: 19 | all: 20 | options: 21 | create: ['tmp'] 22 | 23 | watch: 24 | all: 25 | files: ['src/*', 'lib/*.js', 'test/**.coffee', 'Gruntfile.coffee', 'Makefile', 'binding.gyp'] 26 | tasks: ['test'] 27 | 28 | mochacli: 29 | options: 30 | files: 'test/*_test.coffee' 31 | compilers: ['coffee:coffee-script'] 32 | spec: 33 | options: 34 | reporter: 'spec' 35 | 36 | @registerTask "make", "Run make", -> 37 | done = @async() 38 | 39 | grunt.util.spawn 40 | cmd: "make" 41 | , (error, result, code) -> 42 | grunt.log.writeln(result.stderr) if result.stderr 43 | grunt.log.writeln(result.stdout) if result.stdout 44 | done(!error) 45 | 46 | @registerTask 'default', ['test'] 47 | @registerTask 'build', ['clean', 'jshint', 'make'] 48 | @registerTask 'package', ['build', 'release'] 49 | @registerTask 'test', ['build', 'mkdir', 'mochacli'] 50 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) 2012-2013 by Ruben Vermeersch 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above 11 | copyright notice, this list of conditions and the following 12 | disclaimer in the documentation and/or other materials provided 13 | with the distribution. 14 | * Neither the name of the author nor the names of other 15 | contributors may be used to endorse or promote products derived 16 | from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | This project also includes code from node-zipper, (c) 2011 Dane 31 | Springmeyer. 32 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | LIBZIP=0.10 2 | 3 | .PHONY:all 4 | 5 | all: lib/_zipper.node 6 | 7 | 8 | build/Makefile: binding.gyp 9 | node-gyp configure 10 | 11 | ifeq ($(shell uname), Darwin) 12 | lib/_zipper.node: deps/libzip-$(LIBZIP) build/Makefile 13 | node-gyp build 14 | 15 | deps/libzip-$(LIBZIP): deps/libzip-$(LIBZIP).tar.bz2 16 | tar xpf $< -C deps 17 | ./deps/build-libzip.sh $(LIBZIP) 18 | endif 19 | 20 | ifeq ($(shell uname), Linux) 21 | lib/_zipper.node: build/Makefile /usr/include/zip.h 22 | node-gyp build 23 | 24 | /usr/include/zip.h: 25 | @echo "No zip headers, install libzip-devel (Fedora etc.) or libzip-dev (Ubuntu etc.)." 26 | @exit 1 27 | endif 28 | 29 | clean: 30 | -rm -rf build deps/libzip-*/ 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Insanely simple zipfile creator for node.js. 2 | 3 | [![Build Status](https://travis-ci.org/rubenv/zipper.png?branch=master)](https://travis-ci.org/rubenv/zipper) 4 | 5 | Developed by Flow Pilots: http://www.flowpilots.com/ 6 | 7 | # Usage 8 | 9 | zipper = require('zipper').Zipper; 10 | 11 | var zipfile = new zipper('/path/to/my/zipfile.zip'); 12 | zipfile.addFile('myfile.txt', '/path/to/myfile.txt', function (err) { 13 | if (err) throw err; 14 | // Do stuff 15 | }); 16 | 17 | The zip file will be created if it does not exist already. 18 | 19 | ## Installation 20 | 21 | You can install the latest tag via npm: 22 | 23 | $ npm install zipper 24 | 25 | Be sure to install `libzip-devel` (Fedora etc.) or `libzip-dev` (Ubuntu etc.) 26 | before installing on Linux. 27 | 28 | ## Credits 29 | 30 | Inspired by node-zipfile (written by Dane Springmeyer). 31 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "type": "loadable_module", 5 | "target_name": "zipper", 6 | "variables": { 7 | "libzip%": "0.10" 8 | }, 9 | "sources": [ 10 | "src/_zipper.cc", 11 | "src/zipper.cpp" 12 | ], 13 | "conditions": [ 14 | [ 'OS=="mac"', { 15 | "include_dirs": [ 16 | "deps/libzip-<(libzip)/lib/" 17 | ], 18 | "libraries": [ 19 | "-lz", 20 | "-L../deps/libzip-<(libzip)/lib/.libs/", 21 | "-lzip" 22 | ], 23 | } ], 24 | [ 'OS=="linux"', { 25 | "libraries": [ 26 | "-lz", 27 | "-lzip" 28 | ], 29 | } ] 30 | ] 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /deps/build-libzip.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd deps/libzip-$1/ 4 | CFLAGS='-fPIC -O3 -DNDEBUG -Wall' ./configure --disable-dependency-tracking --enable-static --disable-shared 5 | make 6 | -------------------------------------------------------------------------------- /deps/libzip-0.10.tar.bz2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenv/zipper/407ca82b495a49c4667235bbc8b1924c39edbf23/deps/libzip-0.10.tar.bz2 -------------------------------------------------------------------------------- /examples/test.js: -------------------------------------------------------------------------------- 1 | zipper = require('../lib/').Zipper; 2 | 3 | var zipfile = new zipper('zipfile.zip'); 4 | zipfile.addFile(__dirname + '/../README.md', 'README.txt', function (err) { 5 | if (err) throw err; 6 | // Do stuff 7 | }); 8 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./zipper'); 2 | -------------------------------------------------------------------------------- /lib/zipper.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var zipper = require('../build/Release/zipper'); 3 | 4 | /* assert ABI compatibility */ 5 | assert.ok(zipper.versions.node === process.versions.node, 'The running node version "' + process.versions.node + '" does not match the node version that zipper was compiled against: "' + zipper.versions.node + '"'); 6 | 7 | // push all C++ symbols into js module 8 | for (var k in zipper) { exports[k] = zipper[k]; } 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zipper", 3 | "version": "0.3.0", 4 | "main": "./lib/index.js", 5 | "description": "Insanely simple zipfile creator for node.js", 6 | "keywords": [ 7 | "zip", 8 | "compress", 9 | "archive", 10 | "libzip" 11 | ], 12 | "url": "http://github.com/rubenv/zipper", 13 | "repositories": [ 14 | { 15 | "type": "git", 16 | "url": "git://github.com/rubenv/zipper.git" 17 | } 18 | ], 19 | "author": "Ruben Vermeersch ", 20 | "contributors": [], 21 | "licenses": [ 22 | { 23 | "type": "BSD", 24 | "url": "https://raw.github.com/rubenv/zipper/master/LICENSE.txt" 25 | } 26 | ], 27 | "dependencies": [], 28 | "directories": { 29 | "examples": "examples", 30 | "src": "src" 31 | }, 32 | "engines": { 33 | "node": ">=0.6.0 <0.12" 34 | }, 35 | "scripts": { 36 | "install": "make all", 37 | "test": "grunt test" 38 | }, 39 | "devDependencies": { 40 | "grunt": "~0.4.1", 41 | "grunt-contrib-jshint": "~0.2.0", 42 | "grunt-mkdir": "~0.1.1", 43 | "grunt-contrib-clean": "~0.4.0", 44 | "grunt-release": "~0.3.1", 45 | "grunt-contrib-watch": "~0.3.1", 46 | "grunt-mocha-cli": "~1.0.1", 47 | "coffee-script": "~1.6.1" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/_zipper.cc: -------------------------------------------------------------------------------- 1 | // v8 2 | #include 3 | 4 | // node 5 | #include 6 | #include 7 | 8 | // zipper 9 | #include "zipper.hpp" 10 | 11 | using namespace node; 12 | using namespace v8; 13 | 14 | extern "C" { 15 | 16 | static void init (Handle target) 17 | { 18 | 19 | Zipper::Initialize(target); 20 | 21 | // zipper version 22 | target->Set(String::NewSymbol("version"), String::New("0.0.1")); 23 | 24 | // versions of deps 25 | Local versions = Object::New(); 26 | versions->Set(String::NewSymbol("node"), String::New(NODE_VERSION+1)); 27 | versions->Set(String::NewSymbol("v8"), String::New(V8::GetVersion())); 28 | target->Set(String::NewSymbol("versions"), versions); 29 | 30 | } 31 | 32 | NODE_MODULE(zipper, init); 33 | } 34 | -------------------------------------------------------------------------------- /src/zipper.cpp: -------------------------------------------------------------------------------- 1 | #include "zipper.hpp" 2 | 3 | // stl 4 | #include 5 | #include 6 | #include 7 | #include 8 | //#include 9 | 10 | #include 11 | #include 12 | 13 | 14 | #define TOSTR(obj) (*String::Utf8Value((obj)->ToString())) 15 | 16 | Persistent Zipper::constructor; 17 | 18 | void Zipper::Initialize(Handle target) { 19 | 20 | HandleScope scope; 21 | 22 | constructor = Persistent::New(FunctionTemplate::New(Zipper::New)); 23 | constructor->InstanceTemplate()->SetInternalFieldCount(1); 24 | constructor->SetClassName(String::NewSymbol("Zipper")); 25 | 26 | // functions 27 | NODE_SET_PROTOTYPE_METHOD(constructor, "addFile", addFile); 28 | 29 | target->Set(String::NewSymbol("Zipper"),constructor->GetFunction()); 30 | } 31 | 32 | Zipper::Zipper(std::string const& file_name) : 33 | ObjectWrap(), 34 | file_name_(file_name), 35 | archive_() {} 36 | 37 | Zipper::~Zipper() { 38 | zip_close(archive_); 39 | } 40 | 41 | Handle Zipper::New(const Arguments& args) 42 | { 43 | HandleScope scope; 44 | 45 | if (!args.IsConstructCall()) 46 | return ThrowException(String::New("Cannot call constructor as function, you need to use 'new' keyword")); 47 | 48 | if (args.Length() != 1 || !args[0]->IsString()) 49 | return ThrowException(Exception::TypeError( 50 | String::New("first argument must be a path to a zipfile"))); 51 | 52 | std::string input_file = TOSTR(args[0]); 53 | struct zip *za; 54 | int err; 55 | char errstr[1024]; 56 | if ((za=zip_open(input_file.c_str(), ZIP_CREATE, &err)) == NULL) { 57 | zip_error_to_str(errstr, sizeof(errstr), err, errno); 58 | std::stringstream s; 59 | s << "cannot open file: " << input_file << " error: " << errstr << "\n"; 60 | return ThrowException(Exception::Error( 61 | String::New(s.str().c_str()))); 62 | } 63 | 64 | Zipper* zf = new Zipper(input_file); 65 | zf->archive_ = za; 66 | zf->Wrap(args.This()); 67 | return args.This(); 68 | 69 | } 70 | 71 | 72 | typedef struct { 73 | Zipper* zf; 74 | struct zip *za; 75 | std::string name; 76 | std::string path; 77 | bool error; 78 | std::string error_name; 79 | std::vector data; 80 | Persistent cb; 81 | } closure_t; 82 | 83 | 84 | Handle Zipper::addFile(const Arguments& args) 85 | { 86 | HandleScope scope; 87 | 88 | if (args.Length() < 3) 89 | return ThrowException(Exception::TypeError( 90 | String::New("requires three arguments, the path of a file, a filename and a callback"))); 91 | 92 | // first arg must be path 93 | if(!args[0]->IsString()) 94 | return ThrowException(Exception::TypeError( 95 | String::New("first argument must be a file path to add to the zip"))); 96 | 97 | // second arg must be name 98 | if(!args[1]->IsString()) 99 | return ThrowException(Exception::TypeError( 100 | String::New("second argument must be a file name to add to the zip"))); 101 | 102 | // last arg must be function callback 103 | if (!args[args.Length()-1]->IsFunction()) 104 | return ThrowException(Exception::TypeError( 105 | String::New("last argument must be a callback function"))); 106 | 107 | std::string path = TOSTR(args[0]); 108 | std::string name = TOSTR(args[1]); 109 | 110 | Zipper* zf = ObjectWrap::Unwrap(args.This()); 111 | zf->Ref(); 112 | 113 | uv_work_t *req = new uv_work_t(); 114 | closure_t *closure = new closure_t(); 115 | 116 | // libzip is not threadsafe so we cannot use the zf->archive_ 117 | // instead we open a new zip archive for each thread 118 | struct zip *za; 119 | int err; 120 | char errstr[1024]; 121 | if ((za=zip_open(zf->file_name_.c_str() , ZIP_CREATE, &err)) == NULL) { 122 | zip_error_to_str(errstr, sizeof(errstr), err, errno); 123 | std::stringstream s; 124 | s << "cannot open file: " << zf->file_name_ << " error: " << errstr << "\n"; 125 | zip_close(za); 126 | return ThrowException(Exception::Error(String::New(s.str().c_str()))); 127 | } 128 | 129 | closure->zf = zf; 130 | closure->za = za; 131 | closure->error = false; 132 | closure->path = path; 133 | closure->name = name; 134 | closure->cb = Persistent::New(Handle::Cast(args[args.Length()-1])); 135 | req->data = closure; 136 | 137 | uv_queue_work(uv_default_loop(), req, _AddFile, (uv_after_work_cb)_AfterAddFile); 138 | return Undefined(); 139 | } 140 | 141 | 142 | void Zipper::_AddFile(uv_work_t *req) 143 | { 144 | closure_t *closure = static_cast(req->data); 145 | 146 | struct zip_source *source = zip_source_file(closure->za, closure->path.c_str(), 0, 0); 147 | if (zip_add(closure->za, closure->name.c_str(), source) < 0) { 148 | std::stringstream s; 149 | s << "Cannot prepare file for add to zip: '" << closure->path << "'\n"; 150 | closure->error = true; 151 | closure->error_name = s.str(); 152 | zip_source_free(source); 153 | } 154 | 155 | if (zip_close(closure->za) < 0) { 156 | std::stringstream s; 157 | s << "Cannot add file to zip: '" << closure->path << "' (" << zip_strerror(closure->za) << ")\n"; 158 | closure->error = true; 159 | closure->error_name = s.str(); 160 | } 161 | } 162 | 163 | 164 | void Zipper::_AfterAddFile(uv_work_t *req) 165 | { 166 | HandleScope scope; 167 | 168 | closure_t *closure = static_cast(req->data); 169 | 170 | TryCatch try_catch; 171 | 172 | if (closure->error) { 173 | Local argv[1] = { Exception::Error(String::New(closure->error_name.c_str())) }; 174 | closure->cb->Call(Context::GetCurrent()->Global(), 1, argv); 175 | } else { 176 | Local argv[1] = { Local::New(Null()) }; 177 | closure->cb->Call(Context::GetCurrent()->Global(), 1, argv); 178 | } 179 | 180 | closure->zf->Unref(); 181 | closure->cb.Dispose(); 182 | delete closure; 183 | delete req; 184 | 185 | if (try_catch.HasCaught()) { 186 | FatalException(try_catch); 187 | //try_catch.ReThrow(); 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/zipper.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __ZIPPER_H__ 2 | #define __ZIPPER_H__ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | // stl 9 | #include 10 | #include 11 | 12 | // libzip 13 | #include 14 | #include 15 | #include 16 | //#include 17 | 18 | using namespace v8; 19 | using namespace node; 20 | 21 | class Zipper: public node::ObjectWrap { 22 | public: 23 | static Persistent constructor; 24 | static void Initialize(Handle target); 25 | static Handle New(const Arguments &args); 26 | 27 | // Async 28 | static Handle addFile(const Arguments& args); 29 | static void _AddFile(uv_work_t *req); 30 | static void _AfterAddFile(uv_work_t *req); 31 | 32 | Zipper(std::string const& file_name); 33 | 34 | private: 35 | ~Zipper(); 36 | std::string const file_name_; 37 | struct zip *archive_; 38 | }; 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /test/addfile_test.coffee: -------------------------------------------------------------------------------- 1 | assert = require 'assert' 2 | zipper = require '..' 3 | fs = require 'fs' 4 | 5 | common = require './common' 6 | 7 | describe 'addFile', -> 8 | it 'can add file', (done) -> 9 | zipfile = new zipper.Zipper('tmp/addfile.zip') 10 | zipfile.addFile 'test/sample-files/test.txt', 'test.txt', (err) -> 11 | assert.equal(err, null) 12 | done(err) 13 | 14 | it 'warns if the file cannot be found', (done) -> 15 | zipfile = new zipper.Zipper('tmp/not-found.zip') 16 | zipfile.addFile 'test/sample-files/not-found.txt', 'test.txt', (err) -> 17 | assert.notEqual(err, null) 18 | assert(!fs.existsSync('tmp/not-found.zip')) 19 | done() 20 | 21 | it 'can be extracted and contains the original file', (done) -> 22 | zipName = 'simple' 23 | 24 | zipfile = new zipper.Zipper('tmp/simple.zip') 25 | zipfile.addFile 'test/sample-files/test.txt', 'test.txt', (err) -> 26 | assert.equal(err, null) 27 | 28 | common.extract zipName, (err) -> 29 | assert.equal(err, null) 30 | common.compareFiles('tmp/simple/test.txt', 'test/sample-files/test.txt') 31 | done() 32 | -------------------------------------------------------------------------------- /test/common.coffee: -------------------------------------------------------------------------------- 1 | assert = require('assert') 2 | exec = require('child_process').exec 3 | fs = require('fs') 4 | 5 | module.exports = 6 | extract: (zipName, cb) -> 7 | fs.mkdirSync('tmp/' + zipName) 8 | exec('unzip tmp/' + zipName + '.zip -d tmp/' + zipName, cb) 9 | 10 | compareFiles: (fileA, fileB) -> 11 | srcA = fs.readFileSync(fileA, 'utf8') 12 | srcB = fs.readFileSync(fileB, 'utf8') 13 | assert.equal(srcA, srcB) 14 | -------------------------------------------------------------------------------- /test/sample-files/test.txt: -------------------------------------------------------------------------------- 1 | test 2 | --------------------------------------------------------------------------------