935 lines
40 KiB
JavaScript
935 lines
40 KiB
JavaScript
const fs = require('fs');
|
|
const spawn = require('child_process').spawn;
|
|
const exec = require('child_process').exec;
|
|
const events = require('events');
|
|
const URL = require('url')
|
|
const {
|
|
Worker
|
|
} = require('worker_threads');
|
|
const { queryStringToObject, createQueryStringFromObject } = require('./common.js')
|
|
module.exports = function(s,config,lang){
|
|
const {
|
|
asyncSetTimeout,
|
|
} = require('./basic/utils.js')(process.cwd(),config)
|
|
const {
|
|
splitForFFMPEG,
|
|
applyPartialToConfiguration,
|
|
getWarningChangesForMonitor,
|
|
} = require('./ffmpeg/utils.js')(s,config,lang)
|
|
const {
|
|
processKill,
|
|
monitorStop,
|
|
monitorIdle,
|
|
monitorStart,
|
|
monitorRestart,
|
|
monitorAddViewer,
|
|
monitorRemoveViewer,
|
|
getMonitorConfiguration,
|
|
copyMonitorConfiguration,
|
|
checkObjectsInMonitorDetails,
|
|
spawnSubstreamProcess,
|
|
} = require('./monitor/utils.js')(s,config,lang)
|
|
const {
|
|
canAddMoreMonitors,
|
|
sanitizeMonitorConfig,
|
|
isGroupBelowMaxMonitorCount,
|
|
} = require('./checker/utils.js')(s,config,lang)
|
|
s.initiateMonitorObject = function(e){
|
|
if(!s.group[e.ke]){s.group[e.ke]={}};
|
|
if(!s.group[e.ke].activeMonitors){s.group[e.ke].activeMonitors={}}
|
|
if(!s.group[e.ke].activeMonitors[e.mid]){s.group[e.ke].activeMonitors[e.mid]={}}
|
|
const activeMonitor = s.group[e.ke].activeMonitors[e.mid]
|
|
activeMonitor.ke = e.ke
|
|
activeMonitor.mid = e.mid
|
|
if(!activeMonitor.streamIn){activeMonitor.streamIn={}};
|
|
if(!activeMonitor.emitterChannel){activeMonitor.emitterChannel={}};
|
|
if(!activeMonitor.mp4frag){activeMonitor.mp4frag={}};
|
|
if(!activeMonitor.firstStreamChunk){activeMonitor.firstStreamChunk={}};
|
|
if(!activeMonitor.contentWriter){activeMonitor.contentWriter={}};
|
|
if(!activeMonitor.childNodeStreamWriters){activeMonitor.childNodeStreamWriters={}};
|
|
if(!activeMonitor.eventBasedRecording){activeMonitor.eventBasedRecording={}};
|
|
if(!activeMonitor.watch){activeMonitor.watch = []};
|
|
if(!activeMonitor.fixingVideos){activeMonitor.fixingVideos={}};
|
|
// if(!activeMonitor.viewerConnection){activeMonitor.viewerConnection={}};
|
|
// if(!activeMonitor.viewerConnectionCount){activeMonitor.viewerConnectionCount=0};
|
|
if(!activeMonitor.parsedObjects){activeMonitor.parsedObjects={}};
|
|
if(!activeMonitor.detector_motion_count){activeMonitor.detector_motion_count=[]};
|
|
if(!activeMonitor.eventsCounted){activeMonitor.eventsCounted = {}};
|
|
if(!activeMonitor.isStarted){activeMonitor.isStarted = false};
|
|
if(!activeMonitor.pipe4BufferPieces){activeMonitor.pipe4BufferPieces = []};
|
|
if(!activeMonitor.secondaryDetectorOutput){activeMonitor.secondaryDetectorOutput = new events.EventEmitter()};
|
|
if(activeMonitor.delete){clearTimeout(activeMonitor.delete)}
|
|
if(!s.group[e.ke].rawMonitorConfigurations){s.group[e.ke].rawMonitorConfigurations={}}
|
|
if(!activeMonitor.criticalErrors)activeMonitor.criticalErrors = {
|
|
"404": false,
|
|
"453": false,
|
|
"500": false,
|
|
}
|
|
s.onMonitorInitExtensions.forEach(function(extender){
|
|
extender(e)
|
|
})
|
|
}
|
|
s.sendMonitorStatus = function(e){
|
|
if(!e.status || !e.code)console.error(JSON.stringify(e),new Error());
|
|
const groupKey = e.ke
|
|
const monitorId = e.mid || e.id
|
|
const activeMonitor = s.group[groupKey].activeMonitors[monitorId]
|
|
activeMonitor.monitorStatus = `${e.status}`
|
|
activeMonitor.monitorStatusCode = `${e.code}`
|
|
s.tx(Object.assign(e,{f:'monitor_status'}),'GRP_'+e.ke)
|
|
}
|
|
s.getMonitorCpuUsage = function(e,callback){
|
|
if(s.group[e.ke].activeMonitors[e.mid] && s.group[e.ke].activeMonitors[e.mid].spawn){
|
|
const getUsage = function(callback2){
|
|
fs.promises.readFile("/proc/" + s.group[e.ke].activeMonitors[e.mid].spawn.pid + "/stat").then((data) => {
|
|
const elems = data.toString().split(' ');
|
|
const utime = parseInt(elems[13]);
|
|
const stime = parseInt(elems[14]);
|
|
callback2(utime + stime);
|
|
}).catch((err) => {
|
|
s.debugLog(err)
|
|
clearInterval(0)
|
|
})
|
|
}
|
|
getUsage(function(startTime){
|
|
setTimeout(function(){
|
|
getUsage(function(endTime){
|
|
const delta = endTime - startTime;
|
|
const percentage = 100 * (delta / 10000);
|
|
callback(percentage)
|
|
})
|
|
}, 1000)
|
|
})
|
|
}else{
|
|
callback(0)
|
|
}
|
|
}
|
|
s.buildMonitorUrl = function(e,noPath){
|
|
var authd = ''
|
|
var url
|
|
if(e.details.muser&&e.details.muser!==''&&e.host.indexOf('@')===-1) {
|
|
e.username = e.details.muser
|
|
e.password = e.details.mpass
|
|
authd = e.details.muser+':'+e.details.mpass+'@'
|
|
}
|
|
if(e.port==80&&e.details.port_force!=='1'){e.porty=''}else{e.porty=':'+e.port}
|
|
url = e.protocol+'://'+authd+e.host+e.porty
|
|
if(noPath !== true)url += e.path
|
|
return url
|
|
}
|
|
s.cleanMonitorObjectForDatabase = function(dirtyMonitor){
|
|
var cleanMonitor = {}
|
|
var acceptedFields = [
|
|
'mid',
|
|
'ke',
|
|
'name',
|
|
'details',
|
|
'type',
|
|
'ext',
|
|
'protocol',
|
|
'host',
|
|
'path',
|
|
'port',
|
|
'fps',
|
|
'mode',
|
|
'saveDir',
|
|
'tags',
|
|
'width',
|
|
'height'
|
|
];
|
|
Object.keys(dirtyMonitor).forEach(function(key){
|
|
if(acceptedFields.indexOf(key) > -1){
|
|
cleanMonitor[key] = dirtyMonitor[key]
|
|
}
|
|
})
|
|
return cleanMonitor
|
|
}
|
|
s.cleanMonitorObject = function(e){
|
|
var x = {keys:Object.keys(e),ar:{}};
|
|
x.keys.forEach(function(v){
|
|
if(v!=='last_frame'&&v!=='record'&&v!=='spawn'&&v!=='running'&&(v!=='time'&&typeof e[v]!=='function')){x.ar[v]=e[v];}
|
|
});
|
|
return x.ar;
|
|
}
|
|
s.getStreamsDirectory = (monitor) => {
|
|
return s.dir.streams + monitor.ke + '/' + (monitor.mid || monitor.id) + '/'
|
|
}
|
|
s.getRawSnapshotFromMonitor = function(monitor,options){
|
|
return new Promise((resolve,reject) => {
|
|
options = options instanceof Object ? options : {flags: ''}
|
|
s.checkDetails(monitor)
|
|
let isDetectorStream = false
|
|
var inputOptions = []
|
|
var outputOptions = []
|
|
var streamDir = s.dir.streams + monitor.ke + '/' + monitor.mid + '/'
|
|
var url = options.url
|
|
var secondsInward = options.secondsInward || '5'
|
|
if(secondsInward.length === 1 && !isNaN(secondsInward))secondsInward = '0' + secondsInward;
|
|
var dynamicTimeout = (secondsInward * 1000) + 5000;
|
|
if(options.flags)outputOptions.push(options.flags)
|
|
const checkExists = function(streamDir,callback){
|
|
s.fileStats(streamDir,function(err){
|
|
var response = false
|
|
if(err){
|
|
// s.debugLog(err)
|
|
}else{
|
|
response = true
|
|
}
|
|
callback(response)
|
|
})
|
|
}
|
|
const noIconChecks = function(){
|
|
const runExtraction = async function(){
|
|
var sendTempImage = function(){
|
|
fs.readFile(temporaryImageFile,function(err,buffer){
|
|
if(!err){
|
|
resolve({
|
|
screenShot: buffer,
|
|
isStaticFile: false
|
|
})
|
|
fs.rm(temporaryImageFile,function(){})
|
|
}else{
|
|
resolve({
|
|
screenShot: null,
|
|
isStaticFile: false
|
|
})
|
|
}
|
|
})
|
|
}
|
|
try{
|
|
var temporaryImageFile = streamDir + s.gid(5) + '.jpg'
|
|
var iconImageFile = streamDir + 'icon.jpg'
|
|
const snapRawFilters = monitor.details.cust_snap_raw
|
|
if(snapRawFilters)outputOptions.push(snapRawFilters);
|
|
var ffmpegCmd = splitForFFMPEG(`-y -loglevel warning ${isDetectorStream ? '-live_start_index 2' : ''} -re ${inputOptions.join(' ')} -i "${url}" ${outputOptions.join(' ')} -f mjpeg -an -frames:v 1 "${temporaryImageFile}"`)
|
|
try{
|
|
await fs.promises.mkdir(streamDir, {recursive: true}, (err) => {s.debugLog(err)})
|
|
}catch(err){
|
|
console.error(err)
|
|
}
|
|
const snapProcess = new Worker(__dirname + '/cameraThread/snapshot.js', {
|
|
workerData: {
|
|
jsonData: {
|
|
cmd: ffmpegCmd,
|
|
temporaryImageFile: temporaryImageFile,
|
|
iconImageFile: iconImageFile,
|
|
useIcon: options.useIcon,
|
|
rawMonitorConfig: s.group[monitor.ke].rawMonitorConfigurations[monitor.mid],
|
|
},
|
|
ffmpegAbsolutePath: config.ffmpegDir,
|
|
}
|
|
});
|
|
snapProcess.on('message', function(data){
|
|
s.debugLog(data)
|
|
})
|
|
snapProcess.on('error', (data) => {
|
|
console.log(data)
|
|
processKill(snapProcess)
|
|
})
|
|
snapProcess.on('exit', (code) => {
|
|
clearTimeout(snapProcessTimeout)
|
|
sendTempImage()
|
|
})
|
|
var snapProcessTimeout = setTimeout(function(){
|
|
processKill(snapProcess)
|
|
},dynamicTimeout)
|
|
}catch(err){
|
|
console.log(err)
|
|
}
|
|
}
|
|
if(url){
|
|
runExtraction()
|
|
}else{
|
|
checkExists(streamDir + 's.jpg',function(success){
|
|
if(success === false){
|
|
checkExists(streamDir + 'detectorStream.m3u8',function(success){
|
|
if(success === false){
|
|
checkExists(streamDir + 's.m3u8',function(success){
|
|
if(success === false){
|
|
switch(monitor.type){
|
|
case'h264':
|
|
switch(monitor.protocol){
|
|
case'rtsp':
|
|
if(
|
|
monitor.details.rtsp_transport
|
|
&& monitor.details.rtsp_transport !== ''
|
|
&& monitor.details.rtsp_transport !== 'no'
|
|
){
|
|
inputOptions.push('-rtsp_transport ' + monitor.details.rtsp_transport)
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
url = s.buildMonitorUrl(monitor)
|
|
}else{
|
|
outputOptions.push(`-ss 00:00:${secondsInward}`)
|
|
url = streamDir + 's.m3u8'
|
|
}
|
|
runExtraction()
|
|
})
|
|
}else{
|
|
isDetectorStream = true
|
|
outputOptions.push(`-ss 00:00:${secondsInward}`)
|
|
url = streamDir + 'detectorStream.m3u8'
|
|
runExtraction()
|
|
}
|
|
})
|
|
}else{
|
|
fs.promises.readFile(streamDir + 's.jpg').then(function(snapBuffer){
|
|
resolve({
|
|
screenShot: snapBuffer,
|
|
isStaticFile: true
|
|
})
|
|
}).catch(() => {
|
|
sendTempImage()
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
if(options.useIcon === true){
|
|
checkExists(streamDir + 'icon.jpg',function(success){
|
|
if(success === false){
|
|
noIconChecks()
|
|
}else{
|
|
var snapBuffer = fs.readFileSync(streamDir + 'icon.jpg')
|
|
resolve({
|
|
screenShot: snapBuffer,
|
|
isStaticFile: false
|
|
})
|
|
}
|
|
})
|
|
}else{
|
|
noIconChecks()
|
|
}
|
|
})
|
|
}
|
|
s.mergeDetectorBufferChunks = function(monitor,callback){
|
|
return new Promise((resolve,reject) => {
|
|
var pathDir = s.dir.streams+monitor.ke+'/'+monitor.id+'/'
|
|
var mergedFile = s.formattedTime()+'.mp4'
|
|
var mergedFilepath = pathDir+mergedFile
|
|
fs.readdir(pathDir,function(err,streamDirItems){
|
|
var items = []
|
|
var copiedItems = []
|
|
var videoLength = s.group[monitor.ke].rawMonitorConfigurations[monitor.id].details.detector_send_video_length
|
|
if(!videoLength || videoLength === '')videoLength = '10'
|
|
if(videoLength.length === 1)videoLength = '0' + videoLength
|
|
var createMerged = function(copiedItems){
|
|
var allts = pathDir+items.join('_')
|
|
s.fileStats(allts,function(err,stats){
|
|
if(err){
|
|
//not exist
|
|
var cat = 'cat '+copiedItems.join(' ')+' > '+allts
|
|
exec(cat,function(){
|
|
var merger = spawn(config.ffmpegDir,splitForFFMPEG(('-re -i '+allts+' -acodec copy -vcodec copy -t 00:00:' + videoLength + ' '+pathDir+mergedFile)))
|
|
merger.stderr.on('data',function(data){
|
|
s.userLog(monitor,{type:"Buffer Merge",msg:data.toString()})
|
|
})
|
|
merger.on('close',function(){
|
|
s.file('delete',allts)
|
|
copiedItems.forEach(function(copiedItem){
|
|
s.file('delete',copiedItem)
|
|
})
|
|
setTimeout(function(){
|
|
s.file('delete',mergedFilepath)
|
|
},1000 * 60 * 3)
|
|
delete(merger)
|
|
if(callback)callback(mergedFilepath,mergedFile)
|
|
resolve({
|
|
filePath: mergedFilepath,
|
|
filename: mergedFile,
|
|
})
|
|
})
|
|
})
|
|
}else{
|
|
//file exist
|
|
if(callback)callback(mergedFilepath,mergedFile)
|
|
resolve({
|
|
filePath: mergedFilepath,
|
|
filename: mergedFile,
|
|
})
|
|
}
|
|
})
|
|
}
|
|
streamDirItems.forEach(function(filename){
|
|
if(filename.indexOf('detectorStream') > -1 && filename.indexOf('.m3u8') === -1){
|
|
items.push(filename)
|
|
}
|
|
})
|
|
items.sort()
|
|
// items = items.slice(items.length - 5,items.length)
|
|
items.forEach(function(filename){
|
|
try{
|
|
var tempFilename = filename.split('.')
|
|
tempFilename[0] = tempFilename[0] + 'm'
|
|
tempFilename = tempFilename.join('.')
|
|
var tempWriteStream = fs.createWriteStream(pathDir+tempFilename)
|
|
tempWriteStream.on('finish', function(){
|
|
copiedItems.push(pathDir+tempFilename)
|
|
if(copiedItems.length === items.length){
|
|
createMerged(copiedItems.sort())
|
|
}
|
|
})
|
|
fs.createReadStream(pathDir+filename).pipe(tempWriteStream)
|
|
}catch(err){
|
|
|
|
}
|
|
})
|
|
})
|
|
})
|
|
}
|
|
s.mergeRecordedVideos = function(videoRows,groupKey,callback){
|
|
var tempDir = s.dir.streams + groupKey + '/'
|
|
var pathDir = s.dir.fileBin + groupKey + '/'
|
|
var streamDirItems = fs.readdirSync(pathDir)
|
|
var items = []
|
|
var mergedFile = []
|
|
videoRows.forEach(function(video){
|
|
var filepath = s.getVideoDirectory(video) + s.formattedTime(video.time) + '.' + video.ext
|
|
if(
|
|
filepath.indexOf('.mp4') > -1
|
|
// || filename.indexOf('.webm') > -1
|
|
){
|
|
mergedFile.push(s.formattedTime(video.time))
|
|
items.push(filepath)
|
|
}
|
|
})
|
|
mergedFile.sort()
|
|
mergedFile = mergedFile.join('_') + '.mp4'
|
|
var mergedFilepath = pathDir + mergedFile
|
|
var mergedRawFilepath = pathDir + 'raw_' + mergedFile
|
|
items.sort()
|
|
s.fileStats(mergedFilepath,function(err,stats){
|
|
if(err){
|
|
//not exist
|
|
var tempScriptPath = tempDir + s.gid(5) + '.sh'
|
|
var cat = 'cat '+items.join(' ')+' > '+mergedRawFilepath
|
|
fs.writeFileSync(tempScriptPath,cat,'utf8')
|
|
exec('sh ' + tempScriptPath,function(){
|
|
s.userLog({
|
|
ke: groupKey,
|
|
mid: '$USER'
|
|
},{type:lang['Videos Merge'],msg:mergedFile})
|
|
var merger = spawn(config.ffmpegDir,splitForFFMPEG(('-re -loglevel warning -i ' + mergedRawFilepath + ' -acodec copy -vcodec copy ' + mergedFilepath)))
|
|
merger.stderr.on('data',function(data){
|
|
s.userLog({
|
|
ke: groupKey,
|
|
mid: '$USER'
|
|
},{type:lang['Videos Merge'],msg:data.toString()})
|
|
})
|
|
merger.on('close',function(){
|
|
s.file('delete',mergedRawFilepath)
|
|
s.file('delete',tempScriptPath)
|
|
setTimeout(function(){
|
|
s.fileStats(mergedFilepath,function(err,stats){
|
|
if(!err)s.file('delete',mergedFilepath)
|
|
})
|
|
},1000 * 60 * 60 * 24)
|
|
delete(merger)
|
|
callback(mergedFilepath,mergedFile)
|
|
})
|
|
})
|
|
}else{
|
|
//file exist
|
|
callback(mergedFilepath,mergedFile)
|
|
}
|
|
})
|
|
return items
|
|
}
|
|
s.cameraControlOptionsFromUrl = function(e,monitorConfig){
|
|
URLobject = URL.parse(e)
|
|
if(monitorConfig.details.control_url_method === 'ONVIF' && monitorConfig.details.control_base_url === ''){
|
|
if(monitorConfig.details.onvif_port === ''){
|
|
monitorConfig.details.onvif_port = 8000
|
|
}
|
|
URLobject.port = monitorConfig.details.onvif_port
|
|
}else if(!URLobject.port){
|
|
URLobject.port = 80
|
|
}
|
|
const options = {
|
|
host: URLobject.hostname,
|
|
port: URLobject.port,
|
|
method: monitorConfig.details.control_url_method
|
|
}
|
|
const queryStringObjects = queryStringToObject(URLobject.query || "")
|
|
if (queryStringObjects && queryStringObjects.postData) {
|
|
options.postData = decodeURIComponent(queryStringObjects.postData)
|
|
options.path = URLobject.pathname + '?' + decodeURIComponent(createQueryStringFromObject(Object.assign(queryStringObjects,{postData: null})))
|
|
} else if(URLobject.query){
|
|
options.path = URLobject.pathname + '?' + URLobject.query
|
|
} else {
|
|
options.path = URLobject.pathname
|
|
}
|
|
if(URLobject.username&&URLobject.password){
|
|
options.username = URLobject.username
|
|
options.password = URLobject.password
|
|
options.auth=URLobject.username+':'+URLobject.password
|
|
}else if(URLobject.auth){
|
|
var auth = URLobject.auth.split(':')
|
|
options.auth=URLobject.auth
|
|
options.username = auth[0]
|
|
options.password = auth[1]
|
|
}
|
|
return options
|
|
}
|
|
s.cameraSendSnapshot = async (e,options) => {
|
|
options = Object.assign({
|
|
flags: '-s 500x500'
|
|
},options || {})
|
|
s.checkDetails(e)
|
|
if(e.ke && config.doSnapshot === true){
|
|
if(s.group[e.ke] && s.group[e.ke].rawMonitorConfigurations && s.group[e.ke].rawMonitorConfigurations[e.mid] && s.group[e.ke].rawMonitorConfigurations[e.mid].mode !== 'stop'){
|
|
async function getRaw(){
|
|
var pathDir = s.dir.streams+e.ke+'/'+e.mid+'/'
|
|
const {screenShot, isStaticFile} = await s.getRawSnapshotFromMonitor(s.group[e.ke].rawMonitorConfigurations[e.mid],options)
|
|
if(screenShot){
|
|
s.tx({
|
|
f: 'monitor_snapshot',
|
|
snapshot: screenShot.toString('base64'),
|
|
snapshot_format: 'b64',
|
|
mid: e.mid,
|
|
ke: e.ke
|
|
},'GRP_'+e.ke)
|
|
}else{
|
|
s.debugLog('Damaged Snapshot Data')
|
|
s.tx({f:'monitor_snapshot',snapshot:e.mon.name,snapshot_format:'plc',mid:e.mid,ke:e.ke},'GRP_'+e.ke)
|
|
}
|
|
}
|
|
if(s.group[e.ke].activeMonitors[e.mid].onvifConnection){
|
|
try{
|
|
const screenShot = await s.getSnapshotFromOnvif({
|
|
ke: e.ke,
|
|
mid: e.mid,
|
|
});
|
|
s.tx({
|
|
f: 'monitor_snapshot',
|
|
snapshot: screenShot.toString('base64'),
|
|
snapshot_format: 'b64',
|
|
mid: e.mid,
|
|
ke: e.ke
|
|
},'GRP_'+e.ke)
|
|
}catch(err){
|
|
s.debugLog(err)
|
|
await getRaw()
|
|
}
|
|
}else{
|
|
await getRaw()
|
|
}
|
|
}else{
|
|
s.tx({f:'monitor_snapshot',snapshot:'Disabled',snapshot_format:'plc',mid:e.mid,ke:e.ke},'GRP_'+e.ke)
|
|
}
|
|
}else{
|
|
s.tx({f:'monitor_snapshot',snapshot:e.mon.name,snapshot_format:'plc',mid:e.mid,ke:e.ke},'GRP_'+e.ke)
|
|
}
|
|
}
|
|
s.getCameraSnapshot = async (e,options) => {
|
|
const getDefaultImage = async () => {
|
|
return await fs.promises.readFile(config.defaultMjpeg)
|
|
}
|
|
options = Object.assign({
|
|
flags: '-s 500x500'
|
|
},options || {})
|
|
if(e.ke && config.doSnapshot === true){
|
|
if(s.group[e.ke] && s.group[e.ke].rawMonitorConfigurations && s.group[e.ke].rawMonitorConfigurations[e.mid] && s.group[e.ke].rawMonitorConfigurations[e.mid].mode !== 'stop'){
|
|
var pathDir = s.dir.streams+e.ke+'/'+e.mid+'/'
|
|
const {screenShot, isStaticFile} = await s.getRawSnapshotFromMonitor(s.group[e.ke].rawMonitorConfigurations[e.mid],options)
|
|
if(screenShot){
|
|
return screenShot
|
|
}else{
|
|
return await getDefaultImage()
|
|
}
|
|
}else{
|
|
return await getDefaultImage()
|
|
}
|
|
}else{
|
|
return await getDefaultImage()
|
|
}
|
|
}
|
|
s.addOrEditMonitor = async function(form,callback,user){
|
|
var endData = {
|
|
ok: false
|
|
}
|
|
if(!form.mid || !s.timeReady){
|
|
endData.msg = !s.timeReady ? lang.notReadyYet : lang['No Monitor ID Present in Form']
|
|
if(callback)callback(endData);
|
|
return
|
|
}
|
|
form.mid = form.mid.replace(/[^\w\s]/gi,'').replace(/ /g,'')
|
|
const selectResponse = await s.knexQueryPromise({
|
|
action: "select",
|
|
columns: "*",
|
|
table: "Monitors",
|
|
where: [
|
|
['ke','=',form.ke],
|
|
['mid','=',form.mid],
|
|
]
|
|
});
|
|
const monitorExists = selectResponse.rows && selectResponse.rows[0];
|
|
const systemMax = canAddMoreMonitors();
|
|
const groupMax = isGroupBelowMaxMonitorCount(form.ke);
|
|
const canDoTheDo = systemMax && groupMax;
|
|
var affectMonitor = false
|
|
var monitorQuery = {}
|
|
var txData = {
|
|
f: 'monitor_edit',
|
|
mid: form.mid,
|
|
ke: form.ke,
|
|
mon: form
|
|
}
|
|
// auto correct
|
|
const {
|
|
configPartial,
|
|
warnings,
|
|
probeResponse,
|
|
probeStreams,
|
|
} = await getWarningChangesForMonitor(form)
|
|
applyPartialToConfiguration(form,configPartial)
|
|
form = s.cleanMonitorObjectForDatabase(form)
|
|
//
|
|
if(monitorExists){
|
|
txData.new = false
|
|
Object.keys(form).forEach(function(v){
|
|
if(
|
|
form[v] !== undefined &&
|
|
form[v] !== `undefined` &&
|
|
form[v] !== null &&
|
|
form[v] !== `null` &&
|
|
form[v] !== false &&
|
|
form[v] !== `false`
|
|
){
|
|
if(form[v] instanceof Object){
|
|
form[v] = s.s(form[v])
|
|
}
|
|
monitorQuery[v] = form[v]
|
|
}
|
|
})
|
|
s.userLog(form,{type:'Monitor Updated',msg:'by user : '+user.uid})
|
|
endData.msg = user.lang['Monitor Updated by user']+' : '+user.uid
|
|
s.knexQuery({
|
|
action: "update",
|
|
table: "Monitors",
|
|
update: monitorQuery,
|
|
where: [
|
|
['ke','=',form.ke],
|
|
['mid','=',form.mid],
|
|
]
|
|
})
|
|
affectMonitor = true
|
|
}else if(canDoTheDo){
|
|
txData.new = true
|
|
Object.keys(form).forEach(function(v){
|
|
if(form[v] && form[v] !== ''){
|
|
if(form[v] instanceof Object){
|
|
form[v] = s.s(form[v])
|
|
}
|
|
monitorQuery[v] = form[v]
|
|
}
|
|
})
|
|
s.userLog(form,{type:'Monitor Added',msg:'by user : '+user.uid})
|
|
endData.msg = user.lang['Monitor Added by user']+' : '+user.uid
|
|
s.knexQuery({
|
|
action: "insert",
|
|
table: "Monitors",
|
|
insert: monitorQuery
|
|
})
|
|
affectMonitor = true
|
|
}else{
|
|
txData.f = 'monitor_edit_failed'
|
|
txData.ff = 'max_reached'
|
|
endData.msg = !systemMax ? user.lang.monitorEditFailedMaxReachedUnactivated : user.lang.monitorEditFailedMaxReached
|
|
}
|
|
if(affectMonitor === true){
|
|
form.details = JSON.parse(form.details)
|
|
endData.ok = true
|
|
s.initiateMonitorObject({mid:form.mid,ke:form.ke})
|
|
s.group[form.ke].rawMonitorConfigurations[form.mid] = s.cleanMonitorObject(form)
|
|
if(form.mode === 'stop'){
|
|
await s.camera('stop',form)
|
|
}else{
|
|
let monitorConfig = copyMonitorConfiguration(form.ke,form.mid)
|
|
await s.camera('stop',monitorConfig);
|
|
await asyncSetTimeout(2500)
|
|
await s.camera(form.mode,monitorConfig);
|
|
}
|
|
s.tx(txData,'STR_'+form.ke)
|
|
}
|
|
s.tx(txData,'GRP_'+form.ke)
|
|
if(callback)callback(!endData.ok,endData);
|
|
if(monitorExists || canDoTheDo){
|
|
let monitorConfig = copyMonitorConfiguration(form.ke,form.mid)
|
|
s.onMonitorSaveExtensions.forEach(function(extender){
|
|
extender(monitorConfig,form,endData)
|
|
})
|
|
}
|
|
return endData
|
|
}
|
|
s.getStreamWaitTimeout = function (groupId, monitorId) {
|
|
const monitorConfig = s.group[groupId].rawMonitorConfigurations[monitorId];
|
|
var streamType = monitorConfig.details.stream_type;
|
|
var hls_time;
|
|
if (streamType === 'useSubstream') {
|
|
streamType = monitorConfig.details.substream.output.stream_type;
|
|
hls_time = monitorConfig.details.substream.output.hls_time;
|
|
} else {
|
|
hls_time = monitorConfig.details.hls_time;
|
|
}
|
|
return streamType == 'hls' && hls_time != ''
|
|
? (parseInt(hls_time) * 1000) + 10000
|
|
: 10000;
|
|
}
|
|
s.toggleSubstreamAndWaitForOutput = async function (groupId, monitorId) {
|
|
const monitorConfig = s.group[groupId].rawMonitorConfigurations[monitorId];
|
|
const streamType = monitorConfig.details.stream_type;
|
|
if (streamType === 'useSubstream') {
|
|
const activeMonitor = s.group[groupId].activeMonitors[monitorId];
|
|
if (!activeMonitor.subStreamProcess) {
|
|
spawnSubstreamProcess(monitorConfig);
|
|
}
|
|
if (!activeMonitor.subStreamOutputReady) {
|
|
const checkTime = 250;
|
|
var monitorTimeout = s.getStreamWaitTimeout(groupId, monitorId);
|
|
return await new Promise((resolve, reject) => {
|
|
let totalTime = 0;
|
|
const timer = setInterval(function () {
|
|
totalTime += checkTime;
|
|
if (activeMonitor.subStreamOutputReady || totalTime >= monitorTimeout) {
|
|
clearInterval(timer);
|
|
resolve(activeMonitor.subStreamOutputReady);
|
|
}
|
|
}, checkTime);
|
|
});
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
s.camera = async (selectedMode,e,cn) => {
|
|
// e = monitor object
|
|
// cn = socket connection or callback or options (depends on function chosen)
|
|
if(cn && cn.ke && !e.ke){e.ke = cn.ke}
|
|
const groupKey = e.ke
|
|
const monitorId = e.mid || e.id
|
|
e.functionMode = selectedMode
|
|
if(!e.mode){e.mode = selectedMode}
|
|
s.checkDetails(e)
|
|
s.initiateMonitorObject({ke:e.ke,mid:monitorId})
|
|
checkObjectsInMonitorDetails(e)
|
|
switch(e.functionMode){
|
|
case'watch_on':
|
|
monitorAddViewer(e,cn)
|
|
break;
|
|
case'watch_off':
|
|
monitorRemoveViewer(e,cn)
|
|
break;
|
|
case'restart':
|
|
await monitorRestart(e)
|
|
break;
|
|
case'idle':case'stop':
|
|
await monitorStop(e)
|
|
if(e.functionMode === 'idle'){
|
|
monitorIdle(e)
|
|
}
|
|
break;
|
|
case'start':case'record':
|
|
await monitorStart(e)
|
|
break;
|
|
default:
|
|
console.log('No s.camera execute : ',selectedMode)
|
|
break;
|
|
}
|
|
if(typeof cn === 'function'){cn()}
|
|
}
|
|
//
|
|
s.activateMonitorStates = function(groupKey,stateName,user,callback){
|
|
var endData = {
|
|
ok: false
|
|
}
|
|
s.findPreset([groupKey,'monitorStates',stateName],function(notFound,preset){
|
|
if(notFound === false){
|
|
var sqlQuery = 'SELECT * FROM Monitors WHERE ke=? AND '
|
|
var monitorQuery = []
|
|
var monitorPresets = {}
|
|
preset.details.monitors.forEach(function(monitor){
|
|
const whereConditions = {}
|
|
if(monitorQuery.length === 0){
|
|
whereConditions.ke = groupKey
|
|
monitorQuery.push(['ke','=',groupKey])
|
|
}else{
|
|
monitorQuery.push(['or','ke','=',groupKey])
|
|
}
|
|
monitorQuery.push(['mid','=',monitor.mid])
|
|
monitorPresets[monitor.mid] = monitor
|
|
})
|
|
s.knexQuery({
|
|
action: "select",
|
|
columns: "*",
|
|
table: "Monitors",
|
|
where: monitorQuery
|
|
},function(err,monitors){
|
|
if(monitors && monitors[0]){
|
|
monitors.forEach(function(monitor){
|
|
s.checkDetails(monitor)
|
|
s.checkDetails(monitorPresets[monitor.mid])
|
|
var monitorPreset = monitorPresets[monitor.mid]
|
|
monitorPreset.details = Object.assign(monitor.details,monitorPreset.details)
|
|
monitor = s.cleanMonitorObjectForDatabase(Object.assign(monitor,monitorPreset))
|
|
monitor.details = JSON.stringify(monitor.details)
|
|
s.addOrEditMonitor(Object.assign({},monitor),null,user)
|
|
})
|
|
endData.ok = true
|
|
s.tx({f:'change_group_state',ke:groupKey,name:stateName},'GRP_'+groupKey)
|
|
callback(endData)
|
|
}else{
|
|
endData.msg = user.lang['State Configuration has no monitors associated']
|
|
callback(endData)
|
|
}
|
|
})
|
|
}else{
|
|
endData.msg = user.lang['State Configuration Not Found']
|
|
callback(endData)
|
|
}
|
|
})
|
|
}
|
|
s.getMonitorRestrictions = (permissions,monitorId) => {
|
|
const monitorRestrictions = []
|
|
if(
|
|
!monitorId &&
|
|
permissions.sub &&
|
|
permissions.monitors &&
|
|
permissions.allmonitors !== '1'
|
|
){
|
|
try{
|
|
permissions.monitors = s.parseJSON(permissions.monitors)
|
|
permissions.monitors.forEach(function(v,n){
|
|
if(n === 0){
|
|
monitorRestrictions.push(['mid','=',v])
|
|
}else{
|
|
monitorRestrictions.push(['or','mid','=',v])
|
|
}
|
|
})
|
|
console.log(monitorRestrictions)
|
|
}catch(er){
|
|
}
|
|
}else if(
|
|
monitorId && (
|
|
!permissions.sub ||
|
|
permissions.allmonitors !== '0' ||
|
|
permissions.monitors.indexOf(monitorId) >- 1
|
|
)
|
|
){
|
|
monitorRestrictions.push(['mid','=',monitorId])
|
|
}else if(
|
|
!monitorId &&
|
|
permissions.sub &&
|
|
permissions.allmonitors !== '0'
|
|
){}
|
|
return monitorRestrictions
|
|
}
|
|
s.checkPermission = (user) => {
|
|
// provide "user" object given from "s.auth"
|
|
const isSubAccount = !!user.details.sub
|
|
const isApiKey = !user.login_type;
|
|
const isSessionKey = user.isSessionKey;
|
|
const response = {
|
|
isSubAccount,
|
|
hasAllPermissions: isSubAccount && user.details.allmonitors === '1',
|
|
isRestricted: isSubAccount && user.details.allmonitors !== '1',
|
|
isRestrictedApiKey: false,
|
|
apiKeyPermissions: {},
|
|
userPermissions: {},
|
|
}
|
|
const permissions = user.permissions
|
|
const details = user.details;
|
|
[
|
|
'auth_socket',
|
|
'get_monitors',
|
|
'edit_monitors',
|
|
'control_monitors',
|
|
'get_logs',
|
|
'watch_stream',
|
|
'watch_snapshot',
|
|
'watch_videos',
|
|
'delete_videos',
|
|
].forEach((key) => {
|
|
const permissionOff = !isSessionKey && isApiKey && permissions[key] !== '1';
|
|
response.apiKeyPermissions[key] = isSessionKey || permissions[key] === '1';
|
|
response.apiKeyPermissions[`${key}_disallowed`] = permissionOff;
|
|
response.isRestrictedApiKey = response.isRestrictedApiKey || permissionOff;
|
|
});
|
|
// Base Level Permissions
|
|
// allmonitors : All Monitors and Privileges
|
|
// monitor_create : Can Create and Delete Monitors
|
|
// user_change : Can Change User Settings
|
|
// view_logs : Can View Logs
|
|
[
|
|
'allmonitors',
|
|
'monitor_create',
|
|
'user_change',
|
|
'view_logs',
|
|
].forEach((key) => {
|
|
response.userPermissions[key] = details[key] === '1' || !details[key];
|
|
response.userPermissions[`${key}_disallowed`] = details[key] === '0';
|
|
});
|
|
return response
|
|
}
|
|
s.getMonitorsPermitted = (userDetails,monitorId,permissionTarget) => {
|
|
const monitorRestrictions = []
|
|
const monitors = {}
|
|
permissionTarget = permissionTarget || 'monitors'
|
|
const permissionSet = s.parseJSON(userDetails[permissionTarget]) || []
|
|
// const viewOnlyCheck = permissionTarget === 'monitors'
|
|
function setMonitorPermissions(mid){
|
|
// monitors : Can View Monitor
|
|
// monitor_edit : Can Edit Monitor (Delete as well)
|
|
// video_view : Can View Videos and Events
|
|
// video_delete : Can Delete Videos and Events
|
|
[
|
|
'monitors',
|
|
'monitor_edit',
|
|
'video_view',
|
|
'video_delete',
|
|
].forEach((key) => {
|
|
monitors[`${mid}_${key}`] = userDetails[key] && userDetails[key].indexOf(mid) > -1 || false;
|
|
});
|
|
return true
|
|
}
|
|
function addToQuery(mid,n){
|
|
if(n === 0){
|
|
monitorRestrictions.push(['mid','=',mid])
|
|
}else{
|
|
monitorRestrictions.push(['or','mid','=',mid])
|
|
}
|
|
};
|
|
if(
|
|
!monitorId &&
|
|
userDetails.sub &&
|
|
permissionSet &&
|
|
userDetails.allmonitors !== '1'
|
|
){
|
|
try{
|
|
permissionSet.forEach(function(v,n){
|
|
setMonitorPermissions(v)
|
|
addToQuery(v,n)
|
|
})
|
|
}catch(err){
|
|
s.debugLog(err)
|
|
}
|
|
}else if(
|
|
monitorId && (
|
|
!userDetails.sub ||
|
|
userDetails.allmonitors !== '0' ||
|
|
permissionSet.indexOf(monitorId) >- 1
|
|
)
|
|
){
|
|
setMonitorPermissions(monitorId)
|
|
addToQuery(monitorId,0)
|
|
}
|
|
return {
|
|
monitorPermissions: monitors,
|
|
// queryConditions
|
|
monitorRestrictions: monitorRestrictions,
|
|
}
|
|
}
|
|
}
|