Skip to content

Commit

Permalink
Merge pull request bower#32 from szwacz/file-mode-preservation
Browse files Browse the repository at this point in the history
Added file mode preservation
  • Loading branch information
sheerun committed Apr 1, 2016
2 parents 867e439 + 63663ad commit 6f06708
Show file tree
Hide file tree
Showing 10 changed files with 197 additions and 50 deletions.
25 changes: 15 additions & 10 deletions lib/extractors.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,23 @@ 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;

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);
});
}

Expand All @@ -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};
});
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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 () {
Expand Down Expand Up @@ -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);
};
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Binary file added test/assets/file-mode-pack/deflate.zip
Binary file not shown.
53 changes: 53 additions & 0 deletions test/assets/file-mode-pack/generate.js
Original file line number Diff line number Diff line change
@@ -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);
39 changes: 39 additions & 0 deletions test/assets/file-mode-pack/spec.json
Original file line number Diff line number Diff line change
@@ -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"
}
Binary file added test/assets/file-mode-pack/store.zip
Binary file not shown.
Binary file modified test/assets/main-test-pack/deflate.zip
Binary file not shown.
62 changes: 42 additions & 20 deletions test/assets/main-test-pack/generate.js
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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 });
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);
Binary file modified test/assets/main-test-pack/store.zip
Binary file not shown.
65 changes: 46 additions & 19 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 () {
Expand Down Expand Up @@ -142,7 +165,7 @@ describe('Extract', function () {
throw err;
}

tmpDir = jetpack.cwd(dir, 'extracted');
tmpDir = jetpack.dir(dir + '/extracted', { mode: '755' });
done();
});
});
Expand All @@ -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);
});
});
});
Expand Down

0 comments on commit 6f06708

Please sign in to comment.