api/services/PluginService.js
'use strict'
const Service = require('trails/service')
const download = require('download')
const fs = require('fs-extra')
const npm = require('enpeem')
const _ = require('lodash')
const ERROR_PLUGIN = 'E_PLUGIN_NOT_FOUND'
const ERROR_PLUGIN_DRIVER = 'E_PLUGIN_DRIVER_NOT_FOUND'
/**
* @module PluginService
* @description Service to manage L.I.S.A. plugins
*/
module.exports = class PluginService extends Service {
_getPluginInstance(pluginName) {
let plugin
if (pluginName) {
plugin = this.pluginsManager.plugins[this._getPluginName(pluginName)]
}
return plugin
}
_getPluginName(pluginName) {
return pluginName.replace(/lisa-/, '').replace(/plugin-/, '').toCamelCase()
}
_getPluginPath(pluginName) {
return `${this.app.config.pluginManager.dist}/${pluginName}`
}
_managePluginBots(pluginName, bots) {
if (!bots) {
bots = {}
}
const botIds = Object.keys(bots)
return botIds.length === 0 ? Promise.resolve() : this.app.orm.ChatBot.findAll({
where: {
pluginName: pluginName
}
}).then(chatBots => {
const promises = []
botIds.forEach(botId => {
const bot = chatBots.find(item => botId === item.name)
bots[botId].pluginName = pluginName
if (bot) {
promises.push(this.app.services.ChatBotService.updateBot(botId, bots[botId]))
}
else {
promises.push(this.app.services.ChatBotService.addBot(botId, bots[botId]))
}
})
return Promise.all(promises)
}).catch(err => this.log.error(err))
}
/**
*
* @param pluginRealName
* @private
*/
_loadPlugin(pluginRealName) {
const PluginClass = require(this._getPluginPath(pluginRealName))
const pluginInstance = new PluginClass(this.app.lisa)
this.pluginsManager.plugins[pluginInstance.name] = pluginInstance
return this._managePluginBots(pluginRealName, pluginInstance.bots).then(() => Promise.resolve(pluginInstance))
}
callOnPlugins(toCall, args = []) {
const promises = []
_.each(this.pluginsManager.plugins, (value, key) => {
promises.push(this.pluginsManager.plugins[key][toCall](...args))
})
return Promise.all(promises)
}
callOnPlugin(toCall, pluginName, args = []) {
const plugin = this._getPluginInstance(pluginName)
return plugin[toCall](...args)
}
callOnPluginDriver(toCall, pluginName, driver, args = []) {
const plugin = this._getPluginInstance(pluginName)
if (!plugin) {
return Promise.reject(new Error(ERROR_PLUGIN))
}
if (!plugin.drivers || !plugin.drivers[driver]) {
return Promise.reject(new Error(ERROR_PLUGIN_DRIVER))
}
return plugin.drivers[driver][toCall](...args)
}
setDeviceValue(pluginName, args) {
const plugin = this._getPluginInstance(pluginName)
const driver = args[0].driver
if (!plugin) {
return Promise.reject(new Error(ERROR_PLUGIN))
}
if (!plugin.drivers && !plugin.drivers[driver]) {
return Promise.reject(new Error(ERROR_PLUGIN_DRIVER))
}
return plugin.drivers[driver].setDeviceValue(...args)
}
setDevicesValue(pluginName, args) {
const plugin = this._getPluginInstance(pluginName)
const devices = args[0]
const driver = devices.length > 0 ? devices[0].driver : null
if (!plugin) {
return Promise.reject(new Error(ERROR_PLUGIN))
}
if (!plugin.drivers && !plugin.drivers[driver]) {
return Promise.reject(new Error(ERROR_PLUGIN_DRIVER))
}
return plugin.drivers[driver].setDevicesValue(...args)
}
/**
*
*/
loadPlugins() {
return this.app.orm.Plugin.findAll({
where: {
activated: true
}
}).then(plugins => {
const promises = []
plugins.forEach(plugin => {
this._loadPlugin(plugin.name)
promises.push(this.pluginsManager.plugins[plugin.internalName].init())
})
return Promise.all(promises)
})
}
/**
*
*/
unloadPlugins() {
//this.pluginsManager
}
enablePlugin(pluginName) {
this._loadPlugin(pluginName)
pluginName = this._getPluginName(pluginName)
return this.pluginsManager.plugins[pluginName].init().then(_ => {
return this.app.orm.Plugin.update({
activated: true
}, {
where: {
internalName: pluginName
}
})
})
}
disablePlugin(name) {
name = this._getPluginName(name)
return this.pluginsManager.plugins[name].unload().then(_ => {
delete this.pluginsManager.plugins[name]
return this.app.orm.Plugin.update({
activated: false
}, {
where: {
internalName: name
}
})
})
}
/**
*
*/
installPlugin(name, version = 'master', from = 'github') {
let url
switch (from) {
case 'npm':
url = `https://registry.npmjs.org/${name}/-/${name}-${version}.tgz`
break
default:
url = `https://github.com/mylisabox/${name}/archive/${version}.zip`
}
this.log.silly('download plugin from ' + url)
return download(url, this.app.config.pluginManager.dist, {
extract: true
}).then(() => {
return new Promise((resolve, reject) => {
const path = this._getPluginPath(name)
const fromPath = from === 'npm' ? this._getPluginPath('package') : path + '-' + version
this.log.silly('npm install to' + fromPath)
npm.install({
dir: fromPath,
loglevel: 'silent',
production: true
}, err => {
this.log.silly('npm install finished, error: ' + err)
if (err) return reject(err)
fs.access(path, (fs.constants || fs).R_OK | (fs.constants || fs).W_OK, err => {
if (!err) {
this.log.silly('delete existing ' + path)
fs.removeSync(path)
}
this.log.silly('rename ' + fromPath + ' to ' + path)
fs.rename(fromPath, path, err => {
this.log.silly('renamed error: ' + err)
if (err) return reject(err)
resolve()
})
})
})
}).then(() => this._addPlugin(name))
})
}
_updatePlugin(pluginName) {
const plugin = require(`${this._getPluginPath(pluginName)}/package.json`)
const pluginConfig = require(`${this._getPluginPath(pluginName)}/config`)
return this.app.orm.Plugin.update({
version: plugin.version,
settings: pluginConfig.settings,
devicesSettings: pluginConfig.devices,
infos: pluginConfig.infos
}, { where: { name: plugin.name } }).then(plugin => {
return this._loadPlugin(pluginName).then(() => plugin)
})
}
_addPlugin(pluginName) {
const plugin = require(`${this._getPluginPath(pluginName)}/package.json`)
const pluginConfig = require(`${this._getPluginPath(pluginName)}/config`)
const name = this._getPluginName(plugin.name)
return this.app.orm.Plugin.create({
name: plugin.name,
internalName: name,
camelName: name.toCamelCase(),
version: plugin.version,
settings: pluginConfig.settings,
devicesSettings: pluginConfig.devices,
infos: pluginConfig.infos
}).then(plugin => {
return this._loadPlugin(pluginName).then(() => plugin)
})
}
/**
*
*/
uninstallPlugin(name) {
return this.disablePlugin(name).then(() => {
return this.app.orm.Plugin.destroy({
where: {
name: name,
}
}).then(results => {
return new Promise((resolve, reject) => {
const path = this._getPluginPath(name)
fs.access(path, (fs.constants || fs).R_OK | (fs.constants || fs).W_OK, err => {
if (!err) {
fs.removeSync(path)
}
resolve(results)
})
})
})
})
}
}