1398 lines
65 KiB
JavaScript
1398 lines
65 KiB
JavaScript
//@ts-check
|
|
|
|
'use strict';
|
|
|
|
function Install(options) {
|
|
const fs = require('fs');
|
|
const tools = require('../tools.js');
|
|
const extend = require('node.extend');
|
|
const hostname = tools.getHostName();
|
|
const path = require('path');
|
|
const semver = require('semver');
|
|
const child_process = require('child_process');
|
|
// todo solve it somehow
|
|
const unsafePermAlways = [tools.appName.toLowerCase() + '.zwave'];
|
|
let JSZip;
|
|
|
|
/** @type {Install} */
|
|
let that = this;
|
|
|
|
options = options || {};
|
|
|
|
if (!options.states) throw 'Invalid arguments: states is missing';
|
|
if (!options.objects) throw 'Invalid arguments: objects is missing';
|
|
if (!options.processExit) throw 'Invalid arguments: processExit is missing';
|
|
if (!options.installNpm) throw 'Invalid arguments: installNpm is missing';
|
|
if (!options.getRepository) throw 'Invalid arguments: getRepository is missing';
|
|
|
|
let objects = options.objects;
|
|
let states = options.states;
|
|
let processExit = options.processExit;
|
|
let installNpm = options.installNpm;
|
|
let getRepository = options.getRepository;
|
|
let params = options.params || {};
|
|
let mime;
|
|
|
|
// TODO: promisify States and Objects at some point
|
|
/** @type {(stateId: string) => Promise<void>} */
|
|
const delStateAsync = tools.promisify(states.delState, states);
|
|
/** @type {(objId: string) => Promise<void>} */
|
|
const delObjectAsync = tools.promisify(objects.delObject, objects);
|
|
/** @type {(id: string, name: string) => Promise<void>} */
|
|
const unlinkAsync = tools.promisify(objects.unlink, objects);
|
|
/** @type {(design: string, search: string, params: any, options?: any) => Promise<{rows: {id: string, value: any}[]}>} */
|
|
const getObjectViewAsync = tools.promisify(objects.getObjectView, objects);
|
|
/** @type {(params: any | null) => Promise<{rows: {id: string, value: any}[]}>} */
|
|
const getObjectListAsync = tools.promisify(objects.getObjectList, objects);
|
|
/** @type {(objId: string) => Promise<any>} */
|
|
const getObjectAsync = tools.promisify(objects.getObject, objects);
|
|
/** @type {(objId: string, newObj: any) => Promise<void>} */
|
|
const setObjectAsync = tools.promisify(objects.setObject, objects);
|
|
/** @type {(pattern: string) => Promise<string[]>} */
|
|
const getKeysAsync = tools.promisify(states.getKeys, states);
|
|
|
|
|
|
let installCount = 0;
|
|
|
|
const Upload = require(__dirname + '/setupUpload.js');
|
|
let upload = new Upload(options);
|
|
|
|
function enableAdapters(adapters, isEnable, callback) {
|
|
let count = 0;
|
|
if (adapters) {
|
|
count = adapters.length;
|
|
const ts = new Date().getTime();
|
|
for (let i = 0; i < adapters.length; i++) {
|
|
adapters[i].common.enabled = isEnable;
|
|
console.log('host.' + hostname + ' Adapter "' + adapters[i]._id + '" is ' + (isEnable ? 'started' : 'stopped.'));
|
|
adapters[i].from = 'system.host.' + tools.getHostName() + '.cli';
|
|
adapters[i].ts = ts;
|
|
objects.setObject(adapters[i]._id, adapters[i], function () {
|
|
if (!--count) callback();
|
|
});
|
|
}
|
|
}
|
|
if (!count) callback();
|
|
}
|
|
|
|
function _writeOneFile(zip, targetName, fileName, callback) {
|
|
zip.files[fileName].async('nodebuffer').then(function (data) {
|
|
fs.writeFileSync(path.join(targetName, fileName), data);
|
|
callback();
|
|
}, function (err) {
|
|
callback(err);
|
|
});
|
|
}
|
|
|
|
function extractFiles(fileName, targetName, callback) {
|
|
JSZip = JSZip || require('jszip');
|
|
const zip = new JSZip();
|
|
zip.loadAsync(fs.readFileSync(fileName)).then(function () {
|
|
let count = 0;
|
|
for (let fName in zip.files) {
|
|
if (!zip.files.hasOwnProperty(fName) || !fName || fName[fName.length - 1] === '/') continue;
|
|
count++;
|
|
_writeOneFile(zip, targetName, fName, function (err) {
|
|
if (!--count) callback(err);
|
|
});
|
|
}
|
|
if (!count) callback();
|
|
});
|
|
}
|
|
|
|
this.downloadPacket = function (repoUrl, packetName, options, stoppedList, callback) {
|
|
let url;
|
|
let name;
|
|
if (!options || typeof options !== 'object') {
|
|
options = {};
|
|
}
|
|
|
|
if (typeof stoppedList === 'function') {
|
|
callback = stoppedList;
|
|
stoppedList = null;
|
|
}
|
|
|
|
if (!repoUrl || typeof repoUrl !== 'object') {
|
|
getRepository(repoUrl, params, function (err, sources) {
|
|
if (err) {
|
|
processExit(err);
|
|
return;
|
|
}
|
|
that.downloadPacket(sources, packetName, options, stoppedList, callback);
|
|
});
|
|
return;
|
|
}
|
|
let version;
|
|
if (packetName.indexOf('@') !== -1) {
|
|
const parts = packetName.split('@');
|
|
packetName = parts[0];
|
|
version = parts[1];
|
|
} else {
|
|
version = '';
|
|
}
|
|
|
|
let sources = repoUrl;
|
|
options.unsafePerm = sources[packetName] && sources[packetName].unsafePerm;
|
|
|
|
// Check if flag stopBeforeUpdate is true
|
|
if (sources[packetName] && sources[packetName].stopBeforeUpdate && !stoppedList) {
|
|
objects.getObjectList({startkey: 'system.adapter.' + packetName + '.', endkey: 'system.adapter.' + packetName + '.\u9999'}, function (err, arr) {
|
|
stoppedList = [];
|
|
if (!err && arr) {
|
|
for (let id = 0; id < arr.rows.length; id++) {
|
|
if (arr.rows[id].value.common.enabled) {
|
|
stoppedList.push(arr.rows[id].value);
|
|
}
|
|
}
|
|
}
|
|
enableAdapters(stoppedList, false, function () {
|
|
that.downloadPacket(sources, packetName + '@' + version, options, stoppedList, callback);
|
|
});
|
|
});
|
|
return;
|
|
}
|
|
|
|
// try to extract the information from local sources-dist.json
|
|
if (!sources[packetName]) {
|
|
try {
|
|
const sourcesDist = JSON.parse(fs.readFileSync(__dirname + '/../../conf/sources-dist.json', 'utf8'));
|
|
sources[packetName] = sourcesDist[packetName];
|
|
} catch (e) {
|
|
|
|
}
|
|
}
|
|
|
|
if (sources[packetName]) {
|
|
url = sources[packetName].url;
|
|
|
|
if (url &&
|
|
packetName === 'js-controller' &&
|
|
fs.existsSync(__dirname + '/../../../../node_modules/' + tools.appName + '.js-controller')) {
|
|
url = null;
|
|
}
|
|
|
|
if (!url && packetName !== 'example') {
|
|
// Install node modules
|
|
that.npmInstallWithCheck(tools.appName.toLowerCase() + '.' + packetName + (version ? '@' + version : ''), options, false, function () {
|
|
// command succeeded
|
|
enableAdapters(stoppedList, true, function () {
|
|
if (callback) callback(packetName);
|
|
});
|
|
});
|
|
return;
|
|
}
|
|
if (url && url.match(/\/tarball\/master$/)) {
|
|
// Install node modules
|
|
that.npmInstallWithCheck(url, options, false, function () {
|
|
// command succeeded
|
|
enableAdapters(stoppedList, true, function () {
|
|
if (callback) callback(packetName);
|
|
});
|
|
});
|
|
return;
|
|
}
|
|
// Adapter
|
|
if (!url) {
|
|
console.warn('host.' + hostname + ' Adapter "' + packetName + '" can be updated only together with ' + tools.appName + '.js-controller');
|
|
if (typeof callback === 'function') callback(packetName);
|
|
return;
|
|
}
|
|
name = packetName.replace(/[\/ $&*\\]/g, '_');
|
|
} else {
|
|
url = packetName;
|
|
if (url.indexOf('http://') === -1 && url.indexOf('https://') === -1 && url.indexOf('file://') === -1) {
|
|
console.error('host.' + hostname + ' Unknown packetName ' + packetName);
|
|
processExit(5);
|
|
}
|
|
name = Math.floor(Math.random() * 0xFFFFFFE);
|
|
}
|
|
|
|
const ncp = require('ncp').ncp;
|
|
ncp.limit = 16;
|
|
|
|
console.log('host.' + hostname + ' download ' + url);
|
|
|
|
tools.getFile(url, name + '.zip', function (tmpFile) {
|
|
tmpFile = path.normalize(tmpFile);
|
|
console.log('host.' + hostname + ' unzip ' + tmpFile);
|
|
|
|
// Extract files into tmp/
|
|
extractFiles(tmpFile, path.join(__dirname + '/../../tmp/', name), function (error) {
|
|
if (error) {
|
|
console.error(error);
|
|
processExit(12);
|
|
}
|
|
// Find out the first directory
|
|
const dirs = fs.readdirSync(__dirname + '/../../tmp/' + name);
|
|
if (dirs.length) {
|
|
const source = __dirname + '/../../tmp/' + name + ((dirs.length === 1) ? '/' + dirs[0] : '');
|
|
// Copy files into adapter or controller
|
|
if (fs.existsSync(source + '/io-package.json')) {
|
|
let packetIo;
|
|
try {
|
|
packetIo = JSON.parse(fs.readFileSync(source + '/io-package.json', 'utf8'));
|
|
} catch (e) {
|
|
console.error('host.' + hostname + ' io-package.json has invalid format! Installation terminated.');
|
|
if (typeof callback === 'function') callback(name, 'Invalid io-package.json!');
|
|
processExit(6);
|
|
}
|
|
|
|
let destination = __dirname + '/../..';
|
|
if (!packetIo.common.controller) {
|
|
if (fs.existsSync(destination + '/../../node_modules')) {
|
|
destination += '/../' + tools.appName + '.' + packetIo.common.name;
|
|
} else {
|
|
destination += '/node_modules/' + tools.appName + '.' + packetIo.common.name;
|
|
}
|
|
}
|
|
|
|
destination = path.normalize(destination);
|
|
|
|
console.log('host.' + hostname + ' copying ' + source + ' to ' + destination + '(Version: ' + packetIo.common.version + ')');
|
|
|
|
ncp(source, destination, function (err) {
|
|
if (err) {
|
|
console.error('host.' + hostname + ' ncp error: ' + err);
|
|
processExit(7);
|
|
}
|
|
if (tmpFile.substring(0, (path.normalize(__dirname + '/../../tmp/')).length) === path.normalize(__dirname + '/../../tmp/')) {
|
|
console.log('host.' + hostname + ' delete ' + tmpFile);
|
|
fs.unlinkSync(tmpFile);
|
|
}
|
|
console.log('host.' + hostname + ' delete ' + path.normalize(__dirname + '/../../tmp/' + name));
|
|
tools.rmdirRecursiveSync(__dirname + '/../../tmp/' + name);
|
|
|
|
// Call npm install
|
|
if (typeof callback === 'function') {
|
|
enableAdapters(stoppedList, true, function () {
|
|
if (callback) callback(name, packetIo);
|
|
});
|
|
}
|
|
|
|
});
|
|
} else {
|
|
console.error('host.' + hostname + ' io-package.json not found in ' + source + '/io-package.json. Invalid packet! Installation terminated.');
|
|
if (typeof callback === 'function') callback(name, 'Invalid packet!');
|
|
processExit(8);
|
|
}
|
|
} else {
|
|
console.error('host.' + hostname + ' Packet is empty! Installation terminated.');
|
|
if (typeof callback === 'function') callback(name, 'Packet is empty');
|
|
processExit(12);
|
|
}
|
|
});
|
|
});
|
|
};
|
|
|
|
this.npmInstallWithCheck = function (npmUrl, options, debug, callback) {
|
|
// Get npm version
|
|
try {
|
|
let npmVersion;
|
|
try {
|
|
npmVersion = child_process.execSync('npm -v', {encoding: 'utf8'});
|
|
if (npmVersion) npmVersion = semver.valid(npmVersion.trim());
|
|
console.log('NPM version: ' + npmVersion);
|
|
} catch (e) {
|
|
console.error('Error trying to check npm version: ' + e);
|
|
}
|
|
|
|
if (!npmVersion) {
|
|
console.error('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
|
|
console.error('Aborting install because the npm version could not be checked!');
|
|
console.error('Please check that npm is installed correctly.');
|
|
console.error('Use "npm install -g npm@4" or "npm install -g npm@>=5.7.1" to install a supported version.');
|
|
console.error('You need to make sure to repeat this step after installing an update to NodeJS and/or npm');
|
|
console.error('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
|
|
processExit(25);
|
|
return;
|
|
}
|
|
|
|
if (semver.gte(npmVersion, "5.0.0") && semver.lt(npmVersion, "5.7.1")) {
|
|
console.error('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
|
|
console.error('NPM 5 is only supported starting with version 5.7.1!');
|
|
console.error('Please use "npm install -g npm@4" to downgrade npm to 4.x or ');
|
|
console.error('use "npm install -g npm@>=5.7.1" to install a supported version of npm 5!');
|
|
console.error('You need to make sure to repeat this step after installing an update to NodeJS and/or npm');
|
|
console.error('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!');
|
|
processExit(25);
|
|
return;
|
|
}
|
|
|
|
this.npmInstall(npmUrl, options, debug, callback);
|
|
} catch (e) {
|
|
console.error('Could not check npm version: ' + e);
|
|
console.error('Assuming that correct version is installed.');
|
|
}
|
|
};
|
|
|
|
this.npmInstall = function (npmUrl, options, debug, callback) {
|
|
if (typeof options !== 'object') {
|
|
options = {};
|
|
}
|
|
|
|
// Install node modules
|
|
/** @type {string|string[]} */
|
|
let cwd = __dirname.replace(/\\/g, '/');
|
|
if (fs.existsSync(__dirname + '/../../../../node_modules/' + tools.appName + '.js-controller')) {
|
|
// js-controller installed as npm
|
|
cwd = cwd.split('/');
|
|
cwd.splice(cwd.length - 4, 4);
|
|
cwd = cwd.join('/');
|
|
} else {
|
|
// remove lib
|
|
cwd = cwd.split('/');
|
|
cwd.pop();
|
|
cwd.pop();
|
|
cwd = cwd.join('/');
|
|
}
|
|
|
|
// zwave for example requires always unsafe-perm option
|
|
for (let a = 0; a < unsafePermAlways.length; a++) {
|
|
if (npmUrl.indexOf(unsafePermAlways[a]) !== -1) {
|
|
options.unsafePerm = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
tools.disablePackageLock(function (err) {
|
|
const cmd = 'npm install ' + npmUrl + (options.unsafePerm ? ' --unsafe-perm' : '') + ' --production --save --prefix "' + cwd + '"';
|
|
|
|
console.log(cmd + ' (System call)');
|
|
// Install node modules as system call
|
|
|
|
// System call used for update of js-controller itself,
|
|
// because during installation npm packet will be deleted too, but some files must be loaded even during the install process.
|
|
const exec = require('child_process').exec;
|
|
const child = exec(cmd);
|
|
child.stderr.pipe(process.stdout);
|
|
if (debug || params.debug) {
|
|
child.stdout.pipe(process.stdout);
|
|
}
|
|
child.on('exit', function (code /* , signal */) {
|
|
// code 1 is strange error that cannot be explained. Everything is installed but error :(
|
|
if (code && code !== 1) {
|
|
console.error('host.' + hostname + ' Cannot install ' + npmUrl + ': ' + code);
|
|
processExit(25);
|
|
}
|
|
// create file that indicates, that npm was called there
|
|
if (npmUrl.indexOf(':') === -1 && fs.existsSync(cwd + '/node_modules/' + npmUrl)) {
|
|
fs.writeFileSync(cwd + '/node_modules/' + npmUrl + '/iob_npm.done', ' ');
|
|
}
|
|
// command succeeded
|
|
if (callback) callback(npmUrl, cwd + '/node_modules');
|
|
});
|
|
});
|
|
};
|
|
|
|
this.npmUninstall = function (packageName, options, debug, callback) {
|
|
if (typeof options !== 'object') {
|
|
options = {};
|
|
}
|
|
|
|
// TODO: fine nicer way to find the root directory
|
|
|
|
// Install node modules
|
|
/** @type {string|string[]} */
|
|
let cwd = __dirname.replace(/\\/g, '/');
|
|
if (fs.existsSync(__dirname + '/../../../../node_modules/' + tools.appName + '.js-controller')) {
|
|
// js-controller installed as npm
|
|
cwd = cwd.split('/');
|
|
cwd.splice(cwd.length - 4, 4);
|
|
cwd = cwd.join('/');
|
|
} else {
|
|
// remove lib
|
|
cwd = cwd.split('/');
|
|
cwd.pop();
|
|
cwd.pop();
|
|
cwd = cwd.join('/');
|
|
}
|
|
|
|
tools.disablePackageLock(function (err) {
|
|
let cmd = `npm uninstall ${packageName} --silent --save --prefix "${cwd}"`;
|
|
|
|
console.log(cmd + ' (System call)');
|
|
// Install node modules as system call
|
|
|
|
// System call used for update of js-controller itself,
|
|
// because during installation npm packet will be deleted too, but some files must be loaded even during the install process.
|
|
const exec = require('child_process').exec;
|
|
const child = exec(cmd);
|
|
child.stderr.pipe(process.stdout);
|
|
if (debug || params.debug) {
|
|
child.stdout.pipe(process.stdout);
|
|
}
|
|
child.on('exit', function (code /* , signal */) {
|
|
// code 1 is strange error that cannot be explained. Everything is installed but error :(
|
|
if (code && code !== 1) {
|
|
if (typeof callback === "function") callback(`host.${hostname}: Cannot uninstall ${packageName}: ${code}`);
|
|
}
|
|
// command succeeded
|
|
if (callback) callback();
|
|
});
|
|
});
|
|
};
|
|
/** @type {(packageName: string, options: any, debug: boolean) => Promise<void>} */
|
|
this.npmUninstallAsync = tools.promisify(this.npmUninstall, this);
|
|
|
|
this.uploadStaticObjects = function (adapter, adapterConf, callback) {
|
|
if (typeof adapterConf === 'function') {
|
|
callback = adapterConf;
|
|
adapterConf = null;
|
|
}
|
|
if (!adapterConf) {
|
|
const adapterDir = tools.getAdapterDir(adapter);
|
|
if (!fs.existsSync(adapterDir + '/io-package.json')) {
|
|
console.error('host.' + hostname + ' Adapter directory "' + adapterDir + '" does not exists');
|
|
callback(17, adapter);
|
|
return;
|
|
}
|
|
try {
|
|
adapterConf = JSON.parse(fs.readFileSync(adapterDir + '/io-package.json').toString());
|
|
} catch (e) {
|
|
console.error('host.' + hostname + ' error: reading io-package.json ' + e, adapter);
|
|
callback(17, adapter);
|
|
return;
|
|
}
|
|
}
|
|
|
|
let objs;
|
|
if (adapterConf.objects && adapterConf.objects.length > 0) {
|
|
objs = adapterConf.objects;
|
|
} else {
|
|
objs = [];
|
|
}
|
|
function checkDependencies(deps, _options, callback) {
|
|
if (!deps || !deps.length) {
|
|
if (callback) callback(adapter);
|
|
return;
|
|
}
|
|
|
|
let cnt = 0;
|
|
// Get all installed adapters
|
|
objects.getObjectView('system', 'instance', {}, null, function (err, objs) {
|
|
if (err) console.error(err);
|
|
if (objs && objs.rows && objs.rows.length) {
|
|
for (let i = 0; i < deps.length; i++) {
|
|
let dName;
|
|
let version = null;
|
|
let isFound = false;
|
|
|
|
if (typeof deps[i] === 'object') {
|
|
for (let d in deps[i]) {
|
|
if (!deps[i].hasOwnProperty(d)) continue;
|
|
dName = d;
|
|
version = deps[i][d];
|
|
break;
|
|
}
|
|
} else {
|
|
dName = deps[i];
|
|
}
|
|
|
|
if (dName === 'js-controller') {
|
|
// Check only version
|
|
if (version !== null) {
|
|
const iopkg_ = JSON.parse(fs.readFileSync(__dirname + '/../../package.json', 'utf8'));
|
|
if (!semver.satisfies(iopkg_.version, version)) {
|
|
console.error('host.' + hostname + ' Invalid version of "' + dName + '". Installed "' + iopkg_.version + '", required "' + version);
|
|
processExit(30);
|
|
} else {
|
|
isFound = true;
|
|
}
|
|
} else {
|
|
isFound = true;
|
|
}
|
|
}
|
|
|
|
if (!isFound) {
|
|
for (let t = 0; t < objs.rows.length; t++) {
|
|
if (objs.rows[t] && objs.rows[t].value && objs.rows[t].value.common && objs.rows[t].value.common.name === dName) {
|
|
|
|
if (version !== null) {
|
|
// var iopkg = JSON.parse(fs.readFileSync(__dirname + '/../../package.json'));
|
|
if (!semver.satisfies(objs.rows[t].value.common.version, version)) {
|
|
console.error('host.' + hostname + ' Invalid version of "' + dName + '". Installed "' + objs.rows[t].value.common.version + '", required "' + version);
|
|
processExit(30);
|
|
} else {
|
|
isFound = true;
|
|
}
|
|
} else {
|
|
isFound = true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!isFound) {
|
|
cnt++;
|
|
that.createInstance(dName, _options, function (name) {
|
|
upload.uploadAdapter(name, true, false, function () {
|
|
upload.uploadAdapter(name, false, false, function () {
|
|
cnt--;
|
|
if (!cnt && callback) callback(adapter);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
}
|
|
}
|
|
if (!cnt && callback) callback(adapter);
|
|
});
|
|
}
|
|
|
|
checkDependencies(adapterConf.common.dependencies, params, function () {
|
|
adapterConf.common.installedVersion = adapterConf.common.version;
|
|
|
|
objs.push({
|
|
_id: 'system.adapter.' + adapterConf.common.name,
|
|
type: 'adapter',
|
|
common: adapterConf.common,
|
|
native: adapterConf.native
|
|
});
|
|
|
|
function setObjects(_objs, _callback) {
|
|
if (!_objs || _objs.length === 0) {
|
|
_callback(null, adapter);
|
|
} else {
|
|
let obj = _objs.pop();
|
|
|
|
obj.from = 'system.host.' + tools.getHostName() + '.cli';
|
|
obj.ts = new Date().getTime();
|
|
|
|
objects.extendObject(obj._id, obj, function (err /* , res */) {
|
|
if (err) {
|
|
console.error('host.' + hostname + ' error setObject ' + obj._id + ' ' + err);
|
|
_callback(17, adapter);
|
|
} else {
|
|
console.log('host.' + hostname + ' object ' + obj._id + ' created');
|
|
setImmediate(setObjects, _objs, _callback);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
setObjects(objs, callback);
|
|
});
|
|
};
|
|
|
|
function installAdapter(adapter, callback) {
|
|
const adapterDir = tools.getAdapterDir(adapter);
|
|
|
|
console.log('host.' + hostname + ' install adapter ' + adapter);
|
|
|
|
if (!fs.existsSync(adapterDir + '/io-package.json')) {
|
|
if (installCount === 2) {
|
|
console.error('host.' + hostname + ' Cannot install ' + adapter);
|
|
processExit(13);
|
|
return;
|
|
}
|
|
installCount++;
|
|
|
|
that.downloadPacket(null, adapter, null, function () {
|
|
installAdapter(adapter, callback);
|
|
});
|
|
return;
|
|
}
|
|
installCount = 0;
|
|
let adapterConf;
|
|
try {
|
|
adapterConf = JSON.parse(fs.readFileSync(adapterDir + '/io-package.json').toString());
|
|
} catch (e) {
|
|
console.error('host.' + hostname + ' error: reading io-package.json ' + e);
|
|
processExit(14);
|
|
}
|
|
|
|
// Check if the operation system is ok
|
|
if (adapterConf.common && adapterConf.common.os) {
|
|
if (typeof adapterConf.common.os === 'string' && adapterConf.common.os !== require('os').platform()) {
|
|
console.error('host.' + hostname + ' Adapter does not support current os. Required ' + adapterConf.common.os + '. Actual platform: ' + require('os').platform());
|
|
processExit(15);
|
|
} else {
|
|
if (adapterConf.common.os.indexOf(require('os').platform()) === -1) {
|
|
console.error('host.' + hostname + ' Adapter does not support current os. Required one of ' + adapterConf.common.os.join(', ') + '. Actual platform: ' + require('os').platform());
|
|
processExit(16);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!fs.existsSync(adapterDir + '/node_modules')) {
|
|
// Install node modules
|
|
installNpm(adapter, function (err, _adapter) {
|
|
if (err) {
|
|
processExit(err);
|
|
} else {
|
|
upload.uploadAdapter(_adapter, true, true, function () {
|
|
upload.uploadAdapter(_adapter, false, true, function () {
|
|
callInstallOfAdapter(_adapter, adapterConf, function () {
|
|
that.uploadStaticObjects(adapter, function (err /* , _adapter */) {
|
|
if (err) {
|
|
processExit(err);
|
|
} else {
|
|
callback(adapter);
|
|
}
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}
|
|
});
|
|
} else {
|
|
upload.uploadAdapter(adapter, true, true, function () {
|
|
upload.uploadAdapter(adapter, false, true, function () {
|
|
callInstallOfAdapter(adapter, adapterConf, function () {
|
|
that.uploadStaticObjects(adapter, function (err /* , _adapter */) {
|
|
if (err) {
|
|
processExit(err);
|
|
} else {
|
|
callback(adapter);
|
|
}
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
function callInstallOfAdapter(adapter, config, callback) {
|
|
const path_ = tools.getAdapterDir(adapter);
|
|
|
|
if (config.common.install && fs.existsSync(path_ + '/io-package.json')) {
|
|
// Install node modules
|
|
const exec = require('child_process').exec;
|
|
let cmd = 'node ';
|
|
|
|
let fileName = config.common.main || 'main.js';
|
|
if (!fs.existsSync(path_ + '/' + fileName)) {
|
|
fileName = adapter + '.js';
|
|
}
|
|
cmd += '"' + path + '/' + fileName + '" --install';
|
|
console.log('host.' + hostname + ' command: ' + cmd);
|
|
const child = exec(cmd);
|
|
child.stderr.pipe(process.stdout);
|
|
child.on('exit', function () {
|
|
if (callback) callback(adapter);
|
|
});
|
|
} else {
|
|
if (callback) callback(adapter);
|
|
}
|
|
}
|
|
|
|
//options = enabled, host, port
|
|
this.createInstance = function (adapter, options, callback) {
|
|
const adapterDir = tools.getAdapterDir(adapter);
|
|
if (typeof options === 'function') {
|
|
callback = options;
|
|
options = null;
|
|
}
|
|
let ignoreIfExists = false;
|
|
if (!options) options = {};
|
|
if (!options.host) options.host = tools.getHostName();
|
|
if (options.enabled === 'true') options.enabled = true;
|
|
if (options.enabled === 'false') options.enabled = false;
|
|
if (options.ignoreIfExists !== undefined) {
|
|
ignoreIfExists = !!options.ignoreIfExists;
|
|
delete options.ignoreIfExists;
|
|
}
|
|
|
|
if (!mime) mime = require('mime');
|
|
|
|
objects.getObject('system.adapter.' + adapter, function (err, doc) {
|
|
|
|
// Adapter is not installed - install it now
|
|
if (err || !doc || !doc.common.installedVersion) {
|
|
installAdapter(adapter, function () {
|
|
that.createInstance(adapter, options, callback);
|
|
});
|
|
return;
|
|
}
|
|
|
|
// Check if some web pages should be uploaded
|
|
upload.uploadAdapter(adapter, true, false, function () {
|
|
upload.uploadAdapter(adapter, false, false, function () {
|
|
objects.getObjectView('system', 'instance', {startkey: 'system.adapter.' + adapter + '.', endkey: 'system.adapter.' + adapter + '.\u9999'}, null, function (err, res) {
|
|
let a;
|
|
if (err || !res) {
|
|
console.error('host.' + hostname + ' error: view instanceStats ' + err);
|
|
processExit(18);
|
|
return;
|
|
}
|
|
|
|
// Count started instances
|
|
if (doc.common.singleton && res.rows.length) {
|
|
if (ignoreIfExists) {
|
|
callback && callback();
|
|
return;
|
|
}
|
|
console.error('host.' + hostname + ' error: this adapter does not allow multiple instances');
|
|
processExit(19);
|
|
return;
|
|
}
|
|
|
|
// check singletonHost one on host
|
|
if (doc.common.singletonHost) {
|
|
for (a = 0; a < res.rows.length; a++) {
|
|
if (res.rows[a].value.common.host === hostname) {
|
|
if (ignoreIfExists) {
|
|
callback && callback();
|
|
return;
|
|
}
|
|
console.error('host.' + hostname + ' error: this adapter does not allow multiple instances on one host');
|
|
processExit(21);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
let adapterConf;
|
|
let instance = null;
|
|
|
|
if (options.instance !== undefined) {
|
|
instance = options.instance;
|
|
// find max instance
|
|
if (res.rows.find(obj => parseInt(obj.id.split('.').pop(), 10) === instance)) {
|
|
console.error('host.' + hostname + ' error: instance yet exists');
|
|
processExit(26);
|
|
return;
|
|
}
|
|
} else {
|
|
// find max instance
|
|
for (a = 0; a < res.rows.length; a++) {
|
|
const iInstance = parseInt(res.rows[a].id.split('.').pop(), 10);
|
|
if (instance === null || iInstance > instance) {
|
|
instance = iInstance;
|
|
}
|
|
}
|
|
if (instance === null) {
|
|
instance = 0;
|
|
} else {
|
|
instance++;
|
|
}
|
|
}
|
|
|
|
const instanceObj = doc;
|
|
doc = JSON.parse(JSON.stringify(doc));
|
|
|
|
instanceObj._id = 'system.adapter.' + adapter + '.' + instance;
|
|
instanceObj.type = 'instance';
|
|
if (instanceObj._rev) delete instanceObj._rev;
|
|
instanceObj.common.enabled = (options.enabled === true || options.enabled === false) ? options.enabled :
|
|
((instanceObj.common.enabled === true || instanceObj.common.enabled === false) ? instanceObj.common.enabled : false);
|
|
instanceObj.common.host = options.host;
|
|
|
|
if (options.port) {
|
|
instanceObj.native = instanceObj.native || {};
|
|
instanceObj.native.port = options.port;
|
|
}
|
|
|
|
console.log('host.' + hostname + ' create instance ' + adapter);
|
|
|
|
const _id = 'system.adapter.' + adapter + '.' + instance;
|
|
|
|
let objs;
|
|
if (!instanceObj.common.onlyWWW && instanceObj.common.mode !== 'once') {
|
|
objs = [
|
|
{
|
|
_id: _id + '.alive',
|
|
type: 'state',
|
|
common: {
|
|
name: adapter + '.' + instance + '.alive',
|
|
type: 'boolean',
|
|
read: true,
|
|
write: true,
|
|
role: 'indicator.state'
|
|
},
|
|
native: {}
|
|
},
|
|
{
|
|
_id: _id + '.connected',
|
|
type: 'state',
|
|
common: {
|
|
name: adapter + '.' + instance + '.connected',
|
|
type: 'boolean',
|
|
read: true,
|
|
write: false,
|
|
role: 'indicator.state'
|
|
},
|
|
native: {}
|
|
},
|
|
{
|
|
_id: _id + '.memHeapUsed',
|
|
type: 'state',
|
|
common: {
|
|
name: adapter + '.' + instance + '.memHeapUsed',
|
|
type: 'number',
|
|
read: true,
|
|
write: false,
|
|
role: 'indicator.state',
|
|
unit: 'MB'
|
|
},
|
|
native: {}
|
|
},
|
|
{
|
|
_id: _id + '.memHeapTotal',
|
|
type: 'state',
|
|
common: {
|
|
name: adapter + '.' + instance + '.memHeapTotal',
|
|
read: true,
|
|
write: false,
|
|
type: 'number',
|
|
role: 'indicator.state',
|
|
unit: 'MB'
|
|
},
|
|
native: {}
|
|
},
|
|
{
|
|
_id: _id + '.memRss',
|
|
type: 'state',
|
|
common: {
|
|
name: adapter + '.' + instance + '.memRss',
|
|
desc: 'Resident set size',
|
|
read: true,
|
|
write: false,
|
|
type: 'number',
|
|
role: 'indicator.state',
|
|
unit: 'MB'
|
|
},
|
|
native: {}
|
|
},
|
|
{
|
|
_id: _id + '.uptime',
|
|
type: 'state',
|
|
common: {
|
|
name: adapter + '.' + instance + '.uptime',
|
|
type: 'number',
|
|
read: true,
|
|
write: false,
|
|
role: 'indicator.state',
|
|
unit: 'seconds'
|
|
},
|
|
native: {}
|
|
},
|
|
{
|
|
_id: _id + '.inputCount',
|
|
type: 'state',
|
|
common: {
|
|
name: hostname + ' - inputs level',
|
|
desc: 'State\'s inputs in 15 seconds',
|
|
type: 'number',
|
|
read: true,
|
|
write: false,
|
|
role: 'state',
|
|
unit: 'events/15 seconds'
|
|
},
|
|
native: {}
|
|
},
|
|
{
|
|
_id: _id + '.outputCount',
|
|
type: 'state',
|
|
common: {
|
|
name: hostname + ' outputs level',
|
|
desc: 'State\'s outputs in 15 seconds',
|
|
type: 'number',
|
|
read: true,
|
|
write: false,
|
|
role: 'state',
|
|
unit: 'events/15 seconds'
|
|
},
|
|
native: {}
|
|
}
|
|
];
|
|
} else {
|
|
objs = [];
|
|
}
|
|
|
|
if (fs.existsSync(adapterDir + '/www')) {
|
|
objs.push({
|
|
_id: 'system.adapter.' + adapter + '.upload',
|
|
type: 'state',
|
|
common: {
|
|
name: adapter + '.upload',
|
|
type: 'number',
|
|
read: true,
|
|
write: false,
|
|
role: 'indicator.state',
|
|
unit: '%',
|
|
def: 0,
|
|
desc: 'Upload process indicator'
|
|
},
|
|
native: {}
|
|
});
|
|
}
|
|
|
|
if (instanceObj.common.wakeup) {
|
|
objs.push({
|
|
_id: _id + '.wakeup',
|
|
type: 'state',
|
|
common: {
|
|
name: adapter + '.' + instance + '.wakeup',
|
|
read: true,
|
|
write: true,
|
|
type: 'boolean',
|
|
role: 'adapter.wakeup'
|
|
},
|
|
native: {}
|
|
});
|
|
}
|
|
|
|
if (!adapterConf) {
|
|
try {
|
|
adapterConf = JSON.parse(fs.readFileSync(adapterDir + '/io-package.json').toString());
|
|
} catch (e) {
|
|
console.error('host.' + hostname + ' error: reading io-package.json ' + e);
|
|
processExit(20);
|
|
}
|
|
}
|
|
if (!adapterConf.instanceObjects) adapterConf.instanceObjects = [];
|
|
if (!adapterConf.objects) adapterConf.objects = [];
|
|
|
|
// Create only for this instance the predefined in io-package.json objects
|
|
// It is not necessary to write "system.adapter.name.N." in the object '_id'
|
|
for (let i = 0; i < adapterConf.instanceObjects.length; i++) {
|
|
adapterConf.instanceObjects[i]._id = adapter + '.' + instance + (adapterConf.instanceObjects[i]._id ? ('.' + adapterConf.instanceObjects[i]._id) : '');
|
|
|
|
if (adapterConf.instanceObjects[i].common) {
|
|
if (adapterConf.instanceObjects[i].common.name) {
|
|
adapterConf.instanceObjects[i].common.name = adapterConf.instanceObjects[i].common.name.replace('%INSTANCE%', instance);
|
|
}
|
|
if (adapterConf.instanceObjects[i].common.desc) {
|
|
adapterConf.instanceObjects[i].common.desc = adapterConf.instanceObjects[i].common.desc.replace('%INSTANCE%', instance);
|
|
}
|
|
}
|
|
|
|
objs.push(adapterConf.instanceObjects[i]);
|
|
}
|
|
|
|
/* these are already created on adapter install
|
|
if (adapterConf.objects && adapterConf.objects.length > 0) {
|
|
for (var j = 0, l = adapterConf.objects.length; j < l; j++) {
|
|
objs.push(adapterConf.objects[j]);
|
|
}
|
|
}
|
|
*/
|
|
|
|
function setObjs() {
|
|
if (objs.length > 0) {
|
|
let obj = objs.pop();
|
|
obj.from = 'system.host.' + tools.getHostName() + '.cli';
|
|
obj.ts = new Date().getTime();
|
|
objects.setObject(obj._id, obj, function (err /*, res */) {
|
|
if (err) {
|
|
console.error('host.' + hostname + ' error: ' + err);
|
|
} else {
|
|
console.log('host.' + hostname + ' object ' + obj._id + ' created');
|
|
}
|
|
setTimeout(setObjs, 25);
|
|
});
|
|
} else {
|
|
instanceObj.from = 'system.host.' + tools.getHostName() + '.cli';
|
|
instanceObj.ts = new Date().getTime();
|
|
|
|
objects.setObject(instanceObj._id, instanceObj, function (err /* , res */) {
|
|
if (err) {
|
|
console.error('host.' + hostname + ' error: ' + err);
|
|
} else {
|
|
console.log('host.' + hostname + ' object ' + instanceObj._id + ' created');
|
|
}
|
|
|
|
if (callback) {
|
|
callback(adapter);
|
|
} else {
|
|
processExit(0);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
setObjs();
|
|
});
|
|
});
|
|
});
|
|
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Enumerate all instances of an adapter
|
|
* @type {(knownObjIDs: string[], adapter: string, instance: string) => Promise<void>}
|
|
*/
|
|
this.enumerateAdapterInstances = function enumerateInstances(knownObjIDs, adapter, instance) {
|
|
const startkey = instance ?
|
|
'system.adapter.' + adapter + '.' + instance :
|
|
'system.adapter.' + adapter;
|
|
const endkey = instance ?
|
|
'system.adapter.' + adapter + '.' + instance :
|
|
'system.adapter.' + adapter + '\u9999';
|
|
|
|
return getObjectViewAsync('system', 'instance', {startkey, endkey}, null).then(doc => {
|
|
if (doc.rows.length === 0) {
|
|
console.log('host.' + hostname + ' no instances of adapter ' + adapter + ' found');
|
|
} else {
|
|
// add non-duplicates to the list
|
|
const newObjs = doc.rows
|
|
.filter(row => row && row.value && row.value._id)
|
|
.map(row => row.value._id)
|
|
.filter(id => knownObjIDs.indexOf(id) === -1)
|
|
;
|
|
knownObjIDs.push.apply(knownObjIDs, newObjs);
|
|
if (newObjs.length > 0) {
|
|
console.log(`host.${hostname} Counted ${newObjs.length} instances of ${adapter}`);
|
|
}
|
|
}
|
|
}).catch(err => {
|
|
if (err !== 'Not exists') console.error('host.' + hostname + ' error: ' + err);
|
|
})
|
|
};
|
|
|
|
/**
|
|
* Enumerate all meta objects of an adapter
|
|
* @type {(knownObjIDs: string[], adapter: string) => Promise<void>}
|
|
*/
|
|
this.enumerateAdapterMeta = function enumerateMeta(knownObjIDs, adapter) {
|
|
return getObjectViewAsync('system', 'meta', {startkey: adapter + '.meta', endkey: adapter + '.meta\u9999'}).then(doc => {
|
|
if (doc.rows.length !== 0) {
|
|
// add non-duplicates to the list
|
|
const newObjs = doc.rows
|
|
.filter(row => row && row.value && row.value._id)
|
|
.map(row => row.value._id)
|
|
.filter(id => knownObjIDs.indexOf(id) === -1)
|
|
;
|
|
knownObjIDs.push.apply(knownObjIDs, newObjs);
|
|
if (newObjs.length > 0) {
|
|
console.log(`host.${hostname} Counted ${newObjs.length} meta of ${adapter}`);
|
|
}
|
|
}
|
|
}).catch(err => {
|
|
if (err !== 'Not exists') console.error('host.' + hostname + ' error: ' + err);
|
|
})
|
|
};
|
|
|
|
/**
|
|
* @type {(knownObjIDs: string[], adapter: string) => Promise<number>}
|
|
* @returns 22 if the adapter could not be deleted, 0 otherwise
|
|
*/
|
|
this.enumerateAdapters = tools.poorMansAsync(function*(knownObjIDs, adapter) {
|
|
let resultCode = 0;
|
|
try {
|
|
const doc = yield getObjectViewAsync('system', 'adapter', {startkey: 'system.adapter.' + adapter, endkey: 'system.adapter.' + adapter + '\u9999'})
|
|
if (doc.rows.length !== 0) {
|
|
// change nondeletable adapters
|
|
const nondeletable = doc.rows.filter(row => row.value.common.nondeletable);
|
|
if (nondeletable.length > 0) {
|
|
console.log('host.' + hostname + ' Adapter ' + adapter + ' cannot be deleted completely, because non-deletable.');
|
|
resultCode = 22;
|
|
for (const row of nondeletable) {
|
|
const adapterConf = row.value;
|
|
try {
|
|
let oldObj = yield getObjectAsync(adapterConf._id);
|
|
if (oldObj) {
|
|
oldObj = extend(true, oldObj, {installedVersion: ''});
|
|
} else {
|
|
oldObj = {installedVersion: ''};
|
|
}
|
|
oldObj.from = 'system.host.' + tools.getHostName() + '.cli';
|
|
oldObj.ts = new Date().getTime();
|
|
yield setObjectAsync(adapterConf._id, oldObj);
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
// remember deletable adapters
|
|
const deletable = doc.rows.filter(row => !row.value.common.nondeletable);
|
|
if (deletable.length > 0) {
|
|
// add non-duplicates to the list
|
|
const newObjs = deletable
|
|
.map(row => row.value._id)
|
|
.filter(id => knownObjIDs.indexOf(id) === -1)
|
|
;
|
|
knownObjIDs.push.apply(knownObjIDs, newObjs);
|
|
if (newObjs.length > 0) {
|
|
console.log(`host.${hostname} Counted ${newObjs.length} adapters for ${adapter}`);
|
|
}
|
|
}
|
|
}
|
|
} catch (e) {
|
|
if (e.message !== 'Not exists') console.error('host.' + hostname + ' error: ' + e);
|
|
}
|
|
return resultCode;
|
|
});
|
|
|
|
/**
|
|
* Enumerates the devices of an adapter (or instance)
|
|
* @param {string[]} knownObjIDs The already known object ids
|
|
* @param {string} adapter The adapter to enumerate the devices for
|
|
* @param {string} [instance] The instance to enumerate the devices for (optional)
|
|
*/
|
|
this.enumerateAdapterDevices = function enumerateAdapterDevices(knownObjIDs, adapter, instance) {
|
|
const adapterRegex = new RegExp(`^${adapter}${instance ? `\\.${instance}` : ''}`);
|
|
|
|
return getObjectViewAsync('system', 'device', {}, null).then(doc => {
|
|
if (doc.rows.length !== 0) {
|
|
// add non-duplicates to the list
|
|
const newObjs = doc.rows
|
|
.filter(row => row && row.value && row.value._id)
|
|
.map(row => row.value._id)
|
|
.filter(id => adapterRegex.test(id))
|
|
.filter(id => knownObjIDs.indexOf(id) === -1)
|
|
;
|
|
knownObjIDs.push.apply(knownObjIDs, newObjs);
|
|
if (newObjs.length > 0) {
|
|
console.log(`host.${hostname} Counted ${newObjs.length} devices of ${adapter}${instance ? `.${instance}` : ''}`);
|
|
}
|
|
}
|
|
}).catch(err => {
|
|
if (err !== 'Not exists') console.error('host.' + hostname + ' error: ' + err);
|
|
})
|
|
};
|
|
|
|
/**
|
|
* Enumerates the channels of an adapter (or instance)
|
|
* @param {string[]} knownObjIDs The already known object ids
|
|
* @param {string} adapter The adapter to enumerate the channels for
|
|
* @param {string} [instance] The instance to enumerate the channels for (optional)
|
|
*/
|
|
this.enumerateAdapterChannels = function enumerateAdapterChannels(knownObjIDs, adapter, instance) {
|
|
const adapterRegex = new RegExp(`^${adapter}${instance ? `\\.${instance}` : ''}`);
|
|
|
|
return getObjectViewAsync('system', 'channel', {}, null).then(doc => {
|
|
if (doc.rows.length !== 0) {
|
|
// add non-duplicates to the list
|
|
const newObjs = doc.rows
|
|
.filter(row => row && row.value && row.value._id)
|
|
.map(row => row.value._id)
|
|
.filter(id => adapterRegex.test(id))
|
|
.filter(id => knownObjIDs.indexOf(id) === -1)
|
|
;
|
|
knownObjIDs.push.apply(knownObjIDs, newObjs);
|
|
if (newObjs.length > 0) {
|
|
console.log(`host.${hostname} Counted ${newObjs.length} channels of ${adapter}${instance ? `.${instance}` : ''}`);
|
|
}
|
|
}
|
|
}).catch(err => {
|
|
if (err !== 'Not exists') console.error('host.' + hostname + ' error: ' + err);
|
|
})
|
|
};
|
|
|
|
/**
|
|
* Enumerates the states of an adapter (or instance)
|
|
* @param {string[]} knownObjIDs The already known object ids
|
|
* @param {string} adapter The adapter to enumerate the states for
|
|
* @param {string} [instance] The instance to enumerate the states for (optional)
|
|
*/
|
|
this.enumerateAdapterStateObjects = function enumerateAdapterStateObjects(knownObjIDs, adapter, instance) {
|
|
const adapterRegex = new RegExp(`^${adapter}${instance ? `\\.${instance}` : ''}`);
|
|
const sysAdapterRegex = new RegExp(`^system\\.adapter\\.${adapter}${instance ? `\\.${instance}` : ''}`);
|
|
|
|
return getObjectViewAsync('system', 'state', {}, null).then(doc => {
|
|
if (doc.rows.length !== 0) {
|
|
// add non-duplicates to the list
|
|
const newObjs = doc.rows
|
|
.filter(row => row && row.value && row.value._id)
|
|
.map(row => row.value._id)
|
|
.filter(id => adapterRegex.test(id) || sysAdapterRegex.test(id))
|
|
.filter(id => knownObjIDs.indexOf(id) === -1)
|
|
;
|
|
knownObjIDs.push.apply(knownObjIDs, newObjs);
|
|
if (newObjs.length > 0) {
|
|
console.log(`host.${hostname} Counted ${newObjs.length} states of ${adapter}${instance ? `.${instance}` : ''}`);
|
|
}
|
|
}
|
|
}).catch(err => {
|
|
if (err !== 'Not exists') console.error('host.' + hostname + ' error: ' + err);
|
|
})
|
|
};
|
|
|
|
// TODO: is enumerateAdapterDocs the correct name???
|
|
/**
|
|
* Enumerates the docs of an adapter (or instance)
|
|
* @param {string[]} knownObjIDs The already known object ids
|
|
* @param {string} adapter The adapter to enumerate the states for
|
|
* @param {string} [instance] The instance to enumerate the states for (optional)
|
|
*/
|
|
this.enumerateAdapterDocs = function enumerateAdapterDocs(knownObjIDs, adapter, instance) {
|
|
const adapterRegex = new RegExp(`^${adapter}${instance ? `\\.${instance}` : ''}`);
|
|
const sysAdapterRegex = new RegExp(`^system\\.adapter\\.${adapter}${instance ? `\\.${instance}` : ''}`);
|
|
|
|
return getObjectListAsync({include_docs: true}).then(doc => {
|
|
if (doc.rows.length !== 0) {
|
|
// add non-duplicates to the list
|
|
const newObjs = doc.rows
|
|
.filter(row => row && row.value && row.value._id)
|
|
.map(row => row.value._id)
|
|
.filter(id => adapterRegex.test(id) || sysAdapterRegex.test(id))
|
|
.filter(id => knownObjIDs.indexOf(id) === -1)
|
|
;
|
|
knownObjIDs.push.apply(knownObjIDs, newObjs);
|
|
if (newObjs.length > 0) {
|
|
console.log(`host.${hostname} Counted ${newObjs.length} objects of ${adapter}${instance ? `.${instance}` : ''}`);
|
|
}
|
|
}
|
|
}).catch(err => {
|
|
if (err !== 'Not exists') console.error('host.' + hostname + ' error: ' + err);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Enumerate all state IDs of an adapter (or instance)
|
|
* @type {(knownStateIDs: string[], adapter: string, instance?: string) => Promise<void>}
|
|
*/
|
|
this.enumerateAdapterStates = tools.poorMansAsync(function* (knownStateIDs, adapter, instance) {
|
|
for (const pattern of [
|
|
`io.${adapter}.${instance ? instance + '.' : ''}*`,
|
|
`messagebox.${adapter}.${instance ? instance + '.' : ''}*`,
|
|
`log.${adapter}.${instance ? instance + '.' : ''}*`,
|
|
`${adapter}.${instance ? instance + '.' : ''}*`,
|
|
`system.adapter.${adapter}.${instance ? instance + '.' : ''}*`
|
|
]) {
|
|
try {
|
|
const ids = yield getKeysAsync(pattern);
|
|
if (ids && ids.length) {
|
|
// add non-duplicates to the list
|
|
const newStates = ids
|
|
.filter(id => knownStateIDs.indexOf(id) === -1)
|
|
;
|
|
knownStateIDs.push.apply(knownStateIDs, newStates);
|
|
if (newStates.length > 0) {
|
|
console.log(`host.${hostname} Counted ${newStates.length} states (${pattern}) from states`);
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.error(e);
|
|
}
|
|
}
|
|
});
|
|
|
|
/**
|
|
* delete WWW pages and objects
|
|
* @type {(adapter: string) => Promise<void>}
|
|
*/
|
|
this.deleteWWW = tools.poorMansAsync(function* (adapter) {
|
|
for (const file of [
|
|
adapter, adapter + '.admin'
|
|
]) {
|
|
try {
|
|
yield unlinkAsync(file, '');
|
|
} catch (e) {
|
|
if (e.message !== 'Not exists') console.error(`Cannot delete ${file} files folder: ${e}`);
|
|
}
|
|
}
|
|
|
|
for (const objId of [
|
|
adapter, adapter + '.admin'
|
|
]) {
|
|
try {
|
|
const obj = yield delObjectAsync(objId);
|
|
if (obj) console.log(`host.${hostname} object ${objId} deleted`);
|
|
} catch (e) {
|
|
if (e.message !== 'Not exists') console.error('host.' + hostname + ' error: ' + e);
|
|
}
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @type {(stateIDs: string[]) => Promise<void>}
|
|
*/
|
|
this.deleteAdapterStates = tools.poorMansAsync(function*(stateIDs) {
|
|
if (stateIDs.length > 1000) {
|
|
console.log('host.' + hostname + ' Deleting ' + stateIDs.length + ' state(s). Be patient...');
|
|
} else if (stateIDs.length) {
|
|
console.log('host.' + hostname + ' Deleting ' + stateIDs.length + ' state(s).');
|
|
}
|
|
|
|
while (stateIDs.length > 0) {
|
|
if (stateIDs.length % 200 === 0) {
|
|
// write progress report
|
|
console.log(`host.${hostname}: Only ${stateIDs.length} states left to be deleted.`);
|
|
}
|
|
// try to delete the current state
|
|
try {
|
|
yield delStateAsync(stateIDs.pop());
|
|
} catch (e) { // yep that works!
|
|
if (e.message !== 'Not exists') console.error(e);
|
|
}
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @type {(objIDs: string[]) => Promise<void>}
|
|
*/
|
|
this.deleteAdapterObjects = tools.poorMansAsync(function*(objIDs) {
|
|
if (objIDs.length > 1000) {
|
|
console.log('host.' + hostname + ' Deleting ' + objIDs.length + ' object(s). Be patient...');
|
|
} else if (objIDs.length) {
|
|
console.log('host.' + hostname + ' Deleting ' + objIDs.length + ' object(s).');
|
|
}
|
|
|
|
while (objIDs.length > 0) {
|
|
if (objIDs.length % 200 === 0) {
|
|
// write progress report
|
|
console.log(`host.${hostname}: Only ${objIDs.length} objects left to be deleted.`);
|
|
}
|
|
// try to delete the current state
|
|
try {
|
|
yield delObjectAsync(objIDs.pop());
|
|
} catch (e) {
|
|
if (e.message !== 'Not exists') console.error('host.' + hostname + ' error: ' + e);
|
|
}
|
|
}
|
|
});
|
|
|
|
this.deleteAdapter = function (adapter, callback) {
|
|
const knownObjectIDs = [];
|
|
const knownStateIDs = [];
|
|
let resultCode = 0;
|
|
|
|
const uninstallNpm = tools.poorMansAsync(function*() {
|
|
try {
|
|
// find the adapter's io-package.json
|
|
const adapterNpm = `${tools.appName}.${adapter}`;
|
|
const ioPack = require(`${adapterNpm}/io-package.json`); // yep, it's that easy
|
|
if (!ioPack.common || !ioPack.common.nondeletable) {
|
|
yield that.npmUninstallAsync(adapterNpm, null, false);
|
|
}
|
|
} catch (e) {
|
|
console.error(`Error deleting adapter ${adapter} from disk: ${e}`);
|
|
console.error(`You might have to delete it yourself!`);
|
|
}
|
|
});
|
|
|
|
that.enumerateAdapterInstances(knownObjectIDs, adapter)
|
|
.then(() => that.enumerateAdapterMeta(knownObjectIDs, adapter))
|
|
.then(() => that.enumerateAdapters(knownObjectIDs, adapter).then(ret => resultCode = ret))
|
|
.then(() => that.enumerateAdapterDevices(knownObjectIDs, adapter))
|
|
.then(() => that.enumerateAdapterChannels(knownObjectIDs, adapter))
|
|
.then(() => that.enumerateAdapterStateObjects(knownObjectIDs, adapter))
|
|
.then(() => that.enumerateAdapterStates(knownStateIDs, adapter))
|
|
.then(() => that.deleteWWW(adapter))
|
|
.then(() => that.deleteAdapterObjects(knownObjectIDs))
|
|
.then(() => that.deleteAdapterStates(knownStateIDs))
|
|
.then(uninstallNpm)
|
|
.catch(err => console.error(`There was an error uninstalling ${adapter}: ${err}`))
|
|
.then(() => callback(adapter, resultCode))
|
|
;
|
|
|
|
};
|
|
|
|
this.deleteInstance = function (adapter, instance, callback) {
|
|
const knownObjectIDs = [];
|
|
const knownStateIDs = [];
|
|
|
|
that.enumerateAdapterInstances(knownObjectIDs, adapter, instance)
|
|
.then(() => that.enumerateAdapterDevices(knownObjectIDs, adapter, instance))
|
|
.then(() => that.enumerateAdapterChannels(knownObjectIDs, adapter, instance))
|
|
.then(() => that.enumerateAdapterStateObjects(knownObjectIDs, adapter, instance))
|
|
.then(() => that.enumerateAdapterStates(knownStateIDs, adapter, instance))
|
|
.then(() => that.enumerateAdapterDocs(knownObjectIDs, adapter, instance))
|
|
.then(() => that.deleteAdapterObjects(knownObjectIDs))
|
|
.then(() => that.deleteAdapterStates(knownStateIDs))
|
|
.then(() => callback(adapter, instance))
|
|
;
|
|
|
|
// TODO delete meta objects - i think a recursive deletion of all child object would be less effort.
|
|
};
|
|
}
|
|
|
|
module.exports = Install;
|