'use strict'
/**
* Copyright (c) 2017~2023, OBCon Inc.
* All rights reserved.
*/
/**
* @file
* @copyright 2017~2023, OBCon Inc.
* @author gye hyun james kim [pnuskgh@gmail.com]
*/
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const moment = require('moment'); //--- format : YYYYMMDDHHmmss.SSS ZZ - 20191220172919.083 +0900
const UtilityPrev = require('./UtilityPrev.js');
class Utility {
constructor() {
this.initialize();
}
get module() {
return new UtilityPrev();
}
get type() {
return {
//--- typeof : string (String), bigint (BigInt), number (Number), boolean (Boolean), object (Null), symbol (Symbol), function, undefined (Undefined)
//--- string, number, bigint, boolean, function, undefined, symbol
//--- object : null, object, array
typeof: (param) => Object.prototype.toString.call(param).replace(/\[object /g, '').replace(/\]/g, '').toLowerCase(),
isNull: (param) => ((typeof(param) === 'undefined') || (param == null)),
isNone: (param) => typeof(param) === 'undefined',
isString: (param) => typeof(param) == 'string',
isNumber: (param) => typeof(param) == 'number',
isArray: (param) => this.type.typeof(param) == 'array',
Null: this.type_Null
};
}
type_Null(value) {
return ((typeof(value) === 'undefined') || (value == null)) ? '':value;
}
get link() {
return {
join: this.link_join,
fullUrl: (url) => this.link.join(config.http.baseUrl, url)
}
}
link_join(...paths) {
if (paths.length == 0) {
return '';
}
const links = [ paths[0] ];
let pathPrev = paths[0];
for (let idx = 1;idx < paths.length;idx++) {
if ((paths[idx] != '') && (paths[idx] != null) && (typeof(paths[idx]) == 'string')) {
if ((pathPrev.endsWith('/')) && (paths[idx].startsWith('/'))) {
pathPrev = paths[idx].substring(1);
links.push(pathPrev);
} else if ((pathPrev.endsWith('/') == false) && (paths[idx].startsWith('/') == false)) {
pathPrev = paths[idx];
links.push('/' + pathPrev);
} else {
pathPrev = paths[idx];
links.push(pathPrev);
}
}
}
return links.join('');
}
async _sleep(miliseconds) {
const promise = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve();
}, miliseconds);
});
await promise;
}
async _wait(funcCondition, miliseconds) {
const times = Math.ceil(miliseconds / 100); //--- ceil. 올림, floor. 버림
for (let idx = 0; idx < times; idx++) {
await this._sleep(100);
if (funcCondition()) {
break;
}
}
}
async fetchFile(url, filename) {
try {
const file = path.join(appl.root, 'files', filename);
const folder = path.dirname(file);
if (fs.existsSync(folder) == false) {
fs.mkdirSync(folder);
}
if (fs.existsSync(file)) {
fs.unlinkSync(file);
}
const response = await fetch(url);
const streamPipeline = promisify(pipeline); //--- Callback 함수를 Promise 함수로 변환 한다.
await streamPipeline(response.body, createWriteStream(file));
return true;
} catch (err) {
utils.obj.loggerError(err);
return false;
}
}
async unzip(filename) {
try {
const file = path.join(appl.root, 'files', filename);
const folder = path.join(appl.root, 'files', filename.replace(/\.zip$/, ''));
if (fs.existsSync(folder)) {
fs.rmSync(folder, { recursive: true, force: true });
}
fs.mkdirSync(folder);
await decompress(file, folder);
return true;
} catch (err) {
utils.obj.loggerError(err);
return false;
}
}
get string() {
return {
charLen: this._charLen,
substring_byte: this._substring_byte
};
}
_charLen(str, idx) {
return (128 < str.charCodeAt(idx) ? 2:1);
}
_substring_byte(str, start, end) {
const size = end - start;
let idxByte = 0;
let idxFr = 0;
let idxTo = 0;
for (let idx = 0; idx < str.length; idx++) {
if (idxByte <= start) {
idxFr = idx;
}
if (idxByte <= end) {
idxTo = idx;
}
idxByte = idxByte + ((128 < str.charCodeAt(idx)) ? 2 : 1);
}
// for (let idx = 0; idxTo < end; idx++) {
// idxTo = idxTo + ((128 < str.charCodeAt(idx)) ? 2 : 1);
// }
return str.substring(idxFr, idxTo);
}
decode(val, conditions, defaultValue = null) {
for (let idx = 0; idx < conditions.length / 2; idx++) {
const condition = conditions[idx * 2];
const value = conditions[idx * 2 + 1] || defaultValue;
if (utils.typeof(condition) == 'array') {
if (condition.includes(val)) {
return value;
}
} else {
if (val == condition) {
return value;
}
}
}
return defaultValue;
}
initialize() {
this.getPath = this.module.getPath.bind(this);
this.getCustomPath = this.module.getCustomPath.bind(this);
this.existFile = this.module.existFile.bind(this);
this.getClass = this.module.getClass.bind(this);
this.getModule = this.module.getModule.bind(this);
this.Null = this.type.Null.bind(this);
this.typeof = this.type.typeof.bind(this);
this.isNone = this.type.isNone.bind(this);
this.isNull = this.type.isNull.bind(this);
this.isString = this.type.isString.bind(this);
this.isNumber = this.type.isNumber.bind(this);
this.isArray = this.type.isArray.bind(this);
this.sleep = this._sleep.bind(this);
this.wait = this._wait.bind(this);
this.getFullUrl = this.link.fullUrl.bind(this); //--- Utility class에는 없는 함수 (이름이 다름)
this.getTemplateFullPath = getTemplateFullPath;
this.getTemplateUrl = getTemplateUrl;
this.getHash = getHash;
this.stringFormat = stringFormat;
this.stringLength = stringLength;
this.integerFormat = integerFormat;
this.floatFormat = floatFormat;
this.floatFormat2 = floatFormat2;
this.existField = existField;
this.existIdxField = existIdxField;
this.getObjectValue = getObjectValue;
this.deepCopy = deepCopy;
this.datetimeFormatter = datetimeFormatter;
this.initFlagString = initFlagString;
this.getFlagString = getFlagString;
this.setFlagString = setFlagString;
this.getFlagNumber = getFlagNumber;
this.setFlagNumber = setFlagNumber;
this.checkHacking = checkHacking;
this.randomInteger = randomInteger;
this.randomFloat = randomFloat;
this.isNetworkError = isNetworkError;
this.saveNotificationNormal = saveNotificationNormal;
this.saveNotificationError = saveNotificationError;
this.stringToHex = stringToHex;
this.hexToString = hexToString;
this.isWindows = isWindows;
this.isLinux = isLinux;
this.makeHelp = makeHelp;
this.makeSetting = makeSetting;
this.getClientIp = getClientIp;
this.obj = this.module.getModule('include', 'obconObject.js');
}
}
const zzUtil = new Utility();
//--- 전체 URL을 반환 한다.
function getFullUrl(url='') {
// let rturl = config.http.baseUrl + config.http.path.substring(1);
// return (url == '') ? config.http.baseUrl:zzUtil.link.join(config.http.baseUrl, url);
return zzUtil.link.join(config.http.baseUrl, url);
}
function _getTemplateFile(siteId, ...args) {
const filePath = path.join.apply(this, args);
let files = [];
if ((siteId != null) && (siteId != -1)) {
files.push(zzUtil.getPath('themes', config.service.name, config.theme.name, 'layout', `site_${siteId}`, filePath));
files.push(zzUtil.getPath('themes', config.service.name, config.theme.defaultName, 'layout', `site_${siteId}`,filePath));
files.push(zzUtil.getPath('themes', config.theme.name, 'layout', `site_${siteId}`,filePath));
files.push(zzUtil.getPath('themes', config.theme.defaultName, 'layout', `site_${siteId}`,filePath));
}
files.push(zzUtil.getPath('themes', config.service.name, config.theme.name, 'layout', filePath));
files.push(zzUtil.getPath('themes', config.service.name, config.theme.defaultName, 'layout', filePath));
files.push(zzUtil.getPath('themes', config.theme.name, 'layout', filePath));
files.push(zzUtil.getPath('themes', config.theme.defaultName, 'layout', filePath));
if ((siteId != null) && (siteId != -1)) {
files.push(zzUtil.getPath('themes', config.service.name, config.theme.name, `site_${siteId}`, filePath));
files.push(zzUtil.getPath('themes', config.service.name, config.theme.defaultName, `site_${siteId}`, filePath));
files.push(zzUtil.getPath('themes', config.theme.name, `site_${siteId}`, filePath));
files.push(zzUtil.getPath('themes', config.theme.defaultName, `site_${siteId}`, filePath));
}
files.push(zzUtil.getPath('themes', config.service.name, config.theme.name, filePath));
files.push(zzUtil.getPath('themes', config.service.name, config.theme.defaultName, filePath));
files.push(zzUtil.getPath('themes', config.theme.name, filePath));
files.push(zzUtil.getPath('themes', config.theme.defaultName, filePath));
let rtPath = zzUtil.getPath('themes', config.theme.defaultName, filePath);
for (let idx = 0; idx < files.length; idx++) {
if (fs.existsSync(files[idx])) {
rtPath = files[idx];
break;
}
}
rtPath = rtPath.replace(/\\/g, '/').replace(`${config.folder_root}/themes/`, '');
return rtPath;
}
function getTemplateFullPath(siteId, ...args) {
args.unshift(siteId);
return `${config.folder_root}/themes/${_getTemplateFile.apply(this, args)}`
}
function getTemplateUrl(siteId, ... args) {
if ((args.length == 1) && (args[0].startsWith('http'))) {
return args[0];
}
// Many view modules push static assets with a leading "/" (e.g. "/js/moment.min.js").
// `_getTemplateFile()` treats leading "/" as an absolute path which breaks theme lookup,
// causing 404s for scripts/styles and blank SPA pages.
args = args.map((v) => (typeof v === 'string' && v.startsWith('/')) ? v.replace(/^\/+/, '') : v);
args.unshift(siteId);
if (config.http.useFullUrl) {
return `${config.http.baseUrl}${config.http.path}/${_getTemplateFile.apply(this, args)}`
} else {
return `${config.http.path}/${_getTemplateFile.apply(this, args)}`
}
}
function stringFormat(valStr, len, pad=' ') {
let rtStr = valStr;
while (rtStr.length < len) {
rtStr = pad + rtStr;
}
return rtStr;
}
function stringLength(str) {
let len = 0;
for (let idx = 0; idx < str.length; idx++) {
if (escape(str.charAt(idx)).length == 6) {
len++;
}
len++;
}
return len;
}
function integerFormat(valInt, len, pad=' ', abs=true) {
let rtStr = valInt.toString();
if (-1 < rtStr.indexOf('.')) {
rtStr = rtStr.split('.')[0];
}
while (rtStr.length < len) {
rtStr = pad + rtStr;
}
if ((abs) && (len < rtStr.length)) {
rtStr = rtStr.slice(rtStr.length - len, rtStr.length);
}
return rtStr;
}
//--- To-Do : 향후 개선 한다.
//--- abs : true. 소수점 아래와 소수점 위의 길이를 지킨다.
function floatFormat(valFloat, lenFirst=5, lenSecond=2, padFirst=' ', padSecond='0', abs=true) {
let rtStr = valFloat.toString();
let tmpFirst = null;
let tmpSecond = null;
if (-1 < rtStr.indexOf('.')) {
let items = rtStr.split('.');
tmpFirst = items[0];
tmpSecond = items[1];
if (abs) {
if (lenSecond < tmpSecond.length) {
tmpSecond = tmpSecond.slice(0, lenSecond);
}
}
} else {
tmpFirst = rtStr;
tmpSecond = '';
}
while (tmpSecond.length < lenSecond) {
tmpSecond = tmpSecond + padSecond;
}
rtStr = tmpFirst + '.' + tmpSecond;
while (rtStr.length < (lenFirst + 1 + lenSecond)) {
rtStr = padFirst + rtStr;
}
if ((abs) && ((lenFirst + 1 + lenSecond) < rtStr.length)) {
rtStr = rtStr.slice(rtStr.length - (lenFirst + 1 + lenSecond), rtStr.length);
}
return rtStr;
}
//--- 소수점 아래 자리만 확정 한다.
function floatFormat2(valFloat, lenSecond=2, padSecond='0') {
let rtStr = valFloat.toString();
let tmpFirst = null;
let tmpSecond = null;
if (-1 < rtStr.indexOf('.')) {
let items = rtStr.split('.');
tmpFirst = items[0];
tmpSecond = items[1];
if (lenSecond < tmpSecond.length) {
tmpSecond = tmpSecond.slice(0, lenSecond);
}
} else {
tmpFirst = rtStr;
tmpSecond = '';
}
while (tmpSecond.length < lenSecond) {
tmpSecond = tmpSecond + padSecond;
}
rtStr = tmpFirst + '.' + tmpSecond;
return rtStr;
}
function getHash(plainText) {
let hash = crypto.createHash('md5');
hash.update(plainText);
return hash.digest('hex').toLowerCase();
}
function existField(paramObject, paramField) {
if ((paramObject == null) || (typeof(paramObject) == 'undefined')) {
return false;
}
return typeof(paramObject[paramField]) != 'undefined';
// return Object.getOwnPropertyNames(paramObject).includes(paramField);
// return -1 < Object.getOwnPropertyNames(paramObject).indexOf(paramField);
}
function existIdxField(paramObject, paramField) {
if ((paramObject == null) || (typeof(paramObject) == 'undefined')) {
return false;
}
return typeof(paramObject[`idx_${paramField}`]) != 'undefined';
// return Object.getOwnPropertyNames(paramObject).includes(paramField);
// return -1 < Object.getOwnPropertyNames(paramObject).indexOf(paramField);
}
function getObjectValue(paramObject, paramField, paramDefault) {
return existField(paramObject, paramField) ? paramObject[paramField]:paramDefault;
}
function deepCopy(obj) {
return JSON.parse(JSON.stringify(obj));
}
function randomInteger(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function randomFloat(min, max) {
return Math.random() * (max - min) + min;
}
//--- pos : 1(첫번째 문자), 2(두번째 문자), ..., 32
let maxPos = 32;
function initFlagString(source) {
let tmpStr = source + '0000000000000000000000000000000000000000';
return tmpStr.substring(0, maxPos);
}
function getFlagString(source, pos) {
let tmpStr = initFlagString(source);
tmpStr = tmpStr.substring(pos - 1, pos);
return tmpStr;
}
function setFlagString(source, pos, set) {
let tmpStr = initFlagString(source);
tmpStr = tmpStr.substring(0, pos - 1) + set + tmpStr.substring(pos, maxPos);
return tmpStr;
}
//--- pos : 1(일자리), 2(십자리), ..., 32
function getFlagNumber(source, pos) {
let tmpStr = '0000000000000000' + Math.abs(source);
tmpStr = tmpStr.substring(tmpStr.length - 16);
tmpStr = tmpStr.substring(16 - pos, 16 - pos + 1);
return parseInt(tmpStr);
}
function setFlagNumber(source, pos, set) {
let tmpStr = '0000000000000000' + Math.abs(source);
tmpStr = tmpStr.substring(tmpStr.length - 16);
tmpStr = tmpStr.substring(0, 16 - pos) + set + tmpStr.substring(16 - pos + 1);
return parseInt(tmpStr);
}
function checkHacking(param) {
if (!param) {
return false;
}
if ((-1 < param.indexOf(';')) || (-1 < param.indexOf('\''))) {
return true;
}
return false;
}
//--- 박광서의 요청에 따라 현재 시간이 아니라, 전달되는 데이터의 시간으로 체크 한다. 2019.03.31
//--- 통신 이상 검사 : true. 통신 이상, false. 통신 정상
//---
//--- 한번도 데이터를 받은 적이 없으면 통신 이상
//--- 1. 정류기: 설정한 시간(Default 60초) 이상 데이터가 입력되지 않으면 통신 이상
//--- 2. 원격TB: 통신 주기 * 3 보다 통신이 되지 않은 시간이 클 경우 통신 이상
//--- 3. 배관링크: 통신 주기 * 3 보다 통신이 되지 않은 시간이 클 경우 통신 이상
//--- 4. 수위센서: 통신 주기 * 3 보다 통신이 되지 않은 시간이 클 경우 통신 이상
//--- 5. 관말압력
//--- 피크타임 모드인 경우
//--- 피크타임인 경우 - 통신 주기 * 3 보다 통신이 되지 않은 시간이 클 경우 통신 이상
//--- 피크타임이 아닌 경우 - 항상 통신 정상
//--- 피크타임 모드가 아닌 경우 - 통신 주기 * 3 보다 통신이 되지 않은 시간이 클 경우 통신 이상
//--- 6. 특정정압기: 통신 주기 * 3 보다 통신이 되지 않은 시간이 클 경우 통신 이상
//--- 7. 수신감도: 통신 주기 * 3 보다 통신이 되지 않은 시간이 클 경우 통신 이상
// function isNetworkError(deviceIndex, type, networkCycle, networkCycleUnit, statusDatetime) {
function isNetworkError(siteKey, device, statusDatetime) {
const deviceIndex = siteKey + '_' + device.type + '_' + device.deviceKey;
let type = device.type;
let networkCycle = device.networkCycle;
let networkCycleUnit = utils.decode(device.networkCycleUnit, [ '1', '초', '2', '분', '3', '시' ], device.networkCycleUnit) ;
if (type == '5') { //--- 5. 관말압력
if (device.networkMode == '4') { //--- 4. 피크타임 모드
networkCycle = device.peakNetworkCycle;
networkCycleUnit = '분';
//--- 설정된 피크타임 시간이 아니면, statusDatetime을 현재 시간으로 설정하여 항상 통신 이상이 발생하지 않도록 한다.
let isPeak = false;
let hour = parseInt(moment().format('HH')); //--- 현재 시간 (0 ~ 24)
if ((device.peakTimeStart1 <= hour) && (hour < device.peakTimeEnd1)) {
isPeak = true;
}
if ((device.peakTimeStart2 <= hour) && (hour < device.peakTimeEnd2)) {
isPeak = true;
}
if (isPeak == false) {
statusDatetime = moment();
}
}
}
//--- 통신 이상 여부를 검사 한다.
let rtcd = true;
if (statusDatetime == null) { //--- 한번도 데이터를 받은 적이 없으면 통신 이상 처리
return true;
}
let datetimeFr = statusDatetime; //--- 최종 도착한 데이터의 시간
let datetimeTo = moment(); //--- 현재 시간
let duration = 0;
let unit = '분';
switch (type) {
case '1' :
unit = '초';
duration = moment.duration(datetimeTo.diff(datetimeFr)).asSeconds();
//--- 정류기는 60초 이상 데이터가 입력되지 않으면 통신 이상 처리 -> 설정한 시간으로 비교
// rtcd = (60 < duration);
rtcd = (device.networkCheckTime < duration);
// logger.info(`include/Utility.js :: ${deviceIndex} : Duration - ${floatFormat(duration, 7, 2)} ${unit}, networkCycle : ${device.networkCheckTime} :: ${rtcd}`);
break;
case '2' :
case '3' :
case '4' :
case '5' :
case '6' :
case '7' :
unit = '분';
duration = moment.duration(datetimeTo.diff(datetimeFr)).asMinutes();
if (networkCycleUnit == '시') {
networkCycle = networkCycle * 60;
}
//--- 박광서의 요청으로 임시로 통신 주기 * 3 보다 통신이 되지 않은 시간이 클 경우 오류 처리 (2019-03-11)
rtcd = (3 * networkCycle < duration);
// logger.info(`include/Utility.js :: ${deviceIndex} : Duration - ${floatFormat(duration, 7, 2)} ${unit}, 3 * networkCycle : ${3 * networkCycle} :: ${rtcd}`);
break;
case '8':
case 'A':
case 'B':
default:
switch (networkCycleUnit) {
case '초':
unit = '초';
duration = moment.duration(datetimeTo.diff(datetimeFr)).asSeconds();
break;
case '분':
unit = '분';
duration = moment.duration(datetimeTo.diff(datetimeFr)).asMinutes();
break;
case '시':
unit = '분';
duration = moment.duration(datetimeTo.diff(datetimeFr)).asMinutes();
networkCycle = networkCycle * 60;
break;
}
rtcd = (3 * networkCycle < duration);
// logger.info(`include/global_utils.js :: ${deviceIndex} : Duration - ${floatFormat(duration, 7, 2)} ${unit}, 3 * networkCycle : ${3 * networkCycle} :: ${rtcd}`);
break;
}
return rtcd;
}
function saveNotificationNormal(device, datetimeTo) {
let noti = {};
noti.name = '통신 정상';
noti.site = device.site;
noti.device = device.id;
noti.deviceKey = device.deviceKey;
noti.deviceName = device.name;
noti.branch = -1;
noti.branchName = '';
noti.type = device.type;
noti.eventDatetime = datetimeTo.format('YYYY-MM-DD HH:mm:ss');
noti.level = 'Info';
noti.description = '통신 정상';
//--- branch로 branch 이름을 가져 온다.
let query = { where: { id: device.branch, deleted: false } };
modules.branches.model.table.findOne(query)
.then(function(branch) {
if (branch != null) {
noti.branch = branch.id;
noti.branchName = branch.name;
}
modules.notifications.model.table.create(noti)
.then(function(results) {
// logger.info(JSON.stringify(results));
}).catch(utils.obj.loggerError);
}).catch(function(err) {
logger.error(JSON.stringify(err));
modules.notifications.model.table.create(noti)
.then(function(results) {
// logger.info(JSON.stringify(results));
}).catch(utils.obj.loggerError);
});
}
function saveNotificationError(device, datetimeTo) {
let noti = {};
noti.name = '통신 이상';
noti.site = device.site;
noti.device = device.id;
noti.deviceKey = device.deviceKey;
noti.deviceName = device.name;
noti.branch = -1;
noti.branchName = '';
noti.type = device.type;
noti.eventDatetime = datetimeTo.format('YYYY-MM-DD HH:mm:ss');
noti.level = 'Error';
noti.description = '통신 이상';
//--- branch로 branch 이름을 가져 온다.
let query = { where: { id: device.branch, deleted: false } };
modules.branches.model.table.findOne(query)
.then(function(branch) {
if (branch != null) {
noti.branch = branch.id;
noti.branchName = branch.name;
}
modules.notifications.model.table.create(noti)
.then(function(results) {
// logger.info(JSON.stringify(results));
}).catch(utils.obj.loggerError);
}).catch(function(err) {
logger.error(JSON.stringify(err));
modules.notifications.model.table.create(noti)
.then(function(results) {
// logger.info(JSON.stringify(results));
}).catch(utils.obj.loggerError);
});
}
function stringToHex(str) {
const buf = Buffer.from(str, 'utf8');
return buf.toString('hex');
}
function hexToString(str) {
const buf = new Buffer(str, 'hex');
return buf.toString('utf8');
}
function isWindows() {
return (process.env.HOMEDRIVE) ? true:false;
}
function isLinux() {
return (process.env.HOME) ? true:false;
}
//--- To-Do : LocalStorage, Cookie 등을 사용하여 설정 관리
function makeSetting(id='iconSetting') {
// if ((!config.func) || (!config.func.page) || (!config.func.page.setting)) {
// return '';
// }
// if (config.func.page.setting == false) {
// return '';
// }
//--- https://icons.getbootstrap.com/
//
// return ``;
return `settings`;
}
//--- options.helpPage = 'url';
//--- options.helpPage = [ { name: '', url: '' } ]; //--- To-Do : 선택 목록을 보여준 후 선택시 도움말 화면 표시
function makeHelp(id='iconHelp') {
// if ((!config.func) || (!config.func.page) || (!config.func.page.setting)) {
// return '';
// }
// if (config.func.page.setting == false) {
// return '';
// }
//--- https://icons.getbootstrap.com/
// return ``;
// return ``;
return `help_center`;
}
function getClientIp(req) {
var clientIp = req.body.clientIp ||
req.headers['x-forwarded-for'] ||
req.connection.remoteAddress ||
req.socket.remoteAddress ||
req.connection.socket.remoteAddress || '';
clientIp = clientIp.split(',')[0];
clientIp = clientIp.split(':').slice(-1)[0];
return clientIp;
}
function datetimeFormatter(str, formatFr = 'YYYYMMDDHHmmss', formatTo = 'YYYY-MM-DD HH:mm:ss') {
return (str == '') ? '': moment(str, formatFr).format(formatTo);
}
// async function sleep(miliseconds) {
// let promise = new Promise(function(resolve, reject) {
// setTimeout(function() {
// resolve();
// }, miliseconds);
// });
// await promise;
// }
module.exports = Utility;