diff --git a/src/shell/shell.js b/src/shell/shell.js index c619270..f7e9439 100644 --- a/src/shell/shell.js +++ b/src/shell/shell.js @@ -527,6 +527,7 @@ define(function(require) { Shell.prototype.zip = function(zipfile, paths, options, callback) { var fs = this.fs; + var sh = this; if(typeof options === 'function') { callback = options; options = {}; @@ -551,7 +552,7 @@ define(function(require) { return new TextEncoder('utf8').encode(s); } - function add(path, callback) { + function addFile(path, callback) { fs.readFile(path, function(err, data) { if(err) return callback(err); @@ -562,12 +563,52 @@ define(function(require) { }); } - var zip = new Zlib.Zip(); - async.eachSeries(paths, add, function(err) { - if(err) return callback(err); + function addDir(path, callback) { + fs.readdir(path, function(err, list) { + // Add the directory itself (with no data) and a trailing / + zip.addFile([], { + filename: encode(path + '/'), + compressionMethod: Zlib.Zip.CompressionMethod.STORE + }); - var compressed = zip.compress(); - fs.writeFile(zipfile, compressed, callback); + if(!options.recursive) { + callback(); + } + + // Add all children of this dir, too + async.eachSeries(list, function(entry, callback) { + add(Path.join(path, entry), callback); + }, callback); + }); + } + + function add(path, callback) { + path = Path.resolve(sh.cwd, path); + fs.stat(path, function(err, stats) { + if(err) return callback(err); + + if(stats.isDirectory()) { + addDir(path, callback); + } else { + addFile(path, callback); + } + }); + } + + var zip = new Zlib.Zip(); + + // Make sure the zipfile doesn't already exist. + fs.stat(zipfile, function(err, stats) { + if(stats) { + return callback(new Errors.EEXIST('zipfile already exists')); + } + + async.eachSeries(paths, add, function(err) { + if(err) return callback(err); + + var compressed = zip.compress(); + fs.writeFile(zipfile, compressed, callback); + }); }); }; diff --git a/tests/spec/shell/unzip.spec.js b/tests/spec/shell/unzip.spec.js index 7befeb5..c8cd669 100644 --- a/tests/spec/shell/unzip.spec.js +++ b/tests/spec/shell/unzip.spec.js @@ -1,11 +1,12 @@ define(["Filer", "util"], function(Filer, util) { - describe('FileSystemShell.unzip', function() { + describe('FileSystemShell.zip() and unzip()', function() { beforeEach(util.setup); afterEach(util.cleanup); it('should be a function', function() { var shell = util.shell(); + expect(shell.zip).to.be.a('function'); expect(shell.unzip).to.be.a('function'); }); @@ -49,6 +50,40 @@ define(["Filer", "util"], function(Filer, util) { }); }); + it('should download and unzip the contents of a zip file with a specified destination', function(done) { + var fs = util.fs(); + var shell = fs.Shell(); + var Path = Filer.Path; + var url = "test-file.txt.zip"; + var contents = "This is a test file used in some of the tests.\n"; + + fs.writeFile('/original', contents, function(err) { + if(err) throw err; + + shell.wget(url, {filename: url}, function(err, path) { + if(err) throw err; + + shell.tempDir(function(err, tmp) { + if (err) throw err; + + shell.unzip(path, { destination: tmp }, function(err) { + if(err) throw err; + + fs.readFile('/original', function(err, originalData) { + if(err) throw err; + + fs.readFile(Path.join(tmp, 'test-file.txt'), function(err, data) { + if(err) throw err; + expect(util.typedArrayEqual(data, originalData)).to.be.true; + done(); + }); + }); + }); + }); + }); + }); + }); + it('should be able to zip and unzip a file', function(done) { var fs = util.fs(); var file = '/test-file.txt'; @@ -150,5 +185,98 @@ define(["Filer", "util"], function(Filer, util) { }); }); + it('should be able to re-create (unzip/zip) a deep tree structure in a zip', function(done) { + // test-dir.zip has the following structure: + // + // test-dir/ + // ├── test-dir2 + // │ └── test-file.txt + // ├── test-file.txt + // └── test-file2.txt + + var fs = util.fs(); + var shell = fs.Shell(); + var url = "test-dir.zip"; + var Path = Filer.Path; + var contents = "This is a test file used in some of the tests.\n"; + + function confirmFile(filename, callback) { + filename = Path.join('/unzipped', filename); + fs.readFile(filename, 'utf8', function(err, data) { + if(err) { + console.error('Expected ' + filename + ' to exist.'); + expect(false).to.be.true; + return callback(err); + } + expect(data).to.equal(contents); + callback(); + }); + } + + function confirmDir(dirname, callback) { + dirname = Path.join('/unzipped', dirname); + fs.stat(dirname, function(err, stats) { + if(err) { + console.error('Expected ' + dirname + ' to exist.'); + expect(false).to.be.true; + return callback(err); + } + expect(stats.isDirectory()).to.be.true; + callback(); + }); + } + + shell.wget(url, {filename: url}, function(err, path) { + if(err) throw err; + + shell.unzip(path, function(err) { + if(err) throw err; + + shell.zip('/test-dir2.zip', '/test-dir', { recursive: true }, function(err) { + if(err) throw err; + + shell.mkdirp('/unzipped', function(err) { + if(err) throw err; + + shell.unzip('/test-dir2.zip', { destination: '/unzipped' }, function(err) { + if(err) throw err; + + // Forgive the crazy indenting, trying to match tree structure ;) + confirmDir('test-dir', function() { + confirmDir('test-dir/test-dir2', function() { + confirmFile('test-dir/test-dir2/test-file.txt', function() { + confirmFile('test-dir/test-file.txt', function() { + confirmFile('test-dir/test-file2.txt', function() { + done(); + });});});});}); + + }); + }); + }); + }); + }); + }); + + it('should fail if the zipfile already exists', function(done) { + var fs = util.fs(); + var shell = fs.Shell(); + var file = "/file"; + var zipfile = "/zipfile.zip"; + var contents = "This is a test file used in some of the tests.\n"; + + fs.writeFile(file, contents, function(err) { + if(err) throw err; + + shell.zip(zipfile, file, function(err) { + if(err) throw err; + shell.zip(zipfile, file, function(err) { + expect(err).to.exist; + expect(err.code).to.equal('EEXIST'); + done(); + }); + }); + }); + }); + }); });