/* jshint -W097 */// jshint strict:false /*jslint node: true */ 'use strict'; var cp = require('child_process'); var utils = require(__dirname + '/lib/utils'); // Get common adapter utils var path = require('path'); var dataDir = path.normalize(utils.controllerDir + '/' + require(utils.controllerDir + '/lib/tools').getDefaultDataDir()); var fs = require('fs'); var GetHistory = require(__dirname + '/lib/getHistory.js'); var Aggregate = require(__dirname + '/lib/aggregate.js'); var history = {}; var aliasMap = {}; var subscribeAll = false; var bufferChecker = null; var tasksStart = []; var finished = false; var adapter = new utils.Adapter({ name: 'history', objectChange: function (id, obj) { var formerAliasId = aliasMap[id] ? aliasMap[id] : id; if (obj && obj.common && ( // todo remove history sometime (2016.08) - Do not forget object selector in io-package.json (obj.common.history && obj.common.history[adapter.namespace] && obj.common.history[adapter.namespace].enabled) || (obj.common.custom && obj.common.custom[adapter.namespace] && obj.common.custom[adapter.namespace].enabled) ) ) { var realId = id; var checkForRemove = true; if (obj.common.custom && obj.common.custom[adapter.namespace] && obj.common.custom[adapter.namespace].aliasId) { if (obj.common.custom[adapter.namespace].aliasId !== id) { aliasMap[id] = obj.common.custom[adapter.namespace].aliasId; adapter.log.debug('Registered Alias: ' + id + ' --> ' + aliasMap[id]); id = aliasMap[id]; checkForRemove = false; } else { adapter.log.warn('Ignoring Alias-ID because identical to ID for ' + id); obj.common.custom[adapter.namespace].aliasId = ''; } } if (checkForRemove && aliasMap[id]) { adapter.log.debug('Removed Alias: ' + id + ' !-> ' + aliasMap[id]); delete aliasMap[id]; } var writeNull = !history[id]; var state = history[id] ? history[id].state : null; var list = history[id] ? history[id].list : null; var timeout = history[id] ? history[id].timeout : null; if (!history[formerAliasId] && !subscribeAll) { // unsubscribe for (var _id in history) { if (history.hasOwnProperty(history[_id].realId)) { adapter.unsubscribeForeignStates(history[_id].realId); } } subscribeAll = true; adapter.subscribeForeignStates('*'); } if (history[formerAliasId] && history[formerAliasId].relogTimeout) clearTimeout(history[formerAliasId].relogTimeout); // todo remove history somewhen (2016.08) history[id] = obj.common.custom || obj.common.history; history[id].state = state; history[id].list = list; history[id].timeout = timeout; history[id].realId = realId; if (!history[id][adapter.namespace].maxLength && history[id][adapter.namespace].maxLength !== '0' && history[id][adapter.namespace].maxLength !== 0) { history[id][adapter.namespace].maxLength = parseInt(adapter.config.maxLength, 10) || 960; } else { history[id][adapter.namespace].maxLength = parseInt(history[id][adapter.namespace].maxLength, 10); } if (!history[id][adapter.namespace].retention && history[id][adapter.namespace].retention !== '0' && history[id][adapter.namespace].retention !== 0) { history[id][adapter.namespace].retention = parseInt(adapter.config.retention, 10) || 0; } else { history[id][adapter.namespace].retention = parseInt(history[id][adapter.namespace].retention, 10) || 0; } if (!history[id][adapter.namespace].debounce && history[id][adapter.namespace].debounce !== '0' && history[id][adapter.namespace].debounce !== 0) { history[id][adapter.namespace].debounce = parseInt(adapter.config.debounce, 10) || 1000; } else { history[id][adapter.namespace].debounce = parseInt(history[id][adapter.namespace].debounce, 10); } history[id][adapter.namespace].changesOnly = history[id][adapter.namespace].changesOnly === 'true' || history[id][adapter.namespace].changesOnly === true; if (history[id][adapter.namespace].changesRelogInterval !== undefined && history[id][adapter.namespace].changesRelogInterval !== null && history[id][adapter.namespace].changesRelogInterval !== '') { history[id][adapter.namespace].changesRelogInterval = parseInt(history[id][adapter.namespace].changesRelogInterval, 10) || 0; } else { history[id][adapter.namespace].changesRelogInterval = adapter.config.changesRelogInterval; } if (history[id][adapter.namespace].changesRelogInterval > 0) { history[id].relogTimeout = setTimeout(reLogHelper, (history[id][adapter.namespace].changesRelogInterval * 500 * Math.random()) + history[id][adapter.namespace].changesRelogInterval * 500, id); } if (history[id][adapter.namespace].changesMinDelta !== undefined && history[id][adapter.namespace].changesMinDelta !== null && history[id][adapter.namespace].changesMinDelta !== '') { history[id][adapter.namespace].changesMinDelta = parseFloat(history[id][adapter.namespace].changesMinDelta.toString().replace(/,/g, '.')) || 0; } else { history[id][adapter.namespace].changesMinDelta = adapter.config.changesMinDelta; } // add one day if retention is too small if (history[id][adapter.namespace].retention && history[id][adapter.namespace].retention <= 604800) { history[id][adapter.namespace].retention += 86400; } if (writeNull && adapter.config.writeNulls) { writeNulls(id); } adapter.log.info('enabled logging of ' + id + ', Alias=' + (id !== realId)); } else { if (aliasMap[id]) { adapter.log.debug('Removed Alias: ' + id + ' !-> ' + aliasMap[id]); delete aliasMap[id]; } id = formerAliasId; if (history[id]) { adapter.log.info('disabled logging of ' + id); if (history[id].relogTimeout) clearTimeout(history[id].relogTimeout); if (history[id].timeout) clearTimeout(history[id].timeout); storeCached(true, id); delete history[id]; } } }, stateChange: function (id, state) { id = aliasMap[id] ? aliasMap[id] : id; pushHistory(id, state); }, unload: function (callback) { finish(callback); }, ready: function () { main(); }, message: function (obj) { processMessage(obj); } }); process.on('SIGINT', function () { if (adapter && adapter.setState) { finish(); } }); process.on('SIGTERM', function () { if (adapter && adapter.setState) { finish(); } }); function storeCached(isFinishing, onlyId) { var now = new Date().getTime(); for (var id in history) { if (onlyId !== undefined && onlyId !== id) continue; if (isFinishing) { if (history[id].skipped) { history[id].list.push(history[id].skipped); history[id].skipped = null; } if (adapter.config.writeNulls) { var nullValue = {val: null, ts: now, lc: now, q: 0x40, from: 'system.adapter.' + adapter.namespace}; if (history[id][adapter.namespace].changesOnly && history[id].state && history[id].state !== null) { var state = Object.assign({}, history[id].state); state.ts = now; state.from = 'system.adapter.' + adapter.namespace; history[id].list.push(state); nullValue.ts += 1; nullValue.lc += 1; } // terminate values with null to indicate adapter stop. history[id].list.push(nullValue); } } if (history[id].list && history[id].list.length) { adapter.log.debug('Store the rest for ' + id); appendFile(id, history[id].list); } } } function finish(callback) { adapter.unsubscribeForeignStates('*'); if (bufferChecker) { clearInterval(bufferChecker); bufferChecker = null; } for (var id in history) { if (history[id].relogTimeout) { clearTimeout(history[id].relogTimeout); history[id].relogTimeout = null; } if (history[id].timeout) { clearTimeout(history[id].timeout); history[id].timeout = null; } } if (!finished) { finished = true; storeCached(true); } if (callback) { callback(); } } function processMessage(msg) { if (msg.command === 'getHistory') { getHistory(msg); } else if (msg.command === 'storeState') { storeState(msg); } else if (msg.command === 'enableHistory') { enableHistory(msg); } else if (msg.command === 'disableHistory') { disableHistory(msg); } else if (msg.command === 'getEnabledDPs') { getEnabledDPs(msg); } else if (msg.command === 'stopInstance') { finish(function () { if (msg.callback) { adapter.sendTo(msg.from, msg.command, 'stopped', msg.callback); setTimeout(function () { process.exit(0); }, 200); } }); } } function fixSelector(callback) { // fix _design/custom object adapter.getForeignObject('_design/custom', function (err, obj) { if (!obj || obj.views.state.map.indexOf('common.history') === -1 || obj.views.state.map.indexOf('common.custom') === -1) { obj = { _id: '_design/custom', language: 'javascript', views: { state: { map: 'function(doc) { if (doc.type===\'state\' && (doc.common.custom || doc.common.history)) emit(doc._id, doc.common.custom || doc.common.history) }' } } }; adapter.setForeignObject('_design/custom', obj, function (err) { if (callback) callback(err); }); } else { if (callback) callback(err); } }); } function processStartValues() { if (tasksStart && tasksStart.length) { var task = tasksStart.shift(); if (history[task.id][adapter.namespace].changesOnly) { adapter.getForeignState(history[task.id].realId, function (err, state) { var now = task.now || new Date().getTime(); pushHistory(task.id, { val: null, ts: state ? now - 4 : now, // 4ms because of MS-SQL ack: true, q: 0x40, from: 'system.adapter.' + adapter.namespace}); if (state) { state.ts = now; state.from = 'system.adapter.' + adapter.namespace; pushHistory(task.id, state); } setTimeout(processStartValues, 0); }); } else { pushHistory(task.id, { val: null, ts: task.now || new Date().getTime(), ack: true, q: 0x40, from: 'system.adapter.' + adapter.namespace}); setTimeout(processStartValues, 0); } } } function writeNulls(id, now) { if (!id) { now = new Date().getTime(); for (var _id in history) { if (history.hasOwnProperty(_id)) { writeNulls(_id, now); } } } else { now = now || new Date().getTime(); tasksStart.push({id: id, now: now}); if (tasksStart.length === 1) { processStartValues(); } if (history[id][adapter.namespace].changesRelogInterval > 0) { if (history[id].relogTimeout) clearTimeout(history[id].relogTimeout); history[id].relogTimeout = setTimeout(reLogHelper, (history[id][adapter.namespace].changesRelogInterval * 500 * Math.random()) + history[id][adapter.namespace].changesRelogInterval * 500, id); } } } function main() { adapter.config.storeDir = adapter.config.storeDir || 'history'; adapter.config.storeDir = adapter.config.storeDir.replace(/\\/g, '/'); if (adapter.config.writeNulls === undefined) adapter.config.writeNulls = true; // remove last "/" if (adapter.config.storeDir[adapter.config.storeDir.length - 1] === '/') { adapter.config.storeDir = adapter.config.storeDir.substring(0, adapter.config.storeDir.length - 1); } if (adapter.config.storeDir[0] !== '/' && !adapter.config.storeDir.match(/^\w:\//)) { adapter.config.storeDir = dataDir + adapter.config.storeDir; } adapter.config.storeDir += '/'; if (adapter.config.changesRelogInterval !== null && adapter.config.changesRelogInterval !== undefined) { adapter.config.changesRelogInterval = parseInt(adapter.config.changesRelogInterval, 10); } else { adapter.config.changesRelogInterval = 0; } if (adapter.config.changesMinDelta !== null && adapter.config.changesMinDelta !== undefined) { adapter.config.changesMinDelta = parseFloat(adapter.config.changesMinDelta.toString().replace(/,/g, '.')); } else { adapter.config.changesMinDelta = 0; } // create directory if (!fs.existsSync(adapter.config.storeDir)) { fs.mkdirSync(adapter.config.storeDir); } fixSelector(function () { adapter.objects.getObjectView('custom', 'state', {}, function (err, doc) { var count = 0; if (doc && doc.rows) { for (var i = 0, l = doc.rows.length; i < l; i++) { if (doc.rows[i].value) { var id = doc.rows[i].id; var realId = id; if (doc.rows[i].value[adapter.namespace] && doc.rows[i].value[adapter.namespace].aliasId) { aliasMap[id] = doc.rows[i].value[adapter.namespace].aliasId; adapter.log.debug('Found Alias: ' + id + ' --> ' + aliasMap[id]); id = aliasMap[id]; } history[id] = doc.rows[i].value; // todo remove it somewhen (2016.08) // convert old value if (history[id].enabled !== undefined) { history[id] = history[id].enabled ? {'history.0': history[id]} : null; if (!history[id]) { delete history[id]; continue; } } if (!history[id][adapter.namespace] || history[id][adapter.namespace].enabled === false) { delete history[id]; } else { count++; adapter.log.info('enabled logging of ' + id + ' (Count=' + count + '), Alias=' + (id !== realId)); if (!history[id][adapter.namespace].maxLength && history[id][adapter.namespace].maxLength !== '0' && history[id][adapter.namespace].maxLength !== 0) { history[id][adapter.namespace].maxLength = parseInt(adapter.config.maxLength, 10) || 960; } else { history[id][adapter.namespace].maxLength = parseInt(history[id][adapter.namespace].maxLength, 10); } if (!history[id][adapter.namespace].retention && history[id][adapter.namespace].retention !== '0' && history[id][adapter.namespace].retention !== 0) { history[id][adapter.namespace].retention = parseInt(adapter.config.retention, 10) || 0; } else { history[id][adapter.namespace].retention = parseInt(history[id][adapter.namespace].retention, 10) || 0; } if (!history[id][adapter.namespace].debounce && history[id][adapter.namespace].debounce !== '0' && history[id][adapter.namespace].debounce !== 0) { history[id][adapter.namespace].debounce = parseInt(adapter.config.debounce, 10) || 1000; } else { history[id][adapter.namespace].debounce = parseInt(history[id][adapter.namespace].debounce, 10); } history[id][adapter.namespace].changesOnly = history[id][adapter.namespace].changesOnly === 'true' || history[id][adapter.namespace].changesOnly === true; if (history[id][adapter.namespace].changesRelogInterval !== undefined && history[id][adapter.namespace].changesRelogInterval !== null && history[id][adapter.namespace].changesRelogInterval !== '') { history[id][adapter.namespace].changesRelogInterval = parseInt(history[id][adapter.namespace].changesRelogInterval, 10) || 0; } else { history[id][adapter.namespace].changesRelogInterval = adapter.config.changesRelogInterval; } if (history[id][adapter.namespace].changesRelogInterval > 0) { history[id].relogTimeout = setTimeout(reLogHelper, (history[id][adapter.namespace].changesRelogInterval * 500 * Math.random()) + history[id][adapter.namespace].changesRelogInterval * 500, id); } if (history[id][adapter.namespace].changesMinDelta !== undefined && history[id][adapter.namespace].changesMinDelta !== null && history[id][adapter.namespace].changesMinDelta !== '') { history[id][adapter.namespace].changesMinDelta = parseFloat(history[id][adapter.namespace].changesMinDelta) || 0; } else { history[id][adapter.namespace].changesMinDelta = adapter.config.changesMinDelta; } // add one day if retention is too small if (history[id][adapter.namespace].retention && history[id][adapter.namespace].retention <= 604800) { history[id][adapter.namespace].retention += 86400; } history[id].realId = realId; } } } } if (count < 20) { for (var _id in history) { if (history.hasOwnProperty(_id)) { adapter.subscribeForeignStates(history[_id].realId); } } } else { subscribeAll = true; adapter.subscribeForeignStates('*'); } if (adapter.config.writeNulls) writeNulls(); // store all buffered data every 10 minutes to not lost the data bufferChecker = setInterval(function () { storeCached(); }, 10 * 60000); }); }); adapter.subscribeForeignObjects('*'); } function pushHistory(id, state, timerRelog) { if (timerRelog === undefined) timerRelog = false; // Push into history if (history[id]) { var settings = history[id][adapter.namespace]; if (!settings || !state) return; if (state.ts < 946681200000) state.ts *= 1000; if (state.lc < 946681200000) state.lc *= 1000; if (typeof state.val === 'string') { var f = parseFloat(state.val); if (f == state.val) { state.val = f; } } if (history[id].state && settings.changesOnly && !timerRelog) { if (settings.changesRelogInterval === 0) { if ((history[id].state.val !== null || state.val === null) && state.ts !== state.lc) { history[id].skipped = state; // remember new timestamp adapter.log.debug('value not changed ' + id + ', last-value=' + history[id].state.val + ', new-value=' + state.val + ', ts=' + state.ts); return; } } else if (history[id].lastLogTime) { if ((history[id].state.val !== null || state.val === null) && (state.ts !== state.lc) && (Math.abs(history[id].lastLogTime - state.ts) < settings.changesRelogInterval * 1000)) { history[id].skipped = state; // remember new timestamp adapter.log.debug('value not changed ' + id + ', last-value=' + history[id].state.val + ', new-value=' + state.val + ', ts=' + state.ts); return; } if (state.ts !== state.lc) { adapter.log.debug('value-changed-relog ' + id + ', value=' + state.val + ', lastLogTime=' + history[id].lastLogTime + ', ts=' + state.ts); } } if (history[id].state.val !== null && (settings.changesMinDelta !== 0) && (typeof state.val === 'number') && (Math.abs(history[id].state.val - state.val) < settings.changesMinDelta)) { history[id].skipped = state; // remember new timestamp adapter.log.debug('Min-Delta not reached ' + id + ', last-value=' + history[id].state.val + ', new-value=' + state.val + ', ts=' + state.ts); return; } else if (typeof state.val === 'number') { adapter.log.debug('Min-Delta reached ' + id + ', last-value=' + history[id].state.val + ', new-value=' + state.val + ', ts=' + state.ts); } else { adapter.log.debug('Min-Delta ignored because no number ' + id + ', last-value=' + history[id].state.val + ', new-value=' + state.val + ', ts=' + state.ts); } } if (history[id].relogTimeout) { clearTimeout(history[id].relogTimeout); history[id].relogTimeout = null; } if (settings.changesRelogInterval > 0) { history[id].relogTimeout = setTimeout(reLogHelper, settings.changesRelogInterval * 1000, id); } var ignoreDebonce = false; if (timerRelog) { state.ts = new Date().getTime(); adapter.log.debug('timed-relog ' + id + ', value=' + state.val + ', lastLogTime=' + history[id].lastLogTime + ', ts=' + state.ts); ignoreDebonce = true; } else { if (settings.changesOnly && history[id].skipped) { history[id].state = history[id].skipped; pushHelper(id); } if (history[id].state && ((history[id].state.val === null && state.val !== null) || (history[id].state.val !== null && state.val === null))) { ignoreDebonce = true; } else if (!history[id].state && state.val === null) { ignoreDebonce = true; } // only store state if really changed history[id].state = state; } history[id].lastLogTime = state.ts; history[id].skipped = null; if (settings.debounce && !ignoreDebonce) { // Discard changes in de-bounce time to store last stable value if (history[id].timeout) clearTimeout(history[id].timeout); history[id].timeout = setTimeout(pushHelper, settings.debounce, id); } else { pushHelper(id); } } } function reLogHelper(_id) { if (!history[_id]) { adapter.log.info('non-existing id ' + _id); return; } history[_id].relogTimeout = null; if (history[_id].skipped) { history[_id].state = history[_id].skipped; history[_id].state.from = 'system.adapter.' + adapter.namespace; history[_id].skipped = null; pushHistory(_id, history[_id].state, true); } else { adapter.getForeignState(history[_id].realId, function (err, state) { if (err) { adapter.log.info('init timed Relog: can not get State for ' + _id + ' : ' + err); } else if (!state) { adapter.log.info('init timed Relog: disable relog because state not set so far ' + _id + ': ' + JSON.stringify(state)); } else { adapter.log.debug('init timed Relog: getState ' + _id + ': Value=' + state.val + ', ack=' + state.ack + ', ts=' + state.ts + ', lc=' + state.lc); history[_id].state = state; pushHistory(_id, history[_id].state, true); } }); } } function pushHelper(_id) { if (!history[_id] || !history[_id].state) return; var _settings = history[_id][adapter.namespace]; // if it was not deleted in this time if (_settings) { history[_id].timeout = null; history[_id].list = history[_id].list || []; if (typeof history[_id].state.val === 'string') { var f = parseFloat(history[_id].state.val); if (f == history[_id].state.val) { history[_id].state.val = f; } else if (history[_id].state.val === 'true') { history[_id].state.val = true; } else if (history[_id].state.val === 'false') { history[_id].state.val = false; } } if (history[_id].state.lc !== undefined) delete history[_id].state.lc; if (!adapter.config.storeAck && history[_id].state.ack !== undefined) { delete history[_id].state.ack; } else { history[_id].state.ack = history[_id].state.ack ? 1 : 0; } if (!adapter.config.storeFrom && history[_id].state.from !== undefined) delete history[_id].state.from; history[_id].list.push(history[_id].state); if (history[_id].list.length > _settings.maxLength) { adapter.log.debug('moving ' + history[_id].list.length + ' entries from '+ _id +' to file'); appendFile(_id, history[_id].list); checkRetention(_id); } } } function checkRetention(id) { if (history[id][adapter.namespace].retention) { var d = new Date(); var dt = d.getTime(); // check every 6 hours if (!history[id].lastCheck || dt - history[id].lastCheck >= 21600000/* 6 hours */) { history[id].lastCheck = dt; // get list of directories var dayList = getDirectories(adapter.config.storeDir).sort(function (a, b) { return a - b; }); // calculate date d.setSeconds(-(history[id][adapter.namespace].retention)); var day = GetHistory.ts2day(d.getTime()); for (var i = 0; i < dayList.length; i++) { if (dayList[i] < day) { const file = GetHistory.getFilenameForID(adapter.config.storeDir, dayList[i], id); if (fs.existsSync(file)) { adapter.log.info('Delete old history "' + file + '"'); try { fs.unlinkSync(file); } catch (ex) { adapter.log.error('Cannot delete file "' + file + '": ' + ex); } var files = fs.readdirSync(adapter.config.storeDir + dayList[i]); if (!files.length) { adapter.log.info('Delete old history dir "' + adapter.config.storeDir + dayList[i] + '"'); try { fs.unlinkSync(adapter.config.storeDir + dayList[i]); } catch (ex) { adapter.log.error('Cannot delete directory "' + adapter.config.storeDir + dayList[i] + '": ' + ex); } } } } else { break; } } } } } function appendFile(id, states) { var day = GetHistory.ts2day(states[states.length - 1].ts); const file = GetHistory.getFilenameForID(adapter.config.storeDir, day, id); var data; var i; for (i = states.length - 1; i >= 0; i--) { if (!states[i]) continue; if (GetHistory.ts2day(states[i].ts) !== day) { break; } } data = states.splice(i - states.length + 1); if (fs.existsSync(file)) { try { data = JSON.parse(fs.readFileSync(file)).concat(data); } catch (err) { adapter.log.error('Cannot read file ' + file + ': ' + err); } } try { // create directory if (!fs.existsSync(adapter.config.storeDir + day)) { fs.mkdirSync(adapter.config.storeDir + day); } fs.writeFileSync(file, JSON.stringify(data, null, 2)); } catch (ex) { adapter.log.error('Cannot store file ' + file + ': ' + ex); } if (states.length) { appendFile(id, states); } } function getOneCachedData(id, options, cache, addId) { addId = addId || options.addId; if (history[id]) { var res = history[id].list; // todo can be optimized if (res) { var iProblemCount = 0; var vLast = null; for (var i = res.length - 1; i >= 0 ; i--) { if (!res[i]) { iProblemCount++; continue; } if (options.start && res[i].ts < options.start) { if (options.ack) res[i].ack = !!res[i].ack; if (addId) res[i].id = id; // add one before start cache.unshift(res[i]); break; } else if (res[i].ts > options.end) { // add one after end vLast = res[i]; continue; } if (options.ack) res[i].ack = !!res[i].ack; if (vLast) { if (options.ack) vLast.ack = !!vLast.ack; if (addId) res[i].id = id; cache.unshift(vLast); vLast = null; } if (addId) res[i].id = id; cache.unshift(res[i]); if (!options.start && cache.length >= options.count) break; } if (iProblemCount) adapter.log.warn('got null states ' + iProblemCount + ' times for ' + options.id); adapter.log.debug('got ' + res.length + ' datapoints for ' + options.id); } else { adapter.log.debug('datapoints for ' + options.id + ' do not yet exist'); } } } function getCachedData(options, callback) { var cache = []; if (options.id && options.id !== '*') { getOneCachedData(options.id, options, cache); } else { for (var id in history) { getOneCachedData(id, options, cache, true); } } options.length = cache.length; callback(cache, !options.start && cache.length >= options.count); } function tsSort(a, b) { return b.ts - a.ts; } function getOneFileData(dayList, dayStart, dayEnd, id, options, data, addId) { addId = addId || options.addId; // get all files in directory for (var i = 0; i < dayList.length; i++) { var day = parseInt(dayList[i], 10); if (!isNaN(day) && day > 20100101 && day >= dayStart && day <= dayEnd) { const file = GetHistory.getFilenameForID(options.path, dayList[i], id); if (fs.existsSync(file)) { try { var _data = JSON.parse(fs.readFileSync(file)).sort(tsSort); var last = false; for (var ii in _data) { if (!_data.hasOwnProperty(ii)) continue; if (options.ack) { _data[ii].ack = !!_data[ii].ack; } if (addId) { _data[ii].id = id; } data.push(_data[ii]); if (!options.start && data.length >= options.count) break; if (last) break; if (options.start && _data[ii].ts < options.start) last = true; } } catch (e) { console.log('Cannot parse file ' + file + ': ' + e.message); } } } if (!options.start && data.length >= options.count) break; if (day > dayEnd) break; } } function getFileData(options, callback) { var dayStart = options.start ? parseInt(GetHistory.ts2day(options.start), 10) : 0; var dayEnd = parseInt(GetHistory.ts2day(options.end), 10); var fileData = []; // get list of directories var dayList = getDirectories(options.path).sort(function (a, b) { return b - a; }); if (options.id && options.id !== '*') { getOneFileData(dayList, dayStart, dayEnd, options.id, options, fileData); } else { for (var id in history) { getOneFileData(dayList, dayStart, dayEnd, id, options, fileData, true); } } callback(fileData); } function sortByTs(a, b) { var aTs = a.ts; var bTs = b.ts; return ((aTs < bTs) ? -1 : ((aTs > bTs) ? 1 : 0)); } function getHistory(msg) { var startTime = new Date().getTime(); var options = { id: msg.message.id ? msg.message.id : null, path: adapter.config.storeDir, start: msg.message.options.start, end: msg.message.options.end || ((new Date()).getTime() + 5000000), step: parseInt(msg.message.options.step, 10) || null, count: parseInt(msg.message.options.count, 10) || 500, from: msg.message.options.from || false, ack: msg.message.options.ack || false, q: msg.message.options.q || false, ignoreNull: msg.message.options.ignoreNull, aggregate: msg.message.options.aggregate || 'average', // One of: max, min, average, total limit: parseInt(msg.message.options.limit || adapter.config.limit || 2000, 10), addId: msg.message.options.addId || false, sessionId: msg.message.options.sessionId }; if (options.id && aliasMap[options.id]) { options.id = aliasMap[options.id]; } if (options.start > options.end) { var _end = options.end; options.end = options.start; options.start = _end; } // if less 2000.01.01 00:00:00 if (options.start < 946681200000) { options.start *= 1000; if (options.step !== null && options.step !== undefined) options.step *= 1000; } // if less 2000.01.01 00:00:00 if (options.end < 946681200000) options.end *= 1000; if ((!options.start && options.count) || options.aggregate === 'onchange' || options.aggregate === '' || options.aggregate === 'none') { getCachedData(options, function (cacheData, isFull) { adapter.log.debug('after getCachedData: length = ' + cacheData.length + ', isFull=' + isFull); // if all data read if (isFull && cacheData.length) { cacheData = cacheData.sort(sortByTs); if ((options.count) && (cacheData.length > options.count) && (options.aggregate === 'none')) { cacheData = cacheData.slice(0, options.count); adapter.log.debug('cut cacheData to ' + options.count + ' values'); } adapter.log.debug('Send: ' + cacheData.length + ' values in: ' + (new Date().getTime() - startTime) + 'ms'); adapter.sendTo(msg.from, msg.command, { result: cacheData, step: null, error: null }, msg.callback); } else { var origCount = options.count; options.count -= cacheData.length; getFileData(options, function (fileData) { adapter.log.debug('after getFileData: cacheData.length = ' + cacheData.length + ', fileData.length = ' + fileData.length); cacheData = cacheData.concat(fileData); cacheData = cacheData.sort(sortByTs); options.result = cacheData; options.count = origCount; Aggregate.beautify(options); adapter.log.debug('Send: ' + options.result.length + ' values in: ' + (new Date().getTime() - startTime) + 'ms'); adapter.sendTo(msg.from, msg.command, { result: options.result, step: null, error: null }, msg.callback); }); } }); } else { // to use parallel requests activate this. if (1 || typeof GetHistory === 'undefined') { adapter.log.debug('use parallel requests'); var gh = cp.fork(__dirname + '/lib/getHistory.js', [JSON.stringify(options)], {silent: false}); var ghTimeout = setTimeout(function () { try { gh.kill('SIGINT'); } catch (err) { adapter.log.error(err); } }, 120000); gh.on('message', function (data) { var cmd = data[0]; if (cmd === 'getCache') { var settings = data[1]; getCachedData(settings, function (cacheData) { gh.send(['cacheData', cacheData]); }); } else if (cmd === 'response') { clearTimeout(ghTimeout); ghTimeout = null; var result = data[1]; var overallLength = data[2]; var step = data[3]; if (result) { adapter.log.debug('Send: ' + result.length + ' of: ' + overallLength + ' in: ' + (new Date().getTime() - startTime) + 'ms'); adapter.sendTo(msg.from, msg.command, { result: result, step: step, error: null }, msg.callback); } else { adapter.log.info('No Data'); adapter.sendTo(msg.from, msg.command, { result: [], step: null, error: null }, msg.callback); } } }); } else { GetHistory.initAggregate(options); GetHistory.getFileData(options); getCachedData(options, function (cachedData) { GetHistory.aggregation(options, cachedData); var data = GetHistory.response(options); if (data[0] === 'response') { if (data[1]) { adapter.log.debug('Send: ' + data[1].length + ' of: ' + data[2] + ' in: ' + (new Date().getTime() - startTime) + 'ms'); adapter.sendTo(msg.from, msg.command, { result: data[1], step: data[3], error: null }, msg.callback); } else { adapter.log.info('No Data'); adapter.sendTo(msg.from, msg.command, { result: [], step: null, error: null }, msg.callback); } } else { adapter.log.error('Unknown response type: ' + data[0]); } }); } } } function getDirectories(path) { return fs.readdirSync(path).filter(function (file) { return fs.statSync(path + '/' + file).isDirectory(); }); } function storeState(msg) { if (!msg.message || !msg.message.id || !msg.message.state) { adapter.log.error('storeState called with invalid data'); adapter.sendTo(msg.from, msg.command, { error: 'Invalid call' }, msg.callback); return; } var id; if (Array.isArray(msg.message)) { adapter.log.debug('storeState: store ' + msg.message.length + ' states for multiple ids'); for (var i = 0; i < msg.message.length; i++) { id = aliasMap[msg.message[i].id] ? aliasMap[msg.message[i].id] : msg.message[i].id; if (history[id]) { history[id].state = msg.message[i].state; pushHelper(id); } else { adapter.log.warn('storeState: history not enabled for ' + msg.message[i].id + '. Ignoring'); } } } else if (Array.isArray(msg.message.state)) { adapter.log.debug('storeState: store ' + msg.message.state.length + ' states for ' + msg.message.id); for (var j = 0; j < msg.message.state.length; j++) { id = aliasMap[msg.message.id] ? aliasMap[msg.message.id] : msg.message.id; if (history[id]) { history[id].state = msg.message.state[j]; pushHelper(id); } else { adapter.log.warn('storeState: history not enabled for ' + msg.message.id + '. Ignoring'); } } } else { adapter.log.debug('storeState: store 1 state for ' + msg.message.id); id = aliasMap[msg.message.id] ? aliasMap[msg.message.id] : msg.message.id; if (history[id]) { history[id].state = msg.message.state; pushHelper(id); } else { adapter.log.warn('storeState: history not enabled for ' + msg.message.id + '. Ignoring'); } } adapter.sendTo(msg.from, msg.command, { success: true }, msg.callback); } function enableHistory(msg) { if (!msg.message || !msg.message.id) { adapter.log.error('enableHistory called with invalid data'); adapter.sendTo(msg.from, msg.command, { error: 'Invalid call' }, msg.callback); return; } var obj = {}; obj.common = {}; obj.common.custom = {}; if (msg.message.options) { obj.common.custom[adapter.namespace] = msg.message.options; } else { obj.common.custom[adapter.namespace] = {}; } obj.common.custom[adapter.namespace].enabled = true; adapter.extendForeignObject(msg.message.id, obj, function (err) { if (err) { adapter.log.error('enableHistory: ' + err); adapter.sendTo(msg.from, msg.command, { error: err }, msg.callback); } else { adapter.log.info(JSON.stringify(obj)); adapter.sendTo(msg.from, msg.command, { success: true }, msg.callback); } }); } function disableHistory(msg) { if (!msg.message || !msg.message.id) { adapter.log.error('disableHistory called with invalid data'); adapter.sendTo(msg.from, msg.command, { error: 'Invalid call' }, msg.callback); return; } var obj = {}; obj.common = {}; obj.common.custom = {}; obj.common.custom[adapter.namespace] = {}; obj.common.custom[adapter.namespace].enabled = false; adapter.extendForeignObject(msg.message.id, obj, function (err) { if (err) { adapter.log.error('disableHistory: ' + err); adapter.sendTo(msg.from, msg.command, { error: err }, msg.callback); } else { adapter.log.info(JSON.stringify(obj)); adapter.sendTo(msg.from, msg.command, { success: true }, msg.callback); } }); } function getEnabledDPs(msg) { var data = {}; for (var id in history) { if (!history.hasOwnProperty(id)) continue; data[history[id].realId] = history[id][adapter.namespace]; } adapter.sendTo(msg.from, msg.command, data, msg.callback); }