Permalink
Browse files

Issue 35: Support global R scripts and per-user R scripts.

The system now supports global R scripts, to be run after the
successful connection to R. Each connection will have all listed
R scripts run.

Per-user R scripts are also supported. This allows scripts specific
for a user to be run after that user logs in. Look in the example
configuration file for more information.
  • Loading branch information...
1 parent da872e1 commit 64c06af07d30770aa2c985be66ada940575970ae Jamie Love committed Oct 16, 2010
Showing with 92 additions and 6 deletions.
  1. +1 −1 server/authenticators/basic-user.js
  2. +30 −2 server/etc/config-example.js
  3. +52 −3 server/r-node.js
  4. +9 −0 server/rnodeUtils.js
@@ -99,7 +99,7 @@ BasicUserAuthenticator.prototype.login = function (httpRequest, callback) {
lastAccessTime: new Date()
}
- callback (sid);
+ callback (sid, username);
};
BasicUserAuthenticator.prototype.checkRequest = function (httpRequest, sid, callback) {
@@ -31,8 +31,8 @@
// For "none" no configuration is necessary.
// For "basic-user":
//
- // usersFile: "etc/users-example.js"
- // sessionTimeout: 30 // minutes
+ // "usersFile": "etc/users-example.js",
+ // "sessionTimeout": 30 // minutes
},
"features": {
@@ -105,6 +105,34 @@
// option to true
//
"manageRserver": false
+ },
+
+ "sessionManagement": {
+ // R command files to run after a connection to R is made. There
+ // are two types of such R command files:
+ // 1. Global files, run all the time.
+ // 2. User specific files, run for a specific user.
+ //
+ // First the directory to find global files:
+ // Files in the given directory are read in alphanumeric sorted order,
+ // so it might be useful to name them 001..., 002... etc.
+ "postConnectionScripts": "etc/global-scripts/",
+
+ // Now, where to find per-user R files. Files is this directory
+ // should include the username in their name, in the format:
+ // *_<name>_*
+ //
+ // That is, the user's name surrounded by underscores as part of
+ // the filename.
+ //
+ // The user's files (there can be more than one), are executed one after the other.
+ //
+ // E.g. if a user's name is jlove, then files may be:
+ // 001_jlove_rscript.R
+ // 002_jlove_rscript.R
+ //
+ // etc.
+ "perUserPostConnectionScripts": "etc/user-scripts/"
}
}
View
@@ -141,9 +141,10 @@ function createSessionContext (sid) {
}
function login (req, resp) {
- Authenticator.login (req, function (sid) {
+ Authenticator.login (req, function (sid, username) {
if (sid) {
var session = createSessionContext(sid);
+ session.username = username;
// If now, find a R session for them
if (!session.Rconnection) { // re-logins - we don't replace their session
@@ -282,6 +283,7 @@ function setupRSession (connection, sessionData, callback) {
// copies them to actual files for the user to download.
var graphingFile = rNodeApi.getRaccessibleTempFile ('.png');
+ // Core setup commands, not delegated to separate files.
var rnodeSetupCommands = [
{ command: "R.version.string", callback: function (result) { sessionData.context.Rversion = result[0]; } }
, "rNodePager = function (files, header, title, f) { r <- files; attr(r, 'class') <- 'RNodePager'; attr(r, 'header') <- header; attr(r, 'title') <- title; attr(r, 'delete') <- f; r; }"
@@ -291,6 +293,54 @@ function setupRSession (connection, sessionData, callback) {
, "dev.control(\"enable\");"
]
+ var scripts = [], globalScripts = [], userScripts = [];
+ var globalScriptDirectory = Config.sessionManagement ? (Config.sessionManagement.postConnectionScripts ? Config.sessionManagement.postConnectionScripts : []) : [];
+ var userScriptDirectory = Config.sessionManagement ? (Config.sessionManagement.perUserPostConnectionScripts ? Config.sessionManagement.perUserPostConnectionScripts : []) : [];
+ try {
+ globalScripts = FS.readdirSync(globalScriptDirectory).sort();
+ } catch (e) {
+ nodelog (null, "WARNING: global post connection script directory '" + globalScriptDirectory + "' is not readable: " + e);
+ }
+ try {
+ userScripts = FS.readdirSync(userScriptDirectory).sort();
+ } catch (e) {
+ nodelog (null, "WARNING: user post connection script directory '" + userScriptDirectory + "' is not readable: " + e);
+ }
+
+ scripts = globalScripts.map(function (s) { return [s, globalScriptDirectory + "/" + s]; });
+ if (sessionData.username) { // Username only set on per user sessions, and user login required.
+ var userMatch = '_' + sessionData.username + '_';
+ userScripts.forEach (function (s) {
+ if (s.match (userMatch)) {
+ scripts.push ([s, userScriptDirectory + "/" + s]);
+ }
+ });
+ }
+ nodelog (null, "Running R setup files: " + scripts.map (function (s) { return s[0] }).join (","));
+
+ var runPostConnectionScripts = function(fi) {
+ if (fi < scripts.length) {
+ // Copy file to R's tmp directory, then source it into the session
+ var dest = Config.R.tempDirectoryFromOurPerspective + "/" + scripts[fi][0];
+ UTILS.cp (scripts[fi][1], dest, function (err) {
+ if (err) {
+ nodelog (null, "Error running R setup file '" + scripts[fi][0] + "': " + err);
+ callback (false);
+ } else {
+ connection.request ("source(\"" + dest + "\")", function (resp) {
+ nodelog(null, 'Successfull run R setup file: ' + scripts[fi][0]);
+ runPostConnectionScripts (++fi);
+ });
+ }
+ });
+ } else {
+ callback (true);
+ }
+ }
+
+ // Run aech core setup command in turn, then once finished,
+ // call the above function to do the post connectino scripts,
+ // if there are any.
var runs = function (i) {
if (i < rnodeSetupCommands.length) {
var cmd = rnodeSetupCommands[i];
@@ -306,10 +356,9 @@ function setupRSession (connection, sessionData, callback) {
runs (++i);
});
} else {
- callback (true);
+ runPostConnectionScripts(0);
}
}
-
runs (0);
}
@@ -117,7 +117,16 @@ function getRandomString(prefix, suffix, length) {
return (prefix != null ? prefix : 'tmp_') + salt + (suffix ? suffix : '');
}
+function cp (source, dest ,callback) {
+ var read = FS.createReadStream(source);
+ var write = FS.createWriteStream(dest);
+
+ read.on("end", callback);
+ SYS.pump(read, write);
+}
+
exports.getRandomString = getRandomString;
exports.loadJsonFile = loadJsonFile;
exports.streamFile = streamFile;
exports.nodelog = nodelog;
+exports.cp = cp;

0 comments on commit 64c06af

Please sign in to comment.