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); |
||
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
|
|||
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(); |
||
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){ |
||
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(); |
||
319 | }); |
||
320 | |||
321 | Router.get('/plugin/github/:name', function(req, res, next) { |
||
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) { |
||
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) { |
||
337 | var name = req.params.name; |
||
338 | var plugin = find(name); |
||
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) { |
||
363 | var name = req.params.name; |
||
364 | var plugin = find(name); |
||
365 | return res.render('plugin/edit.ejs', {'title' : i18n('modal.plugin.edit', name)}); |
||
366 | }); |
||
367 | |||
368 | Router.all('/plugin/sort', function(req, res, next) { |
||
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) { |
||
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 || "") }); |
||
386 | } else { |
||
387 | warn('path not found:', fullpath); |
||
388 | } |
||
389 | } |
||
390 | catch (ex){ |
||
391 | warn('Error' , fullpath, ex.stack); |
||
392 | } |
||
393 | }); |
||
394 | |||
395 | Router.all('/plugin/:name*', function(req, res, next) { |
||
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(); } |
||
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); |
||
414 | } else { next(); } |
||
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; |
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.