zip-local
Version:
very simple zipping/uzipping of local files and directories in node.js
322 lines (251 loc) • 9.46 kB
JavaScript
var fs = require('graceful-fs');
var path = require('path');
var async = require('async');
var ZippedFS = require('./ZippedFS.js');
/*****************************************************************************************
******************************** PRIVATE HELPER FUNCTIONS ********************************
*****************************************************************************************/
/*
* extracts an unzipped file to the disk asynchrnously
* @param _path {String}: extraction path
* @param jszip {JSZip}: the jszip object containg the unzipped file
* @param callback {Function}: the function to be called when extracting is done
*/
function extract_to(_path, jszip, callback) {
var extraction_path = _path === null ? "./" : path.normalize(_path);
var absolute_extraction_path = path.resolve(extraction_path);
if(extraction_path[extraction_path.length - 1] !== path.sep) {
extraction_path += path.sep;
}
// make sure that the extraction path points to a existing directory
fs.stat(extraction_path, function (err, stats) {
if (err) {
callback(err);
return;
}
if (!stats.isDirectory()) {
callback(new Error(extraction_path + " is not a directory"));
return;
}
// sort out the unzipped file to directories and files
var dirs = [];
var files = [];
for (var name in jszip.files) {
var entry = jszip.files[name];
var extracted_entry_path = path.resolve(
path.join(absolute_extraction_path, name)
);
if (!extracted_entry_path.startsWith(absolute_extraction_path)) {
callback(new Error("Entry is outside the extraction path"))
return;
}
if (entry.dir)
dirs.push(name);
else
files.push(name);
}
// sort the directories ascendingy by level
dirs.sort(function (a, b) {
function dir_sep_count(dir) { return (dir.match(/\//g) || []).length; }
return dir_sep_count(a) - dir_sep_count(b);
});
// create the directories
async.eachSeries(dirs, function (dir, end_iteration) {
//check first if the directory exists
fs.stat(extraction_path + dir, function(err, stats) {
if((err && err.code === "ENOENT") || !stats.isDirectory()) {
// create the directory if it doesn't exist
fs.mkdir(extraction_path + dir, function (err) {
// end of this iteration
if(err) {
end_iteration(err);
return;
}
end_iteration();
});
}
else if(err && err.code !== "ENOENT") {
end_iteration(err);
return;
}
else {
end_iteration();
}
});
}, function (err) {
if (err) {
callback(err);
return;
}
// write the files
async.each(files, function (file, end_iteration) {
var data = jszip.file(file).asNodeBuffer();
fs.writeFile(extraction_path + file, data, function (err) {
if (err) {
end_iteration(err);
return;
}
// end of this iteration
end_iteration();
});
}, function (err) {
if(err) {
callback(err);
return;
}
// invoke the callback
callback(null);
});
});
});
}
/*
* extracts an unzipped file to the disk synchrnously
* @param _path {String}: extraction path
* @param jszip {JSZip}: the jszip object containg the unzipped file
*/
function extract_to_sync(_path, jszip) {
var extraction_path = _path === null ? "./" : path.normalize(_path);
var absolute_extraction_path = path.resolve(extraction_path);
if(extraction_path[extraction_path.length - 1] !== path.sep) {
extraction_path += path.sep;
}
// make sure that the extraction path points to a existing directory
var stats;
try { stats = fs.statSync(extraction_path); }
catch (err) { throw new Error(extraction_path + " doesn't exist"); }
if (!stats.isDirectory())
throw new Error(extraction_path + " is not a directory");
// sort out the unzipped file to directories and files
var dirs = [];
var files = [];
for (var name in jszip.files) {
var entry = jszip.files[name];
var extracted_entry_path = path.resolve(
path.join(absolute_extraction_path, name)
);
if (!extracted_entry_path.startsWith(absolute_extraction_path))
throw new Error("Entry is outside the extraction path")
if (entry.dir)
dirs.push(name);
else
files.push(name);
}
// sort the directories ascendingy by level
dirs.sort(function (a, b) {
function dir_sep_count(dir) { return (dir.match(/\//g) || []).length; }
return dir_sep_count(a) - dir_sep_count(b);
});
// create the directories
dirs.forEach(function (dir) {
try {
var stats = fs.statSync(extraction_path + dir);
if(!stats.isDirectory()) {
throw new Error("!dir");
}
}
catch(err) {
if(err.code === "ENOENT" || err.message === "!dir") {
fs.mkdirSync(extraction_path + dir);
}
else {
throw err;
}
}
});
// write the files
files.forEach(function (file) {
var data = jszip.file(file).asNodeBuffer();
fs.writeFileSync(extraction_path + file, data);
});
}
/*****************************************************************************************
********************************** MODULE PUBLIC APIS ************************************
***** THE MODULE CONTAINS THE APIS THAT DEAL WITH EXPORTING AFTER ZIPPING IS COMPLETE ****
*****************************************************************************************/
/*
* constructs a ZipExport object out of a JSZip object
* @param jszip {JSZip}: the JSZip object
* @param unzipped {Boolean}: a flag to indicate if the source jszip was from an unzipped file
* @param async {Boolean}: a flag to indicate whether the zipping was asynchrnously
*/
function ZipExport(jszip, unzipped, async) {
// hold the JSZip for exporting
this.content = jszip;
// determines if the exported will be compressed
this.compressed = false;
// determines if the source is an unzipped file
this.src_unzipped = unzipped ? true : false;
// determines if the exported file will be written asynchronously
this.save_async = async ? true : false;
}
/*
* returns the associated JSZip object for low level operations
* @returns {JSZip}: the associated JSZip object
*/
ZipExport.prototype.lowLevel = function () {
return this.content;
}
/*
* sets the object so that the exported format will be compressed
*/
ZipExport.prototype.compress = function () {
if(!this.src_unzipped)
this.compressed = true;
return this;
}
/*
* returns the zipped/unzipped file representation in memory
* @return {Buffer|ZippedFS}: Buffer for a zipped file, a ZippedFS object for unzipped
*/
ZipExport.prototype.memory = function() {
if (!this.src_unzipped) {
// generate the zipped buffer
var buff = this.content.generate({
type: "nodebuffer",
compression: this.compressed ? "DEFLATE" : undefined
});
return new Buffer.from(buff);
}
else {
// return a ZippedFS object of the unzipped file
return new ZippedFS(this.content);
}
}
/*
* writes the zipped/unzipped file to disk
* @param _path {String}: path to the output file
* @param _callback {Function}: the function to be called when the save is done (only in async zipping)
*/
ZipExport.prototype.save = function (_path, _callback) {
var callback = _callback || function () { };
if (!this.src_unzipped) {
// generate the zipped buffer
var buff = this.content.generate({
type: "nodebuffer",
compression: this.compressed ? "DEFLATE" : undefined
});
var normalized_path = path.normalize(_path);
// write the new file
if (!this.save_async) {
fs.writeFileSync(normalized_path, buff);
}
else
fs.writeFile(normalized_path, buff, function (err) {
if (err) {
callback(err);
return;
}
//invoke the callback
callback(null);
});
}
else {
// extract the unzipped file to given directory
if (!this.save_async)
extract_to_sync(_path ? _path : null, this.content);
else
extract_to(_path ? _path : null, this.content, callback);
}
}
module.exports = ZipExport;