ares-ide
Version:
A browser-based code editor and UI designer for Enyo 2 projects
396 lines (360 loc) • 12.1 kB
JavaScript
/* jshint node:true */
/**
* fsDropbox.js -- Ares FileSystem (fs) provider, using Dropbox
*/
// nodejs version checking is done in parent process ide.js
var util = require("util"),
path = require("path"),
fs = require("fs"),
dropbox = require("dropbox"),
FsBase = require(__dirname + "/lib/fsBase"),
HttpError = require(__dirname + "/lib/httpError");
var basename = path.basename(__filename, '.js');
function FsDropbox(inConfig, next) {
inConfig.name = inConfig.name || "fsDropbox";
// inherits FsBase (step 1/2)
FsBase.call(this, inConfig, next);
}
// inherits FsBase (step 2/2)
util.inherits(FsDropbox, FsBase);
FsDropbox.prototype.configure = function(config, next) {
this.log("FsDropbox.configure(): config:", config);
this.parseProxy(config);
if (this.httpAgent) {
dropbox.Xhr.Request.nodejsSet({httpAgent: this.httpAgent});
}
if (this.httpsAgent) {
dropbox.Xhr.Request.nodejsSet({httpsAgent: this.httpsAgent});
}
if (next) {
next();
}
};
FsDropbox.prototype.errorResponse = function(err) {
this.log("FsDropbox.errorResponse(): err:", err);
var response;
if (err instanceof dropbox.ApiError) {
response = {
code: err.status,
body: err.response
};
this.log(err.stack);
} else {
response = FsBase.prototype.errorResponse.bind(this)(err);
}
this.log("FsDropbox.errorResponse(): response:", response);
return response;
};
FsDropbox.prototype.authorize = function(req, res, next) {
var auth;
if (req.cookies.dropbox_auth) {
this.log("FsDropbox.authorize(): req.cookies=", util.inspect(req.cookies));
_authorize.bind(this)(decodeURIComponent(req.cookies.dropbox_auth));
} else if(req.query.auth) {
this.log("FsDropbox.authorize(): req.query=", util.inspect(req.query));
_authorize.bind(this)(req.query.auth);
} else {
next(new HttpError('Missing Authorization', 401));
}
function _authorize(authStr) {
try {
auth = JSON.parse(authStr);
this.log("FsDropbox.authorize(): auth:" + util.inspect(auth));
} catch(e) {
return next(e);
}
req.dropbox = new dropbox.Client({
key: auth.appKey,
secret: auth.appSecret,
sandbox: true
});
req.dropbox.authDriver(new AuthDriver(auth));
req.dropbox.authenticate((function(err, client) {
if (err) {
return next(err);
}
this.log("dropbox:" + util.inspect(client));
next();
}).bind(this));
}
// see https://github.com/dropbox/dropbox-js/blob/master/doc/auth_drivers.md
function AuthDriver(auth) {
if (!auth || !auth.uid ||
!auth.appKey || !auth.appSecret ||
!auth.accessToken || !auth.accessTokenSecret) {
throw new HttpError("Missing OAuth authorization parameters", 400);
}
this.url = function() { return ""; };
this.doAuthorize = function(authUrl, token, tokenSecret, callback) {
console.log("authDriver.doAuthorize(): authUrl:"+ authUrl + " , token:" + token + ", tokenSecret:", tokenSecret);
callback();
};
this.onAuthStateChange = function(client, callback) {
//console.log("authDriver.onAuthStateChange(): client.authState:" + client.authState);
client.setCredentials({
uid: auth.uid,
key: auth.appKey,
secret: auth.appSecret,
token: auth.accessToken,
tokenSecret: auth.accessTokenSecret
});
//console.log("authDriver.onAuthStateChange(): client.authState:" + client.authState);
//console.log("authDriver.onAuthStateChange(): client.oauth:" + util.inspect(client.oauth));
callback();
};
}
};
FsDropbox.prototype.setUserInfo = function(req, res, next) {
this.log("FsDropbox.setUserInfo(): req.query=", util.inspect(req.query));
this.log("FsDropbox.setUserInfo(): req.params=", util.inspect(req.params));
var auth = req.param('auth');
this.log("FsDropbox.setUserInfo(): auth:", auth);
if (auth) {
var exdate=new Date();
exdate.setDate(exdate.getDate() + 10 /*days*/);
var cookieOptions = {
httpOnly: true,
expires: exdate
//maxAge: 1000*3600 // 1 hour
};
res.cookie('dropbox_auth', auth, cookieOptions);
this.log("FsDropbox.setUserInfo(): Set-Cookie: dropbox_auth:", auth);
res.send(200).end();
} else {
next(new HttpError('No User Info', 400 /*Bad Request*/));
}
};
FsDropbox.prototype.getUserInfo = function(req, res, next) {
this.log("FsDropbox.getUserInfo(): req.query:", req.query);
req.dropbox.getUserInfo(function(err, userInfo) {
console.log("getUserInfo(): err=" + util.inspect(err), ", userInfo=" + util.inspect(userInfo));
if (err) {
next(err);
} else {
res.status(200).send(userInfo);
}
});
};
FsDropbox.prototype.propfind = function(req, res, next) {
// 'infinity' is '-1', 'undefined' is '0'
var depthStr = req.param('depth');
var depth = depthStr ? (depthStr === 'infinity' ? -1 : parseInt(depthStr, 10)) : 1;
this._propfind(null, req, req.param('path'), depth, this.respond.bind(this, res));
};
FsDropbox.prototype.move = function(req, res, next) {
this.log("FsDropbox.move()");
this.copyOrMove(req, res, req.dropbox.move.bind(req.dropbox), next);
};
FsDropbox.prototype.copy = function(req, res, next) {
this.log("FsDropbox.copy()");
this.copyOrMove(req, res, req.dropbox.copy.bind(req.dropbox), next);
};
FsDropbox.prototype.copyOrMove = function(req, res, op, next) {
var srcRelPath = req.param('path'),
dstName = req.param('name'),
dstFolderId = req.param('folderId'),
overwriteParam = req.param('overwrite');
var dstRelPath;
this.log("FsDropbox.copyOrMove(): path:", srcRelPath, "name:", dstName, "folderId:", dstFolderId, "overwriteParam:", overwriteParam);
if (dstName) {
// rename/copy file within the same collection (folder)
dstRelPath = path.join(path.dirname(srcRelPath),
path.basename(dstName));
} else if (dstFolderId) {
// move/copy at a new location
dstRelPath = path.join(this.decodeFileId(dstFolderId),
path.basename(srcRelPath));
} else {
next(new HttpError("missing query parameter: 'name' or 'folderId'", 400 /*Bad-Request*/));
return;
}
this.log("FsDropbox.copyOrMove():", srcRelPath, "-> ", dstRelPath);
op(srcRelPath, dstRelPath, (function(err, stat) {
this.log("FsDropbox.copyOrMove(): dropbox err:", err, "stat:", stat);
var node = getNode.bind(this)(stat, 0);
this.log("FsDropbox.copyOrMove(): node:", node);
next(err, {
code: 200,
body: node
});
}).bind(this));
};
FsDropbox.prototype.get = function(req, res, next) {
var relPath = req.param('path');
this.log("FsDropbox.get(): path:", relPath);
var options = {
versionTag: req.param('versionTag'),
arrayBuffer: false, // request 'arraybuffer'
blob: false, // request 'blob'
binary: true, // request 'b'
length: undefined, // chunked request
start: undefined // chunked request
};
req.dropbox.readFile(relPath, options, (function(err, data, stat) {
this.log("FsDropbox.get(): dropbox err:", err, "stat:", stat);
var node = getNode.bind(this)(stat, 0);
this.log("FsDropbox.get(): node:", node);
next(err, {
code: 200,
body: data,
headers: {
'x-ares-node': JSON.stringify(node)
}
});
}).bind(this));
};
FsDropbox.prototype.putFile = function(req, file, next) {
this.log("FsDropbox.putFile(): file.name:", file.name);
var options = {
noOverwrite: false,
lastVersionTag: undefined
};
if (file.path) {
this.log("FsDropbox.putFile(): uploading file:", file.path);
// Dropbox has no Node.js streaming interface, so we
// need to load the entire file in memory.
fs.readFile(file.path, _writeFile.bind(this));
} else {
_writeFile.bind(this)(file.buffer);
}
function _writeFile(err, buffer) {
if (err) {
return next(err);
}
this.log("FsDropbox.putFile(): bytes:", buffer.length, "->", file.name);
req.dropbox.writeFile(file.name, buffer, options, (function(err, stat) {
this.log("FsDropbox.putFile.writeFile(): dropbox err:", err, "stat:", stat);
var node = getNode.bind(this)(stat, 0);
this.log("FsDropbox.putFile.writeFile(): node:", node);
next(err, node);
}).bind(this));
}
};
FsDropbox.prototype.mkcol = function(req, res, next) {
var relPath = req.param('path') + '/' + req.param('name');
this.log("FsDropbox.mkcol(): relPath:", relPath);
req.dropbox.mkdir(relPath, (function(err, stat) {
this.log("FsDropbox.mkcol(): dropbox err:", err, "stat:", stat);
var node = getNode.bind(this)(stat, 0);
this.log("FsDropbox.mkcol(): node:", node);
next(err, {code: 201, body: node});
}).bind(this));
};
FsDropbox.prototype['delete'] = function(req, res, next) {
var relPath = req.param('path');
this.log("FsDropbox.delete(): path:", relPath);
req.dropbox.remove(relPath, (function(err, stat) {
this.log("FsDropbox.delete(): dropbox err:", err, "stat:", stat);
var node = getNode.bind(this)(stat, 0);
this.log("FsDropbox.delete(): node:", node);
next(err, {
code: 200,
body: node
});
}).bind(this));
};
// implementations
FsDropbox.prototype._propfind = function(err, req, relPath, depth, next) {
this.log("FsDropbox._propfind(): err=", err, "relPath:", relPath, "depth:", depth);
if (depth > 1) {
return next(new HttpError("Unsupported depth=" + depth, 403));
}
req.dropbox.readdir("/" + relPath, _onReply.bind(this));
function _onReply(err, entries, dirStat, entriesStat) {
var node;
this.log("FsDropbox._propfind.onReply(): err=", err, "entries:", entries, "dirStat:", dirStat, "entriesStat:", entriesStat);
if (err) {
next(err);
} else {
node = getNode.bind(this)({
path: dirStat.path || '/',
contents: entriesStat,
isFolder: dirStat.isFolder
}, depth);
this.log("FsDropbox._propfind.onReply(): node:", node);
next(null, {code: 200, body: node});
}
}
};
/**
* Convert a Dropbox#Stat {Object} into an Ares#Node {Object}.
*/
function getNode(stat, depth) {
this.log("FsDropbox.getNode(): stat:", stat);
var arNode;
if (stat) {
arNode = {
isDir: stat.isFolder,
path: stat.path,
name: path.basename(stat.path),
id: this.encodeFileId(stat.path)
};
if (arNode.name === '') {
// XXX replace by the Dropbox application folder name?
arNode.name = 'dropbox';
}
if (stat.versionTag) {
arNode.versionTag = stat.versionTag;
}
if (arNode.isDir) {
if (depth) {
arNode.children = [];
stat.contents.forEach(function(stat){
arNode.children.push(getNode.bind(this)(stat, depth-1));
}, this);
}
}
}
this.log("FsDropbox.getNode(): arNode:", arNode);
return arNode;
}
// module/main wrapper
if (path.basename(process.argv[1], '.js') === basename) {
// We are main.js: create & run the object...
var knownOpts = {
"port": Number,
"timeout": Number,
"pathname": String,
"level": ['silly', 'verbose', 'info', 'http', 'warn', 'error'],
"help": Boolean
};
var shortHands = {
"p": "--port",
"t": "--timeout",
"P": "--pathname",
"l": "--level",
"v": "--level verbose",
"h": "--help"
};
var argv = require('nopt')(knownOpts, shortHands, process.argv, 2 /*drop 'node' & basename*/);
argv.pathname = argv.pathname || "/files";
argv.port = argv.port || 0;
argv.timeout = argv.timeout || (2*60*1000);
argv.level = argv.level || "http";
if (argv.help) {
console.log("Usage: node " + basename + "\n" +
" -p, --port port (o) local IP port of the express server (0: dynamic) [default: '0']\n" +
" -t, --timeout milliseconds of inactivity before a server socket is presumed to have timed out [default: '120000']\n" +
" -P, --pathname URL pathname prefix (before /deploy and /build [default: '/files']\n" +
" -l, --level debug level ('silly', 'verbose', 'info', 'http', 'warn', 'error') [default: 'http']\n" +
" -h, --help This message\n");
process.exit(0);
}
new FsDropbox({
pathname: argv.pathname,
port: argv.port,
timeout: argv.timeout,
verbose: (argv.level === 'verbose') // FIXME: rather use npm.log() directly
}, function(err, service){
if (err) {
process.exit(err);
}
// process.send() is only available if the
// parent-process is also node
if (process.send) {
process.send(service);
}
});
} else {
module.exports = FsDropbox;
}