diff --git a/src/extensions/default/UploadFiles/UploadFilesDialog.js b/src/extensions/default/UploadFiles/UploadFilesDialog.js index 8d92f897370..09c082f2342 100644 --- a/src/extensions/default/UploadFiles/UploadFilesDialog.js +++ b/src/extensions/default/UploadFiles/UploadFilesDialog.js @@ -97,10 +97,7 @@ define(function (require, exports, module) { // Turn off the other buttons $fromComputerButton.off("click", self._handleFromComputer.bind(self)); $takeSelfieButton.off("click", self._handleTakeSelfie.bind(self)); - - // Switch to the upload spinner - $dragFilesAreaDiv.hide(); - $uploadFilesDiv.show(); + self.hide(); }, onfilesdone: function() { self.hide(); diff --git a/src/filesystem/impls/filer/ArchiveUtils.js b/src/filesystem/impls/filer/ArchiveUtils.js index eb88b2c30ff..92a240fd5c5 100644 --- a/src/filesystem/impls/filer/ArchiveUtils.js +++ b/src/filesystem/impls/filer/ArchiveUtils.js @@ -18,6 +18,14 @@ define(function (require, exports, module) { var Buffer = Filer.Buffer; var Path = Filer.Path; var fs = Filer.fs(); + var Dialogs = require("widgets/Dialogs"); + var DefaultDialogs = require("widgets/DefaultDialogs"); + var Strings = require("strings"); + var StringUtils = require("utils/StringUtils"); + + var CANCEL_OPERATION = -1, + OVERWRITE_OPERATION = 1, + KEEP_EXISTING_OPERATION = 2; // Mac and Windows clutter zip files with extra files/folders we don't need function skipFile(filename) { @@ -56,6 +64,48 @@ define(function (require, exports, module) { CommandManager.execute(Commands.FILE_REFRESH).always(callback); } + function showOverwriteWarning(path, callback) { + var filepath = stripRoot(path); + + Dialogs.showModalDialog( + DefaultDialogs.DIALOG_ID_INFO, + Strings.FILE_EXISTS_HEADER, + StringUtils.format(Strings.DND_FILE_REPLACE, filepath), + [{ + className : Dialogs.DIALOG_BTN_CLASS_NORMAL, + id : Dialogs.DIALOG_BTN_CANCEL, + text : Strings.CANCEL + }, + { + className : Dialogs.DIALOG_BTN_CLASS_NORMAL, + id : Dialogs.DIALOG_BTN_IMPORT, + text : Strings.USE_IMPORTED + }, + { + className : Dialogs.DIALOG_BTN_CLASS_PRIMARY, + id : Dialogs.DIALOG_BTN_OK, + text : Strings.KEEP_EXISTING + }] + ).getPromise().then(function (id) { + var result; + if (id === Dialogs.DIALOG_BTN_IMPORT) { + result = OVERWRITE_OPERATION; + } else if (id === Dialogs.DIALOG_BTN_OK) { + result = KEEP_EXISTING_OPERATION; + } else if (id === Dialogs.DIALOG_BTN_CANCEL) { + result = CANCEL_OPERATION; + } + callback(null, result); + }, callback); + } + + function stripRoot(path) { + var root = StartupState.project("root"); + var rootRegex = new RegExp("^" + root + "\/?"); + + return path.replace(rootRegex, ""); + } + // zipfile can be a path (string) to a zipfile, or raw binary data. function unzip(zipfile, options, callback) { var projectPrefix = StartupState.project("zipFilenamePrefix").replace(/\/?$/, "/"); @@ -105,28 +155,46 @@ define(function (require, exports, module) { var basedir = Path.dirname(path.absPath); if(path.isDirectory) { - fs.mkdirp(path.absPath, callback); - } else { - // XXX: some zip files don't seem to be structured such that dirs - // get created before files. Create base dir if not there yet. - fs.stat(basedir, function(err, stats) { + return fs.mkdirp(path.absPath, callback); + } + + // XXX: some zip files don't seem to be structured such that dirs + // get created before files. Create base dir if not there yet. + fs.mkdirp(basedir, function (err) { + if (err) { + return callback(err); + } + + fs.stat(path.absPath, function(err, stats) { if(err) { if(err.code !== "ENOENT") { return callback(err); } - fs.mkdirp(basedir, function(err) { - if(err) { - return callback(err); - } + return FilerUtils.writeFileAsBinary(path.absPath, path.data, callback); + } - FilerUtils.writeFileAsBinary(path.absPath, path.data, callback); - }); - } else { - FilerUtils.writeFileAsBinary(path.absPath, path.data, callback); + if (stats.type !== "FILE") { + return callback(); } + + showOverwriteWarning(path.absPath, function (err, result) { + if (err) { + return callback(err); + } + + if (result === OVERWRITE_OPERATION) { + FilerUtils.writeFileAsBinary(path.absPath, path.data, callback); + } else if (result === KEEP_EXISTING_OPERATION) { + callback(); + } else if (result === CANCEL_OPERATION) { + callback(new Error("Operation Cancelled")); + } else { + callback(new Error("Unknown result: " + result)); + } + }); }); - } + }); } async.eachSeries(filenames, decompress, function(err) { @@ -245,11 +313,35 @@ define(function (require, exports, module) { } fs.mkdirp(basedir, function(err) { - if(err && err.code !== "EEXIST") { + if(err) { return callback(err); } - FilerUtils.writeFileAsBinary(path, new Buffer(data), callback); + fs.stat(path, function (err, stats) { + if (err && err.code !== "ENOENT") { + return callback(err); + } + + if (stats.type !== "FILE") { + return callback(); + } + + showOverwriteWarning(path, function (err, result) { + if (err) { + return callback(err); + } + + if (result === OVERWRITE_OPERATION) { + FilerUtils.writeFileAsBinary(path, new Buffer(data), callback); + } else if (result === KEEP_EXISTING_OPERATION) { + callback(); + } else if (result === CANCEL_OPERATION) { + callback(new Error("Operation Cancelled")); + } else { + callback(new Error("Unknown result: " + result)); + } + }); + }); }); } @@ -262,6 +354,10 @@ define(function (require, exports, module) { function writeCallback(err) { if(err) { + if (err.message === "Operation Cancelled") { + finish(err); + return; + } console.error("[Bramble untar] couldn't extract file", err); } diff --git a/src/filesystem/impls/filer/lib/LegacyFileImport.js b/src/filesystem/impls/filer/lib/LegacyFileImport.js index f68b4d112dd..c7a26829b55 100644 --- a/src/filesystem/impls/filer/lib/LegacyFileImport.js +++ b/src/filesystem/impls/filer/lib/LegacyFileImport.js @@ -38,6 +38,7 @@ define(function (require, exports, module) { StringUtils = require("utils/StringUtils"), Filer = require("filesystem/impls/filer/BracketsFiler"), Path = Filer.Path, + fs = Filer.fs(), Content = require("filesystem/impls/filer/lib/content"), ArchiveUtils = require("filesystem/impls/filer/ArchiveUtils"); @@ -57,13 +58,7 @@ define(function (require, exports, module) { return Content.isImage(Path.extname(filename)) || encoding === "utf8"; } - function handleRegularFile(deferred, file, filename, buffer, encoding) { - // Don't write thing like .DS_Store, thumbs.db, etc. - if(ArchiveUtils.skipFile(filename)) { - deferred.resolve(); - return; - } - + function saveFile(deferred, file, filename, buffer, encoding) { file.write(buffer, {encoding: encoding}, function(err) { if (err) { errorList.push({path: filename, error: "unable to write file: " + err.message || ""}); @@ -80,6 +75,50 @@ define(function (require, exports, module) { }); } + function handleRegularFile(deferred, file, filename, buffer, encoding) { + // Don't write thing like .DS_Store, thumbs.db, etc. + if(ArchiveUtils.skipFile(filename)) { + deferred.resolve(); + return; + } + + fs.exists(filename, function(doesExist) { + if (!doesExist) { + // File doesn't exist. Save without prompt + saveFile(deferred, file, filename, buffer, encoding); + return; + } + + // File exists. Prompt user for action + Dialogs.showModalDialog( + DefaultDialogs.DIALOG_ID_INFO, + Strings.FILE_EXISTS_HEADER, + StringUtils.format(Strings.DND_FILE_REPLACE, FileUtils.getBaseName(filename)), + [{ + className : Dialogs.DIALOG_BTN_CLASS_NORMAL, + id : Dialogs.DIALOG_BTN_CANCEL, + text : Strings.CANCEL + }, + { + className : Dialogs.DIALOG_BTN_CLASS_NORMAL, + id : Dialogs.DIALOG_BTN_IMPORT, + text : Strings.USE_IMPORTED + }, + { + className : Dialogs.DIALOG_BTN_CLASS_PRIMARY, + id : Dialogs.DIALOG_BTN_OK, + text : Strings.KEEP_EXISTING + }] + ) + .done(function(id) { + if (id === Dialogs.DIALOG_BTN_IMPORT) { + // Override file per user's request + saveFile(deferred, file, filename, buffer, encoding); + } + }); + }); + } + function handleZipFile(deferred, file, filename, buffer, encoding) { var basename = Path.basename(filename); diff --git a/src/filesystem/impls/filer/lib/WebKitFileImport.js b/src/filesystem/impls/filer/lib/WebKitFileImport.js index db8b8409836..a21fb0b29a5 100644 --- a/src/filesystem/impls/filer/lib/WebKitFileImport.js +++ b/src/filesystem/impls/filer/lib/WebKitFileImport.js @@ -35,8 +35,10 @@ define(function (require, exports, module) { FileSystem = require("filesystem/FileSystem"), FileUtils = require("file/FileUtils"), Strings = require("strings"), + StringUtils = require("utils/StringUtils"), Filer = require("filesystem/impls/filer/BracketsFiler"), Path = Filer.Path, + fs = Filer.fs(), Content = require("filesystem/impls/filer/lib/content"), ArchiveUtils = require("filesystem/impls/filer/ArchiveUtils"); @@ -125,7 +127,7 @@ define(function (require, exports, module) { }); } - function handleRegularFile(deferred, file, filename, buffer, encoding) { + function saveFile(deferred, file, filename, buffer, encoding) { file.write(buffer, {encoding: encoding}, function(err) { if (err) { onError(deferred, filename, err); @@ -141,11 +143,54 @@ define(function (require, exports, module) { }); } + function handleRegularFile(deferred, file, filename, buffer, encoding) { + fs.exists(filename, function(doesExist) { + if (!doesExist) { + // File doesn't exist. Save without prompt + saveFile(deferred, file, filename, buffer, encoding); + return; + } + + // File exists. Prompt user for action + Dialogs.showModalDialog( + DefaultDialogs.DIALOG_ID_INFO, + Strings.FILE_EXISTS_HEADER, + StringUtils.format(Strings.DND_FILE_REPLACE, FileUtils.getBaseName(filename)), + [ + { + className : Dialogs.DIALOG_BTN_CLASS_NORMAL, + id : Dialogs.DIALOG_BTN_CANCEL, + text : Strings.CANCEL + }, + { + className : Dialogs.DIALOG_BTN_CLASS_NORMAL, + id : Dialogs.DIALOG_BTN_IMPORT, + text : Strings.USE_IMPORTED + }, + { + className : Dialogs.DIALOG_BTN_CLASS_PRIMARY, + id : Dialogs.DIALOG_BTN_OK, + text : Strings.KEEP_EXISTING + } + ] + ) + .done(function(id) { + if (id === Dialogs.DIALOG_BTN_IMPORT) { + // Override file per user's request + saveFile(deferred, file, filename, buffer, encoding); + } + }); + }); + } + function handleZipFile(deferred, file, filename, buffer, encoding) { var basename = Path.basename(filename); ArchiveUtils.unzip(buffer, { root: parentPath }, function(err) { if (err) { + if (err.message === "Operation Cancelled") { + return deferred.resolve(); + } onError(deferred, filename, new Error(Strings.DND_ERROR_UNZIP)); return; } @@ -159,6 +204,9 @@ define(function (require, exports, module) { ArchiveUtils.untar(buffer, { root: parentPath }, function(err) { if (err) { + if (err.message === "Operation Cancelled") { + return deferred.resolve(); + } onError(deferred, filename, new Error(Strings.DND_ERROR_UNTAR)); return; } diff --git a/src/widgets/Dialogs.js b/src/widgets/Dialogs.js index a527480f381..ccdc010664e 100644 --- a/src/widgets/Dialogs.js +++ b/src/widgets/Dialogs.js @@ -45,6 +45,7 @@ define(function (require, exports, module) { DIALOG_BTN_OK = "ok", DIALOG_BTN_DONTSAVE = "dontsave", DIALOG_BTN_SAVE_AS = "save_as", + DIALOG_BTN_IMPORT = "import", DIALOG_CANCELED = "_canceled", DIALOG_BTN_DOWNLOAD = "download"; @@ -447,6 +448,7 @@ define(function (require, exports, module) { window.addEventListener("resize", setDialogMaxSize); exports.DIALOG_BTN_CANCEL = DIALOG_BTN_CANCEL; + exports.DIALOG_BTN_IMPORT = DIALOG_BTN_IMPORT; exports.DIALOG_BTN_OK = DIALOG_BTN_OK; exports.DIALOG_BTN_DONTSAVE = DIALOG_BTN_DONTSAVE; exports.DIALOG_BTN_SAVE_AS = DIALOG_BTN_SAVE_AS;