diff --git a/plugins/c9.vfs.standalone/www/test.js b/plugins/c9.vfs.standalone/www/test.js index 189e78dc..17d9b1ed 100644 --- a/plugins/c9.vfs.standalone/www/test.js +++ b/plugins/c9.vfs.standalone/www/test.js @@ -114,8 +114,9 @@ define(function(require, exports, module) { }); }); } - onload && onload(); - + setTimeout(function() { + onload && onload(); + }); }); if (app) { app.on("service", function(name, plugin) { diff --git a/plugins/node_modules/vfs-local/localfs.js b/plugins/node_modules/vfs-local/localfs.js index e0342b88..fc1fc779 100644 --- a/plugins/node_modules/vfs-local/localfs.js +++ b/plugins/node_modules/vfs-local/localfs.js @@ -14,9 +14,9 @@ var getMime = (function(simpleMime) { return function(path) { var mime = simpleMime(path); if (typeof mime != "string") - return "application/octet-stream" + return "application/octet-stream"; return mime; - } + }; })(require("simple-mime")()); var vm = require("vm"); var exists = fs.exists || require("path").exists; @@ -69,9 +69,16 @@ function logToFile(message){ }); } +var auditLog = function(description, data) { +} + //////////////////////////////////////////////////////////////////////////////// module.exports = function setup(fsOptions) { + var debug = fsOptions.debug || false; + + auditLog = fsOptions.auditLog || auditLog; + var pty; if (fsOptions.nodePath) { process.env.NODE_PATH = fsOptions.nodePath; @@ -80,7 +87,7 @@ module.exports = function setup(fsOptions) { if (!fsOptions.nopty) { // on darwin trying to load binary for a wrong version crashes the process [(fsOptions.nodePath || process.env.HOME + "/.c9/node_modules") + "/pty.js", - "pty.js", "pty.nw.js"].some(function(p) { + "pty.js"].some(function(p) { try { pty = require(p); return true; @@ -510,19 +517,27 @@ module.exports = function setup(fsOptions) { }); } - function getMetadata(path, options, callback){ + function resolveMetaPath(path, options, callback) { if (path.charAt(0) == "~") path = join(process.env.HOME, path.substr(1)); var metaPath = join(WSMETAPATH, path); resolvePath(metaPath, options, function (err, path) { + if (err) return callback(err); + callback(null, path); + }); + } + + function getMetadata(path, options, callback){ + resolveMetaPath(path, options, function (err, path) { if (err) return callback(err); fs.readFile(path, callback); }); } function readfile(path, options, callback) { + auditLog("readfile", path); var meta = {}; var originalPath = path; @@ -535,20 +550,83 @@ module.exports = function setup(fsOptions) { return callback(err); } + var metadata = options.metadata && originalPath.indexOf(WSMETAPATH) == -1; + // Basic file info meta.mime = getMime(path); meta.size = stat.size; - meta.etag = calcEtag(stat); + + calcEtagConsideringMetadata(metadata, function(err, etag) { + if (err) return callback(err); + + meta.etag = etag; + + // ETag support + if ((TESTING || stat.mtime % 1000) && options.etag === meta.etag) { + meta.notModified = true; + fs.close(fd); + return callback(null, meta); + } + + supportRange(meta, function(_, stop) { + if (stop) { + fs.close(fd); + return callback(null, meta); + } + + if (metadata) { + addMetadata(meta); + return; + } + done(); + }); + + }); - // ETag support - if ((TESTING || stat.mtime % 1000) && options.etag === meta.etag) { - meta.notModified = true; - fs.close(fd); - return callback(null, meta); + function calcEtagConsideringMetadata(metadata, callback) { + if (!metadata) { + return callback(null, calcEtag(stat)); + } + + resolveMetaPath(originalPath, options, function (err, path) { + if (err) return callback(null, calcEtag(stat)); + + fs.stat(path, function (err, metaStat) { + if (err) return callback(null, calcEtag(stat)); + + var previousMtime = stat.mtime; + var previousSize = stat.size; + var mtime = stat.mtime < metaStat.mtime ? metaStat.mtime : stat.mtime; + var size = parseInt(stat.size.toString() + metaStat.size.toString()); + stat.mtime = mtime; + stat.size = size; + var etag = calcEtag(stat); + stat.mtime = previousMtime; + stat.size = previousSize; + callback(null, etag); + }); + }); + } + + function addMetadata(meta) { + getMetadata(originalPath, options, function (err, data) { + if (err) return done(); + + try { + meta.metadataSize = data.length; + meta.metadataStringLength = data.toString("utf8").length; + done(data); + } catch (e) { + fs.close(fd); + done(); + } + }); } - // Range support - if (options.hasOwnProperty('range') && !(options.range.etag && options.range.etag !== meta.etag)) { + function supportRange(meta, callback) { + if (!(options.hasOwnProperty('range') && !(options.range.etag && options.range.etag !== meta.etag))) + return callback(null, false); + var range = options.range; var start, end; if (range.hasOwnProperty("start")) { @@ -562,42 +640,21 @@ module.exports = function setup(fsOptions) { } else { meta.rangeNotSatisfiable = "Invalid Range"; - fs.close(fd); - return callback(null, meta); + return callback(null, true); } } if (end < start || start < 0 || end >= stat.size) { meta.rangeNotSatisfiable = "Range out of bounds"; - fs.close(fd); - return callback(null, meta); + return callback(null, true); } options.start = start; options.end = end; meta.size = end - start + 1; meta.partialContent = { start: start, end: end, size: stat.size }; - } - - var metaData; - if (options.hasOwnProperty("metadata") && originalPath.indexOf(WSMETAPATH) == -1) { - getMetadata(originalPath, options, function (err, data) { - if (err) - return done(); - try { - meta.metadataSize = data.length; - meta.metadataStringLength = data.toString("utf8").length; - metaData = data; - done(true); - } catch (e) { - fs.close(fd); - done(); - } - }); - } - else { - done(); + callback(null, false); } - function done(fakeStream){ + function done(metadata){ // HEAD request support if (options.hasOwnProperty("head")) { fs.close(fd); @@ -613,7 +670,7 @@ module.exports = function setup(fsOptions) { return callback(err); } - if (fakeStream) { + if (metadata) { var readStream = meta.stream; meta.stream = new Stream(); meta.stream.readable = true; @@ -627,7 +684,7 @@ module.exports = function setup(fsOptions) { }; readStream.pipe(meta.stream, { end : false }); readStream.on("end", function(){ - meta.stream.write(metaData); + meta.stream.write(metadata); meta.stream.end(); }); meta.stream.destroy = function () { @@ -710,6 +767,7 @@ module.exports = function setup(fsOptions) { // file and then renames to the final destination. // It will copy the properties of the existing file is there is one. function mkfile(path, options, realCallback) { + auditLog("mkfile", path); var meta = {}; var called; var callback = function (err) { @@ -1014,10 +1072,12 @@ module.exports = function setup(fsOptions) { } function rmfile(path, options, callback) { + auditLog("rmfile", path); remove(path, fs.unlink, options, callback); } function rmdir(path, options, callback) { + auditLog("rmdir", path); if (options.recursive) { remove(path, function(path, callback) { spawn("rm", {args: ["-rf", path], stdio: 'ignore'}, function(err, child) { @@ -1039,6 +1099,7 @@ module.exports = function setup(fsOptions) { } function rename(path, options, callback) { + auditLog("rename", path); var from, to; if (options.from) { from = options.from; to = path; @@ -1103,6 +1164,7 @@ module.exports = function setup(fsOptions) { } function copy(path, options, callback) { + auditLog("copy", path); var from, to; if (options.from) { from = options.from; to = path; @@ -1191,6 +1253,7 @@ module.exports = function setup(fsOptions) { } function symlink(path, options, callback) { + auditLog("symlink", path); if (!options.target) return callback(new Error("options.target is required")); var meta = {}; // Get real path to target dir @@ -1523,6 +1586,7 @@ module.exports = function setup(fsOptions) { } function chmod(path, options, callback) { + auditLog("chmod", path); resolvePath(path, options, function(err, path){ if (err) return callback(err); @@ -1540,6 +1604,7 @@ module.exports = function setup(fsOptions) { } function spawn(executablePath, options, callback) { + auditLog("spawn", executablePath); if (waitForEnv) return waitForEnv.push(spawn.bind(null, executablePath, options, callback)); @@ -1559,6 +1624,14 @@ module.exports = function setup(fsOptions) { } catch (err) { return callback(err); } + + if (!child.pid) { + err = new Error("spawn " + executablePath+ " ENOENT"); + err.code = "ENOENT"; + err.errno = "ENOENT"; + return callback(err); + } + if (options.resumeStdin) child.stdin.resume(); if (options.hasOwnProperty('stdoutEncoding')) { child.stdout && child.stdout.setEncoding(options.stdoutEncoding); @@ -1571,7 +1644,7 @@ module.exports = function setup(fsOptions) { child.on("error", function(err) { child.emit("exit", 127); }); - + callback(null, { process: child }); @@ -2306,6 +2379,7 @@ module.exports = function setup(fsOptions) { } function execFile(executablePath, options, callback) { + auditLog("execFile", executablePath); if (waitForEnv) return waitForEnv.push(execFile.bind(null, executablePath, options, callback)); diff --git a/plugins/node_modules/vfs-local/test/mock/.c9/metadata/file.txt b/plugins/node_modules/vfs-local/test/mock/.c9/metadata/file.txt new file mode 100644 index 00000000..e75ad610 --- /dev/null +++ b/plugins/node_modules/vfs-local/test/mock/.c9/metadata/file.txt @@ -0,0 +1 @@ +some meta data \ No newline at end of file diff --git a/plugins/node_modules/vfs-local/test/mock/file.txt b/plugins/node_modules/vfs-local/test/mock/file.txt index f5fbe841..17363e9c 100644 --- a/plugins/node_modules/vfs-local/test/mock/file.txt +++ b/plugins/node_modules/vfs-local/test/mock/file.txt @@ -1 +1 @@ -This is a simple file! +This is a file with exactly 42 characters!! diff --git a/plugins/node_modules/vfs-local/test/mock/file2.txt b/plugins/node_modules/vfs-local/test/mock/file2.txt new file mode 100644 index 00000000..b27ea590 --- /dev/null +++ b/plugins/node_modules/vfs-local/test/mock/file2.txt @@ -0,0 +1 @@ +juhu kinners \ No newline at end of file diff --git a/plugins/node_modules/vfs-local/test/test-local.js b/plugins/node_modules/vfs-local/test/test-local.js index 8a6c5227..cf66e033 100644 --- a/plugins/node_modules/vfs-local/test/test-local.js +++ b/plugins/node_modules/vfs-local/test/test-local.js @@ -4,7 +4,8 @@ "use mocha"; require("c9/inline-mocha")(module); - +const async = require("async"); +const assert = require("assert"); var expect = require('chai').expect; describe('vfs-local', function () { @@ -17,7 +18,8 @@ describe('vfs-local', function () { root: root, testing: true, defaultEnv: { CUSTOM: 43 }, - checkSymlinks: true + checkSymlinks: true, + wsmetapath: ".c9/metadata", })); var vfsLoose = require('vfs-lint')(require("vfs-local")({ @@ -38,7 +40,6 @@ describe('vfs-local', function () { }); }); }); - it('should prepend root when resolving virtual paths', function (done) { var vpath = "/dir/stuff.json"; vfs.resolve(vpath, {}, function (err, meta) { @@ -82,7 +83,7 @@ describe('vfs-local', function () { vfs.stat("/file.txt", {}, function (err, stat) { if (err) throw err; expect(stat).property("name").equal("file.txt"); - expect(stat).property("size").equal(23); + expect(stat).property("size").equal(44); expect(stat).property("mime").equal("text/plain"); done(); }); @@ -96,11 +97,86 @@ describe('vfs-local', function () { }); describe('vfs.readfile()', function () { + + it("should work if metadata is requested but there is none", function(done) { + vfs.readfile("/file2.txt", { metadata: true }, function(err, meta) { + if (err) throw err; + var stream = meta.stream; + var chunks = []; + var length = 0; + stream.on("data", function (chunk) { + chunks.push(chunk); + length += chunk.length; + }); + stream.on("end", function () { + expect(length).equal(12); + done(); + }); + }); + }); + + it("should work if metadata is requested but there is none, don't check symlinks", function(done) { + vfs.readfile("/file2.txt", { metadata: true, checkSymlinks: false }, function(err, meta) { + if (err) throw err; + var stream = meta.stream; + var chunks = []; + var length = 0; + stream.on("data", function (chunk) { + chunks.push(chunk); + length += chunk.length; + }); + stream.on("end", function () { + expect(length).equal(12); + done(); + }); + }); + }); + + it("should return metadata", function(done) { + vfs.readfile("/file.txt", { metadata: true }, function(err, meta) { + if (err) throw err; + expect(meta).property("metadataSize").equals(14); + var stream = meta.stream; + var chunks = []; + var length = 0; + stream.on("data", function (chunk) { + chunks.push(chunk); + length += chunk.length; + }); + stream.on("end", function () { + expect(length).equal(58); + var body = chunks.join(""); + expect(body).equal("This is a file with exactly 42 characters!!\nsome meta data"); + done(); + }); + }); + }); + + it("etag determined by latest mtime", function(done) { + async.series({ + withMeta: (next) => { + vfs.readfile("/file.txt", { metadata: true }, function(err, meta) { + next(err, meta.etag); + }) + }, + withoutMeta: (next) => { + vfs.readfile("/file.txt", { }, function(err, meta) { + next(err, meta.etag); + }) + } + }, + function(err, values) { + if (err) throw err; + assert(values.withMeta !== values.withoutMeta); + done(); + }); + }); + it("should read the text file", function (done) { vfs.readfile("/file.txt", {}, function (err, meta) { if (err) throw err; expect(meta).property("mime").equals("text/plain"); - expect(meta).property("size").equals(23); + expect(meta).property("size").equals(44); expect(meta).property("etag"); expect(meta).property("stream").property("readable"); var stream = meta.stream; @@ -111,9 +187,9 @@ describe('vfs-local', function () { length += chunk.length; }); stream.on("end", function () { - expect(length).equal(23); + expect(length).equal(44); var body = chunks.join(""); - expect(body).equal("This is a simple file!\n"); + expect(body).equal("This is a file with exactly 42 characters!!\n"); done(); }); }); @@ -134,7 +210,7 @@ describe('vfs-local', function () { vfs.readfile("/file.txt", {head:true}, function (err, meta) { if (err) throw err; expect(meta).property("mime").equal("text/plain"); - expect(meta).property("size").equal(23); + expect(meta).property("size").equal(44); expect(meta).property("mime").ok; expect(meta.stream).not.ok; done(); @@ -148,7 +224,7 @@ describe('vfs-local', function () { vfs.readfile("/file.txt", {etag:etag}, function (err, meta) { if (err) throw err; expect(meta).property("mime").equal("text/plain"); - expect(meta).property("size").equal(23); + expect(meta).property("size").equal(44); expect(meta).property("notModified").ok; expect(meta.stream).not.ok; done(); @@ -161,7 +237,7 @@ describe('vfs-local', function () { expect(meta).property("mime").equal("text/plain"); expect(meta).property("size").equal(3); expect(meta).property("etag").ok; - expect(meta).property("partialContent").deep.equal({ start: 1, end: 3, size: 23 }); + expect(meta).property("partialContent").deep.equal({ start: 1, end: 3, size: 44 }); expect(meta).property("stream").ok; var stream = meta.stream; var chunks = []; @@ -180,7 +256,7 @@ describe('vfs-local', function () { if (err) throw err; expect(meta).property("size").equal(10); expect(meta).property("etag").ok; - expect(meta).property("partialContent").deep.equal({ start: 13, end: 22, size: 23 }); + expect(meta).property("partialContent").deep.equal({ start: 34, end: 43, size: 44 }); done(); }); }); @@ -212,7 +288,7 @@ describe('vfs-local', function () { parts.push(part); }); stream.on("end", function () { - expect(parts).length(5); + expect(parts).length(7); done(); }); }); @@ -340,8 +416,10 @@ describe('vfs-local', function () { }); }); it("should create intermediate directories", function(done) { - vfs.execFile("rm", {args: ["-rf", root + "/nested"]}, function() { + vfs.execFile("rm", {args: ["-rf", root + "/nested"]}, function(err) { + assert(!err, err); vfs.mkfile("/nested/dir/file.txt", { parents: true }, function(err, meta) { + assert(!err, err); meta.stream.write("juhu"); meta.stream.end(); @@ -627,6 +705,7 @@ describe('vfs-local', function () { var inner = false; vfs.mkfile(vpath, {}, function(err, meta){ + assert(!err, err); var stream = meta.stream; stream.write("Change!"); stream.end(); @@ -1016,7 +1095,8 @@ describe('vfs-local', function () { }); var inner = false; - vfs.mkfile(vpath, { sandbox: sandbox }, function(err, meta){ + vfs.mkfile(vpath, { sandbox: sandbox }, function(err, meta) { + assert(!err, err); var stream = meta.stream; stream.write("Change!"); stream.end();