Skip to content

Commit

Permalink
Issue 35: Support global R scripts and per-user R scripts.
Browse files Browse the repository at this point in the history
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
Jamie Love committed Oct 16, 2010
1 parent da872e1 commit 64c06af
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 6 deletions.
2 changes: 1 addition & 1 deletion server/authenticators/basic-user.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
32 changes: 30 additions & 2 deletions server/etc/config-example.js
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down Expand Up @@ -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/"
}
}

55 changes: 52 additions & 3 deletions server/r-node.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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; }"
Expand All @@ -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];
Expand All @@ -306,10 +356,9 @@ function setupRSession (connection, sessionData, callback) {
runs (++i);
});
} else {
callback (true);
runPostConnectionScripts(0);
}
}

runs (0);
}

Expand Down
9 changes: 9 additions & 0 deletions server/rnodeUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.