Files
yunkong2.js-controller/lib/tools.js
2018-09-17 20:32:19 +08:00

1204 lines
46 KiB
JavaScript

'use strict';
const fs = require('fs');
const path = require('path');
const semver = require('semver');
require('events').EventEmitter.prototype._maxListeners = 100;
let request;
let extend;
let password;
let npmVersion;
let crypto;
/**
* recursively copy values from old object to new one
*
* @alias copyAttributes
* @memberof tools
* @param {object} oldObj source object
* @param {object} newObj destination object
* @param {object} originalObj optional object for read __no_change__ values
* @param {boolean} isNonEdit optional indicator if copy is in nonEdit part
*
*/
function copyAttributes(oldObj, newObj, originalObj, isNonEdit) {
for (let attr in oldObj) {
if (oldObj.hasOwnProperty(attr)) {
if (typeof oldObj[attr] !== 'object' || oldObj[attr] instanceof Array) {
if (oldObj[attr] === '__no_change__' && originalObj && !isNonEdit) {
newObj[attr] = JSON.parse(JSON.stringify(originalObj[attr]));
} else
if (oldObj[attr] === '__delete__' && !isNonEdit) {
if (newObj[attr] !== undefined) {
delete newObj[attr];
}
} else {
newObj[attr] = oldObj[attr];
}
} else {
newObj[attr] = newObj[attr] || {};
copyAttributes(oldObj[attr], newObj[attr], originalObj && originalObj[attr], isNonEdit || attr === 'nonEdit');
}
}
}
}
/**
* Checks the flag nonEdit and restores non-changeable values if required
*
* @alias checkNonEditable
* @memberof tools
* @param {object} oldObject source object
* @param {object} newObject destination object
*
*/
function checkNonEditable(oldObject, newObject) {
if (!oldObject) return true;
if (!oldObject.nonEdit && !newObject.nonEdit) return true;
// if nonEdit is protected with password
if (oldObject.nonEdit && oldObject.nonEdit.passHash) {
// If new Object wants to update the nonEdit information
if (newObject.nonEdit && newObject.nonEdit.password) {
crypto = crypto || require('crypto');
const hash = crypto.createHash('sha256').update(newObject.nonEdit.password).digest('base64');
if (oldObject.nonEdit.passHash !== hash) {
delete newObject.nonEdit;
return false;
} else {
oldObject.nonEdit = JSON.parse(JSON.stringify(newObject.nonEdit));
delete oldObject.nonEdit.password;
delete newObject.nonEdit.password;
oldObject.nonEdit.passHash = hash;
newObject.nonEdit.passHash = hash;
}
copyAttributes(newObject.nonEdit, newObject, newObject);
return true;
} else if (newObject.nonEdit !== undefined) {
delete newObject.nonEdit;
}
} else if (newObject.nonEdit) {
oldObject.nonEdit = JSON.parse(JSON.stringify(newObject.nonEdit));
if (newObject.nonEdit.password) {
crypto = crypto || require('crypto');
const hash = crypto.createHash('sha256').update(newObject.nonEdit.password).digest('base64');
delete oldObject.nonEdit.password;
delete newObject.nonEdit.password;
oldObject.nonEdit.passHash = hash;
newObject.nonEdit.passHash = hash;
}
}
// restore settings
copyAttributes(oldObject.nonEdit, newObject, oldObject);
if (newObject.passHash) delete newObject.passHash;
if (newObject.nonEdit && newObject.nonEdit.password) delete newObject.nonEdit.password;
return true;
}
// Compare versions
function upToDate(online, installed) {
online = online.split('.');
installed = installed.split('.');
online[0] = parseInt(online[0], 10);
installed[0] = parseInt(installed[0], 10);
if (online[0] > installed[0]) {
return false;
} else if (online[0] === installed[0]) {
online[1] = parseInt(online[1], 10);
installed[1] = parseInt(installed[1], 10);
if (online[1] > installed[1]) {
return false;
} else if (online[1] === installed[1]) {
online[2] = parseInt(online[2], 10);
installed[2] = parseInt(installed[2], 10);
return installed[2] >= online[2];
} else {
return true;
}
} else {
return true;
}
}
function encryptPhrase(password, phrase, callback) {
// encrypt secret
crypto = crypto || require('crypto');
const cipher = crypto.createCipher('aes192', password);
let encrypted = '';
cipher.on('readable', function () {
const data = cipher.read();
if (data) {
encrypted += data.toString('hex');
}
});
cipher.on('end', function () {
callback(encrypted);
});
cipher.write(phrase);
cipher.end();
}
function decryptPhrase(password, data, callback) {
crypto = crypto || require('crypto');
const decipher = crypto.createDecipher('aes192', password);
try {
let decrypted = '';
decipher.on('readable', function () {
let data = decipher.read();
if (data) {
decrypted += data.toString('utf8');
}
});
decipher.on('error', function (error) {
console.error('Cannot decode secret: ' + error);
callback(null);
});
decipher.on('end', function () {
callback(decrypted);
});
decipher.write(data, 'hex');
decipher.end();
} catch (e) {
console.error('Cannot decode secret: ' + e);
callback(null);
}
}
function getAppName() {
const parts = __dirname.replace(/\\/g, '/').split('/');
return parts[parts.length - 2].split('.')[0];
}
function rmdirRecursiveSync(path) {
if (fs.existsSync(path)) {
fs.readdirSync(path).forEach(function (file/*, index*/) {
const curPath = path + '/' + file;
if (fs.statSync(curPath).isDirectory()) {
// recurse
rmdirRecursiveSync(curPath);
} else {
// delete file
fs.unlinkSync(curPath);
}
});
// delete (hopefully) empty folder
try {
fs.rmdirSync(path);
} catch (e) {
console.log('Cannot delete directory ' + path + ': ' + e.toString());
}
}
}
function findIPs() {
const ifaces = require('os').networkInterfaces();
let ipArr = [];
for (let dev in ifaces) {
if (!ifaces.hasOwnProperty(dev)) continue;
/*jshint loopfunc:true */
ifaces[dev].forEach(function (details) {
//noinspection JSUnresolvedVariable
if (!details.internal) ipArr.push(details.address);
});
}
return ipArr;
}
function findPath(path, url) {
if (!url) return '';
if (url.substring(0, 'http://'.length) === 'http://' ||
url.substring(0, 'https://'.length) === 'https://') {
return url;
} else {
if (path.substring(0, 'http://'.length) === 'http://' ||
path.substring(0, 'https://'.length) === 'https://') {
return (path + url).replace(/\/\//g, '/').replace('http:/', 'http://').replace('https:/', 'https://');
} else {
if (url && url[0] === '/') {
return __dirname + '/..' + url;
} else {
return __dirname + '/../' + path + url;
}
}
}
}
function getMac(callback) {
const macRegex = /(?:[a-z0-9]{2}[:\-]){5}[a-z0-9]{2}/ig;
const zeroRegex = /(?:[0]{2}[:\-]){5}[0]{2}/;
const command = (process.platform.indexOf('win') === 0) ? 'getmac' : 'ifconfig || ip link';
require('child_process').exec(command, function(err, stdout, stderr) {
if (err) {
callback(err);
} else {
let macAddress;
let match;
let result = null;
while (match = macRegex.exec(stdout)) {
macAddress = match[0];
if (!zeroRegex.test(macAddress) && !result) result = macAddress;
}
if (result === null) {
callback(new Error('could not determine the mac address from:\n' + stdout));
} else {
callback(null, result.replace(/-/g, ':').toLowerCase());
}
}
});
}
// Build unique uuid based on MAC address if possible
function uuid(givenMac, callback) {
if (typeof givenMac === 'function') {
callback = givenMac;
givenMac = '';
}
let mac = (givenMac !== null) ? (givenMac || '') : null;
let u;
if (mac === '') {
const ifaces = require('os').networkInterfaces();
// Find first not empty MAC
for (let n in ifaces) {
if (!ifaces.hasOwnProperty(n)) continue;
for (let c = 0; c < ifaces[n].length; c++) {
if (ifaces[n][c].mac && ifaces[n][c].mac !== '00:00:00:00:00:00') {
mac = ifaces[n][c].mac;
break;
}
}
if (mac) break;
}
}
if (mac === '') {
getMac(function(err, mac) {
uuid(mac || null, callback);
});
return;
}
if (mac) {
const md5sum = require('crypto').createHash('md5');
md5sum.update(mac);
mac = md5sum.digest('hex');
u = mac.substring(0, 8) + '-' + mac.substring(8, 12) + '-' + mac.substring(12, 16) + '-' + mac.substring(16, 20) + '-' + mac.substring(20);
} else {
// Returns a RFC4122 compliant v4 UUID https://gist.github.com/LeverOne/1308368 (DO WTF YOU WANT TO PUBLIC LICENSE)
let a;
let b;
b = a = '';
while (a++ < 36) {
b += ((a * 51) & 52) ? (a ^ 15 ? 8 ^ Math.random() * (a ^ 20 ? 16 : 4) : 4).toString(16) : '-';
}
u = b;
}
// todo => delete it after the vendor works 2018.03.28 BF
if (fs.existsSync('/opt/io-box/config/setup.cfg')) {
u = 'CM' + u;
}
callback(u);
}
function createUuid(_objects, callback) {
let tasks = 2;
let _uuid;
_objects.getObject('system.user.admin', function (err, obj) {
if (err || !obj) {
password = password || require(__dirname + '/password');
// Default Password for user 'admin' is application name in lower case
password(getAppName()).hash(null, null, function (err, res) {
if (err) console.error(err);
// Create user here and not in io-package.js because of hash password
let tasks = 0;
tasks++;
_objects.setObject('system.user.admin', {
type: 'user',
common: {
name: 'admin',
password: res,
dontDelete: true,
enabled: true
},
ts: new Date().getTime(),
from: 'system.host.' + getHostName() + '.tools',
native: {}
}, function () {
console.log('object system.user.admin created');
if (!--tasks && callback) callback(_uuid);
});
});
} else {
if (!--tasks && callback) callback(_uuid);
}
});
_objects.getObject('system.meta.uuid', function (err, obj) {
if (!err && obj && obj.native && obj.native.uuid) {
if (!--tasks && callback) callback();
} else {
uuid(function (res) {
_uuid = res;
_objects.setObject('system.meta.uuid', {
type: 'meta',
common: {
name: 'uuid',
type: 'uuid'
},
ts: new Date().getTime(),
from: 'system.host.' + getHostName() + '.tools',
native: {
uuid: res
}
}, function () {
console.log('object system.meta.uuid created: ' + res);
if (!--tasks && callback) callback(_uuid);
});
});
}
});
}
// Download file to tmp or return file name directly
function getFile(urlOrPath, fileName, callback) {
if (!request) request = require('request');
// If object was read
if (urlOrPath.substring(0, 'http://'.length) === 'http://' ||
urlOrPath.substring(0, 'https://'.length) === 'https://') {
const tmpFile = __dirname + '/../tmp/' + (fileName || Math.floor(Math.random() * 0xFFFFFFE) + '.zip');
request(urlOrPath).on('error', function (error) {
console.log('Cannot download "' + tmpFile + '": ' + error);
if (callback) callback(tmpFile);
}).pipe(fs.createWriteStream(tmpFile)).on('close', function () {
console.log('downloaded ' + tmpFile);
if (callback) callback(tmpFile);
});
} else {
if (fs.existsSync(urlOrPath)) {
if (callback) callback(urlOrPath);
} else
if (fs.existsSync(__dirname + '/../' + urlOrPath)) {
if (callback) callback(__dirname + '/../' + urlOrPath);
} else if (fs.existsSync(__dirname + '/../tmp/' + urlOrPath)) {
if (callback) callback(__dirname + '/../tmp/' + urlOrPath);
} else {
console.log('File not found: ' + urlOrPath);
process.exit(1);
}
}
}
// Return content of the json file. Download it or read directly
function getJson(urlOrPath, callback) {
if (!request) request = require('request');
let sources = {};
// If object was read
if (urlOrPath && typeof urlOrPath === 'object') {
if (callback) callback(urlOrPath);
} else
if (!urlOrPath) {
console.log('Empty url!');
if (callback) callback(null);
} else {
if (urlOrPath.substring(0, 'http://'.length) === 'http://' ||
urlOrPath.substring(0, 'https://'.length) === 'https://') {
request({url: urlOrPath, timeout: 10000}, function (error, response, body) {
if (error || !body || response.statusCode !== 200) {
console.log('Cannot download json from ' + urlOrPath + '. Error: ' + (error || body));
if (callback) callback(null, urlOrPath);
return;
}
try {
sources = JSON.parse(body);
} catch (e) {
console.log('Json file is invalid on ' + urlOrPath);
if (callback) callback(null, urlOrPath);
return;
}
if (callback) callback(sources, urlOrPath);
}).on('error', function (error) {
//console.log('Cannot download json from ' + urlOrPath + '. Error: ' + error);
//if (callback) callback(null, urlOrPath);
});
} else {
if (fs.existsSync(urlOrPath)) {
try {
sources = JSON.parse(fs.readFileSync(urlOrPath, 'utf8'));
} catch (e) {
console.log('Cannot parse json file from ' + urlOrPath + '. Error: ' + e);
if (callback) callback(null, urlOrPath);
return;
}
if (callback) callback(sources, urlOrPath);
} else
if (fs.existsSync(__dirname + '/../' + urlOrPath)) {
try {
sources = JSON.parse(fs.readFileSync(__dirname + '/../' + urlOrPath, 'utf8'));
}catch (e) {
console.log('Cannot parse json file from ' + __dirname + '/../' + urlOrPath + '. Error: ' + e);
if (callback) callback(null, urlOrPath);
return;
}
if (callback) callback(sources, urlOrPath);
} else if (fs.existsSync(__dirname + '/../tmp/' + urlOrPath)) {
try {
sources = JSON.parse(fs.readFileSync(__dirname + '/../tmp/' + urlOrPath, 'utf8'));
} catch (e) {
console.log('Cannot parse json file from ' + __dirname + '/../tmp/' + urlOrPath + '. Error: ' + e);
if (callback) callback(null, urlOrPath);
return;
}
if (callback) callback(sources, urlOrPath);
} else {
//if (urlOrPath.indexOf('/example/') === -1) console.log('Json file not found: ' + urlOrPath);
if (callback) callback(null, urlOrPath);
}
}
}
}
function scanDirectory(dirName, list, regExp) {
if (fs.existsSync(dirName)) {
const dirs = fs.readdirSync(dirName);
for (let i = 0; i < dirs.length; i++) {
try {
const fullPath = path.join(dirName, dirs[i]);
const fileIoName = path.join(fullPath, 'io-package.json');
const fileName = path.join(fullPath, 'package.json');
if (regExp.test(dirs[i]) && fs.existsSync(fileIoName)) {
const ioPackage = JSON.parse(fs.readFileSync(fileIoName, 'utf8'));
const package_ = fs.existsSync(fileName) ? JSON.parse(fs.readFileSync(fileName, 'utf8')) : {};
//noinspection JSUnresolvedVariable
list[ioPackage.common.name] = {
controller: ioPackage.common.controller || false,
version: ioPackage.common.version,
icon: ioPackage.common.extIcon || (ioPackage.common.icon ? '/adapter/' + dirs[i] + '/' + ioPackage.common.icon : ''),
title: ioPackage.common.title,
desc: ioPackage.common.desc,
platform: ioPackage.common.platform,
keywords: ioPackage.common.keywords,
readme: ioPackage.common.readme,
type: ioPackage.common.type,
license: ioPackage.common.license ? ioPackage.common.license : ((package_.licenses && package_.licenses.length) ? package_.licenses[0].type : ''),
licenseUrl: (package_.licenses && package_.licenses.length) ? package_.licenses[0].url : ''
};
}
} catch (e) {
console.log('Cannot read or parse ' + __dirname + '/../node_modules/' + dirs[i] + '/io-package.json: ' + e.toString());
}
}
}
}
// Get list of all installed adapters and controller version on this host
function getInstalledInfo(hostRunningVersion) {
let result = {};
const path = __dirname + '/../';
// Get info about host
const ioPackage = JSON.parse(fs.readFileSync(path + 'io-package.json', 'utf8'));
const package_ = fs.existsSync(path + 'package.json') ? JSON.parse(fs.readFileSync(path + 'package.json', 'utf8')) : {};
const regExp = new RegExp('^' + module.exports.appName + '\\.', 'i');
//noinspection JSUnresolvedVariable
result[ioPackage.common.name] = {
controller: true,
version: ioPackage.common.version,
icon: ioPackage.common.extIcon || ioPackage.common.icon,
title: ioPackage.common.title,
desc: ioPackage.common.desc,
platform: ioPackage.common.platform,
keywords: ioPackage.common.keywords,
readme: ioPackage.common.readme,
runningVersion: hostRunningVersion,
license: ioPackage.common.license ? ioPackage.common.license : ((package_.licenses && package_.licenses.length) ? package_.licenses[0].type : ''),
licenseUrl: (package_.licenses && package_.licenses.length) ? package_.licenses[0].url : ''
};
scanDirectory(__dirname + '/../node_modules', result, regExp);
scanDirectory(__dirname + '/../../node_modules', result, regExp);
if (fs.existsSync(__dirname + '/../../../node_modules/' + module.exports.appName.toLowerCase() + '.js-controller') ||
fs.existsSync(__dirname + '/../../../node_modules/' + module.exports.appName + '.js-controller')) {
scanDirectory(__dirname + '/../..', result, regExp);
}
return result;
}
/**
* Reads an adapter's npm version
* @param {string | null} adapter The adapter to read the npm version from. Null for the root yunkong2 packet
* @param {(err: Error | null, version: string) => void} [callback]
*/
function getNpmVersion(adapter, callback) {
adapter = adapter ? module.exports.appName + '.' + adapter : module.exports.appName;
adapter = adapter.toLowerCase();
const cliCommand = `npm view ${adapter}@latest version`;
const exec = require('child_process').exec;
exec(cliCommand, {timeout: 2000}, (error, stdout, stderr) => {
let version;
if (error) {
// command failed
if (typeof callback === 'function') {
callback(error);
return;
}
} else if (stdout) {
version = semver.valid(stdout.trim());
}
if (typeof callback === 'function') callback(null, version);
});
}
function getIoPack(sources, name, callback) {
getJson(sources[name].meta, function (ioPack) {
const packUrl = sources[name].meta.replace('io-package.json', 'package.json');
if (!ioPack) {
if (sources._helper) sources._helper.failCounter.push(name);
if (callback) callback(sources, name);
} else {
setImmediate(function () {
getJson(packUrl, function (pack) {
const version = sources[name].version;
const type = sources[name].type;
// If installed from git or something else
// js-controller is exception, because can be installed from npm and from git
if (sources[name].url && name !== 'js-controller') {
if (ioPack && ioPack.common) {
sources[name] = extend(true, sources[name], ioPack.common);
// overwrite type of adapter from repository
if (type) {
sources[name].type = type;
}
if (pack && pack.licenses && pack.licenses.length) {
if (!sources[name].license) sources[name].license = pack.licenses[0].type;
if (!sources[name].licenseUrl) sources[name].licenseUrl = pack.licenses[0].url;
}
}
if (callback) callback(sources, name);
} else {
if (ioPack && ioPack.common) {
sources[name] = extend(true, sources[name], ioPack.common);
if (pack && pack.licenses && pack.licenses.length) {
if (!sources[name].license) sources[name].license = pack.licenses[0].type;
if (!sources[name].licenseUrl) sources[name].licenseUrl = pack.licenses[0].url;
}
}
// overwrite type of adapter from repository
if (type) {
sources[name].type = type;
}
if (version) {
sources[name].version = version;
if (callback) callback(sources, name);
} else {
if (sources[name].meta.substring(0, 'http://'.length) === 'http://' ||
sources[name].meta.substring(0, 'https://'.length) === 'https://') {
//installed from npm
getNpmVersion(name, function (err, version) {
if (version) {
sources[name].version = version;
} else {
sources[name].version = 'npm error';
}
if (callback) callback(sources, name);
});
} else {
if (callback) callback(sources, name);
}
}
}
});
});
}
});
}
function _getRepositoryFile(sources, path, callback) {
if (!sources._helper) {
let count = 0;
for (let _name in sources) {
if (!sources.hasOwnProperty(_name)) continue;
count++;
}
sources._helper = {failCounter: []};
sources._helper.timeout = setTimeout(function () {
if (sources._helper) {
delete sources._helper;
for (let __name in sources) {
if (!sources.hasOwnProperty(__name)) continue;
if (sources[__name].processed !== undefined) delete sources[__name].processed;
}
if (callback) callback('Timeout by read all package.json (' + count + ') seconds', sources);
callback = null;
}
}, count * 1000);
}
for (let name in sources) {
if (!sources.hasOwnProperty(name)) continue;
if (sources[name].processed || name === '_helper') continue;
sources[name].processed = true;
if (sources[name].url) sources[name].url = findPath(path, sources[name].url);
if (sources[name].meta) sources[name].meta = findPath(path, sources[name].meta);
if (sources[name].icon) sources[name].icon = findPath(path, sources[name].icon);
if (!sources[name].name && sources[name].meta) {
getIoPack(sources, name, function (ignore/*, name*/) {
if (sources._helper) {
if (sources._helper.failCounter.length > 10) {
clearTimeout(sources._helper.timeout);
delete sources._helper;
for (let _name in sources) {
if (!sources.hasOwnProperty(_name)) continue;
if (sources[_name].processed !== undefined) delete sources[_name].processed;
}
if (callback) callback('Looks like there is no internet.', sources);
callback = null;
} else {
// process next
setImmediate(function () {
_getRepositoryFile(sources, path, callback);
});
}
}
});
return;
}
}
// all packages are processed
if (sources._helper) {
let err;
if (sources._helper.failCounter.length) {
err = 'Following packages cannot be read: ' + sources._helper.failCounter.join(', ');
}
clearTimeout(sources._helper.timeout);
delete sources._helper;
for (let __name in sources) {
if (!sources.hasOwnProperty(__name)) continue;
if (sources[__name].processed !== undefined) delete sources[__name].processed;
}
if (callback) callback(err, sources);
callback = null;
}
}
// Get list of all adapters and controller in some repository file or in /conf/source-dist.json
function getRepositoryFile(urlOrPath, additionalInfo, callback) {
let sources = {};
let path = '';
if (typeof additionalInfo === 'function') {
callback = additionalInfo;
additionalInfo = {};
}
if (!additionalInfo) additionalInfo = {};
if (!extend) extend = require('node.extend');
if (urlOrPath) {
let parts = urlOrPath.split('/');
path = parts.splice(0, parts.length - 1).join('/') + '/';
}
// If object was read
if (urlOrPath && typeof urlOrPath === 'object') {
if (typeof callback === 'function') callback(null, urlOrPath);
} else
if (!urlOrPath) {
try {
sources = JSON.parse(fs.readFileSync(getDefaultDataDir() + 'sources.json', 'utf8'));
} catch (e) {
sources = {};
}
try {
const sourcesDist = JSON.parse(fs.readFileSync(__dirname + '/../conf/sources-dist.json', 'utf8'));
sources = extend(true, sourcesDist, sources);
} catch (e) {
}
for (let s in sources) {
if (sources.hasOwnProperty(s) && additionalInfo[s] && additionalInfo[s].published) {
sources[s].published = additionalInfo[s].published
}
}
_getRepositoryFile(sources, path, function (err) {
if (err) console.error('[' + new Date() + '] ' + err);
if (typeof callback === 'function') callback(err, sources);
});
} else {
getJson(urlOrPath, function (sources) {
if (sources) {
for (let s in sources) {
if (sources.hasOwnProperty(s) && additionalInfo[s] && additionalInfo[s].published) {
sources[s].published = additionalInfo[s].published
}
}
setImmediate(function () {
_getRepositoryFile(sources, path, function (err) {
if (err) console.error('[' + new Date() + '] ' + err);
if (typeof callback === 'function') callback(err, sources);
});
});
} else {
if (typeof callback === 'function') callback('Cannot read "' + urlOrPath + '"', {});
}
});
}
}
function sendDiagInfo(obj, callback) {
if (!request) request = require('request');
console.log('Send diag info: ' + JSON.stringify(obj));
request.post({
url: 'http://download.' + module.exports.appName + '.net/diag.php',
method: 'POST',
headers: {'content-type': 'application/x-www-form-urlencoded'},
body: 'data=' + JSON.stringify(obj),
timeout: 2000
}, function (err, response, body) {
/*if (err || !body || response.statusCode !== 200) {
}*/
if (typeof callback === 'function') callback();
}).on('error', function (error) {
console.log('Cannot send diag info: ' + error.message);
if (typeof callback === 'function') callback(error);
});
}
function getAdapterDir(adapter) {
const appName = module.exports.appName;
if (adapter.substring(0, appName.length + 1) === appName + '.') {
adapter = adapter.substring(appName.length + 1);
}
let possibilities = [
appName.toLowerCase() + '.' + adapter + '/package.json',
appName + '.' + adapter + '/package.json'
];
/** @type {string} */
let controllerPath;
for (let i = 0; i < possibilities.length; i++) {
// special case to not read adapters from js-controller/node_module/adapter adn check first in parent directory
if (fs.existsSync(__dirname + '/../../' + possibilities[i])) {
controllerPath = __dirname + '/../../' + possibilities[i];
} else {
try {
controllerPath = require.resolve(possibilities[i]);
break;
} catch (e) { /* not found */ }
}
}
if (!controllerPath) {
return null; // inactive
} else {
let parts = path.normalize(controllerPath).split(/[\\\/]/g);
parts.pop();
return parts.join('/');
}
}
function getHostName() {
try {
const configName = getConfigFileName();
const config = JSON.parse(fs.readFileSync(configName, 'utf8'));
return config.system ? config.system.hostname || require('os').hostname() : require('os').hostname();
} catch (err) {
return require('os').hostname();
}
}
/**
* Read version of systen npm
*
* @alias getSystemNpmVersion
* @memberof Tools
* @param {function} callback return result
* <pre><code>
* function (err, version) {
* adapter.log.debug('NPM version is: ' + version);
* }
* </code></pre>
*/
function getSystemNpmVersion(callback) {
const exec = require('child_process').exec;
// remove local node_modules\.bin dir from path
// or we potentially get a wrong npm version
let newEnv = Object.assign({}, process.env);
newEnv.PATH = (newEnv.PATH || newEnv.Path || newEnv.path)
.split(path.delimiter)
.filter(dir => {
dir = dir.toLowerCase();
return !(dir.indexOf('yunkong2') > -1 && dir.indexOf(path.join('node_modules', '.bin')) > -1);
})
.join(path.delimiter);
exec('npm -v', {encoding: 'utf8', env: newEnv}, function (error, stdout) {//, stderr) {
if (stdout) stdout = semver.valid(stdout.trim());
if (callback) callback(error, stdout);
});
}
/**
* Collects information about host and available adapters
*
* Following info will be collected:
* - available adapters
* - node.js --version
* - npm --version
*
* @alias getHostInfo
* @memberof Tools
* @param {object} objects
* @param {function} callback return result
* <pre><code>
* function (err, result) {
* adapter.log.debug('Info about host: ' + JSON.stringify(result, null, 2);
* }
* </code></pre>
*/
function getHostInfo(objects, callback) {
const os = require('os');
const cpus = os.cpus();
let data = {
'Platform': os.platform(),
'Architecture': os.arch(),
'CPUs': cpus.length,
'Speed': cpus[0].speed,
'Model': cpus[0].model,
'RAM': os.totalmem(),
'System uptime': Math.round(os.uptime()),
'Node.js': process.version
};
let task = 0;
task++;
objects.getObject('system.config', function (err, systemConfig) {
objects.getObject('system.repositories', function (err, repos) {
// Check if repositories exists
if (!err && repos && repos.native && repos.native.repositories) {
const repo = repos.native.repositories[systemConfig.common.activeRepo];
if (repo && repo.json) {
let count = 0;
for (let a in repo.json) {
count++;
}
data['adapters count'] = count;
}
}
if (!--task) {
callback(err, data);
}
});
});
if (!npmVersion) {
task++;
getSystemNpmVersion(function (err, version) {
data['NPM'] = 'v' + version;
npmVersion = version;
if (!--task) {
callback(err, data);
}
});
} else {
data['NPM'] = npmVersion;
if (!task) {
callback(null, data);
}
}
}
// All pathes are returned always relative to /node_modules/' + module.exports.appName + '.js-controller
// the result has always "/" as last symbol
function getDefaultDataDir() {
//var dataDir = __dirname.replace(/\\/g, '/');
//dataDir = dataDir.split('/');
// if debugging with npm5
if (fs.existsSync(__dirname + '/../../node_modules/' + module.exports.appName + '.js-controller')) {
return '../' + module.exports.appName + '-data/';
} else // If installed with npm
if (fs.existsSync(__dirname + '/../../../node_modules/' + module.exports.appName + '.js-controller')) {
return '../../' + module.exports.appName + '-data/';
} else
{
//dataDir.splice(dataDir.length - 1, 1);
//dataDir = dataDir.join('/');
return './data/';
}
}
function getConfigFileName() {
/** @type {string|string[]} */
let configDir = __dirname.replace(/\\/g, '/');
configDir = configDir.split('/');
// If installed with npm
if (fs.existsSync(__dirname + '/../../../node_modules/' + module.exports.appName.toLowerCase() + '.js-controller') ||
fs.existsSync(__dirname + '/../../../node_modules/' + module.exports.appName + '.js-controller')) {
// remove /node_modules/' + module.exports.appName + '.js-controller/lib
configDir.splice(configDir.length - 3, 3);
configDir = configDir.join('/');
return configDir + '/' + module.exports.appName + '-data/' + module.exports.appName + '.json';
} else
// if debugging with npm5
if (fs.existsSync(__dirname + '/../../node_modules/' + module.exports.appName.toLowerCase() + '.js-controller') ||
fs.existsSync(__dirname + '/../../node_modules/' + module.exports.appName + '.js-controller')) {
// remove /node_modules/' + module.exports.appName + '.js-controller/lib
configDir.splice(configDir.length - 2, 2);
configDir = configDir.join('/');
return configDir + '/' + module.exports.appName + '-data/' + module.exports.appName + '.json';
} else {
// Remove /lib
configDir.splice(configDir.length - 1, 1);
configDir = configDir.join('/');
if (fs.existsSync(__dirname + '/../conf/' + module.exports.appName + '.json')) {
return configDir + '/conf/' + module.exports.appName + '.json';
} else {
return configDir + '/data/' + module.exports.appName + '.json';
}
}
}
/**
* Puts all values from an `arguments` object into an array, starting at the given index
* @param {IArguments} argsObj An `arguments` object as passed to a function
* @param {number} [startIndex=0] The optional index to start taking the arguments from
*/
function sliceArgs(argsObj, startIndex) {
if (startIndex == null) startIndex = 0;
const ret = [];
for (let i = startIndex; i < argsObj.length; i++) {
ret.push(argsObj[i]);
}
return ret;
}
/**
* Promisifies a function which returns an error as the first argument in its callback
* @param {Function} fn The function to promisify
* @param {any} [context=this] (optional) The context (value of `this` to bind the function to)
* @param {string[]} [returnArgNames] (optional) If the callback contains multiple arguments,
* you can combine them into one object by passing the names as an array.
* Otherwise the Promise will resolve with an array
* @returns {(...args: any[]) => Promise<any>}
*/
function promisify(fn, context, returnArgNames) {
return function () {
const args = sliceArgs(arguments);
context = context || this;
return new Promise(function (resolve, reject) {
fn.apply(context, args.concat([
function (error, result) {
if (error) {
return reject(error);
} else {
// decide on how we want to return the callback arguments
switch (arguments.length) {
case 1: // only an error was given
return resolve(); // Promise<void>
case 2: // a single value (result) was returned
return resolve(result);
default: // multiple values should be returned
/** @type {{} | any[]} */
let ret;
const extraArgs = sliceArgs(arguments, 1);
if (returnArgNames && returnArgNames.length === extraArgs.length) {
// we can build an object
ret = {};
for (let i = 0; i < returnArgNames.length; i++) {
ret[returnArgNames[i]] = extraArgs[i];
}
} else {
// we return the raw array
ret = extraArgs;
}
return resolve(ret);
}
}
}
]));
});
};
}
/**
* Promisifies a function which does not provide an error as the first argument in its callback
* @param {Function} fn The function to promisify
* @param {any} context (optional) The context (value of `this` to bind the function to)
* @param {string[]} returnArgNames (optional) If the callback contains multiple arguments,
* you can combine them into one object by passing the names as an array.
* Otherwise the Promise will resolve with an array
* @returns {(...args: any[]) => Promise<any>}
*/
function promisifyNoError(fn, context, returnArgNames) {
return function () {
const args = sliceArgs(arguments);
context = context || this;
return new Promise(function (resolve, reject) {
fn.apply(context, args.concat([
function (result) {
// decide on how we want to return the callback arguments
switch (arguments.length) {
case 0: // no arguments were given
return resolve(); // Promise<void>
case 1: // a single value (result) was returned
return resolve(result);
default: // multiple values should be returned
/** @type {{} | any[]} */
let ret;
const extraArgs = sliceArgs(arguments, 0);
if (returnArgNames && returnArgNames.length === extraArgs.length) {
// we can build an object
ret = {};
for (let i = 0; i < returnArgNames.length; i++) {
ret[returnArgNames[i]] = extraArgs[i];
}
} else {
// we return the raw array
ret = extraArgs;
}
return resolve(ret);
}
}
]));
});
};
}
/**
* Creates and executes an array of promises in sequence
* @param {((...args: any[]) => Promise<any>)[]} promiseFactories An array of promise-returning functions
*/
function promiseSequence(promiseFactories) {
return promiseFactories.reduce((promise, factory) => {
return promise.then(result => factory().then(Array.prototype.concat.bind(result)))
}, Promise.resolve([]));
}
/**
* Poor man's async/await using generator functions. Turns a generator function into a promise returning function.
* yield equals await.
* @template TReturn
* @param {(...args: any[]) => IterableIterator<TReturn>} makeGenerator A generator function to sequentially execute.
* @returns {(...args: any[]) => Promise<TReturn>}
*/
function poorMansAsync(makeGenerator) {
return function () {
let generator = makeGenerator.apply(this, arguments);
function handle(result) {
// result => { done: [Boolean], value: [Object] }
if (result.done) return Promise.resolve(result.value);
return Promise.resolve(result.value).then(function (res) {
return handle(generator.next(res));
}, function (err) {
if (typeof err === 'string') err = new Error(err);
return handle(generator.throw(err));
});
}
try {
return handle(generator.next());
} catch (ex) {
return Promise.reject(ex);
}
}
}
// // Example usage:
// function *test(a, b, c) {
// yield somethingAsyncThatReturnsAPromise(a);
// // write progress report
// yield somethingElseThatReturnsAPromise(b);
// // white progress report
// yield c;
// }
// var testAsync = gen2Async(test);
// testAsync(1,2,3).then(() => /* we're done */ );
let packageLockDisabled = false;
/**
* Ensures that package-lock.json gets ignored. Fixes installation issues on npm5
* @param {(err?) => void} callback The callback to invoke after the command has finished
*/
function disablePackageLock(callback) {
if (packageLockDisabled) return callback();
const npmrcPath = path.join(__dirname, '../../..', '.npmrc');
getSystemNpmVersion(function (err, version) {
packageLockDisabled = true;
if (version && semver.gte(version, '5.0.0')) {
// we need to disable the package lock
if (!fs.existsSync(npmrcPath)) {
// create the file
fs.writeFile(npmrcPath, 'package-lock=false\n', {encoding: 'utf8'}, callback);
return;
}
}
callback();
});
}
module.exports = {
appName: getAppName(),
createUuid,
decryptPhrase,
disablePackageLock,
encryptPhrase,
findIPs,
poorMansAsync,
getAdapterDir,
getConfigFileName,
getDefaultDataDir,
getFile,
getHostInfo,
getHostName,
getInstalledInfo,
getIoPack,
getJson,
getRepositoryFile,
getSystemNpmVersion,
promisify,
promisifyNoError,
promiseSequence,
rmdirRecursiveSync,
sendDiagInfo,
upToDate,
checkNonEditable,
copyAttributes
};