diff --git a/lib/extractors.js b/lib/extractors.js index 32b0a5b..5ea9389 100644 --- a/lib/extractors.js +++ b/lib/extractors.js @@ -16,8 +16,11 @@ var stat = Q.denodeify(fs.stat); // Use a cache of promises for building the directory tree. This allows us to // correctly queue up file extractions for after their path has been created, // avoid trying to create the path twice and still be async. -var mkdir = function (dir, cache) { +var mkdir = function (dir, cache, mode) { dir = path.normalize(path.resolve(process.cwd(), dir) + path.sep); + if (mode === undefined) { + mode = parseInt('777', 8) & (~process.umask()); + } if (!cache[dir]) { var parent; @@ -25,11 +28,11 @@ var mkdir = function (dir, cache) { if (fs.existsSync(dir)) { parent = new Q(); } else { - parent = mkdir(path.dirname(dir), cache); + parent = mkdir(path.dirname(dir), cache, mode); } cache[dir] = parent.then(function () { - return mkpath(dir); + return mkpath(dir, mode); }); } @@ -39,7 +42,7 @@ var mkdir = function (dir, cache) { // Utility methods for writing output files var extractors = { folder: function (folder, destination, zip) { - return mkdir(destination, zip.dirCache) + return mkdir(destination, zip.dirCache, folder.mode) .then(function () { return {folder: folder.path}; }); @@ -52,12 +55,14 @@ var extractors = { } else if (file.uncompressedSize <= zip.chunkSize) { writer = function () { return zip.getBuffer(file._offset, file._offset + file.uncompressedSize) - .then(writeFile.bind(null, destination)); + .then(function (buffer) { + return writeFile(destination, buffer, { mode: file.mode }); + }); }; } else { var input = new stream.Readable(); input.wrap(fs.createReadStream(zip.filename, {start: file._offset, end: file._offset + file.uncompressedSize - 1})); - writer = pipePromise.bind(null, input, destination); + writer = pipePromise.bind(null, input, destination, { mode: file.mode }); } return mkdir(path.dirname(destination), zip.dirCache) @@ -77,7 +82,7 @@ var extractors = { return zip.getBuffer(file._offset, file._offset + file._maxSize) .then(inflateRaw) .then(function (buffer) { - return writeFile(destination, buffer); + return writeFile(destination, buffer, { mode: file.mode }); }); } else { // For node 0.8 we need to create the Zlib stream and attach @@ -87,7 +92,7 @@ var extractors = { input.wrap(fs.createReadStream(zip.filename, {start: file._offset})); var inflater = input.pipe(zlib.createInflateRaw({highWaterMark: 32 * 1024})); - return pipePromise(inflater, destination); + return pipePromise(inflater, destination, { mode: file.mode }); } }) .then(function () { @@ -154,9 +159,9 @@ var getLinkLocation = function (file, destination, zip, basePath) { }); }; -var pipePromise = function (input, destination) { +var pipePromise = function (input, destination, options) { var deferred = Q.defer(); - var output = fs.createWriteStream(destination); + var output = fs.createWriteStream(destination, options); var errorHandler = function (error) { deferred.reject(error); }; diff --git a/package.json b/package.json index 128aa42..01ce53b 100644 --- a/package.json +++ b/package.json @@ -34,9 +34,10 @@ "touch": "0.0.3" }, "devDependencies": { + "archiver": "^0.13.1", "chai": "^1.10.0", "coveralls": "^2.11.2", - "fs-jetpack": "~0.5.2", + "fs-jetpack": "^0.5.3", "grunt": "^0.4.1", "grunt-cli": "^0.1.13", "grunt-contrib-jshint": "^0.11.0", diff --git a/test/assets/file-mode-pack/deflate.zip b/test/assets/file-mode-pack/deflate.zip new file mode 100644 index 0000000..2eecdb1 Binary files /dev/null and b/test/assets/file-mode-pack/deflate.zip differ diff --git a/test/assets/file-mode-pack/generate.js b/test/assets/file-mode-pack/generate.js new file mode 100644 index 0000000..fc193e5 --- /dev/null +++ b/test/assets/file-mode-pack/generate.js @@ -0,0 +1,53 @@ +// This script generates zip files inside this folder. + +'use strict'; + +var jetpack = require('fs-jetpack'); +var archiver = require('archiver'); + +var mainDir = jetpack.cwd(__dirname); + +// ------------------------------------------------------- +// Generate files and folders which we next will compress +// ------------------------------------------------------- + +var extractedDir = mainDir.dir('extracted', { empty: true, mode: '755' }); +extractedDir + .dir('dir1', { mode: '755' }) + .cwd('..') + .dir('dir2', { mode: '711' }) + .cwd('..') + .file('file1', { content: 'abc', mode: '755' }) + .file('file2', { content: 'xyz', mode: '711' }); + +// ------------------------------------------------------- +// Generate spec file +// ------------------------------------------------------- + +// Generate spec file to which we can later compare +// our extracted stuff during tests. +var spec = mainDir.inspectTree('extracted', { checksum: 'sha1', mode: true }); +mainDir.write('spec.json', spec, { jsonIndent: 2 }); + +// ------------------------------------------------------- +// Compress to zip files +// ------------------------------------------------------- + +var compress = function (dest, useStore) { + var output = mainDir.createWriteStream(dest); + output.on('close', function () { + console.log('Archive ' + dest + ' created.'); + }); + var archive = archiver('zip', { store: useStore }); + archive.on('error', function (err){ + console.log(err); + }); + archive.pipe(output); + archive.bulk([ + { expand: true, cwd: extractedDir.path(), src: ['**'] } + ]); + archive.finalize(); +}; + +compress('store.zip', true); +compress('deflate.zip', false); diff --git a/test/assets/file-mode-pack/spec.json b/test/assets/file-mode-pack/spec.json new file mode 100644 index 0000000..d26d004 --- /dev/null +++ b/test/assets/file-mode-pack/spec.json @@ -0,0 +1,39 @@ +{ + "name": "extracted", + "type": "dir", + "mode": 16877, + "size": 6, + "children": [ + { + "name": "dir1", + "type": "dir", + "mode": 16877, + "size": 0, + "children": [], + "sha1": null + }, + { + "name": "dir2", + "type": "dir", + "mode": 16841, + "size": 0, + "children": [], + "sha1": null + }, + { + "name": "file1", + "type": "file", + "size": 3, + "sha1": "a9993e364706816aba3e25717850c26c9cd0d89d", + "mode": 33261 + }, + { + "name": "file2", + "type": "file", + "size": 3, + "sha1": "66b27417d37e024c46526c2f6d358a754fc552f3", + "mode": 33225 + } + ], + "sha1": "ac6f83cc555959a5817f979deb9e845d4d6962d2" +} \ No newline at end of file diff --git a/test/assets/file-mode-pack/store.zip b/test/assets/file-mode-pack/store.zip new file mode 100644 index 0000000..4f6bbeb Binary files /dev/null and b/test/assets/file-mode-pack/store.zip differ diff --git a/test/assets/main-test-pack/deflate.zip b/test/assets/main-test-pack/deflate.zip index b4c9ff6..115993b 100644 Binary files a/test/assets/main-test-pack/deflate.zip and b/test/assets/main-test-pack/deflate.zip differ diff --git a/test/assets/main-test-pack/generate.js b/test/assets/main-test-pack/generate.js index 35847bf..c92b9ee 100644 --- a/test/assets/main-test-pack/generate.js +++ b/test/assets/main-test-pack/generate.js @@ -1,19 +1,15 @@ -// This is how this pack was generated. -// You can use this code to amend it, or to create other packs for different -// test (then of course you have manually compress it). -// This chain of commands does the compression on Ubuntu: -/* -node generate.js -cd extracted -zip -r -0 ../store.zip . -zip -r ../deflate.zip . -cd .. - -*/ +// This script generates zip files inside this folder. 'use strict'; var jetpack = require('fs-jetpack'); +var archiver = require('archiver'); + +var mainDir = jetpack.cwd(__dirname); + +// ------------------------------------------------------- +// Generate files and folders which we next will compress +// ------------------------------------------------------- var fillWithSillyBytes = function (buf) { // Very predictable pattern leads to high compression @@ -28,18 +24,44 @@ var zero = new Buffer(0); var oneKib = fillWithSillyBytes(new Buffer(1024)); var twoMib = fillWithSillyBytes(new Buffer(1024 * 1024 * 2)); -// Generate the files structure -var root = jetpack.dir('extracted', { empty: true }); -root +var extractedDir = mainDir.dir('extracted', { empty: true }); +extractedDir + .dir('empty') + .cwd('..') .dir('dir1') .dir('dir2') .file('0B', { content: zero }) .file('1KiB', { content: oneKib }) - .file('2MiB', { content: twoMib }) -root - .dir('empty'); + .file('2MiB', { content: twoMib }); + +// ------------------------------------------------------- +// Generate spec file +// ------------------------------------------------------- // Generate spec file to which we can later compare // our extracted stuff during tests. -var spec = jetpack.inspectTree('extracted', { checksum: 'sha1' }); -jetpack.write('spec.json', spec, { jsonIndent: 2 }); \ No newline at end of file +var spec = mainDir.inspectTree('extracted', { checksum: 'sha1' }); +mainDir.write('spec.json', spec, { jsonIndent: 2 }); + +// ------------------------------------------------------- +// Compress to zip files +// ------------------------------------------------------- + +var compress = function (dest, useStore) { + var output = mainDir.createWriteStream(dest); + output.on('close', function () { + console.log('Archive ' + dest + ' created.'); + }); + var archive = archiver('zip', { store: useStore }); + archive.on('error', function (err){ + console.log(err); + }); + archive.pipe(output); + archive.bulk([ + { expand: true, cwd: extractedDir.path(), src: ['**'] } + ]); + archive.finalize(); +}; + +compress('store.zip', true); +compress('deflate.zip', false); diff --git a/test/assets/main-test-pack/store.zip b/test/assets/main-test-pack/store.zip index 241b854..72872d6 100644 Binary files a/test/assets/main-test-pack/store.zip and b/test/assets/main-test-pack/store.zip differ diff --git a/test/test.js b/test/test.js index f7c9ee6..8edf56e 100644 --- a/test/test.js +++ b/test/test.js @@ -7,20 +7,43 @@ var DecompressZip = require('../lib/decompress-zip'); var assetsDir = jetpack.cwd(__dirname, 'assets'); -var samples = [ - // main-test-pack - // Most common stuff you may want to extract. - { - // Default deflate algorithm - file: 'main-test-pack/deflate.zip', - treeInspect: 'main-test-pack/spec.json' - }, - { - // "Store" (no compression, just merge stuff together) - file: 'main-test-pack/store.zip', - treeInspect: 'main-test-pack/spec.json' - } -]; +var samples = []; + +// ------------------------------------------ +// main-test-pack +// Most common stuff you may want to extract. + +// Default deflate algorithm +samples.push({ + file: 'main-test-pack/deflate.zip', + treeInspect: 'main-test-pack/spec.json', + inspectOptions: {} +}); +// "Store" (no compression, just merge stuff together) +samples.push({ + file: 'main-test-pack/store.zip', + treeInspect: 'main-test-pack/spec.json', + inspectOptions: {} +}); + +// ------------------------------------------ +// file-mode-pack +// Test if files preserve theirs unix file mode when extracted. +// This test doesn't make sense on Windows platform, so exlude it there. +if (process.platform !== 'win32') { + // Default deflate algorithm + samples.push({ + file: 'file-mode-pack/deflate.zip', + treeInspect: 'file-mode-pack/spec.json', + inspectOptions: { mode: true } + }); + // "Store" (no compression, just merge stuff together) + samples.push({ + file: 'file-mode-pack/store.zip', + treeInspect: 'file-mode-pack/spec.json', + inspectOptions: { mode: true } + }); +} describe('Smoke test', function () { it('should find the public interface', function () { @@ -142,7 +165,7 @@ describe('Extract', function () { throw err; } - tmpDir = jetpack.cwd(dir, 'extracted'); + tmpDir = jetpack.dir(dir + '/extracted', { mode: '755' }); done(); }); }); @@ -163,13 +186,17 @@ describe('Extract', function () { zip.extract({path: tmpDir.path()}); }); - it('should have the same output files as expected', function (done) { - tmpDir.inspectTreeAsync('.', { checksum: 'sha1' }) + it('extracted files should match the spec', function (done) { + var options = sample.inspectOptions; + options.checksum = 'sha1'; + + tmpDir.inspectTreeAsync('.', options) .then(function (inspect) { var validInspect = assetsDir.read(sample.treeInspect, 'json'); - assert.deepEqual(inspect, validInspect, 'extracted files matches the spec'); + assert.deepEqual(inspect, validInspect, 'extracted files are matching the spec'); done(); - }).catch(done); + }) + .catch(done); }); }); });