1 | var fs = require('fs-extra'); |
||
2 | var request = require('request'); |
||
3 | var express = require('express'); |
||
4 | var extend = require('extend'); |
||
5 | |||
6 | // ------------------------------------------ |
||
7 | // CONSTRUCTOR |
||
8 | // ------------------------------------------ |
||
9 | |||
10 | var init = function(){ |
||
11 | info('Starting PluginManager ...'); |
||
12 | |||
13 | // Refresh local plugins |
||
14 | refresh(); |
||
15 | |||
16 | return PluginManager; |
||
17 | } |
||
18 | |||
19 | // ------------------------------------------ |
||
20 | // HELPER: REQUIRE |
||
21 | // ------------------------------------------ |
||
22 | |||
23 | /** |
||
24 | * Removes a module from the cache |
||
25 | */ |
||
26 | require.uncache = function (moduleName) { |
||
27 | // Run over the cache looking for the files |
||
28 | // loaded by the specified module name |
||
29 | require.searchCache(moduleName, function (mod) { |
||
30 | delete require.cache[mod.id]; |
||
31 | }); |
||
32 | }; |
||
33 | |||
34 | /** |
||
35 | * Runs over the cache to search for all the cached files |
||
36 | */ |
||
37 | require.searchCache = function (moduleName, callback) { |
||
38 | // Resolve the module identified by the specified name |
||
39 | var mod = require.resolve(moduleName); |
||
40 | |||
41 | // Check if the module has been resolved and found within |
||
42 | // the cache |
||
43 | if (mod && ((mod = require.cache[mod]) !== undefined)) { |
||
44 | // Recursively go over the results |
||
45 | (function run(mod) { |
||
46 | // Go over each of the module's children and |
||
47 | // run over it |
||
48 | mod.children.forEach(function (child) { |
||
49 | run(child); |
||
50 | }); |
||
51 | |||
52 | // Call the specified callback providing the |
||
53 | // found module |
||
54 | callback(mod); |
||
55 | })(mod); |
||
56 | } |
||
57 | }; |
||
58 | |||
59 | // ------------------------------------------ |
||
60 | // CLASS: PLUGIN |
||
61 | // ------------------------------------------ |
||
62 | |||
63 | var TYPE_MODULES = 'modules'; |
||
64 | var TYPE_PHANTOMS = 'phantoms'; |
||
65 | var TYPE_CRON = 'cron'; |
||
66 | |||
67 | function Plugin(options) { |
||
68 | extend(false, this, options); |
||
69 | |||
70 | // Link configuration |
||
71 | this.config = Config[TYPE_MODULES][this.name]; |
||
72 | this.phantoms = Config[TYPE_PHANTOMS][this.name]; |
||
73 | this.cron = Config[TYPE_CRON][this.name]; |
||
74 | |||
75 | // Check has {plugin}.js |
||
76 | var script = SARAH.ConfigManager.PLUGIN+'/'+this.name+'/'+this.name+'.js'; |
||
77 | if (fs.existsSync(script)){ |
||
78 | this.script = script; |
||
79 | } |
||
80 | |||
81 | // Check has custom portlet.ejs |
||
82 | var template = SARAH.ConfigManager.PLUGIN+'/'+this.name+'/portlet.ejs'; |
||
83 | if (fs.existsSync(template)){ |
||
84 | this.template = template; |
||
85 | } else { |
||
86 | this.template = 'portlet.html'; |
||
87 | } |
||
88 | |||
89 | // Check has index.ejs |
||
90 | var index = SARAH.ConfigManager.PLUGIN+'/'+this.name+'/index.ejs'; |
||
91 | if (fs.existsSync(index)){ |
||
92 | this.index = index; |
||
93 | } |
||
94 | } |
||
95 | |||
96 | Plugin.prototype.isDisabled = function(){ |
||
97 | if (!this.script) return true; |
||
98 | return this.config.disabled; |
||
99 | } |
||
100 | |||
101 | |||
102 | Plugin.prototype.getLocale = function(locale){ |
||
103 | var path = SARAH.ConfigManager.PLUGIN+'/'+this.name+'/locales/'+locale+'.js'; |
||
104 | if (!fs.existsSync(path)){ info('No locals',path); return; } |
||
105 | try { |
||
106 | var json = fs.readFileSync(path,'utf-8'); |
||
107 | info('Loading locales %s', path); |
||
108 | if (json) return JSON.parse(json); |
||
109 | } |
||
110 | catch(ex){ warn("Can't parse %s locales in %s", this.name, locale); } |
||
111 | |||
112 | return false; |
||
113 | } |
||
114 | |||
115 | Plugin.prototype.getInstance = function(uncache){ |
||
116 | try { |
||
117 | // Dispose |
||
118 | if (Config.debug || uncache){ |
||
119 | if (this._script && this._script.dispose){ this._script.dispose(SARAH); } |
||
120 | require.uncache(this.script); |
||
121 | } |
||
122 | |||
123 | // Require |
||
124 | this._script = require(this.script); |
||
125 | |||
126 | // Initialise |
||
127 | if (!this._script.initialized){ |
||
128 | this._script.initialized = true; |
||
129 | if (this._script.init){ this._script.init(SARAH); } |
||
130 | } |
||
131 | |||
132 | // Last Modified |
||
133 | var modified = fs.statSync(this.script).mtime.getTime(); |
||
134 | if (!this._script.lastModified){ |
||
135 | this._script.lastModified = modified; |
||
136 | } |
||
137 | |||
138 | // Reload if new version |
||
139 | if (this._script.lastModified < modified){ |
||
140 | info('Reloading: ', this.name); |
||
141 | return this.getInstance(true); |
||
142 | } |
||
143 | |||
144 | return this._script; |
||
145 | } |
||
146 | catch (ex) { |
||
147 | warn('Error while loading plugin: %s', this.name, ex.message, ex.stack); |
||
0 ignored issues
–
show
Best Practice
introduced
by
![]() |
|||
148 | } |
||
149 | } |
||
150 | |||
151 | // ------------------------------------------ |
||
152 | // CACHE PLUGINS |
||
153 | // ------------------------------------------ |
||
154 | |||
155 | var cache = {}; |
||
156 | var getCache = function(){ return cache; } |
||
157 | |||
158 | var refresh = function(){ |
||
159 | |||
160 | cache = {}; |
||
161 | |||
162 | // Find config |
||
163 | var keys = Object.keys(Config[TYPE_MODULES]); |
||
164 | |||
165 | // Build a list of plugins |
||
166 | for(var i = 0 ; i < keys.length ; i++){ |
||
167 | var key = keys[i]; |
||
168 | cache[key] = new Plugin ({'name' : key }); |
||
169 | cache[key].getInstance(); |
||
170 | } |
||
171 | |||
172 | keys = Object.keys(Config[TYPE_PHANTOMS]); |
||
173 | for(var i = 0 ; i < keys.length ; i++){ |
||
0 ignored issues
–
show
Comprehensibility
Naming
Best Practice
introduced
by
The variable
i already seems to be declared on line 166 . Consider using another variable name or omitting the var keyword.
This check looks for variables that are declared in multiple lines. There may be several reasons for this. In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs. If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared. ![]() |
|||
174 | var key = keys[i]; |
||
0 ignored issues
–
show
Comprehensibility
Naming
Best Practice
introduced
by
The variable
key already seems to be declared on line 167 . Consider using another variable name or omitting the var keyword.
This check looks for variables that are declared in multiple lines. There may be several reasons for this. In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs. If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared. ![]() |
|||
175 | if (cache[key]) continue; |
||
176 | cache[key] = new Plugin ({'name' : key }); |
||
177 | cache[key].getInstance(); |
||
178 | } |
||
179 | |||
180 | keys = Object.keys(Config[TYPE_CRON]); |
||
181 | for(var i = 0 ; i < keys.length ; i++){ |
||
0 ignored issues
–
show
Comprehensibility
Naming
Best Practice
introduced
by
The variable
i already seems to be declared on line 166 . Consider using another variable name or omitting the var keyword.
This check looks for variables that are declared in multiple lines. There may be several reasons for this. In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs. If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared. ![]() |
|||
182 | var key = keys[i]; |
||
0 ignored issues
–
show
Comprehensibility
Naming
Best Practice
introduced
by
The variable
key already seems to be declared on line 167 . Consider using another variable name or omitting the var keyword.
This check looks for variables that are declared in multiple lines. There may be several reasons for this. In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs. If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared. ![]() |
|||
183 | if (cache[key]) continue; |
||
184 | cache[key] = new Plugin ({'name' : key }); |
||
185 | cache[key].getInstance(); |
||
186 | } |
||
187 | } |
||
188 | |||
189 | // ------------------------------------------ |
||
190 | // PLUGIN LOCALES |
||
191 | // ------------------------------------------ |
||
192 | |||
193 | var getLocales = function(locale){ |
||
194 | var prop = {} |
||
195 | var keys = Object.keys(cache); |
||
196 | for(var i = 0 ; i < keys.length ; i++){ |
||
197 | var key = keys[i]; |
||
198 | var json = cache[key].getLocale(locale); |
||
199 | if (json){ extend(true, prop, json); } |
||
200 | } |
||
201 | return prop; |
||
202 | } |
||
203 | |||
204 | // ------------------------------------------ |
||
205 | // PLUGIN LIST |
||
206 | // ------------------------------------------ |
||
207 | |||
208 | var sort = function(ids, xPos, yPos){ |
||
209 | for(var i = 0 ; i < ids.length ; i++){ |
||
210 | var tmp = cache[ids[i]]; |
||
211 | if (tmp){ |
||
212 | var cfg = tmp.config; |
||
213 | cfg.x = parseInt(xPos[i])+1; |
||
214 | cfg.y = parseInt(yPos[i])+1; |
||
215 | } |
||
216 | } |
||
217 | getList(true); |
||
218 | } |
||
219 | |||
220 | var getList = function(clean){ |
||
221 | |||
222 | if (clean){ refresh(); } |
||
223 | |||
224 | var keys = Object.keys(cache); |
||
225 | keys = keys.sort(function(k1, k2){ |
||
226 | var conf1 = cache[k1].config; |
||
227 | var conf2 = cache[k2].config; |
||
228 | |||
229 | if (!conf1.y) return 1; |
||
230 | if (!conf2.y) return -1; |
||
231 | |||
232 | if (conf1.y < conf2.y) return -1; |
||
233 | if (conf1.y > conf2.y) return 1; |
||
234 | return conf1.x < conf2.x ? -1 : 1; |
||
235 | }); |
||
236 | |||
237 | var list = []; |
||
238 | for(var i = 0 ; i < keys.length ; i++){ |
||
239 | var key = keys[i]; |
||
240 | var plugin = cache[key]; |
||
241 | |||
242 | // Skip disabled plugin |
||
243 | if (plugin.isDisabled()){ continue; } |
||
244 | |||
245 | list.push(plugin); |
||
246 | } |
||
247 | return list; |
||
248 | } |
||
249 | |||
250 | |||
251 | // ------------------------------------------ |
||
252 | // FIND / SEEK |
||
253 | // ------------------------------------------ |
||
254 | |||
255 | var find = function(name){ |
||
256 | return cache[name]; |
||
257 | } |
||
258 | |||
259 | var exists = function(name){ |
||
260 | var plugin = find(name); |
||
261 | return plugin ? true : false; |
||
262 | } |
||
263 | |||
264 | var remove = function(name, callback){ |
||
265 | var plugin = find(name); |
||
266 | if (!plugin){ return callback(); } |
||
267 | |||
268 | // Remove from filesystem |
||
269 | var path = SARAH.ConfigManager.PLUGIN+'/'+name; |
||
270 | info('Removing %s plugin...', path); |
||
271 | if (fs.existsSync(path)){ fs.removeSync(path); } |
||
272 | |||
273 | // Remove in memory |
||
274 | refresh(); |
||
275 | |||
276 | callback(); |
||
0 ignored issues
–
show
|
|||
277 | } |
||
278 | |||
279 | // ------------------------------------------ |
||
280 | // EVENT |
||
281 | // ------------------------------------------ |
||
282 | |||
283 | var events = require('events'); |
||
284 | var ee = new events.EventEmitter(); |
||
285 | |||
286 | var listen = function(event, callback){ |
||
287 | ee.on(event, callback); |
||
288 | } |
||
289 | |||
290 | var trigger = function(event, data){ |
||
291 | ee.emit(event, data); |
||
292 | } |
||
293 | |||
294 | var socket = function(io){ |
||
295 | io.on('connection', function(socket){ |
||
296 | socket.on('disconnect', function(){ }); |
||
297 | |||
298 | for(var name in cache){ |
||
0 ignored issues
–
show
A for in loop automatically includes the property of any prototype object, consider checking the key using
hasOwnProperty .
When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically: var someObject;
for (var key in someObject) {
if ( ! someObject.hasOwnProperty(key)) {
continue; // Skip keys from the prototype.
}
doSomethingWith(key);
}
![]() |
|||
299 | plugin = cache[name].getInstance(); |
||
300 | if (plugin.socket) plugin.socket(io, socket); |
||
301 | } |
||
302 | }); |
||
303 | } |
||
304 | |||
305 | // ------------------------------------------ |
||
306 | // ROUTER |
||
307 | // ------------------------------------------ |
||
308 | |||
309 | var Router = express.Router(); |
||
310 | |||
311 | Router.get('/plugin/help/:name', function(req, res, next) { |
||
312 | var name = req.params.name; |
||
313 | var plugin = find(name); |
||
314 | |||
315 | if (plugin && plugin.index) { |
||
316 | return res.render(plugin.index, {'title' : i18n('modal.plugin.help', name)}); |
||
317 | } |
||
318 | next(); |
||
0 ignored issues
–
show
|
|||
319 | }); |
||
320 | |||
321 | Router.get('/plugin/github/:name', function(req, res, next) { |
||
0 ignored issues
–
show
|
|||
322 | var name = req.params.name; |
||
323 | SARAH.Marketplace.getCommits(name, function(commits){ |
||
324 | res.render('plugin/github.ejs', { |
||
325 | 'title' : i18n('modal.plugin.github', name), |
||
326 | 'commits' : commits |
||
327 | }); |
||
328 | }); |
||
329 | }); |
||
330 | |||
331 | Router.get('/plugin/config/:name', function(req, res, next) { |
||
0 ignored issues
–
show
|
|||
332 | var name = req.params.name; |
||
333 | return res.render('plugin/config.ejs', {'title' : i18n('modal.plugin.config', name) }); |
||
334 | }); |
||
335 | |||
336 | Router.post('/plugin/config/:name', function(req, res, next) { |
||
0 ignored issues
–
show
|
|||
337 | var name = req.params.name; |
||
338 | var plugin = find(name); |
||
0 ignored issues
–
show
|
|||
339 | var keys = Object.keys(req.body); |
||
340 | for(var i = 0 ; i < keys.length ; i++){ |
||
341 | var key = keys[i]; |
||
342 | var value = Helper.parse(req.body[key]); |
||
343 | var pfx = key.substring(0, key.indexOf('.')); |
||
344 | var prop = key.substring(key.indexOf('.')+1); |
||
345 | |||
346 | // skip internal parameter like ajax |
||
347 | if (!pfx) continue; |
||
348 | |||
349 | info('[%s] %s.%s.%s = %s',key, pfx, name, prop, value); |
||
350 | Config[pfx][name][prop] = value; |
||
351 | } |
||
352 | SARAH.ConfigManager.save(); |
||
353 | |||
354 | var referer = req.headers.referer; |
||
355 | if (referer && referer.indexOf('/portal') < 0){ |
||
356 | return res.redirect(referer); |
||
357 | } else { |
||
358 | return res.render('plugin/config.ejs', {'title' : i18n('modal.plugin.config', name), 'message' : true }); |
||
359 | } |
||
360 | }); |
||
361 | |||
362 | Router.get('/plugin/edit/:name', function(req, res, next) { |
||
0 ignored issues
–
show
|
|||
363 | var name = req.params.name; |
||
364 | var plugin = find(name); |
||
0 ignored issues
–
show
|
|||
365 | return res.render('plugin/edit.ejs', {'title' : i18n('modal.plugin.edit', name)}); |
||
366 | }); |
||
367 | |||
368 | Router.all('/plugin/sort', function(req, res, next) { |
||
0 ignored issues
–
show
|
|||
369 | sort(req.query.ids, req.query.xPos, req.query.yPos); |
||
370 | SARAH.ConfigManager.save(); |
||
371 | res.end(); |
||
372 | }); |
||
373 | |||
374 | Router.all('/plugin/:name/*', function(req, res, next) { |
||
0 ignored issues
–
show
|
|||
375 | var name = req.params.name; |
||
376 | var plugin = find(name); |
||
377 | if (!plugin) return res.end(); |
||
378 | |||
379 | var path = req.params[0]; |
||
380 | if (!path) return res.end(); |
||
381 | |||
382 | try { |
||
383 | var fullpath = SARAH.ConfigManager.PLUGIN+'/'+name+'/'+path; |
||
384 | if (fs.existsSync(fullpath)){ |
||
385 | res.render(fullpath, { "plugin" : plugin, "title" : (req.query.title || req.body.title || "") }); |
||
0 ignored issues
–
show
|
|||
386 | } else { |
||
387 | warn('path not found:', fullpath); |
||
0 ignored issues
–
show
|
|||
388 | } |
||
389 | } |
||
390 | catch (ex){ |
||
391 | warn('Error' , fullpath, ex.stack); |
||
0 ignored issues
–
show
|
|||
392 | } |
||
393 | }); |
||
394 | |||
395 | Router.all('/plugin/:name*', function(req, res, next) { |
||
0 ignored issues
–
show
|
|||
396 | var plugin = find(req.params.name); |
||
397 | if (!plugin) return res.end(); |
||
398 | |||
399 | var render = function(){ |
||
400 | res.render('portal/portlet.ejs', { "plugin" : plugin}); |
||
401 | } |
||
402 | |||
403 | var instance = plugin.getInstance(); |
||
404 | if (instance.ajax){ instance.ajax(req, res, render); } else { render(); } |
||
0 ignored issues
–
show
|
|||
405 | }); |
||
406 | |||
407 | Router.all('/static/:name/*', function(req, res, next) { |
||
408 | var plugin = find(req.params.name); |
||
409 | if (!plugin) return res.end(); |
||
410 | |||
411 | var instance = plugin.getInstance(); |
||
412 | if (instance.route) { |
||
413 | instance.route(req, res, next); |
||
0 ignored issues
–
show
|
|||
414 | } else { next(); } |
||
0 ignored issues
–
show
|
|||
415 | }); |
||
416 | |||
417 | |||
418 | // ------------------------------------------ |
||
419 | // PUBLIC |
||
420 | // ------------------------------------------ |
||
421 | |||
422 | var PluginManager = { |
||
423 | 'init' : init, |
||
424 | 'getCache' : getCache, |
||
425 | 'getList' : getList, |
||
426 | 'getLocales' : getLocales, |
||
427 | |||
428 | 'find' : find, |
||
429 | 'exists' : exists, |
||
430 | 'remove' : remove, |
||
431 | |||
432 | 'socket' : socket, |
||
433 | 'trigger' : trigger, |
||
434 | 'listen' : listen, |
||
435 | |||
436 | 'Router' : Router |
||
437 | } |
||
438 | |||
439 | // Exports Manager |
||
440 | exports.init = PluginManager.init; |