Created
July 18, 2011 19:29
-
-
Save ctide/1090411 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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