You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

371 lines
14 KiB

/**
*
* yunkong2 vis Adapter
*
* (c) 2014-2017 bluefox, hobbyquaker
*
* CC-NC-BY 4.0 License
*
*/
/* jshint -W097 */
/* jshint strict:false */
/* jslint node: true */
'use strict';
var adapterName = require(__dirname + '/package.json').name.split('.').pop();
var isBeta = adapterName.indexOf('beta') !== -1;
var utils = require(__dirname + '/lib/utils'); // Get common adapter utils
var adapter = new utils.Adapter(adapterName);
var fs = require('fs');
var path = require('path');
var syncWidgetSets = require(__dirname + '/lib/install.js');
var https = require('https');
var jwt = require('jsonwebtoken');
//var minify = require('html-minifier').minify;
adapter.on('ready', function () {
main();
});
function writeFile(fileName, callback) {
var config = require(__dirname + '/www/js/config.js').config;
var index;
var srcFileNameParts = fileName.split('.');
var ext = srcFileNameParts.pop();
var srcFileName = srcFileNameParts.join('.') + '.src.' + ext;
if (fs.existsSync(__dirname + '/www/' + srcFileName)) {
index = fs.readFileSync(__dirname + '/www/' + srcFileName).toString();
} else {
index = fs.readFileSync(__dirname + '/www/' + fileName).toString();
fs.writeFileSync(__dirname + '/www/' + srcFileName, index);
}
// enable cache
index = index.replace('<!--html manifest="cache.manifest" xmlns="http://www.w3.org/1999/html"--><html>',
'<html manifest="cache.manifest" xmlns="http://www.w3.org/1999/html">');
var begin = '<!-- --------------------------------------- DO NOT EDIT INSIDE THIS LINE - BEGIN ------------------------------------------- -->';
var end = '<!-- --------------------------------------- DO NOT EDIT INSIDE THIS LINE - END ------------------------------------------- -->';
var bigInsert = '';
for (var w in config.widgetSets) {
if (!config.widgetSets.hasOwnProperty(w)) continue;
var file;
var name;
if (typeof config.widgetSets[w] === 'object') {
name = config.widgetSets[w].name + '.html';
} else {
name = config.widgetSets[w] + '.html';
}
file = fs.readFileSync(__dirname + '/www/widgets/' + name);
// extract all css and js
bigInsert += '<!-- --------------' + name + '--- START -->\n' + file.toString() + '\n<!-- --------------' + name + '--- END -->\n';
}
var pos = index.indexOf(begin);
if (pos !== -1) {
var start = index.substring(0, pos + begin.length);
pos = index.indexOf(end);
if (pos !== -1) {
var _end = index.substring(pos);
index = start + '\n' + bigInsert + '\n' + _end;
/*index = minify(index, {
removeAttributeQuotes: true,
removeComments: true,
collapseInlineTagWhitespace: true,
collapseWhitespace: true,
decodeEntities: true,
minifyCSS: true,
minifyJS: true,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeStyleLinkTypeAttributes: true
});*/
adapter.readFile(adapterName, fileName, function (err, data) {
if (data && data !== index) {
fs.writeFileSync(__dirname + '/www/' + fileName, index);
adapter.writeFile(adapterName, fileName, index, function () {
if (callback) callback(true);
});
} else {
if (callback) callback(false);
}
});
} else if (callback) {
callback(false);
}
} else if (callback) {
callback(false);
}
}
function upload(callback) {
adapter.log.info('Upload ' + adapter.name + ' anew, while changes detected...');
var file = utils.controllerDir + '/yunkong2.js';
var child = require('child_process').spawn('node', [file, 'upload', adapter.name, 'widgets']);
var count = 0;
child.stdout.on('data', function (data) {
count++;
adapter.log.debug(data.toString().replace('\n', ''));
if ((count % 100) === 0) adapter.log.info(count + ' files uploaded...');
});
child.stderr.on('data', function (data) {
adapter.log.error(data.toString().replace('\n', ''));
});
child.on('exit', function (exitCode) {
adapter.log.info('Uploaded. ' + (exitCode ? 'Exit - ' + exitCode : 0));
callback(exitCode);
});
}
function updateCacheManifest(callback) {
adapter.log.info('Changes in index.html detected => update cache.manifest');
var data = fs.readFileSync(__dirname + '/www/cache.manifest').toString();
var build = data.match(/# dev build ([0-9]+)/);
data = data.replace(/# dev build [0-9]+/, '# dev build ' + (parseInt(build[1] || 0, 10) + 1));
fs.writeFileSync(__dirname + '/www/cache.manifest', data);
adapter.writeFile(adapterName, 'cache.manifest', data, function () {
callback && callback();
});
}
// Update index.html
function checkFiles(configChanged, isBeta) {
if (isBeta) {
adapter.stop();
return;
}
writeFile('index.html', function (indexChanged) {
// Update edit.html
writeFile('edit.html', function (editChanged) {
if (indexChanged || editChanged || configChanged) {
updateCacheManifest(function () {
upload(function () {
adapter.stop();
});
});
} else {
adapter.stop();
}
});
});
}
function copyFiles(root, filesOrDirs, callback) {
if (!filesOrDirs) {
adapter.readDir('vis.0', root, function (err, filesOrDirs) {
copyFiles(root, filesOrDirs || [], callback);
});
return;
}
if (!filesOrDirs.length) {
if (typeof callback === 'function') callback();
return;
}
var task = filesOrDirs.shift();
if (task.isDir) {
copyFiles(root + task.file + '/', null, function () {
setTimeout(copyFiles, 0, root, filesOrDirs, callback);
})
} else {
adapter.readFile('vis.0', root + task.file, function (err, data) {
if (data || data === 0 || data === '') {
adapter.writeFile(adapterName + '.0', root + task.file, data, function () {
setTimeout(copyFiles, 0, root, filesOrDirs, callback);
});
} else {
setTimeout(copyFiles, 0, root, filesOrDirs, callback);
}
});
}
}
function generatePages(isLicenseError) {
var count = 0;
var changed = false;
if (!isBeta) {
changed = syncWidgetSets(false, isLicenseError);
if (changed) {
// upload config.js
count++;
var config = changed;
adapter.readFile(adapterName, 'js/config.js', function (err, data) {
if (data && data !== config) {
adapter.log.info('config.js changed. Upload.');
adapter.writeFile(adapterName, 'js/config.js', config, function () {
if (!--count) checkFiles(changed, isBeta);
});
} else {
if (!--count) checkFiles(changed, isBeta);
}
});
changed = true;
}
} else {
count++;
// try to read vis-beta.0/files
adapter.readDir(adapterName + '.0', '/', function (err, dirs) {
if (!dirs || !dirs.length) {
// copy all directories
copyFiles('/', null, function () {
if (!--count) checkFiles(changed, isBeta);
})
} else {
if (!--count) checkFiles(changed, isBeta);
}
});
}
// create command variable
count++;
adapter.getObject('control.command', function (err, obj) {
if (!obj) {
adapter.setObject('control.command',
{
"type": "state",
"common": {
"name": "Command for vis",
"type": "string",
"desc": "Writing this variable akt as the trigger. Instance and data must be preset before 'command' will be written. 'changedView' will be signalled too",
"states": {
"alert": "alert",
"changeView": "changeView",
"refresh": "refresh",
"reload": "reload",
"dialog": "dialog",
"popup": "popup",
"playSound": "playSound",
"changedView": "changedView",
"tts": "tts"
}
},
"native": {}
},
function () {
if (!--count) checkFiles(changed, isBeta);
}) ;
} else {
if (!--count) checkFiles(changed, isBeta);
}
});
// Create common user CSS file
count++;
adapter.readFile(adapterName, 'css/vis-common-user.css', function (err, data) {
if (err || data === null || data === undefined) {
adapter.writeFile(adapterName, 'css/vis-common-user.css', '', function () {
if (!--count) checkFiles(changed, isBeta);
});
} else {
if (!--count) checkFiles(changed, isBeta);
}
});
}
function indicateError(callback) {
var data = fs.readFileSync(__dirname + '/www/js/config.js').toString();
if (data.indexOf('license: false,') === -1) {
data = data.replace('var visConfig = {', 'var visConfig = {license: false,');
fs.writeFileSync(__dirname + '/www/js/config.js', data);
adapter.writeFile(adapterName, 'js/config.js', data, function () {
updateCacheManifest(callback);
});
} else {
callback && callback();
}
}
function main() {
// Check if noConfig = false
if (adapter.common.noConfig) {
adapter.getForeignObject('system.adapter.' + adapter.namespace, function (err, obj) {
obj.common.noConfig = false;
adapter.setForeignObject(obj._id, obj, function () {
adapter.stop();
});
});
return;
}
// first of all check license
if (!adapter.config.license || typeof adapter.config.license !== 'string') {
indicateError(function () {
adapter.log.error('No license found for vis. Please get one on https://yunkong2.net !');
//adapter.stop();
generatePages(true);
});
} else {
// An object of options to indicate where to post to
var postOptions = {
host: 'yunkong2.net',
path: '/cert/',
method: 'POST',
headers: {
'Content-Type': 'text/plain',
'Content-Length': Buffer.byteLength(adapter.config.license)
}
};
// Set up the request
var postReq = https.request(postOptions, function (res) {
res.setEncoding('utf8');
var result = '';
res.on('data', function (chunk) {
result += chunk;
});
res.on('end', function () {
try {
var data = JSON.parse(result);
if (data.result === 'OK') {
adapter.log.info('vis license is OK.');
generatePages();
} else {
indicateError(function () {
adapter.log.error('License is invalid! Nothing updated. Error: ' + (data ? data.result: 'unknown'));
//adapter.stop();
generatePages(true);
});
}
} catch (e) {
indicateError(function () {
adapter.log.error('Cannot check license! Nothing updated. Error: ' + (data ? data.result: 'unknown'));
//adapter.stop();
generatePages(true);
});
}
});
}).on('error', function (error) {
jwt.verify(adapter.config.license, fs.readFileSync(__dirname + '/lib/cloudCert.crt'), function (err, decoded) {
if (err) {
adapter.log.error('Cannot check license: ' + error);
//adapter.stop();
generatePages(true);
} else {
if (decoded && decoded.expires * 1000 < new Date().getTime()) {
adapter.log.error('Cannot check license: Expired on ' + new Date(decoded.expires * 1000).toString());
adapter.stop();
} else if (!decoded) {
adapter.log.error('Cannot check license: License is empty');
//adapter.stop();
generatePages(true);
} else {
generatePages(false);
}
}
});
});
postReq.write(adapter.config.license);
postReq.end();
}
}