зеркало из https://github.com/mozilla/brackets.git
Fix #306 - Support importing .zip archives
This commit is contained in:
Родитель
079e94eb57
Коммит
486ba1d7de
|
@ -43,3 +43,6 @@
|
|||
[submodule "src/bramble/thirdparty/EventEmitter"]
|
||||
path = src/bramble/thirdparty/EventEmitter
|
||||
url = https://github.com/Wolfy87/EventEmitter.git
|
||||
[submodule "src/thirdparty/jszip"]
|
||||
path = src/thirdparty/jszip
|
||||
url = https://github.com/Stuk/jszip.git
|
||||
|
|
|
@ -21,6 +21,70 @@ define(function (require, exports, module) {
|
|||
mkdir: function(path, callback) {
|
||||
proxyCall("mkdir", {args: [path]}, callback);
|
||||
},
|
||||
/**
|
||||
* Recursively creates the directory at `path`. If the parent
|
||||
* of `path` does not exist, it will be created. From:
|
||||
* https://github.com/filerjs/filer/blob/develop/src/shell/shell.js
|
||||
*/
|
||||
mkdirp: function(path, callback) {
|
||||
callback = callback || function(){};
|
||||
|
||||
// We don't have direct access to Filer.Errors, fake it.
|
||||
function filerError(code, msg) {
|
||||
var err = new Error(msg);
|
||||
err.code = code;
|
||||
return err;
|
||||
}
|
||||
|
||||
if(!path) {
|
||||
return callback(filerError("EINVAL", "missing path argument"));
|
||||
} else if (path === "/") {
|
||||
return callback();
|
||||
}
|
||||
|
||||
function _mkdirp(path, callback) {
|
||||
var parent;
|
||||
|
||||
proxyFS.stat(path, function(err, stat) {
|
||||
if(stat) {
|
||||
if(stat.type === "DIRECTORY") {
|
||||
callback();
|
||||
} else if (stat.type === "FILE") {
|
||||
callback(filerError("ENOTDIR", path));
|
||||
}
|
||||
} else if (err && err.code !== "ENOENT") {
|
||||
callback(err);
|
||||
} else {
|
||||
parent = Path.dirname(path);
|
||||
if(parent === "/") {
|
||||
proxyFS.mkdir(path, function (err) {
|
||||
if (err && err.code !== "EEXIST") {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
callback();
|
||||
});
|
||||
} else {
|
||||
_mkdirp(parent, function (err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
proxyFS.mkdir(path, function (err) {
|
||||
if (err && err.code !== "EEXIST") {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
callback();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_mkdirp(path, callback);
|
||||
},
|
||||
rmdir: function(path, callback) {
|
||||
proxyCall("rmdir", {args: [path]}, callback);
|
||||
},
|
||||
|
@ -31,6 +95,11 @@ define(function (require, exports, module) {
|
|||
proxyCall("rename", {args: [oldPath, newPath]}, callback);
|
||||
},
|
||||
readFile: function(path, options, callback) {
|
||||
if(typeof options === "function") {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
|
||||
// Always do binary reads, and decode in callback if necessary
|
||||
proxyCall("readFile", {args: [path, {encoding: null}]}, function(err, data) {
|
||||
if(err) {
|
||||
|
@ -47,6 +116,11 @@ define(function (require, exports, module) {
|
|||
});
|
||||
},
|
||||
writeFile: function(path, data, encoding, callback) {
|
||||
if(typeof encoding === "function") {
|
||||
callback = encoding;
|
||||
encoding = null;
|
||||
}
|
||||
|
||||
// Always do binary write, and send ArrayBuffer over transport
|
||||
if (typeof(data) === "string") {
|
||||
data = new FilerBuffer(data, "utf8");
|
||||
|
|
|
@ -0,0 +1,217 @@
|
|||
|
||||
/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */
|
||||
/*global define */
|
||||
|
||||
define(function (require, exports, module) {
|
||||
"use strict";
|
||||
|
||||
var CommandManager = require("command/CommandManager");
|
||||
var Commands = require("command/Commands");
|
||||
var async = require("filesystem/impls/filer/lib/async");
|
||||
var StartupState = require("bramble/StartupState");
|
||||
var JSZip = require("thirdparty/jszip/dist/jszip.min");
|
||||
var BlobUtils = require("filesystem/impls/filer/BlobUtils");
|
||||
var Filer = require("filesystem/impls/filer/BracketsFiler");
|
||||
var Buffer = Filer.Buffer;
|
||||
var Path = Filer.Path;
|
||||
var fs = Filer.fs();
|
||||
|
||||
// zipfile can be a path (string) to a zipfile, or raw binary data.
|
||||
function unzip(zipfile, options, callback) {
|
||||
if(typeof options === 'function') {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
options = options || {};
|
||||
callback = callback || function(){};
|
||||
|
||||
if(!zipfile) {
|
||||
callback(new Error("missing zipfile argument"));
|
||||
return;
|
||||
}
|
||||
|
||||
var root = StartupState.project("root");
|
||||
var destination = Path.resolve(options.destination || root);
|
||||
|
||||
// Mac and Windows clutter zip files with extra files/folders we don't need
|
||||
function skipFile(filename) {
|
||||
var basename = Path.basename(filename);
|
||||
|
||||
// Skip OS X additions we don't care about in the browser fs
|
||||
if(/^__MACOSX\//.test(filename)) {
|
||||
// http://superuser.com/questions/104500/what-is-macosx-folder
|
||||
return true;
|
||||
}
|
||||
if(basename === ".DS_Store") {
|
||||
// https://en.wikipedia.org/wiki/.DS_Store
|
||||
return true;
|
||||
}
|
||||
if(/^\._/.test(basename)) {
|
||||
// http://apple.stackexchange.com/questions/14980/why-are-dot-underscore-files-created-and-how-can-i-avoid-them
|
||||
return true;
|
||||
}
|
||||
|
||||
// Skip Windows additiosn we don't care about in the browser fs
|
||||
if(/(Tt)humbs\.db/.test(basename)) {
|
||||
// https://en.wikipedia.org/wiki/Windows_thumbnail_cache
|
||||
return true;
|
||||
}
|
||||
if(basename === "desktop.ini") {
|
||||
// http://www.computerhope.com/issues/ch001060.htm
|
||||
return true;
|
||||
}
|
||||
|
||||
// Include this file, don't skip.
|
||||
return false;
|
||||
}
|
||||
|
||||
function _unzip(data){
|
||||
// TODO: it would be great to move this bit to a worker.
|
||||
var archive = new JSZip(data);
|
||||
var filenames = [];
|
||||
|
||||
archive.filter(function(relPath, file) {
|
||||
if(skipFile(file.name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var isDir = file.options.dir;
|
||||
filenames.push({
|
||||
absPath: Path.join(destination, file.name),
|
||||
isDirectory: isDir,
|
||||
data: isDir ? null : new Buffer(file.asArrayBuffer())
|
||||
});
|
||||
});
|
||||
|
||||
function decompress(path, callback) {
|
||||
if(path.isDirectory) {
|
||||
fs.mkdirp(path.absPath, callback);
|
||||
} else {
|
||||
fs.writeFile(path.absPath, path.data, callback);
|
||||
}
|
||||
}
|
||||
|
||||
async.eachSeries(filenames, decompress, function(err) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
// Generate Blob URLs for all the files we imported so they work
|
||||
// in the preview.
|
||||
BlobUtils.preload(root, function(err) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
// Update the file tree to show the new files
|
||||
CommandManager.execute(Commands.FILE_REFRESH);
|
||||
callback();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if(typeof zipfile === "string") {
|
||||
fs.readFile(Path.resolve(root, zipfile), function(err, data) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
_unzip(data);
|
||||
});
|
||||
} else {
|
||||
// zipfile is raw zip data, process it directly
|
||||
_unzip(zipfile);
|
||||
}
|
||||
}
|
||||
|
||||
function zip(zipfile, paths, options, callback) {
|
||||
if(typeof options === 'function') {
|
||||
callback = options;
|
||||
options = {};
|
||||
}
|
||||
options = options || {};
|
||||
callback = callback || function(){};
|
||||
|
||||
if(!zipfile) {
|
||||
return callback(new Error("missing zipfile argument"));
|
||||
}
|
||||
if(!paths) {
|
||||
return callback(new Error("missing paths argument"));
|
||||
}
|
||||
if(typeof paths === "string") {
|
||||
paths = [paths];
|
||||
}
|
||||
|
||||
var root = StartupState.project("root");
|
||||
zipfile = Path.resolve(root, zipfile);
|
||||
|
||||
function toRelPath(path) {
|
||||
// Make path relative within the zip
|
||||
return path.replace(/^\//, '');
|
||||
}
|
||||
|
||||
function addFile(path, callback) {
|
||||
fs.readFile(path, function(err, data) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
archive.file(toRelPath(path), data.buffer, {binary: true});
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
function addDir(path, callback) {
|
||||
fs.readdir(path, function(err, list) {
|
||||
// Add the directory itself
|
||||
archive.folder(toRelPath(path));
|
||||
|
||||
if(!options.recursive) {
|
||||
return 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(root, path);
|
||||
|
||||
fs.stat(path, function(err, stats) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if(stats.type === "DIRECTORY") {
|
||||
addDir(path, callback);
|
||||
} else {
|
||||
addFile(path, callback);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var archive = new JSZip();
|
||||
|
||||
// Make sure the zipfile doesn't already exist.
|
||||
fs.exists(zipfile, function(exists) {
|
||||
if(exists) {
|
||||
return callback(new Error('zipfile already exists', zipfile));
|
||||
}
|
||||
|
||||
async.eachSeries(paths, add, function(err) {
|
||||
if(err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var compressed = archive.generate({type: 'arraybuffer'});
|
||||
fs.writeFile(zipfile, compressed, callback);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
exports.zip = zip;
|
||||
exports.unzip = unzip;
|
||||
});
|
|
@ -0,0 +1 @@
|
|||
Subproject commit f8534d472f2348aa9d186af59301bc01336ef7a5
|
|
@ -46,7 +46,8 @@ define(function (require, exports, module) {
|
|||
Path = Filer.Path,
|
||||
Content = require("filesystem/impls/filer/lib/content"),
|
||||
LanguageManager = require("language/LanguageManager"),
|
||||
StartupState = require("bramble/StartupState");
|
||||
StartupState = require("bramble/StartupState"),
|
||||
unzip = require("filesystem/impls/filer/ZipUtils").unzip;
|
||||
|
||||
// 3M size limit for imported files
|
||||
var byteLimit = 3 * 1024 * 1000;
|
||||
|
@ -181,6 +182,76 @@ define(function (require, exports, module) {
|
|||
var pathList = [];
|
||||
var errorList = [];
|
||||
|
||||
function shouldOpenFile(filename, encoding) {
|
||||
var ext = Path.extname(filename).replace(/^\./, "");
|
||||
var language = LanguageManager.getLanguageForExtension(ext);
|
||||
var id = language && language.getId();
|
||||
var isImage = id === "image" || id === "svg";
|
||||
|
||||
return isImage || encoding === "utf8";
|
||||
}
|
||||
|
||||
function handleRegularFile(deferred, file, filename, buffer, encoding) {
|
||||
file.write(buffer, {encoding: encoding}, function(err) {
|
||||
if (err) {
|
||||
deferred.reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
// See if this file is worth trying to open in the editor or not
|
||||
if(shouldOpenFile(filename, encoding)) {
|
||||
pathList.push(filename);
|
||||
}
|
||||
|
||||
deferred.resolve();
|
||||
});
|
||||
}
|
||||
|
||||
function handleZipFile(deferred, file, filename, buffer, encoding) {
|
||||
var basename = Path.basename(filename);
|
||||
var message = "<p>Do you want to extract the contents of the zip file: <b>" +
|
||||
basename + "</b>?</p>" +
|
||||
"<p><em>NOTE: This can take some time.</em><p>";
|
||||
|
||||
// TODO: l10n, UX audit
|
||||
Dialogs.showModalDialog(
|
||||
DefaultDialogs.DIALOG_ID_INFO,
|
||||
"Unzip file",
|
||||
message,
|
||||
[
|
||||
{
|
||||
className : Dialogs.DIALOG_BTN_CLASS_NORMAL,
|
||||
id : Dialogs.DIALOG_BTN_CANCEL,
|
||||
text : "No"
|
||||
},
|
||||
{
|
||||
className : Dialogs.DIALOG_BTN_CLASS_PRIMARY,
|
||||
id : Dialogs.DIALOG_BTN_OK,
|
||||
text : "Yes"
|
||||
}
|
||||
]
|
||||
)
|
||||
.done(function(id) {
|
||||
if (id === Dialogs.DIALOG_BTN_OK) {
|
||||
// TODO: we need to give a spinner or something to indicate we're working
|
||||
unzip(buffer, function(err) {
|
||||
if (err) {
|
||||
deferred.reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
Dialogs.showModalDialog(
|
||||
DefaultDialogs.DIALOG_ID_INFO,
|
||||
"Unzip Completed Successfully",
|
||||
"Successfully unzipped <b>" + basename + "</b>."
|
||||
).then(deferred.resolve, deferred.reject);
|
||||
});
|
||||
} else {
|
||||
handleRegularFile(deferred, file, filename, buffer, encoding);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function prepareDropPaths(fileList) {
|
||||
// Convert FileList object to an Array with all image files first, then CSS
|
||||
// followed by HTML files at the end, since we need to write any .css, .js, etc.
|
||||
|
@ -267,15 +338,12 @@ define(function (require, exports, module) {
|
|||
buffer = buffer.toString();
|
||||
}
|
||||
|
||||
file.write(buffer, {encoding: encoding}, function(err) {
|
||||
if (err) {
|
||||
deferred.reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
pathList.push(filename);
|
||||
deferred.resolve();
|
||||
});
|
||||
// Special-case .zip files, so we can offer to extract the contents
|
||||
if(Path.extname(filename) === ".zip") {
|
||||
handleZipFile(deferred, file, filename, buffer, encoding);
|
||||
} else {
|
||||
handleRegularFile(deferred, file, filename, buffer, encoding);
|
||||
}
|
||||
};
|
||||
|
||||
// Deal with error cases, for example, trying to drop a folder vs. file
|
||||
|
|
Загрузка…
Ссылка в новой задаче