webapi-eca/js-coffee/module-manager.js
2014-02-18 09:48:18 +01:00

182 lines
5.7 KiB
JavaScript

/*
# Module Manager
> The module manager takes care of the module and rules loading in the initialization
> phase and on user request.
> Event and Action modules are loaded as strings and stored in the database,
> then compiled into node modules and rules
*/
'use strict';
var fs = require('fs'),
path = require('path'),
log = require('./logging'),
db, funcLoadAction, funcLoadRule;
exports = module.exports = function(args) {
args = args || {};
log(args);
return module.exports;
};
exports.addDBLink = function(db_link) {
db = db_link;
};
exports.requireFromString = function(src, name, dir) {
if(!dir) dir = __dirname;
var id = path.resolve(dir, name, name + '.vm');
var vm = require('vm'),
// FIXME not log but debug module is required to provide information to the user
sandbox = {
id: id, // use this to gather kill info
needle: require('needle'), //https://github.com/tomas/needle
log: log,
exports: {}
};
//TODO child_process to run module!
// Define max runtime per loop as 10 seconds, after that the child will be killed
// it can still be active after that if there was a timing function or a callback used...
// kill the child each time? how to determine whether there's still a token in the module?
try {
var mod = vm.runInNewContext(src, sandbox, id);
} catch (err) {
log.error('ML', 'Error running module in sandbox: ' + err.message);
}
return sandbox.exports;
};
exports.loadModule = function(directory, name, callback) {
try {
fs.readFile(path.resolve(__dirname, '..', directory, name, name + '.js'), 'utf8', function (err, data) {
if (err) {
log.error('LM', 'Loading module file!');
return;
}
var mod = exports.requireFromString(data, name, directory);
if(mod && fs.existsSync(path.resolve(__dirname, '..', directory, name, 'credentials.json'))) {
fs.readFile(path.resolve(__dirname, '..', directory, name, 'credentials.json'), 'utf8', function (err, auth) {
if (err) {
log.error('LM', 'Loading credentials file for "' + name + '"!');
callback(name, data, mod, null);
return;
}
if(mod.loadCredentials) mod.loadCredentials(JSON.parse(auth));
callback(name, data, mod, auth);
});
} else {
// Hand back the name, the string contents and the compiled module
callback(name, data, mod, null);
}
});
} catch(err) {
log.error('LM', 'Failed loading module "' + name + '"');
}
};
exports.loadModules = function(directory, callback) {
fs.readdir(path.resolve(__dirname, '..', directory), function (err, list) {
if (err) {
log.error('LM', 'loading modules directory: ' + err);
return;
}
log.print('LM', 'Loading ' + list.length + ' modules from "' + directory + '"');
list.forEach(function (file) {
fs.stat(path.resolve(__dirname, '..', directory, file), function (err, stat) {
if (stat && stat.isDirectory()) {
exports.loadModule(directory, file, callback);
}
});
});
});
};
exports.storeEventModule = function (objUser, obj, answHandler) {
try {
// TODO in the future we might want to link the modules close to the user
// and allow for e.g. private modules
// we need a child process to run this code and kill it after invocation
var m = exports.requireFromString(obj.data, obj.id);
obj.methods = Object.keys(m);
answHandler.answerSuccess('Thank you for the event module!');
db.storeEventModule(obj.id, obj);
} catch (err) {
answHandler.answerError(err.message);
console.error(err);
}
};
exports.getAllEventModules = function ( objUser, obj, answHandler ) {
db.getEventModules(function(err, obj) {
if(err) answHandler.answerError('Failed fetching event modules: ' + err.message);
else answHandler.answerSuccess(obj);
});
};
exports.storeActionModule = function (objUser, obj, answHandler) {
var m = exports.requireFromString(obj.data, obj.id);
obj.methods = Object.keys(m);
answHandler.answerSuccess('Thank you for the action module!');
db.storeActionModule(obj.id, obj);
};
exports.getAllActionModules = function ( objUser, obj, answHandler ) {
db.getActionModules(function(err, obj) {
if(err) answHandler.answerError('Failed fetching action modules: ' + err.message);
else answHandler.answerSuccess(obj);
});
};
exports.storeRule = function (objUser, obj, answHandler) {
//TODO fix, twice same logic
var cbEventModule = function (lstParams) {
return function(err, data) {
if(err) {
err.addInfo = 'fetching event module';
log.error('MM', err);
}
if(!err && data) {
if(data.params) {
lstParams.eventmodules[data.id] = data.params;
}
}
if(--semaphore === 0) answHandler.answerSuccess(lstParams);
};
};
var cbActionModule = function (lstParams) {
return function(err, data) {
if(err) {
err.addInfo = 'fetching action module';
log.error('MM', err);
}
if(!err && data) {
if(data.params) {
lstParams.actionmodules[data.id] = data.params;
}
}
if(--semaphore === 0) answHandler.answerSuccess(lstParams);
};
};
var semaphore = 1;
var lst = {
eventmodules: {},
actionmodules: {}
};
try {
var objRule = JSON.parse(obj.data);
for(var i = 0; i < objRule.actions.length; i++) {
semaphore++;
db.getActionModule(objRule.actions[i].module.split('->')[0], cbActionModule(lst));
}
db.getEventModule(objRule.event.split('->')[0], cbEventModule(lst));
db.storeRule(objRule.id, objUser.username, obj.data);
} catch(err) {
answHandler.answerError(err.message);
log.error('MM', err);
}
};