Skip to content

Commit

Permalink
1.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
max-mapper committed Oct 17, 2014
0 parents commit 1d14cee
Show file tree
Hide file tree
Showing 7 changed files with 228 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
@@ -0,0 +1,2 @@
node_modules
.DS_Store
17 changes: 17 additions & 0 deletions cli.js
@@ -0,0 +1,17 @@
#!/usr/bin/env node

var path = require('path')
var minimist = require('minimist')
var extract = require('./')

var args = process.argv.slice(2)
var source = args[0]
var dest = args[1] || process.cwd()
if (!source) {
console.error('Usage: extract-zip foo.zip <targetDirectory>')
process.exit(1)
}

extract(source, {dir: dest}, function(err, results) {
if (err) console.error('error!', err)
})
113 changes: 113 additions & 0 deletions index.js
@@ -0,0 +1,113 @@
var fs = require('fs')
var path = require('path')
var async = require('async')
var yauzl = require('yauzl')
var mkdirp = require('mkdirp')
var concat = require('concat-stream')
var debug = require('debug')('extract-zip')

// lolol zips
var MAGIC_SYMLINK_ATTR_1 = 2716663808
var MAGIC_SYMLINK_ATTR_2 = 2716680192

module.exports = function(zipPath, opts, cb) {
debug('opening', zipPath, 'with opts', opts)
yauzl.open(zipPath, {autoClose: false}, function(err, zipfile) {
if (err) return cb(err)

var cancelled = false
var finished = false

var q = async.queue(extractEntry, 1)

q.drain = function() {
if (!finished) return
debug('zip extraction complete')
cb()
}

zipfile.on("entry", function(entry) {
debug('zipfile entry', entry.fileName)
if (/\/$/.test(entry.fileName)) {
// directory file names end with '/'
return
}

q.push(entry, function(err) {
debug('finished processing', entry.fileName, {err: err})
})
})

zipfile.on('end', function() {
finished = true
})

function extractEntry(entry, done) {
if (cancelled) {
debug('skipping entry', entry.fileName, {cancelled: cancelled})
return setImmediate(done)
} else {
debug('extracting entry', entry.fileName)
}

var dest = path.join(opts.dir, entry.fileName)
var destDir = path.dirname(dest)
var symlink = false

if (entry.externalFileAttributes === MAGIC_SYMLINK_ATTR_1 ||
entry.externalFileAttributes === MAGIC_SYMLINK_ATTR_2) {
symlink = true
}

zipfile.openReadStream(entry, function(err, readStream) {
if (err) {
debug('openReadStream error', err)
cancelled = true
return done(err)
}

readStream.on('error', function(err) {
console.log('read err', err)
})

mkdirp(destDir, function(err) {
if (err) {
debug('mkdirp error', destDir, {error: err})
cancelled = true
return done(err)
}

if (symlink) writeSymlink()
else writeStream()
})

function writeStream() {
var writeStream = fs.createWriteStream(dest)
readStream.pipe(writeStream)
writeStream.on('finish', function() {
done()
})
writeStream.on('error', function(err) {
debug('write error', {error: err})
cancelled = true
return done(err)
})
}

// AFAICT the content of the symlink file itself is the symlink target filename string
function writeSymlink() {
readStream.pipe(concat(function(data) {
var link = data.toString()
debug('creating symlink', link, dest)
fs.symlink(link, dest, function(err) {
if (err) cancelled = true
done(err)
})
}))
}

})
}

})
}
36 changes: 36 additions & 0 deletions package.json
@@ -0,0 +1,36 @@
{
"name": "extract-zip",
"version": "1.0.0",
"description": "unzip a zip file into a directory using 100% pure gluten-free organic javascript",
"main": "index.js",
"bin": "cli.js",
"scripts": {
"test": "node test/test.js"
},
"author": "max ogden",
"license": "BSD",
"repository": {
"type": "git",
"url": "git@github.com:maxogden/extract-zip.git"
},
"keywords": [
"unzip",
"zip",
"extract"
],
"bugs": {
"url": "https://github.com/maxogden/extract-zip/issues"
},
"homepage": "https://github.com/maxogden/extract-zip",
"dependencies": {
"debug": "0.7.4",
"async": "0.9.0",
"mkdirp": "0.5.0",
"concat-stream": "^1.4.6",
"through2": "0.6.3",
"yauzl": "^2.0.3"
},
"devDependencies": {
"rimraf": "^2.2.8"
}
}
46 changes: 46 additions & 0 deletions readme.md
@@ -0,0 +1,46 @@
# extract-zip

Streaming unzip written in pure JavaScript. Available as a library or a command line program.

Uses the [`yauzl`](http://npmjs.org/yauzl) ZIP parser.

[![NPM](https://nodei.co/npm/extract-zip.png?global=true)](https://nodei.co/npm/extract-zip/)

## Installation

Get the library:

```
npm install extract-zip --save
```

Install the command line program:

```
npm install extract-zip -g
```

## JS API

```js
var extract = require('extract-zip')
extract(source, {dir: target}, function(err) {

})
```

If not specified, `dir` will default to `process.cwd()`.

## CLI Usage

```
extract-zip foo.zip <targetDirectory>
```

If not specified, `targetDirectory` will default to `process.cwd()`.

## stuff to figure out

-- better handling of magic symlink attr
-- ignore __macosx folder?
-- when to chmod +x (internal file attribute?)
Binary file added test/cats.zip
Binary file not shown.
14 changes: 14 additions & 0 deletions test/test.js
@@ -0,0 +1,14 @@
var os = require('os')
var path = require('path')
var rimraf = require('rimraf')
var extract = require('../')

var source = path.join(__dirname, 'cats.zip')
var target = path.join(os.tmpdir(), 'cat-extract-test')
rimraf.sync(target)

console.log('extracting to', target)

extract(source, {dir: target}, function(err, results) {
console.log('FIN', err, results)
})

0 comments on commit 1d14cee

Please sign in to comment.