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

544 lines
23 KiB
JavaScript

'use strict';
function Upload(options) {
const fs = require('fs');
const tools = require(__dirname + '/../tools.js');
options = options || {};
if (!options.states) throw 'Invalid arguments: states is missing';
if (!options.objects) throw 'Invalid arguments: objects is missing';
let states = options.states;
let objects = options.objects;
let mime;
// get all instances of one adapter
function getInstances(adapter, callback) {
objects.getObjectList({startkey: 'system.adapter.' + adapter + '.', endkey: 'system.adapter.' + adapter + '.\u9999'}, (err, arr) => {
let instances = [];
if (!err && arr && arr.rows) {
for (let i = 0; i < arr.rows.length; i++) {
if (arr.rows[i].value.type !== 'instance') continue;
instances.push(arr.rows[i].value._id);
}
}
callback(instances);
});
}
// get all instances of all adapters in the list
function getAllInstances(adapters, callback) {
let instances = [];
let count = 0;
for (let k = 0; k < adapters.length; k++) {
if (!adapters[k]) continue;
if (adapters[k].indexOf('.') === -1) count++;
}
for (let i = 0; i < adapters.length; i++) {
if (!adapters[i]) continue;
if (adapters[i].indexOf('.') === -1) {
getInstances(adapters[i], inst => {
for (let j = 0; j < inst.length; j++) {
if (instances.indexOf(inst[j]) === -1) {
instances.push(inst[j]);
}
}
if (!--count && callback) {
callback(instances);
callback = null;
}
});
} else {
if (instances.indexOf(adapters[i]) === -1) {
instances.push(adapters[i]);
}
}
}
if (!count && callback) {
callback(instances);
callback = null;
}
}
// Check if some adapters must be restarted and restart them
function checkRestartOther(adapter, callback) {
let adapterDir = tools.getAdapterDir(adapter);
try {
let adapterConf = JSON.parse(fs.readFileSync(adapterDir + '/io-package.json').toString());
if (adapterConf.common.restartAdapters) {
if (typeof adapterConf.common.restartAdapters !== 'object') adapterConf.common.restartAdapters = [adapterConf.common.restartAdapters];
if (adapterConf.common.restartAdapters.length && adapterConf.common.restartAdapters[0]) {
getAllInstances(adapterConf.common.restartAdapters, instances => {
if (!instances || !instances.length) {
if (callback) {
callback();
callback = null;
}
} else {
let instancesCount = instances.length;
for (let r = 0; r < instances.length; r++) {
objects.getObject(instances[r], (err, obj) => {
// if instance is enabled
if (!err && obj && obj.common.enabled) {
obj.common.enabled = false; // disable instance
obj.from = 'system.host.' + tools.getHostName() + '.cli';
obj.ts = Date.now();
objects.setObject(obj._id, obj, err => {
if (!err) {
obj.common.enabled = true; // enable instance
obj.from = 'system.host.' + tools.getHostName() + '.cli';
obj.ts = Date.now();
objects.setObject(obj._id, obj, err => {
console.log('Adapter "' + obj._id + '" restarted.');
if (!--instancesCount && callback) {
callback();
callback = null;
}
});
} else {
if (err) {
console.error('Cannot restart adapter "' + obj._id + '": ' + err);
} else {
console.warn('Adapter "' + obj._id + '" is disabled and cannot be restarted.');
}
if (!--instancesCount && callback) {
callback();
callback = null;
}
}
});
} else if (!--instancesCount && callback) {
callback();
callback = null;
}
});
}
}
});
} else if (callback) {
callback();
callback = null;
}
} else if (callback) {
callback();
callback = null;
}
} catch (e) {
console.error('Cannot parse ' + adapterDir + '/io-package.json:' + e);
if (callback) {
callback();
callback = null;
}
}
}
this.uploadAdapterFull = (adapters, callback) => {
if (!adapters || !adapters.length) {
if (callback) callback();
return;
}
const adapter = adapters.pop();
this.uploadAdapter(adapter, true, true, () => {
this.upgradeAdapterObjects(adapter, () => {
this.uploadAdapter(adapter, false, true, () => {
setImmediate(() => this.uploadAdapterFull(adapters, callback));
});
});
});
};
this.uploadFile = (source, target, callback) => {
const request = require('request');
target = target.replace(/\\/g, '/');
source = source.replace(/\\/g, '/');
if (target[0] === '/') target = target.substring(1);
if (target[target.length - 1] === '/') {
let name = source.split('/').pop();
name = name.split('?')[0];
if (name.indexOf('.') === -1) name = 'index.html';
target += name;
}
let parts = target.split('/');
const adapter = parts[0];
parts.splice(0, 1);
target = parts.join('/');
if (source.match(/^http:\/\/|^https:\/\//)) {
request(source, (error, response, body) => {
if (!error && response.statusCode === 200) {
objects.writeFile(adapter, target, body, err => {
if (err) console.error(err);
if (typeof callback === 'function') callback(err, adapter + '/' + target);
});
} else {
console.error('Cannot get URL: ' + error || response.statusCode);
if (typeof callback === 'function') callback(error || response.statusCode, adapter + '/' + target);
}
});
} else {
try {
objects.writeFile(adapter, target, fs.readFileSync(source), err => {
if (err) console.error(err);
if (typeof callback === 'function') callback(err, adapter + '/' + target);
});
} catch (err) {
console.error('Cannot read file "' + source + '": ' + err);
if (typeof callback === 'function') callback(err, adapter + '/' + target);
}
}
};
// Upload www folder of adapter into ObjectsDB
this.uploadAdapter = (adapter, isAdmin, forceUpload, subTree, callback) => {
const id = adapter + (isAdmin ? '.admin' : '');
const adapterDir = tools.getAdapterDir(adapter);
let dir = adapterDir + (isAdmin ? '/admin' : '/www');
let files = [];
let rev;
if (typeof subTree === 'function') {
callback = subTree;
subTree = null;
}
if (subTree) {
dir += '/' + subTree;
}
if (!isAdmin) {
let cfg;
// check for common.wwwDontUpload (required for legacy adapter)
if (fs.existsSync(adapterDir + '/io-package.json')) {
cfg = require(adapterDir + '/io-package.json');
}
if (cfg && cfg.common && cfg.common.wwwDontUpload) {
if (typeof callback === 'function') callback(adapter);
return;
}
}
// do not upload www dir of admin adapter
if (adapter === 'admin' && !isAdmin) {
// To DO remove after a while
console.log('This should never happens!');
if (typeof callback === 'function') callback(adapter);
return;
}
// Create "upload progress" object if not exists
if (!isAdmin) {
objects.getObject('system.adapter.' + adapter + '.upload', (err, obj) => {
if (err || !obj) {
objects.setObject('system.adapter.' + adapter + '.upload',
{
_id: 'system.adapter.' + adapter + '.upload',
type: 'state',
common: {
name: adapter + '.upload',
type: 'number',
role: 'indicator.state',
unit: '%',
def: 0,
desc: 'Upload process indicator'
},
from: 'system.host.' + tools.getHostName() + '.cli',
ts: Date.now(),
native: {}
}
);
}
});
// Set indicator to 0
states.setState('system.adapter.' + adapter + '.upload', 0, true);
}
if (!mime) mime = require('mime');
function done(err, res) {
if (err) {
callback();
} else {
console.log('got ' + dir);
files = res;
setTimeout(_adapter => {
maxFiles = files.length || 1;
upload(_adapter);
}, 25, adapter);
}
}
let maxFiles = 0;
let lastProgressUpdate = (new Date()).getTime();
function upload(adapter) {
let file;
if (!files.length) {
if (!isAdmin) {
states.setState('system.adapter.' + adapter + '.upload', {val: 0, ack: true}, () => {
if (typeof callback === 'function') callback(adapter);
});
} else {
if (typeof callback === 'function') callback(adapter);
}
} else {
file = files.pop();
if (file === '.gitignore') {
upload(adapter);
return;
}
const mimeType = mime.lookup(file);
let attName;
attName = file.split('/' + tools.appName + '.');
if (attName.length === 1) {
// try to find anyway if adapter is not lower case
const pos = file.toLowerCase().indexOf(tools.appName.toLowerCase());
if (pos !== -1) {
attName = ['', file.substring(tools.appName.length + 2)];
}
}
attName = attName.pop();
attName = attName.split('/').slice(2).join('/');
if (files.length > 100) {
if (!(files.length % 50)) {
console.log('upload [' + files.length + ']', id, file, attName, mimeType);
}
} else if (files.length > 20) {
if (!(files.length % 10)) {
console.log('upload [' + files.length + ']', id, file, attName, mimeType);
}
} else {
console.log('upload [' + files.length + ']', id, file, attName, mimeType);
}
// Update upload indicator
if (!isAdmin) {
const now = (new Date()).getTime();
if (now - lastProgressUpdate > 1000) {
lastProgressUpdate = now;
states.setState('system.adapter.' + adapter + '.upload', {val: Math.round(1000 * (maxFiles - files.length) / maxFiles) / 10, ack: true});
}
}
fs.createReadStream(file).pipe(
objects.insert(id, attName, null, mimeType, {
rev: rev
}, (err, res) => {
if (err) {
console.log(err);
if (typeof callback === 'function') callback(adapter);
}
if (res) rev = res.rev;
setTimeout(upload, 50, adapter);
})
);
}
}
function walk(dir, done) {
let results = [];
fs.readdir(dir, (err, list) => {
if (err) return done(err);
let i = 0;
(function next() {
let file = list[i++];
if (!file) return done(null, results);
file = dir + '/' + file;
fs.stat(file, (err, stat) => {
if (stat && stat.isDirectory()) {
walk(file, (err, res) => {
results = results.concat(res);
next();
});
} else {
if (!file.match(/\.npmignore$/) && !file.match(/\.gitignore$/)) results.push(file);
next();
}
});
})();
});
}
objects.getObject(id, (err, res) => {
if (err || !res) {
objects.setObject(id, {
type: 'meta',
common: {
name: id.split('.').pop(),
type: isAdmin ? 'admin' : 'www'
},
from: 'system.host.' + tools.getHostName() + '.cli',
ts: Date.now(),
native: {}
}, (err, res) => {
if (res) rev = res.rev;
if (!isAdmin) {
checkRestartOther(adapter, () => walk(dir, done));
} else {
walk(dir, done);
}
});
} else {
if (!forceUpload) {
if (typeof callback === 'function') callback(adapter);
} else {
rev = res.rev;
if (!isAdmin) {
checkRestartOther(adapter, () => walk(dir, done));
} else {
walk(dir, done);
}
}
}
});
};
function extendNative(target, additional) {
for (let attr in additional) {
if (additional.hasOwnProperty(attr)) {
if (target[attr] === undefined) {
target[attr] = additional[attr];
} else if (typeof additional[attr] === 'object' && !(additional[attr] instanceof Array)) {
try {
target[attr] = target[attr] || {};
} catch (e) {
console.warn(`Cannot update attribute ${attr} of native`);
}
extendNative(target[attr], additional[attr]);
}
}
}
return target;
}
function extendCommon(target, additional) {
for (let attr in additional) {
if (additional.hasOwnProperty(attr)) {
if (attr === 'title' || attr === 'schedule' || attr === 'mode' || attr === 'loglevel' || attr === 'enabled') {
if (target[attr] === undefined) {
target[attr] = additional[attr];
}
} else if (typeof additional[attr] !== 'object' || (additional[attr] instanceof Array)) {
try {
target[attr] = additional[attr];
} catch (e) {
console.warn(`Cannot update attribute ${attr} of common`);
}
} else {
target[attr] = target[attr] || {};
if (typeof target[attr] !== 'object') {
target[attr] = {}; // here we clean the simple value with object
}
extendCommon(target[attr], additional[attr]);
}
}
}
return target;
}
this.upgradeAdapterObjects = (name, iopack, callback) => {
if (typeof iopack === 'function') {
callback = iopack;
iopack = null;
}
if (!iopack) {
const adapterDir = tools.getAdapterDir(name);
try {
iopack = JSON.parse(fs.readFileSync(adapterDir + '/io-package.json', 'utf8'));
} catch (e) {
console.error('Cannot find io-package.json in ' + adapterDir);
iopack = null;
}
}
if (!iopack) {
callback(name);
} else {
objects.getObject('system.adapter.' + name, (err, obj) => {
if (err || !obj) {
console.error('system.adapter.' + name + ' does not exist');
callback(name);
} else {
obj.common = iopack.common || {};
obj.native = iopack.native || {};
obj.common.installedVersion = iopack.common.version;
const hostname = tools.getHostName();
obj.from = 'system.host.' + tools.getHostName() + '.cli';
obj.ts = Date.now();
objects.setObject('system.adapter.' + name, obj, () => {
// Update all instances of this host
objects.getObjectView('system', 'instance', {startkey: 'system.adapter.' + name + '.', endkey: 'system.adapter.' + name + '.\u9999'}, null, function (err, res) {
let cntr = 0;
if (res) {
for (let i = 0; i < res.rows.length; i++) {
if (res.rows[i].value.common.host === hostname) {
cntr++;
objects.getObject(res.rows[i].id, (err, _obj) => {
let newObject = JSON.parse(JSON.stringify(_obj));
// all common settings should be taken from new one
newObject.common = extendCommon(newObject.common, iopack.common);
newObject.native = extendNative(newObject.native, iopack.native);
newObject.common.installedVersion = iopack.common.version;
newObject.common.version = iopack.common.version;
// Compare objects to reduce restarts of instances
if (JSON.stringify(newObject) !== JSON.stringify(_obj)) {
console.log('Update "' + newObject._id + '"');
newObject.from = 'system.host.' + tools.getHostName() + '.cli';
newObject.ts = Date.now();
objects.setObject(newObject._id, newObject, () => {
if (!--cntr && callback) callback(name);
});
} else {
if (!--cntr && callback) callback(name);
}
});
}
}
}
// updates "_design/system" and co
if (iopack.objects && typeof iopack.objects === 'object') {
for (let _id in iopack.objects) {
if (!iopack.objects.hasOwnProperty(_id)) continue;
cntr++;
iopack.objects[_id].from = 'system.host.' + tools.getHostName() + '.cli';
iopack.objects[_id].ts = Date.now();
objects.setObject(iopack.objects[_id]._id, iopack.objects[_id], err => {
if (err) console.error('Cannot update object: ' + err);
if (!--cntr && callback) callback(name);
});
}
}
if (!cntr && callback) callback(name);
});
});
}
});
}
};
}
module.exports = Upload;