Skip to content

Instantly share code, notes, and snippets.

@ctide
Created July 18, 2011 19:29
Show Gist options
  • Save ctide/1090411 to your computer and use it in GitHub Desktop.
Save ctide/1090411 to your computer and use it in GitHub Desktop.
Test Coverage
+------------------------------------------+----------+------+------+--------+
| filename | coverage | LOC | SLOC | missed |
+------------------------------------------+----------+------+------+--------+
| Common/node/lconfig.js | 60.00 | 39 | 5 | 2 |
| Common/node/lconsole.js | 43.33 | 55 | 30 | 17 |
| lockerd.js | 66.22 | 154 | 74 | 25 |
| Common/node/lscheduler.js | 59.52 | 129 | 42 | 17 |
| Common/node/lfs.js | 37.14 | 141 | 70 | 44 |
| Common/node/lservicemanager.js | 65.96 | 515 | 141 | 48 |
| Common/node/levents.js | 93.75 | 66 | 16 | 1 |
| Common/node/locker.js | 40.63 | 102 | 32 | 19 |
| Ops/dashboard.js | 82.35 | 35 | 17 | 3 |
| Ops/webservice.js | 52.91 | 397 | 189 | 89 |
| Common/node/lpquery.js | 2.91 | 517 | 309 | 300 |
| Common/node/lcrypto.js | 59.09 | 107 | 66 | 27 |
| Connectors/Facebook/migrations/1309052824000.js | NaN | 13 | 0 | 0 |
| Connectors/foursquare/migrations/1309052824000.js | 100.00 | 13 | 1 | 0 |
| Connectors/GitHub/migrations/1309052824000.js | 100.00 | 13 | 8 | 0 |
| Connectors/GoogleContacts/migrations/1309052824000.js | 100.00 | 13 | 8 | 0 |
| Connectors/IMAP/migrations/1308690468483.js | 72.73 | 17 | 11 | 3 |
| Connectors/IMAP/migrations/1309052268000.js | 100.00 | 13 | 8 | 0 |
| Connectors/Twitter/migrations/1309052824000.js | 100.00 | 13 | 8 | 0 |
| Common/node/lmongoclient.js | 86.36 | 46 | 22 | 3 |
+------------------------------------------+----------+------+------+--------+
| 43.42 | 2398 | 1057 | 598 |
+----------+------+------+--------+
Common/node/lconfig.js:
1 | | /*
2 | | *
3 | | * Copyright (C) 2011, The Locker Project
4 | | * All rights reserved.
5 | | *
6 | | * Please see the LICENSE file for more information.
7 | | *
8 | | */
9 | |
10 | | //just a place for lockerd.js to populate config info
11 | 1 | var fs = require('fs');
12 | |
13 | |
14 | 1 | exports.load = function(filepath) {
15 | 3 | var config = JSON.parse(fs.readFileSync(filepath));
16 | 3 | exports.lockerHost = config.lockerHost || 'localhost';
17 | 3 | exports.externalHost = config.externalHost || 'localhost';
18 | 3 | exports.lockerPort = config.lockerPort || 8042;
19 | 3 | exports.externalPort = config.externalPort || exports.lockerPort;
20 | 3 | exports.externalSecure = config.externalSecure;
21 | 3 | exports.externalPath = config.externalPath || '';
22 | 3 | setBase();
23 | 3 | exports.scannedDirs = config.scannedDirs;
24 | 3 | exports.mongo = config.mongo;
25 | 3 | exports.me = config.me || "Me";
26 | 3 | exports.lockerDir = process.cwd();
27 | | }
28 | |
29 | 1 | function setBase() {
30 | 3 | exports.lockerBase = 'http://' + exports.lockerHost +
31 | | (exports.lockerPort && exports.lockerPort != 80 ? ':' + exports.lockerPort : '');
32 | 3 | exports.externalBase = 'http';
33 | 3 | if(exports.externalSecure === true || (exports.externalPort == 443 && exports.externalSecure !== false))
34 | 0 | exports.externalBase += 's';
35 | 3 | exports.externalBase += '://' + exports.externalHost +
36 | | (exports.externalPort && exports.externalPort != 80 && exports.externalPort != 443 ? ':' + exports.externalPort : '');
37 | 3 | if(exports.externalPath)
38 | 0 | exports.externalBase += exports.externalPath;
39 | | }
Common/node/lconsole.js:
1 | | /*
2 | | *
3 | | * Copyright (C) 2011, The Locker Project
4 | | * All rights reserved.
5 | | *
6 | | * Please see the LICENSE file for more information.
7 | | *
8 | | */
9 | |
10 | 1 | console.baseColor = "\033[0m"; // white
11 | 1 | console.errorColors = [/* yellow */"\033[33;40m", /* red */ "\033[31;40m"];
12 | 1 | console.moduleColor = "\033[32;40m"; // green
13 | 1 | console.realLog = console.log;
14 | |
15 | 1 | Number.prototype.zeroPad = function(width) {
16 | 0 | var n = Math.abs(this);
17 | 0 | var count = Math.max(0, width - Math.floor(n).toString().length);
18 | 0 | var zeroString = Math.pow(10, count).toString().substr(1);
19 | 0 | if (this < 0) zeroString = "-" + zeroString;
20 | 0 | return zeroString + n;
21 | | }
22 | |
23 | | function paddedTimestamp()
24 | 1 | {
25 | 0 | var now = new Date();
26 | 0 | return now.getHours().zeroPad(2) + ":" + now.getMinutes().zeroPad(2) + ":" + now.getSeconds().zeroPad(2);
27 | | }
28 | 1 | console.outputModule = "Locker";
29 | 1 | console.logHeader = function()
30 | | {
31 | 0 | return console.baseColor + "[" + paddedTimestamp() + "][" + console.moduleColor + console.outputModule + console.baseColor + "]";
32 | | }
33 | |
34 | 1 | console.log = function ()
35 | | {
36 | 0 | args = Array.prototype.slice.call(arguments);
37 | 0 | args[0] = console.logHeader() + " " + args[0];
38 | 0 | console.realLog.apply(this, args);
39 | | }
40 | |
41 | 1 | console.realWarn = console.warn;
42 | 1 | console.warn = function()
43 | | {
44 | 0 | args = Array.prototype.slice.call(arguments);
45 | 0 | args[0] = console.logHeader() + "[" + console.errorColors[0] + "WARNING" + console.baseColor + "] " + args[0];
46 | 0 | console.realWarn.apply(this, args);
47 | | }
48 | |
49 | 1 | console.realError = console.error;
50 | 1 | console.error = function()
51 | | {
52 | 0 | args = Array.prototype.slice.call(arguments);
53 | 0 | args[0] = console.logHeader() + "[" + console.errorColors[1] + "ERROR" + console.baseColor + "] " + console.errorColors[1] + args[0];
54 | 0 | console.realWarn.apply(this, args);
55 | | }
lockerd.js:
1 | | /*
2 | | *
3 | | * Copyright (C) 2011, The Locker Project
4 | | * All rights reserved.
5 | | *
6 | | * Please see the LICENSE file for more information.
7 | | *
8 | | */
9 | |
10 | | /* random notes:
11 | | on startup scan all folders
12 | | Apps Collections Connectors - generate lists of "available"
13 | | Me/* - generate lists of "existing"
14 | |
15 | | when asked, run any existing and return localhost:port
16 | | if first time
17 | | check dependencies
18 | | create Me/ dir
19 | | create me.json settings
20 | | pick a port
21 | | */
22 | |
23 | 1 | require.paths.push(__dirname + "/Common/node");
24 | 1 | var spawn = require('child_process').spawn;
25 | 1 | var fs = require('fs');
26 | 1 | var path = require('path');
27 | |
28 | | // This lconfig stuff has to come before and other locker modules are loaded!!
29 | 1 | var lconfig = require('lconfig');
30 | 1 | lconfig.load((process.argv[2] == '--config'? process.argv[3] : 'config.json'));
31 | |
32 | |
33 | | //var crypto = require('crypto');
34 | 1 | var lconsole = require("lconsole");
35 | 1 | var lscheduler = require("lscheduler");
36 | 1 | var serviceManager = require("lservicemanager");
37 | 1 | var dashboard = require(__dirname + "/Ops/dashboard.js");
38 | 1 | var mongodb = require('mongodb');
39 | 1 | var webservice = require(__dirname + "/Ops/webservice.js");
40 | 1 | var lcrypto = require("lcrypto");
41 | |
42 | |
43 | 1 | if(lconfig.lockerHost != "localhost" && lconfig.lockerHost != "127.0.0.1") {
44 | 0 | console.warn('if I\'m running on a public IP I needs to have password protection,' + // uniquely self (de?)referential? lolz!
45 | | 'which if so inclined can be hacked into lockerd.js and added since' +
46 | | ' it\'s apparently still not implemented :)\n\n');
47 | | }
48 | 1 | var shuttingDown_ = false;
49 | |
50 | 1 | var mongoProcess;
51 | 1 | path.exists(lconfig.me + '/' + lconfig.mongo.dataDir, function(exists) {
52 | 1 | if(!exists) {
53 | 1 | try {
54 | | //ensure there is a Me dir
55 | 1 | fs.mkdirSync(lconfig.me, 0755);
56 | | } catch(err) {
57 | 1 | if(err.code !== 'EEXIST')
58 | 0 | console.error('err', err);
59 | | }
60 | 1 | fs.mkdirSync(lconfig.me + '/' + lconfig.mongo.dataDir, 0755);
61 | | }
62 | 1 | mongoProcess = spawn('mongod', ['--dbpath', lconfig.lockerDir + '/' + lconfig.me + '/' + lconfig.mongo.dataDir,
63 | | '--port', lconfig.mongo.port]);
64 | 1 | mongoProcess.stderr.on('data', function(data) {
65 | 0 | console.error('mongod err: ' + data);
66 | | });
67 | |
68 | 1 | var mongoOutput = "";
69 | 1 | var mongodExit = function(errorCode) {
70 | 0 | if(shuttingDown_) return;
71 | 0 | if(errorCode !== 0) {
72 | 0 | var db = new mongodb.Db('locker', new mongodb.Server('127.0.0.1', lconfig.mongo.port, {}), {});
73 | 0 | db.open(function(error, client) {
74 | 0 | if(error) {
75 | 0 | console.error('mongod did not start successfully and was not already running ('+errorCode+'), here was the stdout: '+mongoOutput);
76 | 0 | shutdown(1);
77 | | } else {
78 | 0 | console.error('found a previously running mongodb running on port '+lconfig.mongo.port+' so we will use that');
79 | 0 | db.close();
80 | 0 | checkKeys();
81 | | }
82 | | });
83 | | }
84 | | };
85 | 1 | mongoProcess.on('exit', mongodExit);
86 | |
87 | | // watch for mongo startup
88 | 1 | var callback = function(data) {
89 | 2 | mongoOutput += data;
90 | 2 | if(mongoOutput.match(/ waiting for connections on port/g)) {
91 | 1 | mongoProcess.stdout.removeListener('data', callback);
92 | 1 | checkKeys();
93 | | }
94 | | };
95 | 1 | mongoProcess.stdout.on('data', callback);
96 | | });
97 | |
98 | |
99 | 1 | function checkKeys() {
100 | 1 | lcrypto.generateSymKey(function(hasKey) {
101 | 1 | if (!hasKey) {
102 | 0 | shutdown(1);
103 | 0 | return;
104 | | }
105 | 1 | lcrypto.generatePKKeys(function(hasKeys) {
106 | 1 | if (!hasKeys) {
107 | 0 | shutdown(1);
108 | 0 | return;
109 | | }
110 | 1 | finishStartup();
111 | | });
112 | | });
113 | | }
114 | |
115 | 1 | function finishStartup() {
116 | | // look for available things
117 | 1 | lconfig.scannedDirs.forEach(function(dirToScan) {
118 | 4 | console.log(dirToScan);
119 | 4 | var installable = true;
120 | 5 | if (dirToScan === "Collections") installable = false;
121 | 4 | serviceManager.scanDirectory(dirToScan, installable);
122 | | });
123 | |
124 | | // look for existing things
125 | 1 | serviceManager.findInstalled();
126 | |
127 | 1 | lscheduler.masterScheduler.loadAndStart();
128 | |
129 | 1 | webservice.startService(lconfig.lockerPort);
130 | |
131 | 1 | var lockerPortNext = "1"+lconfig.lockerPort;
132 | 1 | dashboard.start(lockerPortNext);
133 | 1 | lockerPortNext++;
134 | |
135 | 1 | console.log('locker is running, use your browser and visit ' + lconfig.lockerBase);
136 | | }
137 | |
138 | 1 | function shutdown(returnCode) {
139 | 0 | process.stdout.write("\n");
140 | 0 | shuttingDown_ = true;
141 | 0 | dashboard.instance.kill(dashboard.pid, "SIGINT");
142 | 0 | serviceManager.shutdown(function() {
143 | 0 | mongoProcess.kill();
144 | 0 | console.log("Shutdown complete.");
145 | 0 | process.exit(returnCode);
146 | | });
147 | | }
148 | |
149 | 1 | process.on("SIGINT", function() {
150 | 0 | shutdown(0);
151 | | });
152 | |
153 | | // Export some things so this can be used by other processes, mainly for the test runner
154 | 1 | exports.shutdown = shutdown;
Common/node/lscheduler.js:
1 | | /*
2 | | *
3 | | * Copyright (C) 2011, The Locker Project
4 | | * All rights reserved.
5 | | *
6 | | * Please see the LICENSE file for more information.
7 | | *
8 | | */
9 | |
10 | 1 | var lfs = require("lfs");
11 | 1 | var fs = require("fs");
12 | 1 | var serviceManager = require("lservicemanager");
13 | 1 | var url = require("url");
14 | 1 | var http = require("http");
15 | 1 | var request = require("request");
16 | 1 | var lconfig = require('lconfig');
17 | |
18 | 1 | SCHEDULE_ACTION_DIRECT = 0; // Live direct callbacks, not savable
19 | 1 | SCHEDULE_ACTION_URI = 1; // Indirect service URIs, savable
20 | |
21 | 1 | exports.Scheduler = function() {
22 | 1 | this.scheduledActions = [];
23 | 1 | this.filename = lconfig.me + "/scheduler.json";
24 | | };
25 | |
26 | 1 | exports.Scheduler.prototype.loadAndStart = function() {
27 | 1 | var self = this;
28 | 1 | lfs.readObjectsFromFile(this.filename, function(objects) {
29 | 1 | objects.forEach(function(action) {
30 | 1 | self.scheduleURL(new Date(action.at), action.serviceId, action.url);
31 | | });
32 | | });
33 | | }
34 | |
35 | 1 | exports.Scheduler.prototype.savePending = function() {
36 | 7 | var data = "";
37 | 7 | for(var i = 0; i < this.scheduledActions.length; ++i) {
38 | 3 | if (this.scheduledActions[i].type == SCHEDULE_ACTION_URI) {
39 | 3 | data += JSON.stringify(this.scheduledActions[i]) + '\n';
40 | | }
41 | | }
42 | 7 | fs.writeFileSync(this.filename, data);
43 | | }
44 | |
45 | 1 | exports.Scheduler.prototype.scheduleURL = function(atTime, serviceID, callbackURL) {
46 | 5 | if(callbackURL.substr(0,1) != "/") callbackURL = "/"+callbackURL; // be flexible in what you take
47 | 4 | var trackingInfo = {
48 | | at:atTime,
49 | | type:SCHEDULE_ACTION_URI,
50 | | serviceId:serviceID,
51 | | url:callbackURL
52 | | };
53 | 4 | this.scheduledActions.push(trackingInfo);
54 | 4 | if (typeof(atTime) == "number") {
55 | 0 | runTime = new Date;
56 | 0 | runTime.setTime(runTime.getTime() + atTime);
57 | 0 | atTime = runTime;
58 | | }
59 | 4 | var milliseconds = atTime.getTime() - Date.now();
60 | 6 | if (milliseconds < 0) milliseconds = 0;
61 | |
62 | 4 | var self = this;
63 | 4 | function runUrl() {
64 | 3 | request.get({url:lconfig.lockerBase + "/Me/" + serviceID + callbackURL}, function() {
65 | 3 | self.scheduledActions.splice(self.scheduledActions.indexOf(trackingInfo));
66 | 3 | self.savePending();
67 | | });
68 | | }
69 | 4 | setTimeout(function() {
70 | 4 | if (!serviceManager.isInstalled(serviceID)) {
71 | 1 | self.scheduledActions.splice(self.scheduledActions.indexOf(trackingInfo));
72 | 1 | self.savePending();
73 | | } else {
74 | 3 | if (!serviceManager.isRunning(serviceID)) {
75 | 1 | serviceManager.spawn(serviceID, runUrl);
76 | | } else {
77 | 2 | runUrl();
78 | | }
79 | | }
80 | | }, milliseconds);
81 | | }
82 | |
83 | 1 | exports.Scheduler.prototype.scheduleInternal = function(atTime, callback) {
84 | 0 | if (typeof(atTime) == "number") {
85 | 0 | runTime = new Date;
86 | 0 | runTime.setTime(runTime.getTime() + atTime);
87 | 0 | atTime = runTime;
88 | | }
89 | 0 | var trackingInfo = {
90 | | at:atTime,
91 | | type:SCHEDULE_ACTION_DIRECT,
92 | | cb:callback
93 | | };
94 | 0 | var self = this;
95 | 0 | this.scheduledActions.push(trackingInfo);
96 | 0 | var now = new Date;
97 | 0 | setTimeout(function() {
98 | 0 | self.scheduledActions.splice(self.scheduledActions.indexOf(trackingInfo));
99 | 0 | self.savePending();
100 | 0 | trackingInfo.cb();
101 | | }, trackingInfo.at.getTime() - now.getTime());
102 | | }
103 | |
104 | | /**
105 | | * Register a callback for a given time
106 | | *
107 | | * There are two ways that this may be called. For both methods the first argument
108 | | * is always a date object that the action should be fired at or as close to as
109 | | * possible.
110 | | *
111 | | * For a direct internal function callback the second argument is the callback to be
112 | | * fired when the time is hit.
113 | | *
114 | | * For a service URI callback the second argument is the service id to call into and
115 | | * the third argument is the URI path to call.
116 | | */
117 | 1 | exports.Scheduler.prototype.at = function() {
118 | 3 | if (arguments.length == 2) {
119 | 0 | this.scheduleInternal.apply(this, arguments);
120 | 3 | } else if (arguments.length == 3) {
121 | 3 | this.scheduleURL.apply(this, arguments);
122 | | } else {
123 | 0 | console.error("Invalid scheduler call.");
124 | | }
125 | 3 | this.savePending();
126 | | }
127 | |
128 | 1 | exports.masterScheduler = new exports.Scheduler;
129 | |
Common/node/lfs.js:
1 | | /*
2 | | *
3 | | * Copyright (C) 2011, The Locker Project
4 | | * All rights reserved.
5 | | *
6 | | * Please see the LICENSE file for more information.
7 | | *
8 | | */
9 | |
10 | 1 | var fs = require('fs'),
11 | | sys = require('sys'),
12 | | url = require('url'),
13 | | https = require('https'),
14 | | http = require('http'),
15 | | spawn = require('child_process').spawn;
16 | |
17 | | /**
18 | | * Appends an array of objects as lined-delimited JSON to the file at the specified path
19 | | */
20 | 1 | exports.appendObjectsToFile = function(path, objects) {
21 | 1 | var stream = fs.createWriteStream(path, {'flags':'a', 'encoding': 'utf-8'});
22 | 1 | for(var i = 0; i < objects.length; i++)
23 | 1 | stream.write(JSON.stringify(objects[i]) + '\n');
24 | 1 | stream.end();
25 | | }
26 | |
27 | | /**
28 | | * Writes an array of objects as lined-delimited JSON to the file at the specified path
29 | | */
30 | 1 | exports.writeObjectsToFile = function(path, objects) {
31 | 0 | var stream = fs.createWriteStream(path, {'encoding': 'utf-8'});
32 | 0 | for(var i = 0; i < objects.length; i++)
33 | 0 | stream.write(JSON.stringify(objects[i]) + '\n');
34 | 0 | stream.end();
35 | | }
36 | |
37 | | /**
38 | | * Reads an array of objects as lined-delimited JSON from the file at the specified path
39 | | */
40 | 1 | exports.readObjectsFromFile = function(path, callback) {
41 | 1 | var stream = fs.createReadStream(path, {'encoding': 'utf-8'});
42 | 1 | var data = "";
43 | 1 | stream.on('data', function(newData) {
44 | 1 | data += newData;
45 | | });
46 | 1 | stream.on('end', function() {
47 | 1 | var itemStrings = data.split('\n');
48 | 1 | var items = [];
49 | 1 | for(var i = 0; i < itemStrings.length; i++) {
50 | 1 | if(itemStrings[i])
51 | 1 | items.push(JSON.parse(itemStrings[i]));
52 | | }
53 | 1 | callback(items);
54 | | });
55 | 1 | stream.on('error', function(err) {
56 | 0 | callback([]);
57 | | });
58 | | }
59 | |
60 | | /**
61 | | * Reads an array of objects as lined-delimited JSON from the file at the specified path
62 | | */
63 | 1 | exports.readObjectFromFile = function(path, callback) {
64 | 0 | var stream = fs.createReadStream(path, {'encoding': 'utf-8'});
65 | 0 | var data = "";
66 | 0 | stream.on('data', function(newData) {
67 | 0 | data += newData;
68 | | });
69 | 0 | stream.on('end', function() {
70 | 0 | var item = {};
71 | 0 | try {
72 | 0 | item = JSON.parse(data);
73 | | } catch(err) {
74 | | }
75 | 0 | callback(item);
76 | | });
77 | 0 | stream.on('error', function(err) {
78 | 0 | callback({});
79 | | });
80 | | }
81 | |
82 | 1 | exports.writeObjectToFile = function(path, object) {
83 | 0 | fs.writeFileSync(path, JSON.stringify(object));
84 | | }
85 | |
86 | | /**
87 | | * Writes the me object to the me file (me.json)
88 | | */
89 | 1 | exports.syncMeData = function(metadata) {
90 | 0 | fs.writeFileSync('me.json', JSON.stringify(metadata)); // write this back to locker service?
91 | | }
92 | |
93 | | /**
94 | | * Reads the metadata file (meta.json) from the specificed account, or the first one found
95 | | * if no account is specified
96 | | */
97 | 1 | exports.loadMeData = function() {
98 | 0 | try {
99 | 0 | return JSON.parse(fs.readFileSync('me.json'));
100 | | } catch(err) {
101 | 0 | return {};
102 | | }
103 | | }
104 | |
105 | 1 | exports.saveUrl = function(requestURL, filename, callback) {
106 | 0 | var port = (url.parse(requestURL).protocol == 'http:') ? 80 : 443;
107 | 0 | var host = url.parse(requestURL).hostname;
108 | 0 | var client;
109 | 0 | if(port == 80)
110 | 0 | client = http;
111 | | else
112 | 0 | client = https;
113 | 0 | var parsedUrl = url.parse(requestURL, true);
114 | |
115 | 0 | var request = client.get({ host: host, port:port, path: (parsedUrl.pathname + parsedUrl.search)}, function(res) {
116 | 0 | var downloadfile = fs.createWriteStream(filename);
117 | 0 | res.pipe(downloadfile);
118 | 0 | res.on('end', function() {
119 | 0 | callback();
120 | | });
121 | | })
122 | 0 | request.on('error', function(error) {
123 | 0 | console.log('errorrs!!! '+requestURL);
124 | | });
125 | | }
126 | |
127 | | /**
128 | | * Lists the subdirectories at the specified path
129 | | */
130 | 1 | function listSubdirectories(path) {
131 | 0 | var files = fs.readdirSync(path);
132 | 0 | var dirs = [];
133 | 0 | for(var i in files) {
134 | 0 | var fullPath = path + '/' + files[i];
135 | 0 | var stats = fs.statSync(fullPath);
136 | 0 | if(!stats.isDirectory())
137 | 0 | continue;
138 | 0 | dirs.push(files[i]);
139 | | }
140 | 0 | return dirs;
141 | | }
Common/node/lservicemanager.js:
1 | | /*
2 | | *
3 | | * Copyright (C) 2011, The Locker Project
4 | | * All rights reserved.
5 | | *
6 | | * Please see the LICENSE file for more information.
7 | | *
8 | | */
9 | |
10 | 1 | var fs = require("fs");
11 | 1 | var path = require("path");
12 | 1 | var lconfig = require("lconfig");
13 | 1 | var crypto = require("crypto");
14 | 1 | var util = require("util");
15 | 1 | var spawn = require('child_process').spawn;
16 | 1 | var levents = require('levents');
17 | 1 | var wrench = require('wrench');
18 | |
19 | 1 | var serviceMap = {
20 | | available:[],
21 | | disabled:[],
22 | | installed:{}
23 | | };
24 | |
25 | 1 | var shuttingDown = null;
26 | 1 | var lockerPortNext = parseInt("1" + lconfig.lockerPort, 10);
27 | 1 | console.log('lservicemanager lockerPortNext = ' + lockerPortNext);
28 | |
29 | 1 | exports.serviceMap = function() {
30 | | // XXX Sterilize?
31 | 2 | return serviceMap;
32 | | }
33 | |
34 | 1 | exports.providers = function(types) {
35 | 8 | var services = [];
36 | 8 | for(var svcId in serviceMap.installed) {
37 | 240 | if (!serviceMap.installed.hasOwnProperty(svcId)) continue;
38 | 240 | var service = serviceMap.installed[svcId];
39 | 352 | if (!service.hasOwnProperty("provides")) continue;
40 | 128 | if (service.provides.some(function(svcType, index, actualArray) {
41 | 176 | for (var i = 0; i < types.length; i++) {
42 | 218 | var currentType = types[i];
43 | 218 | var currentTypeSlashIndex = currentType.indexOf("/");
44 | 218 | if (currentTypeSlashIndex < 0) {
45 | | // This is a primary only comparison
46 | 44 | var svcTypeSlashIndex = svcType.indexOf("/");
47 | 44 | if (svcTypeSlashIndex < 0 && currentType == svcType) return true;
48 | 48 | if (currentType == svcType.substring(0, svcTypeSlashIndex)) return true;
49 | 40 | continue;
50 | | }
51 | | // Full comparison
52 | 180 | if (currentType == svcType) return true;
53 | | }
54 | 166 | return false;
55 | | })) {
56 | 10 | services.push(service);
57 | | }
58 | | }
59 | 8 | return services;
60 | | }
61 | |
62 | | /**
63 | | * Map a meta data file JSON with a few more fields and make it available
64 | | */
65 | 1 | function mapMetaData(file, type, installable) {
66 | 46 | var metaData = JSON.parse(fs.readFileSync(file, 'utf-8'));
67 | 46 | metaData.srcdir = path.dirname(file);
68 | 46 | metaData.is = type;
69 | 46 | metaData.installable = installable;
70 | 46 | metaData.externalUri = lconfig.externalBase+"/Me/"+metaData.id+"/";
71 | 46 | serviceMap.available.push(metaData);
72 | 46 | if (type === "collection") {
73 | 5 | if(!metaData.handle) {
74 | 0 | console.error("missing handle for "+file);
75 | 0 | return;
76 | | }
77 | 5 | if (metaData.status != 'stub') {
78 | 2 | fs.stat(lconfig.lockerDir+"/" + lconfig.me + "/"+metaData.handle,function(err,stat){
79 | 2 | if(err || !stat) {
80 | 0 | metaData.id=metaData.handle;
81 | 0 | metaData.uri = lconfig.lockerBase+"/Me/"+metaData.id+"/";
82 | 0 | metaData.externalUri = lconfig.externalBase+"/Me/"+metaData.id+"/";
83 | 0 | serviceMap.installed[metaData.id] = metaData;
84 | 0 | fs.mkdirSync(lconfig.lockerDir + "/" + lconfig.me + "/"+metaData.id,0755);
85 | 0 | fs.writeFileSync(lconfig.lockerDir + "/" + lconfig.me + "/"+metaData.id+'/me.json',JSON.stringify(metaData, null, 4));
86 | | }
87 | | });
88 | | }
89 | | }
90 | |
91 | 46 | return metaData;
92 | | }
93 | |
94 | | /**
95 | | * The types of services that are currently understood
96 | | */
97 | 1 | var scannedTypes = ["collection", "connector", "app"];
98 | |
99 | | /**
100 | | * Scans a directory for available services
101 | | */
102 | 1 | exports.scanDirectory = function(dir, installable) {
103 | 107 | if (typeof(installable) == "undefined") {
104 | 0 | installable = true;
105 | | }
106 | |
107 | 107 | var files = fs.readdirSync(dir);
108 | 107 | for (var i = 0; i < files.length; i++) {
109 | 371 | var fullPath = dir + '/' + files[i];
110 | 371 | var stats = fs.statSync(fullPath);
111 | 371 | if(stats.isDirectory()) {
112 | 103 | exports.scanDirectory(fullPath, installable);
113 | 103 | continue;
114 | | }
115 | 268 | scannedTypes.forEach(function(scanType) {
116 | 804 | if (RegExp("\\." + scanType + "$").test(fullPath)) {
117 | 46 | mapMetaData(fullPath, scanType, installable);
118 | | }
119 | | });
120 | | }
121 | | }
122 | |
123 | | /**
124 | | * Scans the Me directory for instaled services
125 | | */
126 | 1 | exports.findInstalled = function () {
127 | 1 | serviceMap.installed = {};
128 | 1 | var dirs = fs.readdirSync(lconfig.me );
129 | 1 | for (var i = 0; i < dirs.length; i++) {
130 | 36 | if(dirs[i] == "diary") continue;
131 | 36 | var dir = lconfig.me + '/' + dirs[i];
132 | 36 | try {
133 | 41 | if(!fs.statSync(dir).isDirectory()) continue;
134 | 31 | if(!fs.statSync(dir+'/me.json').isFile()) continue;
135 | 30 | var js = JSON.parse(fs.readFileSync(dir+'/me.json', 'utf-8'));
136 | 30 | delete js.pid;
137 | 30 | delete js.starting;
138 | 30 | js.externalUri = lconfig.externalBase+"/Me/"+js.id+"/";
139 | 30 | exports.migrate(dir, js);
140 | 30 | addEvents(js);
141 | 30 | console.log("Loaded " + js.id);
142 | 30 | serviceMap.installed[js.id] = js;
143 | 30 | if (js.disabled) {
144 | 1 | serviceMap.disabled.push(js.id);
145 | | }
146 | | } catch (E) {
147 | | // console.log("Me/"+dirs[i]+" does not appear to be a service (" +E+ ")");
148 | | }
149 | | }
150 | | }
151 | |
152 | 1 | addEvents = function(info) {
153 | 31 | if (info.events) {
154 | 1 | for (var i = 0; i < info.events.length; i++) {
155 | 1 | var ev = info.events[i];
156 | 1 | levents.addListener(ev[0], info.id, ev[1]);
157 | | }
158 | | }
159 | |
160 | | }
161 | |
162 | | /**
163 | | * Migrate a service if necessary
164 | | */
165 | 1 | exports.migrate = function(installedDir, metaData) {
166 | 57 | if (!metaData.version) { metaData.version = 1; }
167 | 30 | var migrations = [];
168 | 30 | try {
169 | 30 | migrations = fs.readdirSync(metaData.srcdir + "/migrations");
170 | | } catch (E) {}
171 | 30 | if (migrations) {
172 | 30 | for (var i = 0; i < migrations.length; i++) {
173 | 13 | if (migrations[i].substring(0, 13) > metaData.version) {
174 | 11 | try {
175 | 11 | var cwd = process.cwd();
176 | 11 | migrate = require(cwd + "/" + metaData.srcdir + "/migrations/" + migrations[i]);
177 | 11 | if (migrate(installedDir)) {
178 | 11 | metaData.version = migrations[i].substring(0, 13);
179 | | }
180 | 11 | process.chdir(cwd);
181 | | } catch (E) {
182 | 0 | console.log("error running migration : " + migrations[i] + " for service " + metaData.title + " ---- " + E);
183 | 0 | process.chdir(cwd);
184 | | }
185 | | }
186 | | }
187 | | }
188 | 30 | return;
189 | | }
190 | |
191 | | /**
192 | | * Install a service
193 | | */
194 | 1 | exports.install = function(metaData) {
195 | 2 | var serviceInfo;
196 | 2 | serviceMap.available.some(function(svcInfo) {
197 | 57 | if (svcInfo.srcdir == metaData.srcdir) {
198 | 1 | serviceInfo = {};
199 | 11 | for(var a in svcInfo){serviceInfo[a]=svcInfo[a];}
200 | 1 | return true;
201 | | }
202 | 56 | return false;
203 | | });
204 | 2 | if (!serviceInfo || !serviceInfo.installable) {
205 | 1 | return serviceInfo;
206 | | }
207 | 1 | var authInfo;
208 | | // local/internal name for the service on disk and whatnot, try to make it more friendly to devs/debugging
209 | 1 | if(serviceInfo.handle) {
210 | 1 | try {
211 | 1 | var apiKeys = JSON.parse(fs.readFileSync(lconfig.lockerDir + "/" + lconfig.me + "/apikeys.json", 'ascii'));
212 | 1 | authInfo = apiKeys[serviceInfo.handle];
213 | | } catch (E) {}
214 | | // the inanity of this try/catch bullshit is drrrrrrnt but async is stupid here and I'm offline to find a better way atm
215 | 1 | var inc = 0;
216 | 1 | try {
217 | 1 | if(fs.statSync(lconfig.lockerDir+"/" + lconfig.me + "/"+serviceInfo.handle).isDirectory()) {
218 | 0 | inc++;
219 | 0 | while(fs.statSync(lconfig.lockerDir+"/" + lconfig.me + "/"+serviceInfo.handle+"-"+inc).isDirectory()) {inc++;}
220 | | }
221 | | } catch (E) {
222 | 1 | var suffix = (inc > 0)?"-"+inc:"";
223 | 1 | serviceInfo.id = serviceInfo.handle+suffix;
224 | | }
225 | | } else {
226 | 0 | var hash = crypto.createHash('md5');
227 | 0 | hash.update(Math.random()+'');
228 | 0 | serviceInfo.id = hash.digest('hex');
229 | | }
230 | 1 | serviceInfo.uri = lconfig.lockerBase+"/Me/"+serviceInfo.id+"/";
231 | 1 | serviceInfo.version = Date.now();
232 | 1 | serviceMap.installed[serviceInfo.id] = serviceInfo;
233 | 1 | fs.mkdirSync(lconfig.lockerDir + "/" + lconfig.me + "/"+serviceInfo.id,0755);
234 | 1 | fs.writeFileSync(lconfig.lockerDir + "/" + lconfig.me + "/"+serviceInfo.id+'/me.json',JSON.stringify(serviceInfo, null, 4));
235 | 1 | if (authInfo) {
236 | 0 | fs.writeFileSync(lconfig.lockerDir + "/" + lconfig.me + "/" + serviceInfo.id + '/auth.json', JSON.stringify(authInfo));
237 | | }
238 | 1 | addEvents(serviceInfo);
239 | 1 | serviceInfo.externalUri = lconfig.externalBase+"/Me/"+serviceInfo.id+"/";
240 | 1 | return serviceInfo;
241 | | }
242 | |
243 | | //! Spawn a service instance
244 | | /**
245 | | * \param svc The service description to start
246 | | * \param callback Completion callback
247 | | *
248 | | * The service will be spanwed as described in its configuration file. The service can
249 | | * read its environment description from stdin which will consist of one JSON object. The
250 | | * object will have a mandatory port and workingDirectory field, but the rest is optional.
251 | | * \code
252 | | * {
253 | | * port:18044,
254 | | * workingDirectory:"/some/path/"
255 | | * }
256 | | * \endcode
257 | | * Once the service has completed its startup it will write out to stdout a single JSON object
258 | | * with the used port and any other environment information. The port must be the actual
259 | | * port the service is listening on.
260 | | * \code
261 | | * {
262 | | * port:18044
263 | | * }
264 | | * \encode
265 | | */
266 | 1 | exports.spawn = function(serviceId, callback) {
267 | 34 | var svc = exports.metaInfo(serviceId);
268 | 34 | if (!svc) {
269 | 0 | console.error("Attempting to spawn an unknown service " + serviceId);
270 | 0 | return;
271 | | }
272 | |
273 | | // Already running
274 | 34 | if (svc.pid) return;
275 | | // Queue up callbacks if we are already trying to start this service
276 | 34 | if (callback) {
277 | 34 | if (svc.hasOwnProperty("starting")) {
278 | 14 | console.log(svc.id + " is still spawning, adding callback to queue.");
279 | 14 | svc.starting.push(callback);
280 | 14 | return;
281 | | } else {
282 | 20 | svc.starting = [callback];
283 | | }
284 | | }
285 | |
286 | | //get the run command from the serviceMap based on the service's source directory (possible versioning problem here)
287 | 20 | var run;
288 | 20 | var serviceInfo;
289 | 20 | for(var i in serviceMap.available) {
290 | 731 | if(serviceMap.available[i].srcdir == svc.srcdir) {
291 | 15 | serviceInfo = serviceMap.available[i];
292 | 15 | if (serviceInfo.static == "true") {
293 | 0 | run = "node " + __dirname + "/app/static.js";
294 | | } else {
295 | 15 | run = serviceInfo.run;
296 | | }
297 | 15 | break;
298 | | }
299 | | }
300 | 20 | run = run || svc.run;
301 | 20 | if(!run) {
302 | 0 | console.error('Could not spawn service from source directory', svc.srcdir);
303 | 0 | return;
304 | | }
305 | |
306 | 20 | run = run.split(" "); // node foo.js
307 | |
308 | 20 | svc.port = ++lockerPortNext;
309 | 20 | console.log('spawning into: ' + lconfig.lockerDir + '/' + lconfig.me + '/' + svc.id);
310 | 20 | var processInformation = {
311 | | port: svc.port, // This is just a suggested port
312 | | sourceDirectory: lconfig.lockerDir + "/" + svc.srcdir,
313 | | workingDirectory: lconfig.lockerDir + '/' + lconfig.me + '/' + svc.id, // A path into the me directory
314 | | lockerUrl:lconfig.lockerBase,
315 | | externalBase:lconfig.externalBase + '/Me/' + svc.id + '/'
316 | | };
317 | 20 | if(serviceInfo && serviceInfo.mongoCollections) {
318 | 9 | processInformation.mongo = {
319 | | host: lconfig.mongo.host,
320 | | port: lconfig.mongo.port
321 | | }
322 | 9 | processInformation.mongo.collections = serviceInfo.mongoCollections;
323 | | }
324 | 20 | var env = process.env;
325 | 20 | env["NODE_PATH"] = lconfig.lockerDir+'/Common/node/';
326 | 20 | app = spawn(run.shift(), run, {cwd: svc.srcdir, env:process.env});
327 | 20 | app.stderr.on('data', function (data) {
328 | 25 | var mod = console.outputModule;
329 | 25 | console.outputModule = svc.title;
330 | 25 | console.error(data);
331 | 25 | console.outputModule = mod;
332 | | });
333 | 20 | app.stdout.on('data',function (data) {
334 | 26 | var mod = console.outputModule;
335 | 26 | console.outputModule = svc.title;
336 | 26 | if (svc.hasOwnProperty("pid")) {
337 | | // We're already running so just log it for them
338 | 6 | console.log(data);
339 | | } else {
340 | | // Process the startup json info
341 | 20 | try {
342 | 20 | var returnedProcessInformation = JSON.parse(data);
343 | | // if they tell us a port, use that
344 | 20 | if(returnedProcessInformation.port)
345 | 20 | svc.port = returnedProcessInformation.port;
346 | 20 | svc.uriLocal = "http://localhost:"+svc.port+"/";
347 | | // save out all updated meta fields
348 | 20 | fs.writeFileSync(lconfig.lockerDir + "/" + lconfig.me + "/" + svc.id + '/me.json',JSON.stringify(svc, null, 4));
349 | | // Set the pid after the write because it's transient to this locker instance only
350 | | // I'm confused why we have to use startingPid and app.pid is invalid here
351 | 20 | svc.pid = svc.startingPid;
352 | 20 | delete svc.startingPid;
353 | 20 | console.log(svc.id + " started at pid " + svc.pid + ", running startup callbacks.");
354 | 20 | svc.starting.forEach(function(cb) {
355 | 34 | cb.call();
356 | | // See if it ended whilst running the callbacks
357 | 34 | if (!svc.hasOwnProperty("pid") && svc.starting.length > 0) {
358 | | // We'll try again in a sec
359 | 0 | setTimeout(function() {
360 | 0 | exports.spawn(svc.id);
361 | | }, 10);
362 | 0 | return;
363 | | }
364 | | });
365 | 20 | delete svc.starting;
366 | | } catch(error) {
367 | 0 | console.error("The process did not return valid startup information. "+error);
368 | 0 | app.kill();
369 | | }
370 | | }
371 | 26 | console.outputModule = mod;
372 | |
373 | | });
374 | 20 | app.on('exit', function (code) {
375 | 2 | console.log(svc.id + " process has ended.");
376 | 2 | var id = svc.id;
377 | | //remove transient fields
378 | 2 | delete svc.pid;
379 | 2 | delete svc.port;
380 | 2 | delete svc.uriLocal;
381 | | // save out all updated meta fields (pretty print!)
382 | 2 | if (!svc.uninstalled) {
383 | 1 | fs.writeFileSync(lconfig.lockerDir + "/" + lconfig.me + "/" + id + '/me.json', JSON.stringify(svc, null, 4));
384 | | }
385 | 2 | checkForShutdown();
386 | | });
387 | 20 | console.log("sending "+svc.id+" startup info of "+JSON.stringify(processInformation));
388 | 20 | app.stdin.write(JSON.stringify(processInformation)+"\n"); // Send them the process information
389 | | // We track this here because app.pid doesn't seem to work inside the next context
390 | 20 | svc.startingPid = app.pid;
391 | | }
392 | |
393 | | /**
394 | | * Retrieve the meta information for a service
395 | | */
396 | 1 | exports.metaInfo = function(serviceId) {
397 | 151 | return serviceMap.installed[serviceId];
398 | | }
399 | |
400 | 1 | exports.isInstalled = function(serviceId) {
401 | 141 | if (serviceMap.disabled.indexOf(serviceId) > -1) {
402 | 0 | return false;
403 | | }
404 | 141 | return serviceId in serviceMap.installed;
405 | | }
406 | |
407 | 1 | exports.isAvailable = function(serviceId) {
408 | 0 | return serviceId in serviceMap.available;
409 | | }
410 | |
411 | 1 | exports.isDisabled = function(serviceId) {
412 | 57 | return (serviceMap.disabled.indexOf(serviceId) > -1);
413 | | }
414 | |
415 | | /**
416 | | * Shutdown all running services
417 | | *
418 | | * \param cb Callback to call when the shutdown is complete
419 | | */
420 | 1 | exports.shutdown = function(cb) {
421 | 0 | shuttingDown = cb;
422 | 0 | for(var mapEntry in serviceMap.installed) {
423 | 0 | var svc = serviceMap.installed[mapEntry];
424 | 0 | if (svc.pid) {
425 | 0 | try {
426 | 0 | console.log("Killing running service " + svc.id + " at pid " + svc.pid);
427 | 0 | process.kill(svc.pid, "SIGINT");
428 | | } catch(e) {
429 | | }
430 | | }
431 | | }
432 | 0 | checkForShutdown();
433 | | }
434 | |
435 | 1 | exports.disable = function(id) {
436 | 1 | if(!id)
437 | 0 | return;
438 | 1 | serviceMap.disabled.push(id);
439 | 1 | var svc = serviceMap.installed[id];
440 | 1 | if(!svc)
441 | 0 | return;
442 | 1 | svc.disabled = true;
443 | 1 | if (svc) {
444 | 1 | if (svc.pid) {
445 | 1 | try {
446 | 1 | console.log("Killing running service " + svc.id + " at pid " + svc.pid);
447 | 1 | process.kill(svc.pid, "SIGINT");
448 | | } catch (e) {}
449 | | }
450 | | }
451 | | // save out all updated meta fields (pretty print!)
452 | 1 | fs.writeFileSync(lconfig.lockerDir + "/" + lconfig.me + "/" + id + '/me.json', JSON.stringify(svc, null, 4));
453 | | }
454 | |
455 | 1 | exports.uninstall = function(serviceId, callback) {
456 | 1 | var svc = serviceMap.installed[serviceId];
457 | 1 | var lmongoclient = require('lmongoclient')(lconfig.mongo.host, lconfig.mongo.port, svc.id, svc.mongoCollections);
458 | 1 | lmongoclient.connect(function(mongo) {
459 | 1 | var keys = Object.getOwnPropertyNames(mongo.collections);
460 | 1 | (function deleteCollection (keys, callback) {
461 | 3 | if (keys.length > 0) {
462 | 2 | key = keys.splice(0, 1);
463 | 2 | coll = mongo.collections[key];
464 | 4 | coll.drop(function() {deleteCollection(keys, callback);});
465 | | } else {
466 | 1 | callback();
467 | | }
468 | | })(keys, function() {
469 | 1 | svc.uninstalled = true;
470 | 1 | if (svc.pid) {
471 | 1 | process.kill(svc.pid, "SIGINT");
472 | | }
473 | 1 | wrench.rmdirSyncRecursive(lconfig.me + "/" + serviceId);
474 | 1 | delete serviceMap.installed[serviceId];
475 | 1 | callback();
476 | | });
477 | | })
478 | | };
479 | |
480 | 1 | exports.enable = function(id) {
481 | 1 | if(!id)
482 | 0 | return;
483 | 1 | serviceMap.disabled.splice(serviceMap.disabled.indexOf(id), 1);
484 | 1 | var svc;
485 | 1 | for(var i in serviceMap.installed) {
486 | 31 | if(serviceMap.installed[i].id === id) {
487 | 1 | svc = serviceMap.installed[i];
488 | 1 | delete svc.disabled;
489 | | }
490 | | }
491 | 1 | if(!svc)
492 | 0 | return;
493 | | // save out all updated meta fields (pretty print!)
494 | 1 | fs.writeFileSync(lconfig.lockerDir + "/" + lconfig.me + "/" + id + '/me.json', JSON.stringify(svc, null, 4));
495 | | };
496 | |
497 | | /**
498 | | * Return whether the service is running
499 | | */
500 | 1 | exports.isRunning = function(serviceId) {
501 | 60 | return exports.isInstalled(serviceId) && exports.metaInfo(serviceId).pid;
502 | | }
503 | |
504 | 1 | function checkForShutdown() {
505 | 4 | if (!shuttingDown) return;
506 | 0 | for(var mapEntry in serviceMap.installed) {
507 | 0 | var svc = serviceMap.installed[mapEntry];
508 | 0 | if (svc.pid) {
509 | 0 | console.log(svc.id + " is still running, cannot complete shutdown.");
510 | 0 | return;
511 | | }
512 | | }
513 | 0 | shuttingDown();
514 | 0 | shuttingDown = null;
515 | | }
Common/node/levents.js:
1 | | /*
2 | | *
3 | | * Copyright (C) 2011, The Locker Project
4 | | * All rights reserved.
5 | | *
6 | | * Please see the LICENSE file for more information.
7 | | *
8 | | */
9 | |
10 | 1 | var http = require("http");
11 | 1 | var url = require("url");
12 | 1 | require.paths.push(__dirname);
13 | |
14 | 1 | var locker = require("locker");
15 | 1 | var serviceManager = require("lservicemanager");
16 | |
17 | 1 | var eventListeners = {};
18 | |
19 | 1 | exports.addListener = function(type, id, cb) {
20 | 7 | console.log("Adding a listener for " + id + cb + " to " + type);
21 | 13 | if (!eventListeners.hasOwnProperty(type)) eventListeners[type] = [];
22 | 7 | eventListeners[type].push({"id":id, "cb":cb});
23 | | }
24 | |
25 | 1 | exports.removeListener = function(type, id, cb) {
26 | 1 | console.log("Going to remove " + id + cb + " from " + type);
27 | 1 | if (!eventListeners.hasOwnProperty(type)) return;
28 | 1 | var pos = findListenerPosition(type, id, cb);
29 | 2 | if (pos >= 0) eventListeners[type].splice(pos, 1);
30 | | }
31 | |
32 | 1 | exports.fireEvent = function(type, id, obj) {
33 | 5 | if (!eventListeners.hasOwnProperty(type)) return;
34 | | // console.log("Firing " + eventListeners[type].length + " listeners for " + type + " from " + id);
35 | 5 | eventListeners[type].forEach(function(listener) {
36 | 6 | if (!serviceManager.isInstalled(listener.id)) return;
37 | 6 | function sendEvent() {
38 | 6 | var serviceInfo = serviceManager.metaInfo(listener.id);
39 | 6 | var cbUrl = url.parse(serviceInfo.uriLocal);
40 | 6 | var httpOpts = {
41 | | host: cbUrl.hostname,
42 | | port: cbUrl.port,
43 | | path: listener.cb,
44 | | method:"POST",
45 | | headers: {
46 | | "Content-Type":"application/json"
47 | | }
48 | | };
49 | | // console.log("Firing event to " + listener.id + " to " + listener.cb);
50 | 6 | locker.makeRequest(httpOpts, JSON.stringify({obj:obj, _via:[id]}));
51 | | }
52 | 6 | if (!serviceManager.isRunning(listener.id)) {
53 | 2 | serviceManager.spawn(listener.id, sendEvent);
54 | | } else {
55 | 4 | sendEvent();
56 | | }
57 | | });
58 | | }
59 | |
60 | 1 | function findListenerPosition(type, id, cb) {
61 | 1 | for (var i = 0; i < eventListeners[type].length; ++i) {
62 | 1 | var listener = eventListeners[type][i];
63 | 2 | if (listener.id == id && listener.cb == cb) return i;
64 | | }
65 | 0 | return -1;
66 | | }
Common/node/locker.js:
1 | | /*
2 | | *
3 | | * Copyright (C) 2011, The Locker Project
4 | | * All rights reserved.
5 | | *
6 | | * Please see the LICENSE file for more information.
7 | | *
8 | | */
9 | |
10 | 1 | var request = require('request'),
11 | | fs = require("fs"),
12 | | sys = require('sys'),
13 | | http = require("http"),
14 | | url = require("url"),
15 | | querystring = require("querystring");
16 | |
17 | 1 | var lmongoclient;
18 | |
19 | 1 | var lockerBase;
20 | 1 | var localServiceId;
21 | 1 | var baseServiceUrl;
22 | |
23 | 1 | exports.initClient = function(instanceInfo) {
24 | 0 | var meData = fs.readFileSync(instanceInfo.workingDirectory + "/me.json");
25 | 0 | var svcInfo = JSON.parse(meData);
26 | 0 | localServiceId = svcInfo.id;
27 | 0 | lockerBase = instanceInfo.lockerUrl;
28 | 0 | baseServiceUrl = lockerBase + "/core/" + localServiceId;
29 | 0 | if(instanceInfo.mongo) {
30 | 0 | lmongoclient = require(__dirname + '/lmongoclient')(instanceInfo.mongo.host, instanceInfo.mongo.port,
31 | | localServiceId, instanceInfo.mongo.collections);
32 | 0 | exports.connectToMongo = lmongoclient.connect;
33 | | }
34 | | };
35 | |
36 | 1 | exports.at = function(uri, delayInSec) {
37 | 0 | request.get({
38 | | url:baseServiceUrl + '/at?' + querystring.stringify({
39 | | cb:uri,
40 | | at:((new Date().getTime() + (delayInSec * 1000))/1000)
41 | | })
42 | | });
43 | | };
44 | |
45 | 1 | exports.diary = function(message, level) {
46 | 0 | request.get({
47 | | url:baseServiceUrl + '/diary?' + querystring.stringify({
48 | | message:message,
49 | | level:level
50 | | })
51 | | });
52 | | };
53 | |
54 | 1 | exports.makeRequest = function(httpOpts, body, callback) {
55 | 6 | var req = http.request(httpOpts, callback);
56 | 6 | req.write(body);
57 | 6 | req.end();
58 | | };
59 | |
60 | 1 | exports.map = function(callback) {
61 | 0 | request.get({url:lockerBase + "/map"}, function(error, res, body) {
62 | 0 | callback(error, body ? JSON.parse(body) : undefined);
63 | | });
64 | | };
65 | |
66 | 1 | exports.providers = function(types, callback) {
67 | 0 | if (typeof(types) == "string") types = [types];
68 | 0 | request.get({url:lockerBase + "/providers?" + querystring.stringify({"types":types.join(",")})},
69 | | function(error, res, body) {
70 | 0 | callback(error, body ? JSON.parse(body) : undefined);
71 | | });
72 | | };
73 | |
74 | | /**
75 | | * Post an event
76 | | * type - the MIME-style type of the object (e.g. photo/flickr, message/IMAP, or link/firefox)
77 | | * obj - the object to make a JSON string of as the event body
78 | | */
79 | 1 | exports.event = function(type, obj) {
80 | 0 | request.post({
81 | | url:baseServiceUrl + "/event",
82 | | json:{"type":type,"obj":obj}
83 | | });
84 | | };
85 | |
86 | | /**
87 | | * Sign up to be notified of events
88 | | * type - the MIME-style type of the object (e.g. photo/flickr, message/IMAP, or link/firefox)
89 | | * callback - the URL path at the listener to callback to
90 | | *
91 | | * for example, if our id is "foo" and we want to get a ping at "/photoListener"
92 | | * for photos from a flickr connector with id "bar", our call would look like this:
93 | | *
94 | | * listen("photo/flickr", "/photoListener");
95 | | */
96 | 1 | exports.listen = function(type, callbackEndpoint, callbackFunction) {
97 | 0 | request.get({url:baseServiceUrl + '/listen?' + querystring.stringify({'type':type, 'cb':callbackEndpoint})},
98 | | function(error, response, body) {
99 | 0 | if(error) sys.debug(error);
100 | 0 | if(callbackFunction) callbackFunction(error);
101 | | });
102 | | };
Ops/dashboard.js:
1 | | /*
2 | | *
3 | | * Copyright (C) 2011, The Locker Project
4 | | * All rights reserved.
5 | | *
6 | | * Please see the LICENSE file for more information.
7 | | *
8 | | */
9 | |
10 | 1 | var spawn = require('child_process').spawn;
11 | 1 | var lconfig = require('../Common/node/lconfig.js');
12 | 1 | var dashboard;
13 | |
14 | 1 | lconfig.load('config.json');
15 | 1 | exports.instance = dashboard;
16 | |
17 | 1 | exports.start = function(port) {
18 | | // start dashboard
19 | 1 | dashboard = spawn('node', ['dashboard-client.js', lconfig.lockerHost, lconfig.lockerPort, port, lconfig.externalBase],
20 | | {cwd: __dirname + '/Dashboard'});
21 | 1 | dashboard.uriLocal = 'http://' + lconfig.lockerHost + ':' + port;
22 | 1 | dashboard.port = port;
23 | 1 | console.log('Spawned dashboard pid: ' + dashboard.pid);
24 | 1 | dashboard.stdout.on('data',function (data){
25 | 0 | console.log('dashboard stdout: '+data);
26 | | });
27 | 1 | dashboard.stderr.on('data',function (data){
28 | 0 | console.log('Error dashboard: '+data);
29 | | });
30 | 1 | dashboard.on('exit', function (code) {
31 | 0 | if(code > 0) console.log('dashboard died with code ' + code);
32 | | });
33 | 1 | exports.instance = dashboard;
34 | | }
35 | |
Ops/webservice.js:
1 | | /*
2 | | *
3 | | * Copyright (C) 2011, The Locker Project
4 | | * All rights reserved.
5 | | *
6 | | * Please see the LICENSE file for more information.
7 | | *
8 | | */
9 | |
10 | 1 | var url = require("url");
11 | 1 | var http = require('http');
12 | 1 | var request = require('request');
13 | 1 | var lscheduler = require("lscheduler");
14 | 1 | var levents = require("levents");
15 | 1 | var serviceManager = require("lservicemanager");
16 | 1 | var dashboard = require(__dirname + "/dashboard.js");
17 | 1 | var express = require('express');
18 | 1 | var connect = require('connect');
19 | 1 | var request = require('request');
20 | 1 | var sys = require('sys');
21 | 1 | var fs = require("fs");
22 | 1 | var url = require('url');
23 | 1 | var lfs = require(__dirname + "/../Common/node/lfs.js");
24 | 1 | var httpProxy = require('http-proxy');
25 | 1 | var lpquery = require("lpquery");
26 | 1 | var lconfig = require("lconfig");
27 | |
28 | 1 | var lcrypto = require("lcrypto");
29 | |
30 | 1 | var proxy = new httpProxy.HttpProxy();
31 | 1 | var scheduler = lscheduler.masterScheduler;
32 | |
33 | 1 | var locker = express.createServer(
34 | | // we only use bodyParser to create .params for callbacks from services, connect should have a better way to do this
35 | | function(req, res, next) {
36 | 89 | if (req.url.substring(0, 6) == "/core/" ) {
37 | 22 | connect.bodyParser()(req, res, next);
38 | | } else {
39 | 67 | next();
40 | | }
41 | | }
42 | | );
43 | |
44 | |
45 | 1 | var listeners = new Object(); // listeners for events
46 | |
47 | | // return the known map of our world
48 | 1 | locker.get('/map', function(req, res) {
49 | 2 | res.writeHead(200, {
50 | | 'Content-Type': 'text/javascript',
51 | | "Access-Control-Allow-Origin" : "*"
52 | | });
53 | 2 | res.end(JSON.stringify(serviceManager.serviceMap()));
54 | | });
55 | |
56 | 1 | locker.get("/providers", function(req, res) {
57 | 8 | console.log("Looking for providers of type " + req.param("types"));
58 | 8 | if (!req.param("types")) {
59 | 0 | res.writeHead(400);
60 | 0 | res.end("[]");
61 | 0 | return;
62 | | }
63 | 8 | res.writeHead(200, {"Content-Type":"application/json"});
64 | 8 | res.end(JSON.stringify(serviceManager.providers(req.param("types").split(","))));
65 | | });
66 | |
67 | 1 | locker.get("/encrypt", function(req, res) {
68 | 0 | if (!req.param("s")) {
69 | 0 | res.writeHead(400);
70 | 0 | res.end();
71 | 0 | return;
72 | | }
73 | 0 | console.log("encrypting " + req.param("s"));
74 | 0 | res.end(lcrypto.encrypt(req.param("s")));
75 | | });
76 | |
77 | 1 | locker.get("/decrypt", function(req, res) {
78 | 0 | if (!req.param("s")) {
79 | 0 | res.writeHead(400);
80 | 0 | res.end();
81 | 0 | return;
82 | | }
83 | 0 | res.end(lcrypto.decrypt(req.param("s")));
84 | | });
85 | |
86 | | // search interface
87 | 1 | locker.get("/query/:query", function(req, res) {
88 | 0 | var data = decodeURIComponent(req.originalUrl.substr(6)).replace(/%21/g, '!').replace(/%27/g, "'").replace(/%28/g, '(').replace(/%29/g, ')').replace(/%2a/ig, '*');
89 | 0 | try {
90 | 0 | var query = lpquery.buildMongoQuery(lpquery.parse(data));
91 | 0 | var providers = serviceManager.serviceMap().installed;
92 | 0 | var provider = undefined;
93 | 0 | for (var key in providers) {
94 | 0 | if (providers.hasOwnProperty(key) && providers[key].provides && providers[key].provides.indexOf(query.collection) >= 0 )
95 | 0 | provider = providers[key];
96 | | }
97 | |
98 | 0 | if (provider == undefined) {
99 | 0 | res.writeHead(404);
100 | 0 | res.end(query.collection + " not found to query");
101 | 0 | return;
102 | | }
103 | |
104 | 0 | var mongo = require("lmongoclient")(lconfig.mongo.host, lconfig.mongo.port, provider.id, provider.mongoCollections);
105 | 0 | mongo.connect(function(mongo) {
106 | 0 | try {
107 | 0 | var collection = mongo.collections[provider.mongoCollections[0]];
108 | 0 | console.log("Querying " + JSON.stringify(query));
109 | 0 | var options = {};
110 | 0 | if (query.limit) options.limit = query.limit;
111 | 0 | if (query.skip) options.skip = query.skip;
112 | 0 | collection.find(query.query, options, function(err, foundObjects) {
113 | 0 | if (err) {
114 | 0 | res.writeHead(500);
115 | 0 | res.end(err);
116 | 0 | return;
117 | | }
118 | |
119 | 0 | foundObjects.toArray(function(err, objects) {
120 | 0 | res.end(JSON.stringify(objects));
121 | | });
122 | | });
123 | | } catch (E) {
124 | 0 | res.writeHead(500);
125 | 0 | res.end('Something broke while trying to query Mongo : ' + E);
126 | | }
127 | | });
128 | | } catch (E) {
129 | 0 | res.writeHead(400);
130 | 0 | res.end("Invalid query " + req.originalUrl.substr(6) + "<br />" + E);
131 | | }
132 | | });
133 | |
134 | | // let any service schedule to be called, it can only have one per uri
135 | 1 | locker.get('/core/:svcId/at', function(req, res) {
136 | 3 | var seconds = req.param("at");
137 | 3 | var cb = req.param('cb');
138 | 3 | var svcId = req.params.svcId;
139 | 3 | if (!seconds || !svcId || !cb) {
140 | 0 | res.writeHead(400);
141 | 0 | res.end("Invalid arguments");
142 | 0 | return;
143 | | }
144 | 3 | if (!serviceManager.isInstalled(svcId)) {
145 | 0 | res.writeHead(404);
146 | 0 | res.end(svcId+" doesn't exist, but does anything really? ");
147 | 0 | return;
148 | | }
149 | 3 | res.writeHead(200, {
150 | | 'Content-Type': 'text/html'
151 | | });
152 | 3 | at = new Date;
153 | 3 | at.setTime(seconds * 1000);
154 | 3 | scheduler.at(at, svcId, cb);
155 | 3 | console.log("scheduled "+ svcId + " " + cb + " at " + at);
156 | 3 | res.end("true");
157 | | });
158 | |
159 | | // given a bunch of json describing a service, make a home for it on disk and add it to our map
160 | 1 | locker.post('/core/:svcId/install', function(req, res) {
161 | 3 | if (!req.body.hasOwnProperty("srcdir")) {
162 | 1 | res.writeHead(400);
163 | 1 | res.end("{}")
164 | 1 | return;
165 | | }
166 | 2 | var metaData = serviceManager.install(req.body);
167 | 2 | if (!metaData) {
168 | 1 | res.writeHead(404);
169 | 1 | res.end("{}");
170 | 1 | return;
171 | | }
172 | 1 | res.writeHead(200, {
173 | | 'Content-Type': 'application/json'
174 | | });
175 | 1 | res.end(JSON.stringify(metaData));
176 | | });
177 | |
178 | 1 | locker.post('/core/:svcId/uninstall', function(req, res) {
179 | 1 | console.log('/core/:svcId/uninstal, :svcId == ' + req.params.svcId);
180 | 1 | var svcId = req.body.serviceId;
181 | 1 | if(!serviceManager.isInstalled(svcId)) {
182 | 0 | res.writeHead(404);
183 | 0 | res.end(svcId+" doesn't exist, but does anything really? ");
184 | 0 | return;
185 | | }
186 | 1 | serviceManager.uninstall(svcId, function() {
187 | 1 | res.writeHead(200);
188 | 1 | res.end("OKTHXBI");
189 | | });
190 | | })
191 | |
192 | 1 | locker.post('/core/:svcId/disable', function(req, res) {
193 | 1 | var svcId = req.body.serviceId;
194 | 1 | if(!serviceManager.isInstalled(svcId)) {
195 | 0 | res.writeHead(404);
196 | 0 | res.end(svcId+" doesn't exist, but does anything really? ");
197 | 0 | return;
198 | | }
199 | 1 | serviceManager.disable(svcId);
200 | 1 | res.writeHead(200);
201 | 1 | res.end("OKTHXBI");
202 | | })
203 | |
204 | 1 | locker.post('/core/:svcId/enable', function(req, res) {
205 | 1 | var svcId = req.body.serviceId;
206 | 1 | if(!serviceManager.isDisabled(svcId)) {
207 | 0 | res.writeHead(404);
208 | 0 | res.end(svcId+" isn't disabled");
209 | 0 | return;
210 | | }
211 | 1 | serviceManager.enable(svcId);
212 | 1 | res.writeHead(200);
213 | 1 | res.end("OKTHXBI");
214 | | })
215 | |
216 | |
217 | | // ME PROXY
218 | | // all of the requests to something installed (proxy them, moar future-safe)
219 | 1 | locker.get('/Me/*', function(req,res){
220 | 53 | proxyRequest('GET', req, res);
221 | | });
222 | |
223 | | // all of the requests to something installed (proxy them, moar future-safe)
224 | 1 | locker.post('/Me/*', function(req,res){
225 | 3 | proxyRequest('POST', req, res);
226 | | });
227 | |
228 | 1 | function proxyRequest(method, req, res) {
229 | 56 | var slashIndex = req.url.indexOf("/", 4);
230 | 58 | if (slashIndex < 0) slashIndex = req.url.length;
231 | 56 | var id = req.url.substring(4, slashIndex);
232 | 56 | var ppath = req.url.substring(slashIndex);
233 | 56 | if(serviceManager.isDisabled(id)) {
234 | 2 | res.writeHead(503);
235 | 2 | res.end('This service has been disabled.');
236 | 2 | return;
237 | | }
238 | 54 | if(!serviceManager.isInstalled(id)) { // make sure it exists before it can be opened
239 | 3 | res.writeHead(404);
240 | 3 | res.end("so sad, couldn't find "+id);
241 | 3 | return;
242 | | }
243 | 51 | if (!serviceManager.isRunning(id)) {
244 | 31 | console.log("Having to spawn " + id);
245 | 31 | var buffer = proxy.buffer(req);
246 | 31 | serviceManager.spawn(id,function(){
247 | 31 | proxied(method, serviceManager.metaInfo(id),ppath,req,res,buffer);
248 | | });
249 | | } else {
250 | 20 | proxied(method, serviceManager.metaInfo(id),ppath,req,res);
251 | | }
252 | 51 | console.log("Proxy complete");
253 | 1 | };
254 | |
255 | | // DIARY
256 | | // Publish a user visible message
257 | 1 | locker.get("/core/:svcId/diary", function(req, res) {
258 | 1 | var level = req.param("level") || 0;
259 | 1 | var message = req.param("message");
260 | 1 | var svcId = req.params.svcId;
261 | |
262 | 1 | var now = new Date;
263 | 1 | try {
264 | 1 | fs.mkdirSync(lconfig.me + "/diary", 0700, function(err) {
265 | 0 | if (err && err.errno != process.EEXIST) console.error("Error creating diary: " + err);
266 | | });
267 | | } catch (E) {
268 | | // Why do I still have to catch when it has an error callback?!
269 | | }
270 | 1 | fs.mkdir(lconfig.me + "/diary/" + now.getFullYear(), 0700, function(err) {
271 | 1 | fs.mkdir(lconfig.me + "/diary/" + now.getFullYear() + "/" + now.getMonth(), 0700, function(err) {
272 | 1 | var fullPath = lconfig.me + "/diary/" + now.getFullYear() + "/" + now.getMonth() + "/" + now.getDate() + ".json";
273 | 1 | lfs.appendObjectsToFile(fullPath, [{"timestamp":now, "level":level, "message":message, "service":svcId}]);
274 | 1 | res.writeHead(200);
275 | 1 | res.end("{}");
276 | | })
277 | | });
278 | | });
279 | |
280 | | // Retrieve the current days diary or the given range
281 | 1 | locker.get("/diary", function(req, res) {
282 | 1 | var now = new Date;
283 | 1 | var fullPath = lconfig.me + "/diary/" + now.getFullYear() + "/" + now.getMonth() + "/" + now.getDate() + ".json";
284 | 1 | res.writeHead(200, {
285 | | "Content-Type": "text/javascript",
286 | | "Access-Control-Allow-Origin" : "*"
287 | | });
288 | 1 | fs.readFile(fullPath, function(err, file) {
289 | 1 | if (err) {
290 | 0 | res.write("[]");
291 | 0 | res.end();
292 | 0 | return;
293 | | }
294 | 1 | var rawLines = file.toString().trim().split("\n");
295 | 2 | diaryLines = rawLines.map(function(line) { return JSON.parse(line) });
296 | 1 | res.write(JSON.stringify(diaryLines), "binary");
297 | 1 | res.end();
298 | | });
299 | 1 | res.write
300 | | });
301 | |
302 | |
303 | | // EVENTING
304 | | // anybody can listen into any service's events
305 | 1 | locker.get('/core/:svcId/listen', function(req, res) {
306 | 6 | var type = req.param('type'), cb = req.param('cb');
307 | 6 | var svcId = req.params.svcId;
308 | 6 | if(!serviceManager.isInstalled(svcId)) {
309 | 0 | console.log("Could not find " + svcId);
310 | 0 | res.writeHead(404);
311 | 0 | res.end(svcId+" doesn't exist, but does anything really? ");
312 | 0 | return;
313 | | }
314 | 6 | if (!type || !cb) {
315 | 0 | res.writeHead(400);
316 | 0 | res.end("Invalid type or callback");
317 | 0 | return;
318 | | }
319 | 7 | if(cb.substr(0,1) != "/") cb = '/'+cb; // ensure it's a root path
320 | 6 | levents.addListener(type, svcId, cb);
321 | 6 | res.writeHead(200);
322 | 6 | res.end("OKTHXBI");
323 | | });
324 | |
325 | | // Stop listening to some events
326 | 1 | locker.get("/core/:svcId/deafen", function(req, res) {
327 | 1 | var type = req.param('type'), cb = req.param('cb');
328 | 1 | var svcId = req.params.svcId;
329 | 1 | if(!serviceManager.isInstalled(svcId)) {
330 | 0 | res.writeHead(404);
331 | 0 | res.end(svcId+" doesn't exist, but does anything really? ");
332 | 0 | return;
333 | | }
334 | 1 | if (!type || !cb) {
335 | 0 | res.writeHead(400);
336 | 0 | res.end("Invalid type or callback");
337 | 0 | return;
338 | | }
339 | 1 | if(cb.substr(0,1) != "/") cb = '/'+cb; // ensure it's a root path
340 | 1 | levents.removeListener(type, svcId, cb);
341 | 1 | res.writeHead(200);
342 | 1 | res.end("OKTHXBI");
343 | | });
344 | |
345 | | // publish an event to any listeners
346 | 1 | locker.post('/core/:svcId/event', function(req, res) {
347 | 5 | if (!req.body ) {
348 | 0 | res.writeHead(400);
349 | 0 | res.end("Post data missing");
350 | 0 | return;
351 | | }
352 | 5 | var type = req.body['type'], obj = req.body['obj'];
353 | 5 | var svcId = req.params.svcId;
354 | 5 | if(!serviceManager.isInstalled(svcId)) {
355 | 0 | res.writeHead(404);
356 | 0 | res.end(svcId+" doesn't exist, but does anything really? ");
357 | 0 | return;
358 | | }
359 | 5 | if (!type || !obj) {
360 | 0 | res.writeHead(400);
361 | 0 | res.end("Invalid type or object");
362 | 0 | return;
363 | | }
364 | 5 | levents.fireEvent(type, svcId, obj);
365 | 5 | res.writeHead(200);
366 | 5 | res.end("OKTHXBI");
367 | | });
368 | |
369 | |
370 | | // fallback everything to the dashboard
371 | 1 | locker.get('/*', function(req, res) {
372 | 0 | proxied('GET', dashboard.instance,req.url.substring(1),req,res);
373 | | });
374 | |
375 | | // fallback everything to the dashboard
376 | 1 | locker.post('/*', function(req, res) {
377 | 0 | proxied('POST', dashboard.instance,req.url.substring(1),req,res);
378 | | });
379 | |
380 | 1 | locker.get('/', function(req, res) {
381 | 0 | proxied('GET', dashboard.instance,"",req,res);
382 | | });
383 | |
384 | 1 | function proxied(method, svc, ppath, req, res, buffer) {
385 | 52 | if(ppath.substr(0,1) != "/") ppath = "/"+ppath;
386 | 51 | console.log("proxying " + method + " " + req.url + " to "+ svc.uriLocal + ppath);
387 | 51 | req.url = ppath;
388 | 51 | proxy.proxyRequest(req, res, {
389 | | host: url.parse(svc.uriLocal).hostname,
390 | | port: url.parse(svc.uriLocal).port,
391 | | buffer: buffer
392 | | });
393 | | }
394 | |
395 | 1 | exports.startService = function(port) {
396 | 1 | locker.listen(port);
397 | | }
Common/node/lpquery.js:
1 | | /*
2 | | *
3 | | * Copyright (C) 2011, The Locker Project
4 | | * All rights reserved.
5 | | *
6 | | * Please see the LICENSE file for more information.
7 | | *
8 | | */
9 | |
10 | | /* Jison generated parser */
11 | 1 | var lpquery = (function(){
12 | 1 | var parser = {trace: function trace() { },
13 | | yy: {},
14 | | symbols_: {"error":2,"queryBase":3,"prefix":4,"argList":5,"EOF":6,"literal_op":7,"GREATER":8,"GREATER_EQUAL":9,"LESSER":10,"LESSER_EQUAL":11,"NOT_EQUAL":12,"literal":13,"NUMBER":14,"STRING":15,"member":16,"KEY":17,".":18,"expression":19,"colon":20,"OR":21,"AND":22,"expressionSubset":23,"(":24,"expressionList":25,")":26,",":27,"array":28,"[":29,"]":30,"arg":31,"=":32,"&":33,"$accept":0,"$end":1},
15 | | terminals_: {2:"error",4:"prefix",6:"EOF",8:"GREATER",9:"GREATER_EQUAL",10:"LESSER",11:"LESSER_EQUAL",12:"NOT_EQUAL",14:"NUMBER",15:"STRING",17:"KEY",18:".",20:"colon",21:"OR",22:"AND",24:"(",26:")",27:",",29:"[",30:"]",32:"=",33:"&"},
16 | | productions_: [0,[3,3],[7,1],[7,1],[7,1],[7,1],[7,1],[13,1],[13,1],[13,2],[16,1],[16,3],[19,1],[19,3],[19,3],[19,3],[19,1],[23,3],[25,1],[25,3],[28,3],[31,3],[31,3],[5,1],[5,3]],
17 | | performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) {
18 | |
19 | 0 | var $0 = $$.length - 1;
20 | 0 | switch (yystate) {
21 | 0 | case 1:return [$$[$0-2], $$[$0-1]];
22 | 0 | break;
23 | 0 | case 2:this.$ = $$[$0];
24 | 0 | break;
25 | 0 | case 3:this.$ = $$[$0];
26 | 0 | break;
27 | 0 | case 4:this.$ = $$[$0];
28 | 0 | break;
29 | 0 | case 5:this.$ = $$[$0];
30 | 0 | break;
31 | 0 | case 6:this.$ = $$[$0];
32 | 0 | break;
33 | 0 | case 7:this.$ = Number(yytext);
34 | 0 | break;
35 | 0 | case 8:this.$ = $$[$0];
36 | 0 | break;
37 | 0 | case 9:this.$ = [$$[$0], $$[$0-1]];
38 | 0 | break;
39 | 0 | case 10:this.$ = $$[$0];
40 | 0 | break;
41 | 0 | case 11:this.$ = $$[$0-2] + '.' + $$[$0];
42 | 0 | break;
43 | 0 | case 12:this.$ = ['literal', $$[$0]]
44 | 0 | break;
45 | 0 | case 13:this.$ = ['keyValue', $$[$0-2], $$[$0]];
46 | 0 | break;
47 | 0 | case 14:this.$ = ['OR', [$$[$0-2], $$[$0]]]
48 | 0 | break;
49 | 0 | case 15:this.$ = ['AND', [$$[$0-2], $$[$0]]]
50 | 0 | break;
51 | 0 | case 16:this.$ = $$[$0];
52 | 0 | break;
53 | 0 | case 17:this.$ = ['subset', $$[$0-1]];
54 | 0 | break;
55 | 0 | case 18:this.$ = [$$[$0]];
56 | 0 | break;
57 | 0 | case 19:this.$ = $$[$0-2]; this.$.push($$[$0]);
58 | 0 | break;
59 | 0 | case 20:this.$ = $$[$0-1];
60 | 0 | break;
61 | 0 | case 21:this.$ = [$$[$0-2], $$[$0]];
62 | 0 | break;
63 | 0 | case 22:this.$ = [$$[$0-2], $$[$0]];
64 | 0 | break;
65 | 0 | case 23:this.$ = {}; this.$[$$[$0][0]] = $$[$0][1];
66 | 0 | break;
67 | 0 | case 24:$$[$0-2][$$[$0][0]] = $$[$0][1];
68 | 0 | break;
69 | | }
70 | | },
71 | | table: [{3:1,4:[1,2]},{1:[3]},{5:3,17:[1,5],31:4},{6:[1,6],33:[1,7]},{6:[2,23],33:[2,23]},{32:[1,8]},{1:[2,1]},{17:[1,5],31:9},{13:11,14:[1,13],15:[1,14],28:10,29:[1,12]},{6:[2,24],33:[2,24]},{6:[2,21],33:[2,21]},{6:[2,22],7:15,8:[1,16],9:[1,17],10:[1,18],11:[1,19],12:[1,20],33:[2,22]},{13:23,14:[1,13],15:[1,14],16:24,17:[1,26],19:22,23:25,24:[1,27],25:21},{6:[2,7],8:[2,7],9:[2,7],10:[2,7],11:[2,7],12:[2,7],21:[2,7],22:[2,7],26:[2,7],27:[2,7],30:[2,7],33:[2,7]},{6:[2,8],8:[2,8],9:[2,8],10:[2,8],11:[2,8],12:[2,8],21:[2,8],22:[2,8],26:[2,8],27:[2,8],30:[2,8],33:[2,8]},{6:[2,9],8:[2,9],9:[2,9],10:[2,9],11:[2,9],12:[2,9],21:[2,9],22:[2,9],26:[2,9],27:[2,9],30:[2,9],33:[2,9]},{6:[2,2],8:[2,2],9:[2,2],10:[2,2],11:[2,2],12:[2,2],21:[2,2],22:[2,2],26:[2,2],27:[2,2],30:[2,2],33:[2,2]},{6:[2,3],8:[2,3],9:[2,3],10:[2,3],11:[2,3],12:[2,3],21:[2,3],22:[2,3],26:[2,3],27:[2,3],30:[2,3],33:[2,3]},{6:[2,4],8:[2,4],9:[2,4],10:[2,4],11:[2,4],12:[2,4],21:[2,4],22:[2,4],26:[2,4],27:[2,4],30:[2,4],33:[2,4]},{6:[2,5],8:[2,5],9:[2,5],10:[2,5],11:[2,5],12:[2,5],21:[2,5],22:[2,5],26:[2,5],27:[2,5],30:[2,5],33:[2,5]},{6:[2,6],8:[2,6],9:[2,6],10:[2,6],11:[2,6],12:[2,6],21:[2,6],22:[2,6],26:[2,6],27:[2,6],30:[2,6],33:[2,6]},{27:[1,29],30:[1,28]},{21:[1,30],22:[1,31],26:[2,18],27:[2,18],30:[2,18]},{7:15,8:[1,16],9:[1,17],10:[1,18],11:[1,19],12:[1,20],21:[2,12],22:[2,12],26:[2,12],27:[2,12],30:[2,12]},{18:[1,33],20:[1,32]},{21:[2,16],22:[2,16],26:[2,16],27:[2,16],30:[2,16]},{18:[2,10],20:[2,10]},{13:23,14:[1,13],15:[1,14],16:24,17:[1,26],19:22,23:25,24:[1,27],25:34},{6:[2,20],33:[2,20]},{13:23,14:[1,13],15:[1,14],16:24,17:[1,26],19:35,23:25,24:[1,27]},{13:23,14:[1,13],15:[1,14],16:24,17:[1,26],19:36,23:25,24:[1,27]},{13:23,14:[1,13],15:[1,14],16:24,17:[1,26],19:37,23:25,24:[1,27]},{13:38,14:[1,13],15:[1,14]},{17:[1,39]},{26:[1,40],27:[1,29]},{21:[1,30],22:[1,31],26:[2,19],27:[2,19],30:[2,19]},{21:[2,14],22:[2,14],26:[2,14],27:[2,14],30:[2,14]},{21:[2,15],22:[2,15],26:[2,15],27:[2,15],30:[2,15]},{7:15,8:[1,16],9:[1,17],10:[1,18],11:[1,19],12:[1,20],21:[2,13],22:[2,13],26:[2,13],27:[2,13],30:[2,13]},{18:[2,11],20:[2,11]},{21:[2,17],22:[2,17],26:[2,17],27:[2,17],30:[2,17]}],
72 | | defaultActions: {6:[2,1]},
73 | | parseError: function parseError(str, hash) {
74 | 0 | throw new Error(str);
75 | | },
76 | | parse: function parse(input) {
77 | 0 | var self = this,
78 | | stack = [0],
79 | | vstack = [null], // semantic value stack
80 | | lstack = [], // location stack
81 | | table = this.table,
82 | | yytext = '',
83 | | yylineno = 0,
84 | | yyleng = 0,
85 | | recovering = 0,
86 | | TERROR = 2,
87 | | EOF = 1;
88 | |
89 | | //this.reductionCount = this.shiftCount = 0;
90 | |
91 | 0 | this.lexer.setInput(input);
92 | 0 | this.lexer.yy = this.yy;
93 | 0 | this.yy.lexer = this.lexer;
94 | 0 | if (typeof this.lexer.yylloc == 'undefined')
95 | 0 | this.lexer.yylloc = {};
96 | 0 | var yyloc = this.lexer.yylloc;
97 | 0 | lstack.push(yyloc);
98 | |
99 | 0 | if (typeof this.yy.parseError === 'function')
100 | 0 | this.parseError = this.yy.parseError;
101 | |
102 | 0 | function popStack (n) {
103 | 0 | stack.length = stack.length - 2*n;
104 | 0 | vstack.length = vstack.length - n;
105 | 0 | lstack.length = lstack.length - n;
106 | | }
107 | |
108 | 0 | function lex() {
109 | 0 | var token;
110 | 0 | token = self.lexer.lex() || 1; // $end = 1
111 | | // if token isn't its numeric value, convert
112 | 0 | if (typeof token !== 'number') {
113 | 0 | token = self.symbols_[token] || token;
114 | | }
115 | 0 | return token;
116 | 0 | };
117 | |
118 | 0 | var symbol, preErrorSymbol, state, action, a, r, yyval={},p,len,newState, expected;
119 | 0 | while (true) {
120 | | // retreive state number from top of stack
121 | 0 | state = stack[stack.length-1];
122 | |
123 | | // use default actions if available
124 | 0 | if (this.defaultActions[state]) {
125 | 0 | action = this.defaultActions[state];
126 | | } else {
127 | 0 | if (symbol == null)
128 | 0 | symbol = lex();
129 | | // read action for current state and first input
130 | 0 | action = table[state] && table[state][symbol];
131 | | }
132 | |
133 | | // handle parse error
134 | 0 | if (typeof action === 'undefined' || !action.length || !action[0]) {
135 | |
136 | 0 | if (!recovering) {
137 | | // Report error
138 | 0 | expected = [];
139 | 0 | for (p in table[state]) if (this.terminals_[p] && p > 2) {
140 | 0 | expected.push("'"+this.terminals_[p]+"'");
141 | | }
142 | 0 | var errStr = '';
143 | 0 | if (this.lexer.showPosition) {
144 | 0 | errStr = 'Parse error on line '+(yylineno+1)+":\n"+this.lexer.showPosition()+'\nExpecting '+expected.join(', ');
145 | | } else {
146 | 0 | errStr = 'Parse error on line '+(yylineno+1)+": Unexpected " +
147 | | (symbol == 1 /*EOF*/ ? "end of input" :
148 | | ("'"+(this.terminals_[symbol] || symbol)+"'"));
149 | | }
150 | 0 | this.parseError(errStr,
151 | | {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected});
152 | | }
153 | |
154 | | // just recovered from another error
155 | 0 | if (recovering == 3) {
156 | 0 | if (symbol == EOF) {
157 | 0 | throw new Error(errStr || 'Parsing halted.');
158 | | }
159 | |
160 | | // discard current lookahead and grab another
161 | 0 | yyleng = this.lexer.yyleng;
162 | 0 | yytext = this.lexer.yytext;
163 | 0 | yylineno = this.lexer.yylineno;
164 | 0 | yyloc = this.lexer.yylloc;
165 | 0 | symbol = lex();
166 | | }
167 | |
168 | | // try to recover from error
169 | 0 | while (1) {
170 | | // check for error recovery rule in this state
171 | 0 | if ((TERROR.toString()) in table[state]) {
172 | 0 | break;
173 | | }
174 | 0 | if (state == 0) {
175 | 0 | throw new Error(errStr || 'Parsing halted.');
176 | | }
177 | 0 | popStack(1);
178 | 0 | state = stack[stack.length-1];
179 | | }
180 | |
181 | 0 | preErrorSymbol = symbol; // save the lookahead token
182 | 0 | symbol = TERROR; // insert generic error symbol as new lookahead
183 | 0 | state = stack[stack.length-1];
184 | 0 | action = table[state] && table[state][TERROR];
185 | 0 | recovering = 3; // allow 3 real symbols to be shifted before reporting a new error
186 | | }
187 | |
188 | | // this shouldn't happen, unless resolve defaults are off
189 | 0 | if (action[0] instanceof Array && action.length > 1) {
190 | 0 | throw new Error('Parse Error: multiple actions possible at state: '+state+', token: '+symbol);
191 | | }
192 | |
193 | 0 | switch (action[0]) {
194 | |
195 | | case 1: // shift
196 | | //this.shiftCount++;
197 | |
198 | 0 | stack.push(symbol);
199 | 0 | vstack.push(this.lexer.yytext);
200 | 0 | lstack.push(this.lexer.yylloc);
201 | 0 | stack.push(action[1]); // push state
202 | 0 | symbol = null;
203 | 0 | if (!preErrorSymbol) { // normal execution/no error
204 | 0 | yyleng = this.lexer.yyleng;
205 | 0 | yytext = this.lexer.yytext;
206 | 0 | yylineno = this.lexer.yylineno;
207 | 0 | yyloc = this.lexer.yylloc;
208 | 0 | if (recovering > 0)
209 | 0 | recovering--;
210 | | } else { // error just occurred, resume old lookahead f/ before error
211 | 0 | symbol = preErrorSymbol;
212 | 0 | preErrorSymbol = null;
213 | | }
214 | 0 | break;
215 | |
216 | | case 2: // reduce
217 | | //this.reductionCount++;
218 | |
219 | 0 | len = this.productions_[action[1]][1];
220 | |
221 | | // perform semantic action
222 | 0 | yyval.$ = vstack[vstack.length-len]; // default to $$ = $1
223 | | // default location, uses first token for firsts, last for lasts
224 | 0 | yyval._$ = {
225 | | first_line: lstack[lstack.length-(len||1)].first_line,
226 | | last_line: lstack[lstack.length-1].last_line,
227 | | first_column: lstack[lstack.length-(len||1)].first_column,
228 | | last_column: lstack[lstack.length-1].last_column
229 | | };
230 | 0 | r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack);
231 | |
232 | 0 | if (typeof r !== 'undefined') {
233 | 0 | return r;
234 | | }
235 | |
236 | | // pop off stack
237 | 0 | if (len) {
238 | 0 | stack = stack.slice(0,-1*len*2);
239 | 0 | vstack = vstack.slice(0, -1*len);
240 | 0 | lstack = lstack.slice(0, -1*len);
241 | | }
242 | |
243 | 0 | stack.push(this.productions_[action[1]][0]); // push nonterminal (reduce)
244 | 0 | vstack.push(yyval.$);
245 | 0 | lstack.push(yyval._$);
246 | | // goto new state = table[STATE][NONTERMINAL]
247 | 0 | newState = table[stack[stack.length-2]][stack[stack.length-1]];
248 | 0 | stack.push(newState);
249 | 0 | break;
250 | |
251 | | case 3: // accept
252 | 0 | return true;
253 | | }
254 | |
255 | | }
256 | |
257 | 0 | return true;
258 | | }};/* Jison generated lexer */
259 | 2 | var lexer = (function(){var lexer = ({EOF:1,
260 | | parseError:function parseError(str, hash) {
261 | 0 | if (this.yy.parseError) {
262 | 0 | this.yy.parseError(str, hash);
263 | | } else {
264 | 0 | throw new Error(str);
265 | | }
266 | | },
267 | | setInput:function (input) {
268 | 0 | this._input = input;
269 | 0 | this._more = this._less = this.done = false;
270 | 0 | this.yylineno = this.yyleng = 0;
271 | 0 | this.yytext = this.matched = this.match = '';
272 | 0 | this.conditionStack = ['INITIAL'];
273 | 0 | this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0};
274 | 0 | return this;
275 | | },
276 | | input:function () {
277 | 0 | var ch = this._input[0];
278 | 0 | this.yytext+=ch;
279 | 0 | this.yyleng++;
280 | 0 | this.match+=ch;
281 | 0 | this.matched+=ch;
282 | 0 | var lines = ch.match(/\n/);
283 | 0 | if (lines) this.yylineno++;
284 | 0 | this._input = this._input.slice(1);
285 | 0 | return ch;
286 | | },
287 | | unput:function (ch) {
288 | 0 | this._input = ch + this._input;
289 | 0 | return this;
290 | | },
291 | | more:function () {
292 | 0 | this._more = true;
293 | 0 | return this;
294 | | },
295 | | pastInput:function () {
296 | 0 | var past = this.matched.substr(0, this.matched.length - this.match.length);
297 | 0 | return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
298 | | },
299 | | upcomingInput:function () {
300 | 0 | var next = this.match;
301 | 0 | if (next.length < 20) {
302 | 0 | next += this._input.substr(0, 20-next.length);
303 | | }
304 | 0 | return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, "");
305 | | },
306 | | showPosition:function () {
307 | 0 | var pre = this.pastInput();
308 | 0 | var c = new Array(pre.length + 1).join("-");
309 | 0 | return pre + this.upcomingInput() + "\n" + c+"^";
310 | | },
311 | | next:function () {
312 | 0 | if (this.done) {
313 | 0 | return this.EOF;
314 | | }
315 | 0 | if (!this._input) this.done = true;
316 | |
317 | 0 | var token,
318 | | match,
319 | | col,
320 | | lines;
321 | 0 | if (!this._more) {
322 | 0 | this.yytext = '';
323 | 0 | this.match = '';
324 | | }
325 | 0 | var rules = this._currentRules();
326 | 0 | for (var i=0;i < rules.length; i++) {
327 | 0 | match = this._input.match(this.rules[rules[i]]);
328 | 0 | if (match) {
329 | 0 | lines = match[0].match(/\n.*/g);
330 | 0 | if (lines) this.yylineno += lines.length;
331 | 0 | this.yylloc = {first_line: this.yylloc.last_line,
332 | | last_line: this.yylineno+1,
333 | | first_column: this.yylloc.last_column,
334 | | last_column: lines ? lines[lines.length-1].length-1 : this.yylloc.last_column + match[0].length}
335 | 0 | this.yytext += match[0];
336 | 0 | this.match += match[0];
337 | 0 | this.matches = match;
338 | 0 | this.yyleng = this.yytext.length;
339 | 0 | this._more = false;
340 | 0 | this._input = this._input.slice(match[0].length);
341 | 0 | this.matched += match[0];
342 | 0 | token = this.performAction.call(this, this.yy, this, rules[i],this.conditionStack[this.conditionStack.length-1]);
343 | 0 | if (token) return token;
344 | 0 | else return;
345 | | }
346 | | }
347 | 0 | if (this._input === "") {
348 | 0 | return this.EOF;
349 | | } else {
350 | 0 | this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(),
351 | | {text: "", token: null, line: this.yylineno});
352 | | }
353 | | },
354 | | lex:function lex() {
355 | 0 | var r = this.next();
356 | 0 | if (typeof r !== 'undefined') {
357 | 0 | return r;
358 | | } else {
359 | 0 | return this.lex();
360 | | }
361 | | },
362 | | begin:function begin(condition) {
363 | 0 | this.conditionStack.push(condition);
364 | | },
365 | | popState:function popState() {
366 | 0 | return this.conditionStack.pop();
367 | | },
368 | | _currentRules:function _currentRules() {
369 | 0 | return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules;
370 | | }});
371 | 1 | lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
372 | |
373 | 0 | var YYSTATE=YY_START
374 | 0 | switch($avoiding_name_collisions) {
375 | | case 0:/* skip */
376 | 0 | break;
377 | 0 | case 1:return 21;
378 | 0 | break;
379 | 0 | case 2:return 22;
380 | 0 | break;
381 | 0 | case 3:return 33;
382 | 0 | break;
383 | 0 | case 4:return 18;
384 | 0 | break;
385 | 0 | case 5:{yy_.yytext = yy_.yytext.substr(1, yy_.yyleng-2); return 15;}
386 | 0 | break;
387 | 0 | case 6:{yy_.yytext = yy_.yytext.substr(1, yy_.yyleng-2); return 15;}
388 | 0 | break;
389 | 0 | case 7:return 14;
390 | 0 | break;
391 | 0 | case 8:return 29
392 | 0 | break;
393 | 0 | case 9:return 30
394 | 0 | break;
395 | 0 | case 10:return 17;
396 | 0 | break;
397 | 0 | case 11:return '?';
398 | 0 | break;
399 | 0 | case 12:return 32;
400 | 0 | break;
401 | 0 | case 13:return 27;
402 | 0 | break;
403 | 0 | case 14:return 24;
404 | 0 | break;
405 | 0 | case 15:return 26;
406 | 0 | break;
407 | 0 | case 16:return 20;
408 | 0 | break;
409 | 0 | case 17:return 9;
410 | 0 | break;
411 | 0 | case 18:return 8;
412 | 0 | break;
413 | 0 | case 19:return 11;
414 | 0 | break;
415 | 0 | case 20:return 10;
416 | 0 | break;
417 | 0 | case 21:return 12;
418 | 0 | break;
419 | 0 | case 22:yy_.yytext = yy_.yytext.substr(4, yy_.yyleng-5); return 4;
420 | 0 | break;
421 | 0 | case 23:return 6;
422 | 0 | break;
423 | | }
424 | | };
425 | 1 | lexer.rules = [/^\s+/,/^OR/,/^AND/,/^&/,/^\./,/^'[^']+'/,/^"[^"]+"/,/^[0-9]+/,/^\[/,/^\]/,/^[a-zA-z]+/,/^\?/,/^=/,/^,/,/^\(/,/^\)/,/^:/,/^\+\./,/^\+/,/^\-\./,/^\-/,/^\!=/,/^\/get.+\?/,/^$/];
426 | 2 | lexer.conditions = {"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23],"inclusive":true}};return lexer;})()
427 | 1 | parser.lexer = lexer;
428 | 1 | return parser;
429 | | })();
430 | |
431 | 1 | exports.parser = lpquery;
432 | 1 | exports.parse = function () { return lpquery.parse.apply(lpquery, arguments); }
433 | |
434 | 1 | exports.buildMongoQuery = function(parseTree) {
435 | 0 | var treeTranslation = {
436 | | translateNode:function(node) {
437 | 0 | if (node.hasOwnProperty("length") && treeTranslation.hasOwnProperty(node[0])) {
438 | 0 | return treeTranslation[node[0]](node);
439 | | } else {
440 | 0 | if (typeof(node) == "number")
441 | 0 | return Number(node);
442 | | else
443 | 0 | return String(node);
444 | | }
445 | | },
446 | | // The actual functions
447 | | "keyValue":function(node) {
448 | 0 | var ret = {};
449 | 0 | ret[node[1]] = treeTranslation.translateNode(node[2]);
450 | 0 | return ret;
451 | | },
452 | | "subset":function(node) {
453 | 0 | var ret = {};
454 | 0 | node[1].forEach(function(nodeTerm) {
455 | 0 | var translatedNode = treeTranslation.translateNode(nodeTerm);
456 | 0 | for (var key in translatedNode) {
457 | 0 | ret[key] = translatedNode[key];
458 | | }
459 | | });
460 | 0 | return ret;
461 | | },
462 | | "AND":function(node) {
463 | 0 | var ret = {$and:[]};
464 | 0 | node[1].forEach(function(nodeTerm) {
465 | 0 | ret["$and"].push(treeTranslation.translateNode(nodeTerm));
466 | | });
467 | 0 | return ret;
468 | | },
469 | | "OR":function(node) {
470 | 0 | var ret = {$or:[]};
471 | 0 | node[1].forEach(function(nodeTerm) {
472 | 0 | ret["$or"].push(treeTranslation.translateNode(nodeTerm));
473 | | });
474 | 0 | return ret;
475 | | },
476 | | "-":function(node) {
477 | 0 | return {$lt:treeTranslation.translateNode(node[1])};
478 | | },
479 | | "-.":function(node) {
480 | 0 | return {$lte:treeTranslation.translateNode(node[1])};
481 | | },
482 | | "+":function(node) {
483 | 0 | return {$gt:treeTranslation.translateNode(node[1])};
484 | | },
485 | | "+.":function(node) {
486 | 0 | return {$gte:treeTranslation.translateNode(node[1])};
487 | | },
488 | | "!=":function(node) {
489 | 0 | return {$ne:treeTranslation.translateNode(node[1])};
490 | | }
491 | | };
492 | 0 | var queryResult = {
493 | | // TODO: This needs a lookup on the actual collection name
494 | | collection:parseTree[0].toLowerCase(), // Add the collection we're looking into
495 | | query:{}
496 | | }
497 | | // If we have terms to query with, put them in
498 | 0 | if (parseTree[1].hasOwnProperty("terms")) {
499 | 0 | parseTree[1]["terms"].forEach(function(term) {
500 | 0 | var termRet = treeTranslation.translateNode(term);
501 | 0 | for (var key in termRet) {
502 | 0 | queryResult.query[key] = termRet[key];
503 | | }
504 | | });
505 | | }
506 | | // If we have a limit put that on
507 | 0 | if (parseTree[1].hasOwnProperty("limit")) {
508 | 0 | queryResult.limit = Number(parseTree[1]["limit"]);
509 | | }
510 | | // Skip into the offset supplied
511 | 0 | if (parseTree[1].hasOwnProperty("offset")) {
512 | 0 | queryResult.skip = Number(parseTree[1]["offset"]);
513 | | }
514 | |
515 | 0 | return queryResult;
516 | | }
517 | |
Common/node/lcrypto.js:
1 | | /*
2 | | *
3 | | * Copyright (C) 2011, The Locker Project
4 | | * All rights reserved.
5 | | *
6 | | * Please see the LICENSE file for more information.
7 | | *
8 | | */
9 | 1 | var crypto = require("crypto");
10 | 1 | var path = require("path");
11 | 1 | var spawn = require("child_process").spawn;
12 | 1 | var fs = require("fs");
13 | 1 | var lconfig = require('lconfig');
14 | |
15 | 1 | var idKey,idKeyPub,symKey;
16 | |
17 | 1 | exports.generateSymKey = function(cb) {
18 | 1 | path.exists(lconfig.me + "/symKey", function(exists) {
19 | 1 | if (exists) {
20 | 0 | symKey = fs.readFileSync(lconfig.me + "/symKey", "utf8");
21 | 0 | cb(true);
22 | | } else {
23 | 1 | var openssl = spawn("openssl", ["rand", "-out", lconfig.me + "/symKey", "24"]);
24 | 1 | openssl.on("exit", function(code) {
25 | 1 | var ret = true;
26 | 1 | if (code !== 0) {
27 | 0 | ret = false;
28 | 0 | console.error("could not generate a symmetric key");
29 | | } else {
30 | 1 | symKey = fs.readFileSync(lconfig.me + "/symKey", "utf8");
31 | | }
32 | 1 | cb(ret);
33 | | });
34 | | }
35 | | });
36 | | }
37 | |
38 | | // load up private key or create if none, just KISS for now
39 | 1 | exports.loadKeys = function(callback) {
40 | 1 | if(!(symKey && idKey && idKeyPub)) {
41 | 1 | path.exists(__dirname + "/../../" + lconfig.me + "/symKey", function(exists) {
42 | 1 | if (exists === true)
43 | 0 | symKey = fs.readFileSync(__dirname + "/../../" + lconfig.me + "/symKey", "utf8");
44 | 1 | path.exists(__dirname + "/../../" + lconfig.me + "/key", function(exists) {
45 | 1 | if (exists === true)
46 | 0 | idKey = fs.readFileSync(__dirname + '/../../' + lconfig.me + '/key','utf-8');
47 | 1 | path.exists(__dirname + "/../../" + lconfig.me + "/key.pub", function(exists) {
48 | 1 | if (exists === true)
49 | 0 | idKeyPub = fs.readFileSync(__dirname + '/../../' + lconfig.me + '/key.pub','utf-8');
50 | 1 | if(typeof callback === 'function')
51 | 0 | callback();
52 | | });
53 | | });
54 | | });
55 | 0 | } else if(typeof callback === 'function') {
56 | 0 | process.nextTick(callback);
57 | | }
58 | | }
59 | |
60 | 1 | exports.generatePKKeys = function(cb) {
61 | 1 | path.exists(lconfig.me + '/key',function(exists){
62 | 1 | if(exists) {
63 | 0 | exports.loadKeys();
64 | 0 | cb(true);
65 | | } else {
66 | 1 | openssl = spawn('openssl', ['genrsa', '-out', 'key', '1024'], {cwd: lconfig.me});
67 | 1 | console.log('generating id private key');
68 | | // openssl.stdout.on('data',function (data){console.log(data);});
69 | | // openssl.stderr.on('data',function (data){console.log('Error:'+data);});
70 | 1 | openssl.on('exit', function (code) {
71 | 1 | console.log('generating id public key');
72 | 1 | openssl = spawn('openssl', ['rsa', '-pubout', '-in', 'key', '-out', 'key.pub'], {cwd: lconfig.me});
73 | 1 | openssl.on('exit', function (code) {
74 | 1 | var ret = true;
75 | 1 | if (code !== 0) {
76 | 0 | ret = false;
77 | | } else {
78 | 1 | exports.loadKeys();
79 | | }
80 | 1 | cb(ret);
81 | | });
82 | | });
83 | | }
84 | | });
85 | | }
86 | |
87 | 1 | exports.encrypt = function(data) {
88 | 0 | if (!data) {
89 | 0 | console.warn("Error encrypting " + data);
90 | 0 | return "";
91 | | }
92 | 0 | var cipher = crypto.createCipher("aes192", symKey);
93 | 0 | var ret = cipher.update(data, "utf8", "hex");
94 | 0 | ret += cipher.final("hex");
95 | 0 | return ret;
96 | | }
97 | |
98 | 1 | exports.decrypt = function(data) {
99 | 0 | if (!data) {
100 | 0 | console.warn("Error encrypting " + data);
101 | 0 | return "";
102 | | }
103 | 0 | var cipher = crypto.createDecipher("aes192", symKey);
104 | 0 | var ret = cipher.update(data, "hex", "utf8");
105 | 0 | ret += cipher.final("utf8");
106 | 0 | return ret;
107 | | }
Connectors/Facebook/migrations/1309052824000.js:
1 | 2 | module.exports = function(dir) {
2 | 3 | process.chdir(dir);
3 | 3 | var path = require('path');
4 | 3 | var fs = require('fs');
5 | |
6 | 3 | var me = JSON.parse(fs.readFileSync('me.json'));
7 | |
8 | 3 | me.run = 'node init.js';
9 | |
10 | 3 | fs.writeFileSync('me.json', JSON.stringify(me, null, 4));
11 | |
12 | 3 | return true;
13 | | };
Connectors/foursquare/migrations/1309052824000.js:
1 | 1 | module.exports = function(dir) {
2 | 2 | process.chdir(dir);
3 | 2 | var path = require('path');
4 | 2 | var fs = require('fs');
5 | |
6 | 2 | var me = JSON.parse(fs.readFileSync('me.json'));
7 | |
8 | 2 | me.run = 'node init.js';
9 | |
10 | 2 | fs.writeFileSync('me.json', JSON.stringify(me, null, 4));
11 | |
12 | 2 | return true;
13 | | };
Connectors/GitHub/migrations/1309052824000.js:
1 | 1 | module.exports = function(dir) {
2 | 1 | process.chdir(dir);
3 | 1 | var path = require('path');
4 | 1 | var fs = require('fs');
5 | |
6 | 1 | var me = JSON.parse(fs.readFileSync('me.json'));
7 | |
8 | 1 | me.run = 'node init.js';
9 | |
10 | 1 | fs.writeFileSync('me.json', JSON.stringify(me, null, 4));
11 | |
12 | 1 | return true;
13 | | };
Connectors/GoogleContacts/migrations/1309052824000.js:
1 | 1 | module.exports = function(dir) {
2 | 1 | process.chdir(dir);
3 | 1 | var path = require('path');
4 | 1 | var fs = require('fs');
5 | |
6 | 1 | var me = JSON.parse(fs.readFileSync('me.json'));
7 | |
8 | 1 | me.run = 'node init.js';
9 | |
10 | 1 | fs.writeFileSync('me.json', JSON.stringify(me, null, 4));
11 | |
12 | 1 | return true;
13 | | };
Connectors/IMAP/migrations/1308690468483.js:
1 | 1 | module.exports = function(dir) {
2 | 1 | process.chdir(dir);
3 | 1 | var path = require('path');
4 | 1 | var fs = require('fs');
5 | |
6 | 1 | if (path.exists('allKnownIDs.json')) {
7 | 0 | fs.unlinkSync('allKnownIDs.json');
8 | | }
9 | 1 | if (path.exists('updateState.json')) {
10 | 0 | fs.unlinkSync('updateState.json');
11 | | }
12 | 1 | if (path.exists('messages.json')) {
13 | 0 | fs.unlinkSync('messages.json');
14 | | }
15 | |
16 | 1 | return true;
17 | | };
Connectors/IMAP/migrations/1309052268000.js:
1 | 1 | module.exports = function(dir) {
2 | 1 | process.chdir(dir);
3 | 1 | var path = require('path');
4 | 1 | var fs = require('fs');
5 | |
6 | 1 | var me = JSON.parse(fs.readFileSync('me.json'));
7 | |
8 | 1 | me.run = 'node init.js';
9 | |
10 | 1 | fs.writeFileSync('me.json', JSON.stringify(me, null, 4));
11 | |
12 | 1 | return true;
13 | | };
Connectors/Twitter/migrations/1309052824000.js:
1 | 1 | module.exports = function(dir) {
2 | 1 | process.chdir(dir);
3 | 1 | var path = require('path');
4 | 1 | var fs = require('fs');
5 | |
6 | 1 | var me = JSON.parse(fs.readFileSync('me.json'));
7 | |
8 | 1 | me.run = 'node init.js';
9 | |
10 | 1 | fs.writeFileSync('me.json', JSON.stringify(me, null, 4));
11 | |
12 | 1 | return true;
13 | | };
Common/node/lmongoclient.js:
1 | | /*
2 | | *
3 | | * Copyright (C) 2011, The Locker Project
4 | | * All rights reserved.
5 | | *
6 | | * Please see the LICENSE file for more information.
7 | | *
8 | | */
9 | |
10 | 1 | var mongodb = require('mongodb');
11 | |
12 | 1 | module.exports = function(host, port, localServiceId, theCollectionNames) {
13 | 1 | var mongo = {};
14 | |
15 | 1 | mongo.serviceID = localServiceId;
16 | 1 | mongo.collectionNames = theCollectionNames;
17 | 1 | mongo.collections = {};
18 | 1 | mongo.db = new mongodb.Db('locker', new mongodb.Server(host, port, {}), {});
19 | 1 | function connectToDB(callback, isRetry) {
20 | 1 | mongo.db.open(function(error, client) {
21 | | // in case the mongod process was a bit slow to start up
22 | 1 | if(error && !isRetry) {
23 | 0 | setTimeout(function() {
24 | 0 | _connectToDB(callback, true);
25 | | }, 2000);
26 | 1 | } else if (error)
27 | 0 | throw error;
28 | 1 | mongo.dbClient = client;
29 | 1 | callback();
30 | | });
31 | | }
32 | |
33 | 1 | mongo.connect = function(callback) {
34 | 1 | connectToDB(function() {
35 | 1 | for(var i in mongo.collectionNames)
36 | 2 | mongo.addCollection(mongo.collectionNames[i]);
37 | 1 | callback(mongo);
38 | | })
39 | | }
40 | |
41 | 1 | mongo.addCollection = function(name) {
42 | 2 | mongo.collections[name] = new mongodb.Collection(mongo.dbClient, 'a' + mongo.serviceID + '_' + name);
43 | | }
44 | |
45 | 1 | return mongo;
46 | | }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment