diff --git a/plugins/c9.ide.download/download.js b/plugins/c9.ide.download/download.js index 05c3fdfa..dd6a585f 100644 --- a/plugins/c9.ide.download/download.js +++ b/plugins/c9.ide.download/download.js @@ -2,7 +2,7 @@ define(function(require, exports, module) { "use strict"; main.consumes = [ - "Plugin", "c9", "ui", "menus", "tree", "info", "vfs" + "Plugin", "c9", "ui", "menus", "tree", "info", "vfs", "preferences", "settings" ]; main.provides = ["download"]; return main; @@ -15,7 +15,12 @@ define(function(require, exports, module) { var tree = imports.tree; var vfs = imports.vfs; var info = imports.info; - + var prefs = imports.preferences; + var settings = imports.settings; + + var SETTING_NAME = "downloadFilesAs"; + var SETTING_PATH = "user/general/@" + SETTING_NAME; + /***** Initialization *****/ var plugin = new Plugin("Ajax.org", main.consumes); @@ -40,6 +45,28 @@ define(function(require, exports, module) { onclick: download }), 140, plugin); }); + + // Preferences + prefs.add({ + "General" : { + "Tree & Navigate" : { + "Download Files As" : { + type: "dropdown", + path: SETTING_PATH, + items: [ + { caption : "auto", value : "auto" }, + { caption : "tar.gz", value : "tar.gz" }, + { caption : "zip", value : "zip" } + ], + position: 5000 + } + } + } + }, plugin); + + settings.on("read", function() { + settings.setDefaults("user/general", [[SETTING_NAME, "auto"]]); + }, plugin); } function download() { @@ -55,25 +82,40 @@ define(function(require, exports, module) { if (node.isFolder && node.path == "/") downloadProject(); else if (paths.length > 1) - vfs.download(paths); + downloadPaths(paths); else if (node.isFolder) downloadFolder(node.path); else downloadFile(node.path); - + } - + function downloadProject() { - vfs.download("/", info.getWorkspace().name + ".tar.gz"); + vfs.download("/", info.getWorkspace().name + getArchiveFileExtension()); + } + + function downloadPaths(paths) { + vfs.download(paths, info.getWorkspace().name + getArchiveFileExtension()); } function downloadFolder(path) { - vfs.download(path.replace(/\/*$/, "/")); + var withTrailingSlash = path.replace(/\/*$/, "/"); + var parts = withTrailingSlash.split("/"); + var lastPart = parts[parts.length - 2]; + vfs.download(withTrailingSlash, lastPart + getArchiveFileExtension()); } - + function downloadFile(path) { vfs.download(path.replace(/\/*$/, ""), null, true); } + + function getArchiveFileExtension() { + var downloadFilesAs = settings.get(SETTING_PATH); + if (downloadFilesAs === 'auto' || !downloadFilesAs) { + downloadFilesAs = /Win/.test(navigator.platform) ? 'zip' : 'tar.gz'; + } + return '.' + downloadFilesAs; + } /***** Lifecycle *****/ diff --git a/plugins/c9.vfs.server/download.js b/plugins/c9.vfs.server/download.js index 6ac1da92..b1dbea6f 100644 --- a/plugins/c9.vfs.server/download.js +++ b/plugins/c9.vfs.server/download.js @@ -80,41 +80,62 @@ define(function(require, exports, module) { }); } else { - // TODO add support for downloding as zip on windows - // var cwd; - // var args = ["-r", "-"]; - // paths.forEach(function(path) { - // if (!path) return; - // var dir = Path.dirname(path); - // if (!cwd) cwd = dir; - // var name = Path.relative(cwd, path); - // if (name[0] == "-") name = "./" + name; - // args.push(name); - // }); - // vfs.spawn("zip", { args: args, cwd: cwd } - - var args = ["-zcf", "-"]; - paths.forEach(function(path) { + var executable, args, contentType; + + if (/\.zip$/.test(filename)) { + executable = "zip"; + args = ["-r", "-", "--"]; + contentType = "application/zip" + } + else { + executable = "tar"; + args = ["-zcf", "-", "--"]; + contentType = "application/x-gzip" + } + + // Find the longest common parent directory of all the paths. + var cwd = null; + paths.forEach(function (path) { if (!path) return; var dir = Path.dirname(path); - var name = Path.basename(path); - if (name[0] == "-") - name = "--add-file=" + name; - args.push("-C" + dir, name); + if (!cwd) { + cwd = dir; + } + else { + var relative = Path.relative(cwd, dir).split(Path.sep); + var i = 0; + while (relative[i] === '..') { + cwd = Path.resolve(cwd, '..'); + i++; + } + } }); - vfs.spawn("tar", { args: args }, function (err, meta) { + paths.forEach(function(path) { + if (!path) return; + path = Path.relative(cwd, path); + // tar misinterprets the Windows path separator as an escape sequence, so use forward slash. + if (Path.sep === '\\') { + path = path.replace(/\\/g, '/'); + } + args.push(path); + }); + + vfs.spawn(executable, { + args: args, + cwd: cwd + }, function (err, meta) { if (err) return next(err); - + process = meta.process; - - // once we receive data on stdout pipe it to the response + + // once we receive data on stdout pipe it to the response process.stdout.once("data", function (data) { if (res.headerSent) return; res.writeHead(200, { - "Content-Type": "application/x-gzip", + "Content-Type": contentType, "Content-Disposition": filenameHeader }); res.write(data); @@ -125,7 +146,7 @@ define(function(require, exports, module) { process.stderr.on("data", function (data) { stderr += data; }); - + process.on("exit", function(code, signal) { if (res.headerSent) return; @@ -133,16 +154,16 @@ define(function(require, exports, module) { var err; if (code == 127) { err = new error.PreconditionFailed( - "Your instance seems to be missing the 'tar' utility\n" + + "Your instance seems to be missing the '" + executable + "' utility\n" + "If you are using an SSH workspace, please do:\n" + - " 'sudo apt-get install tar'"); + " 'sudo apt-get install " + executable + "'"); } else if (code) { err = new error.InternalServerError( - "'tar' utility failed with exit code " + code + + "'" + executable + "' utility failed with exit code " + code + " and stderr:/n'" + stderr + "'"); } else if (signal) { err = new error.InternalServerError( - "'tar' utility was terminated by signal " + signal + "'" + executable + "' utility was terminated by signal " + signal ); } diff --git a/plugins/c9.vfs.server/download_test.js b/plugins/c9.vfs.server/download_test.js index 5f9109c6..ab170687 100755 --- a/plugins/c9.vfs.server/download_test.js +++ b/plugins/c9.vfs.server/download_test.js @@ -53,8 +53,9 @@ describe(__filename, function(){ describe("download", function() { - it("should download", function(next) { + it("should download as tar", function(next) { tmp.dir({unsafeCleanup: true}, function(err, path) { + path = path.replace(/\w:/, ''); var filename = path + "/download.tar.gz"; var file = fs.createWriteStream(filename); http.get("http://localhost:8787/?download=download.tar.gz", function(res) { @@ -77,21 +78,22 @@ describe(__filename, function(){ }); }); - it("should download sub directory", function(next) { + it("should download sub directory as tar", function(next) { tmp.dir({unsafeCleanup: true}, function(err, path) { + path = path.replace(/\w:/, ''); assert.equal(err, null); var filename = path + "/download.tar.gz"; var file = fs.createWriteStream(filename); - http.get("http://localhost:8787/views?download=download.tar.gz", function(res) { + http.get("http://localhost:8787/test?download=download.tar.gz", function(res) { res.pipe(file); res.on("end", function() { - execFile("tar", ["-zxvf", filename, "views/status.html.ejs"], {cwd: path}, function(err) { + execFile("tar", ["-zxvf", filename, "test/dir1/testdata1.txt"], {cwd: path}, function(err) { assert.equal(err, null); assert.equal( - fs.readFileSync(__dirname + "/views/status.html.ejs", "utf8"), - fs.readFileSync(path + "/views/status.html.ejs", "utf8") + fs.readFileSync(__dirname + "/test/dir1/testdata1.txt", "utf8"), + fs.readFileSync(path + "/test/dir1/testdata1.txt", "utf8") ); next(); }); @@ -102,22 +104,180 @@ describe(__filename, function(){ it("should download without specifying a name", function(next) { tmp.dir({unsafeCleanup: true}, function(err, path) { + path = path.replace(/\w:/, ''); assert.equal(err, null); var filename = path + "/download.tar.gz"; var file = fs.createWriteStream(filename); - http.get("http://localhost:8787/views?download", function(res) { + http.get("http://localhost:8787/test?download", function(res) { assert.equal(res.headers["content-type"], "application/x-gzip"); - assert.equal(res.headers["content-disposition"], "attachment; filename*=utf-8''views.tar.gz"); + assert.equal(res.headers["content-disposition"], "attachment; filename*=utf-8''test.tar.gz"); res.pipe(file); res.on("end", function() { - execFile("tar", ["-zxvf", filename, "views/status.html.ejs"], {cwd: path}, function(err) { + execFile("tar", ["-zxvf", filename, "test/dir1/testdata1.txt"], {cwd: path}, function(err) { assert.equal(err, null); assert.equal( - fs.readFileSync(__dirname + "/views/status.html.ejs", "utf8"), - fs.readFileSync(path + "/views/status.html.ejs", "utf8") + fs.readFileSync(__dirname + "/test/dir1/testdata1.txt", "utf8"), + fs.readFileSync(path + "/test/dir1/testdata1.txt", "utf8") + ); + next(); + }); + }); + }); + }); + }); + + it("should download several files in same directory as tar", function(next) { + tmp.dir({unsafeCleanup: true}, function(err, path) { + path = path.replace(/\w:/, ''); + assert.equal(err, null); + + var filename = path + "/download.tar.gz"; + var file = fs.createWriteStream(filename); + http.get("http://localhost:8787/test/dir2/testdata2a.txt,/test/dir2/testdata2b.txt?download=download.tar.gz", function(res) { + res.pipe(file); + res.on("end", function() { + execFile("tar", ["-zxvf", filename], {cwd: path}, function(err) { + assert.equal(err, null); + assert.equal( + fs.readFileSync(__dirname + "/test/dir2/testdata2a.txt", "utf8"), + fs.readFileSync(path + "/testdata2a.txt", "utf8") + ); + assert.equal( + fs.readFileSync(__dirname + "/test/dir2/testdata2b.txt", "utf8"), + fs.readFileSync(path + "/testdata2b.txt", "utf8") + ); + next(); + }); + }); + }); + }); + }); + + it("should download several files in different directories as tar", function(next) { + tmp.dir({unsafeCleanup: true}, function(err, path) { + path = path.replace(/\w:/, ''); + assert.equal(err, null); + + var filename = path + "/download.tar.gz"; + var file = fs.createWriteStream(filename); + http.get("http://localhost:8787/test/dir1/testdata1.txt,/test/dir2/testdata2a.txt?download=download.tar.gz", function(res) { + res.pipe(file); + res.on("end", function() { + execFile("tar", ["-zxvf", filename], {cwd: path}, function(err) { + assert.equal(err, null); + assert.equal( + fs.readFileSync(__dirname + "/test/dir1/testdata1.txt", "utf8"), + fs.readFileSync(path + "/dir1/testdata1.txt", "utf8") + ); + assert.equal( + fs.readFileSync(__dirname + "/test/dir2/testdata2a.txt", "utf8"), + fs.readFileSync(path + "/dir2/testdata2a.txt", "utf8") + ); + next(); + }); + }); + }); + }); + }); + + it("should download as zip", function(next) { + tmp.dir({unsafeCleanup: true}, function(err, path) { + path = path.replace(/\w:/, ''); + var filename = path + "/download.zip"; + var file = fs.createWriteStream(filename); + http.get("http://localhost:8787/?download=download.zip", function(res) { + assert.equal(res.headers["content-type"], "application/zip"); + assert.equal(res.headers["content-disposition"], "attachment; filename*=utf-8''download.zip"); + + res.pipe(file); + + res.on("end", function() { + execFile("unzip", [filename, "c9.vfs.server/download.js"], {cwd: path}, function(err, stdout, stderr) { + assert.equal(err, null); + assert.equal( + fs.readFileSync(__dirname + "/download.js", "utf8"), + fs.readFileSync(path + "/c9.vfs.server/download.js", "utf8") + ); + next(); + }); + }); + }); + }); + }); + + it("should download sub directory as zip", function(next) { + tmp.dir({unsafeCleanup: true}, function(err, path) { + path = path.replace(/\w:/, ''); + assert.equal(err, null); + + var filename = path + "/download.zip"; + var file = fs.createWriteStream(filename); + http.get("http://localhost:8787/test?download=download.zip", function(res) { + res.pipe(file); + + res.on("end", function() { + execFile("unzip", [filename, "test/dir1/testdata1.txt"], {cwd: path}, function(err) { + assert.equal(err, null); + assert.equal( + fs.readFileSync(__dirname + "/test/dir1/testdata1.txt", "utf8"), + fs.readFileSync(path + "/test/dir1/testdata1.txt", "utf8") + ); + next(); + }); + }); + }); + }); + }); + + it("should download several files in same directory as zip", function(next) { + tmp.dir({unsafeCleanup: true}, function(err, path) { + path = path.replace(/\w:/, ''); + assert.equal(err, null); + + var filename = path + "/download.zip"; + var file = fs.createWriteStream(filename); + http.get("http://localhost:8787/test/dir2/testdata2a.txt,/test/dir2/testdata2b.txt?download=download.zip", function(res) { + res.pipe(file); + res.on("end", function() { + execFile("unzip", [filename], {cwd: path}, function(err) { + assert.equal(err, null); + assert.equal( + fs.readFileSync(__dirname + "/test/dir2/testdata2a.txt", "utf8"), + fs.readFileSync(path + "/testdata2a.txt", "utf8") + ); + assert.equal( + fs.readFileSync(__dirname + "/test/dir2/testdata2b.txt", "utf8"), + fs.readFileSync(path + "/testdata2b.txt", "utf8") + ); + next(); + }); + }); + }); + }); + }); + + it("should download several files in different directories as zip", function(next) { + tmp.dir({unsafeCleanup: true}, function(err, path) { + path = path.replace(/\w:/, ''); + assert.equal(err, null); + + var filename = path + "/download.zip"; + var file = fs.createWriteStream(filename); + http.get("http://localhost:8787/test/dir1/testdata1.txt,/test/dir2/testdata2a.txt?download=download.zip", function(res) { + res.pipe(file); + res.on("end", function() { + execFile("unzip", [filename], {cwd: path}, function(err) { + assert.equal(err, null); + assert.equal( + fs.readFileSync(__dirname + "/test/dir1/testdata1.txt", "utf8"), + fs.readFileSync(path + "/dir1/testdata1.txt", "utf8") + ); + assert.equal( + fs.readFileSync(__dirname + "/test/dir2/testdata2a.txt", "utf8"), + fs.readFileSync(path + "/dir2/testdata2a.txt", "utf8") ); next(); }); diff --git a/plugins/c9.vfs.server/test/dir1/testdata1.txt b/plugins/c9.vfs.server/test/dir1/testdata1.txt new file mode 100644 index 00000000..e558cabb --- /dev/null +++ b/plugins/c9.vfs.server/test/dir1/testdata1.txt @@ -0,0 +1 @@ +test data 1 diff --git a/plugins/c9.vfs.server/test/dir2/testdata2a.txt b/plugins/c9.vfs.server/test/dir2/testdata2a.txt new file mode 100644 index 00000000..db0f784c --- /dev/null +++ b/plugins/c9.vfs.server/test/dir2/testdata2a.txt @@ -0,0 +1 @@ +test data 2a diff --git a/plugins/c9.vfs.server/test/dir2/testdata2b.txt b/plugins/c9.vfs.server/test/dir2/testdata2b.txt new file mode 100644 index 00000000..37dc0027 --- /dev/null +++ b/plugins/c9.vfs.server/test/dir2/testdata2b.txt @@ -0,0 +1 @@ +test data 2b