From 0a509561bb0dd1ed7adfc41d90fdce78966ec8e0 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 13 Nov 2018 20:53:14 -0500 Subject: [PATCH 0001/1203] allow libavresample-dev --- distros/ubuntu1604/control | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/distros/ubuntu1604/control b/distros/ubuntu1604/control index 68bc1757f..475214f10 100644 --- a/distros/ubuntu1604/control +++ b/distros/ubuntu1604/control @@ -10,7 +10,7 @@ Build-Depends: debhelper (>= 9), dh-systemd, python-sphinx | python3-sphinx, apa ,libavcodec-dev (>= 6:10~) ,libavformat-dev (>= 6:10~) ,libavutil-dev (>= 6:10~) - ,libswresample-dev + ,libswresample-dev | libavresample-dev ,libswscale-dev (>= 6:10~) ,ffmpeg | libav-tools ,net-tools From 312dab2c2050101fc02be886bc750a3e1aeafc1b Mon Sep 17 00:00:00 2001 From: Javier Gonzalez Date: Sat, 20 Apr 2019 17:14:43 +0200 Subject: [PATCH 0002/1203] Fixing video export view --- web/skins/classic/views/video.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/skins/classic/views/video.php b/web/skins/classic/views/video.php index aab9278d6..ca4c46f4b 100644 --- a/web/skins/classic/views/video.php +++ b/web/skins/classic/views/video.php @@ -46,7 +46,7 @@ if ( isset($_REQUEST['scale']) ) else $scale = reScale(SCALE_BASE, $event['DefaultScale'], ZM_WEB_DEFAULT_SCALE); -$Event = new Event($event['Id']); +$Event = new ZM\Event($event['Id']); $eventPath = $Event->Path(); $videoFormats = array(); From cfca67f6633b22395d96a51a9937d0409dc8e355 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 7 Jun 2019 14:07:23 -0400 Subject: [PATCH 0003/1203] spacing and quotes --- web/ajax/stream.php | 53 +++++++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/web/ajax/stream.php b/web/ajax/stream.php index f37012e54..910b18cb5 100644 --- a/web/ajax/stream.php +++ b/web/ajax/stream.php @@ -21,38 +21,38 @@ if ( sem_acquire($semaphore,1) !== false ) { } $localSocketFile = ZM_PATH_SOCKS.'/zms-'.sprintf('%06d',$_REQUEST['connkey']).'w.sock'; - if ( file_exists( $localSocketFile ) ) { + if ( file_exists($localSocketFile) ) { ZM\Warning("sock file $localSocketFile already exists?! Is someone else talking to zms?"); // They could be. We can maybe have concurrent requests from a browser. } - if ( !socket_bind( $socket, $localSocketFile ) ) { - ajaxError("socket_bind( $localSocketFile ) failed: ".socket_strerror(socket_last_error()) ); + if ( !socket_bind($socket, $localSocketFile) ) { + ajaxError("socket_bind( $localSocketFile ) failed: ".socket_strerror(socket_last_error())); } switch ( $_REQUEST['command'] ) { case CMD_VARPLAY : - ZM\Logger::Debug( 'Varplaying to '.$_REQUEST['rate'] ); - $msg = pack( 'lcn', MSG_CMD, $_REQUEST['command'], $_REQUEST['rate']+32768 ); + ZM\Logger::Debug('Varplaying to '.$_REQUEST['rate']); + $msg = pack('lcn', MSG_CMD, $_REQUEST['command'], $_REQUEST['rate']+32768); break; case CMD_ZOOMIN : - ZM\Logger::Debug( 'Zooming to '.$_REQUEST['x'].','.$_REQUEST['y'] ); - $msg = pack( 'lcnn', MSG_CMD, $_REQUEST['command'], $_REQUEST['x'], $_REQUEST['y'] ); + ZM\Logger::Debug('Zooming to '.$_REQUEST['x'].','.$_REQUEST['y']); + $msg = pack('lcnn', MSG_CMD, $_REQUEST['command'], $_REQUEST['x'], $_REQUEST['y']); break; case CMD_PAN : - ZM\Logger::Debug( 'Panning to '.$_REQUEST['x'].','.$_REQUEST['y'] ); - $msg = pack( 'lcnn', MSG_CMD, $_REQUEST['command'], $_REQUEST['x'], $_REQUEST['y'] ); + ZM\Logger::Debug('Panning to '.$_REQUEST['x'].','.$_REQUEST['y']); + $msg = pack('lcnn', MSG_CMD, $_REQUEST['command'], $_REQUEST['x'], $_REQUEST['y']); break; case CMD_SCALE : - ZM\Logger::Debug( 'Scaling to '.$_REQUEST['scale'] ); - $msg = pack( 'lcn', MSG_CMD, $_REQUEST['command'], $_REQUEST['scale'] ); + ZM\Logger::Debug('Scaling to '.$_REQUEST['scale']); + $msg = pack('lcn', MSG_CMD, $_REQUEST['command'], $_REQUEST['scale']); break; case CMD_SEEK : - ZM\Logger::Debug( 'Seeking to '.$_REQUEST['offset'] ); - $msg = pack( 'lcN', MSG_CMD, $_REQUEST['command'], $_REQUEST['offset'] ); + ZM\Logger::Debug('Seeking to '.$_REQUEST['offset']); + $msg = pack('lcN', MSG_CMD, $_REQUEST['command'], $_REQUEST['offset']); break; default : ZM\Logger::Debug('Sending command ' . $_REQUEST['command']); - $msg = pack( 'lc', MSG_CMD, $_REQUEST['command'] ); + $msg = pack('lc', MSG_CMD, $_REQUEST['command']); break; } @@ -60,7 +60,8 @@ if ( sem_acquire($semaphore,1) !== false ) { // Pi can take up to 3 seconds for zms to start up. $max_socket_tries = 1000; // FIXME This should not exceed web_ajax_timeout - while ( !file_exists($remSockFile) && $max_socket_tries-- ) { //sometimes we are too fast for our own good, if it hasn't been setup yet give it a second. + while ( !file_exists($remSockFile) && $max_socket_tries-- ) { + //sometimes we are too fast for our own good, if it hasn't been setup yet give it a second. // WHY? We will just send another one... // ANSWER: Because otherwise we get a log of errors logged @@ -71,27 +72,27 @@ if ( sem_acquire($semaphore,1) !== false ) { if ( !file_exists($remSockFile) ) { ajaxError("Socket $remSockFile does not exist. This file is created by zms, and since it does not exist, either zms did not run, or zms exited early. Please check your zms logs and ensure that CGI is enabled in apache and check that the PATH_ZMS is set correctly. Make sure that ZM is actually recording. If you are trying to view a live stream and the capture process (zmc) is not running then zms will exit. Please go to http://zoneminder.readthedocs.io/en/latest/faq.html#why-can-t-i-see-streamed-images-when-i-can-see-stills-in-the-zone-window-etc for more information."); } else { - if ( !@socket_sendto( $socket, $msg, strlen($msg), 0, $remSockFile ) ) { - ajaxError( "socket_sendto( $remSockFile ) failed: ".socket_strerror(socket_last_error()) ); + if ( !@socket_sendto($socket, $msg, strlen($msg), 0, $remSockFile) ) { + ajaxError("socket_sendto( $remSockFile ) failed: ".socket_strerror(socket_last_error())); } } - $rSockets = array( $socket ); + $rSockets = array($socket); $wSockets = NULL; $eSockets = NULL; $timeout = MSG_TIMEOUT - ( time() - $start_time ); - $numSockets = socket_select( $rSockets, $wSockets, $eSockets, intval($timeout/1000), ($timeout%1000)*1000 ); + $numSockets = socket_select($rSockets, $wSockets, $eSockets, intval($timeout/1000), ($timeout%1000)*1000); if ( $numSockets === false ) { - ZM\Error('socket_select failed: ' . socket_strerror(socket_last_error()) ); - ajaxError( 'socket_select failed: '.socket_strerror(socket_last_error()) ); + ZM\Error('socket_select failed: ' . socket_strerror(socket_last_error())); + ajaxError('socket_select failed: '.socket_strerror(socket_last_error())); } else if ( $numSockets < 0 ) { - ZM\Error( "Socket closed $remSockFile" ); - ajaxError( "Socket closed $remSockFile" ); + ZM\Error("Socket closed $remSockFile"); + ajaxError("Socket closed $remSockFile"); } else if ( $numSockets == 0 ) { - ZM\Error( "Timed out waiting for msg $remSockFile" ); + ZM\Error("Timed out waiting for msg $remSockFile"); socket_set_nonblock($socket); #ajaxError("Timed out waiting for msg $remSockFile"); } else if ( $numSockets > 0 ) { @@ -101,7 +102,7 @@ if ( sem_acquire($semaphore,1) !== false ) { } } - switch( $nbytes = @socket_recvfrom( $socket, $msg, MSG_DATA_SIZE, 0, $remSockFile ) ) { + switch( $nbytes = @socket_recvfrom($socket, $msg, MSG_DATA_SIZE, 0, $remSockFile) ) { case -1 : ajaxError("socket_recvfrom( $remSockFile ) failed: ".socket_strerror(socket_last_error())); break; @@ -157,7 +158,7 @@ if ( sem_acquire($semaphore,1) !== false ) { } sem_release($semaphore); } else { - ZM\Logger::Debug("Couldn't get semaphore"); + ZM\Logger::Debug('Couldn\'t get semaphore'); ajaxResponse(array()); } From 4da5c52cd254dc56cdbcb1d0c7ad95bc9bd15252 Mon Sep 17 00:00:00 2001 From: tolland Date: Wed, 19 Jun 2019 20:18:38 +0000 Subject: [PATCH 0004/1203] remove extra px in svg tag --- web/skins/classic/views/montage.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/skins/classic/views/montage.php b/web/skins/classic/views/montage.php index 6527a6e8f..aac008dbc 100644 --- a/web/skins/classic/views/montage.php +++ b/web/skins/classic/views/montage.php @@ -269,7 +269,7 @@ foreach ( $monitors as $monitor ) { } // end foreach Zone ?> - + '; From edd52e7fbf407ac11441a7e59bd206f9cb91e34f Mon Sep 17 00:00:00 2001 From: tolland Date: Thu, 20 Jun 2019 12:46:04 +0000 Subject: [PATCH 0005/1203] add js method to track liveStream img size for zones --- web/skins/classic/views/js/montage.js | 155 +++++++++++++++++++++++--- web/skins/classic/views/montage.php | 9 +- 2 files changed, 149 insertions(+), 15 deletions(-) diff --git a/web/skins/classic/views/js/montage.js b/web/skins/classic/views/js/montage.js index 23d2f32ec..0879b6601 100644 --- a/web/skins/classic/views/js/montage.js +++ b/web/skins/classic/views/js/montage.js @@ -204,6 +204,11 @@ function Monitor(monitorData) { } } // end function Monitor +/** + * called when the layoutControl select element is changed, or the page + * is rendered?? + * @return null + */ function selectLayout(element) { layout = $j(element).val(); @@ -265,15 +270,18 @@ function selectLayout(element) { // APPLET's and OBJECTS need to be re-initialized } streamImg.style.width = '100%'; + + updateZoneSvg(monitor, streamImg.getSize().x, streamImg.getSize().y); } - var zonesSVG = $('zones'+monitor.id); - if ( zonesSVG ) { - zonesSVG.style.width = ''; - } + } // end foreach monitor } } // end function selectLayout(element) +/** + * called when the widthControl|heightControl select elements are changed + * @return null + */ function changeSize() { var width = $('width').get('value'); var height = $('height').get('value'); @@ -308,12 +316,12 @@ function changeSize() { streamImg.style.width = width ? width : null; streamImg.style.height = height ? height : null; //streamImg.style.height = ''; + + // not sure how the above block works... so rescale zone if img changed?? + updateZoneSvg(monitor, streamImg.getSize().x, streamImg.getSize().y); } - var zonesSVG = $('zones'+monitor.id); - if ( zonesSVG ) { - zonesSVG.style.width = width ? width : '100%'; - zonesSVG.style.height = height; - } + + } $('scale').set('value', ''); Cookie.write('zmMontageScale', '', {duration: 10*365}); @@ -322,9 +330,14 @@ function changeSize() { selectLayout('#zmMontageLayout'); } // end function changeSize() +/** + * called when the scaleControl select element is changed + * @return null + */ function changeScale() { var scale = $('scale').get('value'); $('width').set('value', 'auto'); + // console.dir($('width')); $('height').set('value', 'auto'); Cookie.write('zmMontageScale', scale, {duration: 10*365}); Cookie.write('zmMontageWidth', '', {duration: 10*365}); @@ -335,6 +348,10 @@ function changeScale() { } for ( var i = 0, length = monitors.length; i < length; i++ ) { var monitor = monitors[i]; + + // console.log("scale = "+scale); + // console.log("SCALE_BASE = " + SCALE_BASE); + var newWidth = ( monitorData[i].width * scale ) / SCALE_BASE; var newHeight = ( monitorData[i].height * scale ) / SCALE_BASE; @@ -367,14 +384,126 @@ function changeScale() { streamImg.style.width = newWidth + "px"; streamImg.style.height = newHeight + "px"; } - var zonesSVG = $('zones'+monitor.id); - if ( zonesSVG ) { - zonesSVG.style.width = newWidth + "px"; - zonesSVG.style.height = newHeight + "px"; + + // console.log("monitor_frame.height = " + monitor_frame.height()); + // console.log("newHeight = " + newHeight); + + updateZoneSvg(monitor, newWidth, newHeight); + } +} + + +// +// function updateZoneSvg(monitor, monitor_frame) { + +/** + * cause the zone overlay Svg to track dims of associated monitor frame + * @arg monitor_frame is a jQuery element object + */ +function updateZoneSvg(monitor, newWidth, newHeight) { + console.log("calling updateZoneSvg"); + // console.dir(monitor_frame); + + // newWidth = monitor_frame.width(); + // newHeight = monitor_frame.height(); + + if (!newWidth) + debugger; + if (!newHeight || newHeight < 10) + debugger; + if (!monitor) + monitor; + + // zonesSVG is a mootools object + var zonesSVG = $('zones' + monitor.id); + if (zonesSVG) { + + console.log("newWidth = " + newWidth); + console.log("newHeight = " + newHeight); + orig_scale = zonesSVG.getProperty('data-scale-orig'); + + //debugger; + //console.log("zonesSVG.style.width =" + zonesSVG.style.width); + zonesSVG.style.width = newWidth + "px"; + //console.log("newWidth = " + newWidth + "px"); + //console.log("zonesSVG.style.width = " + zonesSVG.style.width); + zonesSVG.style.height = newHeight + "px"; + + polygons = zonesSVG.getElements("polygon"); + + for (let index = 0; index < polygons.length; index++) { + const polygon = polygons[index]; + //console.dir(polygon); + + points = coordsToPoints(polygon.getProperty("data-coords-orig")); + //console.dir(points); + + newPoints = scaleZonePoints(points, + newWidth / monitorData[index].width, + newHeight / monitorData[index].height); + + //console.dir(newPoints); + + //console.dir(newPoints); + //console.dir(); + //console.dir(pointsToCoords(newPoints)); + + polygon.setProperty("points", pointsToCoords(newPoints)); + } } } +/** + * + * @param {*} points + * @param {*} scale + */ +function scaleZonePoints(points, scalex, scaley) { + if (!points) + debugger; + if (!scalex) + debugger; + if (!scaley) + debugger; + // console.dir(points); + //debugger; + var newPoints = []; + for (let index = 0; index < points.length; index++) { + const point = points[index]; + newPoints.push([point[0] * scalex, point[1] * scaley ]); + } + // console.dir(newPoints); + return newPoints; +} + +/** + * convery coords string - "37,411 1246,542 1263,694 ..." to js array + * @param {*} coords + */ +function coordsToPoints(coords) { + var points = []; + points = coords.split(" ").map(function (item) { + return item.split(",").map(Math.round); + }); + // console.dir(points); + return points; +} + +/** + * convery points array to string - "37,411 1246,542 1263,694 ..." + * @param {*} coords + */ +function pointsToCoords(points) { + // console.dir(points); + var coords = []; + coords = points.map(function (item) { + return item.map(Math.round).join(","); + }).join(","); + // console.dir(coords); + return coords; +} + function toGrid(value) { return Math.round(value / 80) * 80; } diff --git a/web/skins/classic/views/montage.php b/web/skins/classic/views/montage.php index aac008dbc..ac6691bcb 100644 --- a/web/skins/classic/views/montage.php +++ b/web/skins/classic/views/montage.php @@ -43,6 +43,7 @@ $widths = array( $heights = array( 'auto' => 'auto', '240px' => '240px', + '320px' => '320px', '480px' => '480px', '720px' => '720px', '1080px' => '1080px', @@ -253,6 +254,9 @@ foreach ( $monitors as $monitor ) { $zones = array(); foreach( dbFetchAll('SELECT * FROM Zones WHERE MonitorId=? ORDER BY Area DESC', NULL, array($monitor->Id()) ) as $row ) { $row['Points'] = coordsToPoints($row['Coords']); + $row['Points_orig'] = $row['Points']; + $row['Coords_orig'] = $row['Coords']; + // $monitor.orig_points = $row['Points']; if ( $scale ) { limitPoints($row['Points'], 0, 0, $monitor->Width(), $monitor->Height()); @@ -269,10 +273,11 @@ foreach ( $monitors as $monitor ) { } // end foreach Zone ?> - + + '; + echo ''; } // end foreach zone ?> Sorry, your browser does not support inline SVG From ffaad88bf0dcc02460eabc6c385e7f2da3db5408 Mon Sep 17 00:00:00 2001 From: tolland Date: Thu, 20 Jun 2019 17:11:22 +0000 Subject: [PATCH 0006/1203] switch to using SVG scaling to deal with zone polygons --- web/skins/classic/views/js/montage.js | 132 +------------------------- web/skins/classic/views/montage.php | 9 +- 2 files changed, 3 insertions(+), 138 deletions(-) diff --git a/web/skins/classic/views/js/montage.js b/web/skins/classic/views/js/montage.js index 0879b6601..6140f260d 100644 --- a/web/skins/classic/views/js/montage.js +++ b/web/skins/classic/views/js/montage.js @@ -206,7 +206,7 @@ function Monitor(monitorData) { /** * called when the layoutControl select element is changed, or the page - * is rendered?? + * is rendered * @return null */ function selectLayout(element) { @@ -270,10 +270,7 @@ function selectLayout(element) { // APPLET's and OBJECTS need to be re-initialized } streamImg.style.width = '100%'; - - updateZoneSvg(monitor, streamImg.getSize().x, streamImg.getSize().y); } - } // end foreach monitor } } // end function selectLayout(element) @@ -316,12 +313,7 @@ function changeSize() { streamImg.style.width = width ? width : null; streamImg.style.height = height ? height : null; //streamImg.style.height = ''; - - // not sure how the above block works... so rescale zone if img changed?? - updateZoneSvg(monitor, streamImg.getSize().x, streamImg.getSize().y); } - - } $('scale').set('value', ''); Cookie.write('zmMontageScale', '', {duration: 10*365}); @@ -337,7 +329,6 @@ function changeSize() { function changeScale() { var scale = $('scale').get('value'); $('width').set('value', 'auto'); - // console.dir($('width')); $('height').set('value', 'auto'); Cookie.write('zmMontageScale', scale, {duration: 10*365}); Cookie.write('zmMontageWidth', '', {duration: 10*365}); @@ -348,10 +339,6 @@ function changeScale() { } for ( var i = 0, length = monitors.length; i < length; i++ ) { var monitor = monitors[i]; - - // console.log("scale = "+scale); - // console.log("SCALE_BASE = " + SCALE_BASE); - var newWidth = ( monitorData[i].width * scale ) / SCALE_BASE; var newHeight = ( monitorData[i].height * scale ) / SCALE_BASE; @@ -384,126 +371,9 @@ function changeScale() { streamImg.style.width = newWidth + "px"; streamImg.style.height = newHeight + "px"; } - - // console.log("monitor_frame.height = " + monitor_frame.height()); - // console.log("newHeight = " + newHeight); - - updateZoneSvg(monitor, newWidth, newHeight); } } - -// -// function updateZoneSvg(monitor, monitor_frame) { - -/** - * cause the zone overlay Svg to track dims of associated monitor frame - * @arg monitor_frame is a jQuery element object - */ -function updateZoneSvg(monitor, newWidth, newHeight) { - console.log("calling updateZoneSvg"); - // console.dir(monitor_frame); - - // newWidth = monitor_frame.width(); - // newHeight = monitor_frame.height(); - - if (!newWidth) - debugger; - if (!newHeight || newHeight < 10) - debugger; - if (!monitor) - monitor; - - // zonesSVG is a mootools object - var zonesSVG = $('zones' + monitor.id); - if (zonesSVG) { - - console.log("newWidth = " + newWidth); - console.log("newHeight = " + newHeight); - orig_scale = zonesSVG.getProperty('data-scale-orig'); - - //debugger; - //console.log("zonesSVG.style.width =" + zonesSVG.style.width); - zonesSVG.style.width = newWidth + "px"; - //console.log("newWidth = " + newWidth + "px"); - //console.log("zonesSVG.style.width = " + zonesSVG.style.width); - zonesSVG.style.height = newHeight + "px"; - - polygons = zonesSVG.getElements("polygon"); - - for (let index = 0; index < polygons.length; index++) { - const polygon = polygons[index]; - //console.dir(polygon); - - points = coordsToPoints(polygon.getProperty("data-coords-orig")); - //console.dir(points); - - newPoints = scaleZonePoints(points, - newWidth / monitorData[index].width, - newHeight / monitorData[index].height); - - //console.dir(newPoints); - - //console.dir(newPoints); - //console.dir(); - //console.dir(pointsToCoords(newPoints)); - - polygon.setProperty("points", pointsToCoords(newPoints)); - - } - } -} - -/** - * - * @param {*} points - * @param {*} scale - */ -function scaleZonePoints(points, scalex, scaley) { - if (!points) - debugger; - if (!scalex) - debugger; - if (!scaley) - debugger; - // console.dir(points); - //debugger; - var newPoints = []; - for (let index = 0; index < points.length; index++) { - const point = points[index]; - newPoints.push([point[0] * scalex, point[1] * scaley ]); - } - // console.dir(newPoints); - return newPoints; -} - -/** - * convery coords string - "37,411 1246,542 1263,694 ..." to js array - * @param {*} coords - */ -function coordsToPoints(coords) { - var points = []; - points = coords.split(" ").map(function (item) { - return item.split(",").map(Math.round); - }); - // console.dir(points); - return points; -} - -/** - * convery points array to string - "37,411 1246,542 1263,694 ..." - * @param {*} coords - */ -function pointsToCoords(points) { - // console.dir(points); - var coords = []; - coords = points.map(function (item) { - return item.map(Math.round).join(","); - }).join(","); - // console.dir(coords); - return coords; -} - function toGrid(value) { return Math.round(value / 80) * 80; } diff --git a/web/skins/classic/views/montage.php b/web/skins/classic/views/montage.php index ac6691bcb..d8bd27ae6 100644 --- a/web/skins/classic/views/montage.php +++ b/web/skins/classic/views/montage.php @@ -254,13 +254,9 @@ foreach ( $monitors as $monitor ) { $zones = array(); foreach( dbFetchAll('SELECT * FROM Zones WHERE MonitorId=? ORDER BY Area DESC', NULL, array($monitor->Id()) ) as $row ) { $row['Points'] = coordsToPoints($row['Coords']); - $row['Points_orig'] = $row['Points']; - $row['Coords_orig'] = $row['Coords']; - // $monitor.orig_points = $row['Points']; if ( $scale ) { limitPoints($row['Points'], 0, 0, $monitor->Width(), $monitor->Height()); - scalePoints($row['Points'], $scale); } else { limitPoints($row['Points'], 0, 0, ( $width ? $width-1 : $monitor->Width()-1 ), @@ -273,11 +269,10 @@ foreach ( $monitors as $monitor ) { } // end foreach Zone ?> - - + '; + echo ''; } // end foreach zone ?> Sorry, your browser does not support inline SVG From d026c610777fda32540f6c80b6427c0cad2c9c7b Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 20 Jun 2019 13:48:14 -0400 Subject: [PATCH 0007/1203] Don't allow saving to built in layouts --- web/skins/classic/views/js/montage.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/web/skins/classic/views/js/montage.js b/web/skins/classic/views/js/montage.js index 6140f260d..3170174a5 100644 --- a/web/skins/classic/views/js/montage.js +++ b/web/skins/classic/views/js/montage.js @@ -214,7 +214,6 @@ function selectLayout(element) { if ( layout_id = parseInt(layout) ) { layout = layouts[layout]; - console.log(layout); for ( var i = 0, length = monitors.length; i < length; i++ ) { monitor = monitors[i]; @@ -397,6 +396,17 @@ function edit_layout(button) { function save_layout(button) { var form = button.form; + var name = form.elements['Name'].value; + + if ( !name ) + name = form.elements['zmMontageLayout'].options[form.elements['zmMontageLayout'].selectedIndex].text; + console.log(name); + + if ( name == 'Freeform' || name=='2 Wide' || name=='3 Wide' || name=='4 Wide' || name == '5 Wide' ) { + alert('You cannot edit the built in layouts. Please give the layout a new name.'); + return; + } + // In fixed positioning, order doesn't matter. In floating positioning, it does. var Positions = {}; for ( var i = 0, length = monitors.length; i < length; i++ ) { From 7914583a9e982a7623aea7f0c01bf02481ac07e7 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 24 Jun 2019 11:50:58 -0400 Subject: [PATCH 0008/1203] Add support for specifying PPA --- utils/do_debian_package.sh | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/utils/do_debian_package.sh b/utils/do_debian_package.sh index 0b9e18ee7..fc1eaaeff 100755 --- a/utils/do_debian_package.sh +++ b/utils/do_debian_package.sh @@ -30,6 +30,10 @@ case $i in INTERACTIVE="${i#*=}" shift # past argument=value ;; + -p=*|--ppa=*) + PPA="${i#*=}" + shift # past argument=value + ;; -r=*|--release=*) RELEASE="${i#*=}" shift @@ -120,20 +124,21 @@ else fi; fi -PPA=""; -if [ "$RELEASE" != "" ]; then - # We need to use our official tarball for the original source, so grab it and overwrite our generated one. - IFS='.' read -r -a VERSION <<< "$RELEASE" - if [ "${VERSION[0]}.${VERSION[1]}" == "1.30" ]; then - PPA="ppa:iconnor/zoneminder-stable" +if [ "$PPA" == "" ]; then + if [ "$RELEASE" != "" ]; then + # We need to use our official tarball for the original source, so grab it and overwrite our generated one. + IFS='.' read -r -a VERSION <<< "$RELEASE" + if [ "${VERSION[0]}.${VERSION[1]}" == "1.30" ]; then + PPA="ppa:iconnor/zoneminder-stable" + else + PPA="ppa:iconnor/zoneminder-${VERSION[0]}.${VERSION[1]}" + fi; else - PPA="ppa:iconnor/zoneminder-${VERSION[0]}.${VERSION[1]}" - fi; -else - if [ "$BRANCH" == "" ]; then - PPA="ppa:iconnor/zoneminder-master"; - else - PPA="ppa:iconnor/zoneminder-$BRANCH"; + if [ "$BRANCH" == "" ]; then + PPA="ppa:iconnor/zoneminder-master"; + else + PPA="ppa:iconnor/zoneminder-$BRANCH"; + fi; fi; fi; @@ -327,6 +332,7 @@ EOF dput $PPA $SC fi; else + echo "dputting to $PPA"; dput $PPA $SC fi; fi; From 288f2f3e8f1f91bfaec70c6597d4d0c06d078fae Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 11 Jul 2019 17:56:22 -0400 Subject: [PATCH 0009/1203] Convert zm_dump_frame from a function to a define, this way we get line #'s from where we call zm_dump_frame instead of from the line in zm_ffmpeg where the function was. --- src/zm_ffmpeg.cpp | 19 ------------------- src/zm_ffmpeg.h | 31 ++++++++++++++++++++++++++++++- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/src/zm_ffmpeg.cpp b/src/zm_ffmpeg.cpp index 4ee6e6707..2ea1142bf 100644 --- a/src/zm_ffmpeg.cpp +++ b/src/zm_ffmpeg.cpp @@ -298,25 +298,6 @@ void zm_dump_video_frame(const AVFrame *frame, const char *text) { frame->pts ); } -void zm_dump_frame(const AVFrame *frame,const char *text) { - Debug(1, "%s: format %d %s sample_rate %" PRIu32 " nb_samples %d channels %d" - " duration %" PRId64 - " layout %d pts %" PRId64, - text, - frame->format, - av_get_sample_fmt_name((AVSampleFormat)frame->format), - frame->sample_rate, - frame->nb_samples, -#if LIBAVCODEC_VERSION_CHECK(56, 8, 0, 60, 100) - frame->channels, - frame->pkt_duration, -#else -0, 0, -#endif - frame->channel_layout, - frame->pts - ); -} #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) void zm_dump_codecpar ( const AVCodecParameters *par ) { diff --git a/src/zm_ffmpeg.h b/src/zm_ffmpeg.h index 18e018e26..7618a461b 100644 --- a/src/zm_ffmpeg.h +++ b/src/zm_ffmpeg.h @@ -299,7 +299,36 @@ void zm_dump_codec(const AVCodecContext *codec); #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) void zm_dump_codecpar(const AVCodecParameters *par); #endif -void zm_dump_frame(const AVFrame *frame, const char *text="Frame"); +#if LIBAVCODEC_VERSION_CHECK(56, 8, 0, 60, 100) +#define zm_dump_frame(frame, text) Debug(1, "%s: format %d %s sample_rate %" PRIu32 " nb_samples %d channels %d" \ + " duration %" PRId64 \ + " layout %d pts %" PRId64,\ + text, \ + frame->format, \ + av_get_sample_fmt_name((AVSampleFormat)frame->format), \ + frame->sample_rate, \ + frame->nb_samples, \ + frame->channels, \ + frame->pkt_duration, \ + frame->channel_layout, \ + frame->pts \ + ); +#else +#define zm_dump_frame(frame, text) Debug(1, "%s: format %d %s sample_rate %" PRIu32 " nb_samples %d channels %d" \ + " duration %" PRId64 \ + " layout %d pts %" PRId64, \ + text, \ + frame->format, \ + av_get_sample_fmt_name((AVSampleFormat)frame->format), \ + frame->sample_rate, \ + frame->nb_samples, \ + 0, 0, \ + frame->channel_layout, \ + frame->pts \ + ); + +#endif + void zm_dump_video_frame(const AVFrame *frame, const char *text="Frame"); #if LIBAVCODEC_VERSION_CHECK(56, 8, 0, 60, 100) From 13c91bdf606568ae4b3ad1887c528c19c00329a9 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 11 Jul 2019 17:56:53 -0400 Subject: [PATCH 0010/1203] Add pts adjustment to the delayed flushed encoder packets --- src/zm_videostore.cpp | 74 +++++++++++++++++++++++++++++-------------- 1 file changed, 50 insertions(+), 24 deletions(-) diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index 5ffec165b..355578ffc 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -476,7 +476,51 @@ VideoStore::~VideoStore() { break; } #endif + + // Need to adjust pts and dts and duration + pkt.stream_index = audio_out_stream->index; + + pkt.duration = av_rescale_q( + pkt.duration, + audio_out_ctx->time_base, + audio_out_stream->time_base); + // Scale the PTS of the outgoing packet to be the correct time base + if ( pkt.pts != AV_NOPTS_VALUE ) { + pkt.pts = av_rescale_q( + pkt.pts, + audio_out_ctx->time_base, + audio_in_stream->time_base); + // audio_first_pts is in audio_in_stream time base + pkt.pts -= audio_first_pts; + pkt.pts = av_rescale_q( + pkt.pts, + audio_in_stream->time_base, + audio_out_stream->time_base); + + Debug(2, "audio pkt.pts = %" PRId64 " from first_pts(%" PRId64 ")", + pkt.pts, audio_first_pts); + } else { + Debug(2, "pkt.pts = undef"); + pkt.pts = AV_NOPTS_VALUE; + } + + if ( pkt.dts != AV_NOPTS_VALUE ) { + pkt.dts = av_rescale_q( + pkt.dts, + audio_out_ctx->time_base, + audio_in_stream->time_base); + pkt.dts -= audio_first_dts; + pkt.dts = av_rescale_q( + pkt.dts, + audio_in_ctx->time_base, + audio_out_stream->time_base); + Debug(2, "pkt.dts = %" PRId64 " - first_dts(%" PRId64 ")", + pkt.dts, audio_first_dts); + } else { + pkt.dts = AV_NOPTS_VALUE; + } + dumpPacket(audio_out_stream, &pkt, "writing flushed packet"); av_interleaved_write_frame(oc, &pkt); zm_av_packet_unref(&pkt); @@ -1196,28 +1240,9 @@ int VideoStore::resample_audio() { av_make_error_string(ret).c_str()); return 0; } - zm_dump_frame(out_frame, "Out frame after convert"); - -#if 0 - // out_frame pts is in the input pkt pts... needs to be adjusted before sending to the encoder - if ( out_frame->pts != AV_NOPTS_VALUE ) { - if ( !video_first_pts ) { - video_first_pts = out_frame->pts; - Debug(1, "No video_first_pts setting to %" PRId64, video_first_pts); - out_frame->pts = 0; - } else { - out_frame->pts = out_frame->pts - video_first_pts; - } - // - } else { - // sending AV_NOPTS_VALUE doesn't really work but we seem to get it in ffmpeg 2.8 - out_frame->pts = audio_next_pts; - } - audio_next_pts = out_frame->pts + out_frame->nb_samples; -#endif - - if ((ret = av_audio_fifo_realloc(fifo, av_audio_fifo_size(fifo) + out_frame->nb_samples)) < 0) { + ret = av_audio_fifo_realloc(fifo, av_audio_fifo_size(fifo) + out_frame->nb_samples); + if ( ret < 0 ) { Error("Could not reallocate FIFO"); return 0; } @@ -1232,7 +1257,7 @@ int VideoStore::resample_audio() { // Reset frame_size to output_frame_size int frame_size = audio_out_ctx->frame_size; - // AAC requires 1024 samples per encode. Our input tends to be 160, so need to buffer them. + // AAC requires 1024 samples per encode. Our input tends to be something else, so need to buffer them. if ( frame_size > av_audio_fifo_size(fifo) ) { return 0; } @@ -1245,10 +1270,11 @@ int VideoStore::resample_audio() { // resampling changes the duration because the timebase is 1/samples // I think we should be dealing in codec timebases not stream if ( in_frame->pts != AV_NOPTS_VALUE ) { + // pkt_duration is in avstream timebase units out_frame->pkt_duration = av_rescale_q( in_frame->pkt_duration, - audio_in_ctx->time_base, - audio_out_ctx->time_base); + audio_in_stream->time_base, + audio_out_stream->time_base); out_frame->pts = av_rescale_q( in_frame->pts, audio_in_ctx->time_base, From 05be9008c76553cee28be56927c5250ad8f0b87d Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 11 Jul 2019 17:57:11 -0400 Subject: [PATCH 0011/1203] use FFMPEGInit to initialise ffmpeg instead of doing it ourselves --- src/zm_ffmpeg_input.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/zm_ffmpeg_input.cpp b/src/zm_ffmpeg_input.cpp index 5cca6b35b..03d89348d 100644 --- a/src/zm_ffmpeg_input.cpp +++ b/src/zm_ffmpeg_input.cpp @@ -7,8 +7,7 @@ FFmpeg_Input::FFmpeg_Input() { input_format_context = NULL; video_stream_id = -1; audio_stream_id = -1; - av_register_all(); - avcodec_register_all(); + FFMPEGInit(); streams = NULL; frame = NULL; } From 52e7cde66d3802fd46d5afc611768444ab44e913 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 11 Jul 2019 19:04:51 -0400 Subject: [PATCH 0012/1203] If Email has EIMOD, not only attach the image if it exists, but replace %EIMOD% with the link to it --- scripts/zmfilter.pl.in | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/zmfilter.pl.in b/scripts/zmfilter.pl.in index 6527742c8..b572d40fa 100644 --- a/scripts/zmfilter.pl.in +++ b/scripts/zmfilter.pl.in @@ -728,6 +728,7 @@ sub substituteTags { } if ( $text =~ s/%EIMOD%//g ) { + $text =~ s/%EIMOD%/$url?view=frame&mid=$Event->{MonitorId}&eid=$Event->{Id}&fid=objdetect/g; my $path = $Event->Path().'/objdetect.jpg'; if ( -e $path ) { push @$attachments_ref, { type=>'image/jpeg', path=>$path }; From 67168a23870ea1e9749640ec6b30f8fc6aea963e Mon Sep 17 00:00:00 2001 From: Pliable Pixels Date: Fri, 12 Jul 2019 14:31:39 -0400 Subject: [PATCH 0013/1203] demote token log (#2663) --- src/zm_user.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zm_user.cpp b/src/zm_user.cpp index 35f25f7c9..0c8142374 100644 --- a/src/zm_user.cpp +++ b/src/zm_user.cpp @@ -193,7 +193,7 @@ User *zmLoadTokenUser (std::string jwt_token_str, bool use_remote_addr ) { } Debug (1,"Got stored expiry time of %u",stored_iat); - Info ("Authenticated user '%s' via token", username.c_str()); + Debug (1,"Authenticated user '%s' via token", username.c_str()); mysql_free_result(result); return user; From ef9fe2dbd62b68ec190e9882c11ec2a0e3b43214 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 17 Jul 2019 10:06:30 -0400 Subject: [PATCH 0014/1203] Free up hwFrame, preventing memleak --- src/zm_ffmpeg_camera.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/zm_ffmpeg_camera.cpp b/src/zm_ffmpeg_camera.cpp index b96142aab..87b53c1d6 100644 --- a/src/zm_ffmpeg_camera.cpp +++ b/src/zm_ffmpeg_camera.cpp @@ -137,6 +137,7 @@ FfmpegCamera::FfmpegCamera( hwaccel = false; hwFrame = NULL; + hw_device_ctx = NULL; mFormatContext = NULL; mVideoStreamId = -1; @@ -648,6 +649,10 @@ int FfmpegCamera::Close() { av_frame_free(&mRawFrame); mRawFrame = NULL; } + if ( hwFrame ) { + av_frame_free(&hwFrame); + hwFrame = NULL; + } #if HAVE_LIBSWSCALE if ( mConvertContext ) { @@ -675,6 +680,12 @@ int FfmpegCamera::Close() { #endif mAudioCodecContext = NULL; // Freed by av_close_input_file } +#if 0 + if ( hw_device_ctx ) { + hwdevice_ctx_free + } +#endif + if ( mFormatContext ) { #if !LIBAVFORMAT_VERSION_CHECK(53, 17, 0, 25, 0) From 75af3972239f746ffca58ea185e47e341e68083f Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 17 Jul 2019 20:33:23 -0400 Subject: [PATCH 0015/1203] hwFrame isn't defined unless we have HWCONTEXT_H --- src/zm_ffmpeg_camera.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/zm_ffmpeg_camera.cpp b/src/zm_ffmpeg_camera.cpp index 87b53c1d6..d5a02ddb7 100644 --- a/src/zm_ffmpeg_camera.cpp +++ b/src/zm_ffmpeg_camera.cpp @@ -649,10 +649,12 @@ int FfmpegCamera::Close() { av_frame_free(&mRawFrame); mRawFrame = NULL; } +#if HAVE_LIBAVUTIL_HWCONTEXT_H if ( hwFrame ) { av_frame_free(&hwFrame); hwFrame = NULL; } +#endif #if HAVE_LIBSWSCALE if ( mConvertContext ) { From 2b7610a5acf1588c54affbda7bf8364b3dbf45d5 Mon Sep 17 00:00:00 2001 From: Pliable Pixels Date: Wed, 17 Jul 2019 20:37:27 -0400 Subject: [PATCH 0016/1203] fixed ffmpeg log association to zm log levels (#2664) --- src/zm_ffmpeg.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/zm_ffmpeg.cpp b/src/zm_ffmpeg.cpp index 2ea1142bf..1df9b7718 100644 --- a/src/zm_ffmpeg.cpp +++ b/src/zm_ffmpeg.cpp @@ -73,14 +73,14 @@ static bool bInit = false; void FFMPEGInit() { if ( !bInit ) { - if ( logDebugging() ) + if ( logDebugging() && config.log_ffmpeg ) { av_log_set_level( AV_LOG_DEBUG ); - else + av_log_set_callback(log_libav_callback); + Info("Enabling ffmpeg logs, as LOG_DEBUG+LOG_FFMPEG are enabled in options"); + } else { + Info("Not enabling ffmpeg logs, as LOG_FFMPEG and/or LOG_DEBUG is disabled in options, or this monitor not part of your debug targets"); av_log_set_level( AV_LOG_QUIET ); - if ( config.log_ffmpeg ) - av_log_set_callback(log_libav_callback); - else - Info("Not enabling ffmpeg logs, as LOG_FFMPEG is disabled in options"); + } #if LIBAVFORMAT_VERSION_CHECK(58, 9, 0, 64, 0) #else av_register_all(); From a9d01ba3d2035fd2032fcc2311025c19f3a90685 Mon Sep 17 00:00:00 2001 From: Pliable Pixels Date: Wed, 17 Jul 2019 20:38:58 -0400 Subject: [PATCH 0017/1203] Alarm api (#2665) * fixed alarm api to use tokens if present * clearer debug logs for tokens * space --- src/zm_user.cpp | 5 +++-- web/api/app/Controller/MonitorsController.php | 7 ++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/zm_user.cpp b/src/zm_user.cpp index 0c8142374..b873b8547 100644 --- a/src/zm_user.cpp +++ b/src/zm_user.cpp @@ -157,6 +157,7 @@ User *zmLoadTokenUser (std::string jwt_token_str, bool use_remote_addr ) { std::pair ans = verifyToken(jwt_token_str, key); std::string username = ans.first; unsigned int iat = ans.second; + Debug (1,"retrieved user '%s' from token", username.c_str()); if (username != "") { char sql[ZM_SQL_MED_BUFSIZ] = ""; @@ -178,7 +179,7 @@ User *zmLoadTokenUser (std::string jwt_token_str, bool use_remote_addr ) { if ( n_users != 1 ) { mysql_free_result(result); - Error("Unable to authenticate user %s", username.c_str()); + Error("Unable to authenticate user '%s'", username.c_str()); return NULL; } @@ -188,7 +189,7 @@ User *zmLoadTokenUser (std::string jwt_token_str, bool use_remote_addr ) { if (stored_iat > iat ) { // admin revoked tokens mysql_free_result(result); - Error("Token was revoked for %s", username.c_str()); + Error("Token was revoked for '%s'", username.c_str()); return NULL; } diff --git a/web/api/app/Controller/MonitorsController.php b/web/api/app/Controller/MonitorsController.php index f83aecf97..0686232df 100644 --- a/web/api/app/Controller/MonitorsController.php +++ b/web/api/app/Controller/MonitorsController.php @@ -254,6 +254,7 @@ class MonitorsController extends AppController { throw new BadRequestException(__('Invalid command')); } $zm_path_bin = Configure::read('ZM_PATH_BIN'); + $mToken = $this->request->query('token') ? $this->request->query('token') : null; switch ($cmd) { case 'on': @@ -281,8 +282,12 @@ class MonitorsController extends AppController { $zmAuthRelay = $config['Config']['Value']; $auth = ''; + if ( $zmOptAuth ) { - if ( $zmAuthRelay == 'hashed' ) { + if ($mToken) { + $auth = ' -T '.$mToken; + } + elseif ( $zmAuthRelay == 'hashed' ) { $options = array('conditions' => array('Config.' . $this->Config->primaryKey => 'ZM_AUTH_HASH_SECRET')); $config = $this->Config->find('first', $options); $zmAuthHashSecret = $config['Config']['Value']; From 542d88b6a4abf4955e2540615d07a97adb2198d6 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 17 Jul 2019 21:10:24 -0400 Subject: [PATCH 0018/1203] fix compile without HWCONTEXT --- src/zm_ffmpeg_camera.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/zm_ffmpeg_camera.cpp b/src/zm_ffmpeg_camera.cpp index d5a02ddb7..f0f6a3b1d 100644 --- a/src/zm_ffmpeg_camera.cpp +++ b/src/zm_ffmpeg_camera.cpp @@ -136,8 +136,6 @@ FfmpegCamera::FfmpegCamera( } hwaccel = false; - hwFrame = NULL; - hw_device_ctx = NULL; mFormatContext = NULL; mVideoStreamId = -1; @@ -155,6 +153,8 @@ FfmpegCamera::FfmpegCamera( packetqueue = NULL; error_count = 0; #if HAVE_LIBAVUTIL_HWCONTEXT_H + hwFrame = NULL; + hw_device_ctx = NULL; hw_pix_fmt = AV_PIX_FMT_NONE; #endif From f3166663a5eb0a224e46f7a638c295f8f591da53 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 19 Jul 2019 12:51:31 -0400 Subject: [PATCH 0019/1203] unref hw_device_ctx on Close. I think this should release all the other hwaccel related stuff --- src/zm_ffmpeg_camera.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/zm_ffmpeg_camera.cpp b/src/zm_ffmpeg_camera.cpp index f0f6a3b1d..8c61dfcff 100644 --- a/src/zm_ffmpeg_camera.cpp +++ b/src/zm_ffmpeg_camera.cpp @@ -682,13 +682,13 @@ int FfmpegCamera::Close() { #endif mAudioCodecContext = NULL; // Freed by av_close_input_file } -#if 0 + +#if HAVE_LIBAVUTIL_HWCONTEXT_H if ( hw_device_ctx ) { - hwdevice_ctx_free + av_buffer_unref(&hw_device_ctx); } #endif - if ( mFormatContext ) { #if !LIBAVFORMAT_VERSION_CHECK(53, 17, 0, 25, 0) av_close_input_file(mFormatContext); From 9a31f8792cdf546e588f8af9ef6f809699dd4066 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 19 Jul 2019 13:55:35 -0400 Subject: [PATCH 0020/1203] return proper error codes when failed auth or fail permissions --- src/zms.cpp | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/zms.cpp b/src/zms.cpp index 5e6e4c2d6..c78dba1d2 100644 --- a/src/zms.cpp +++ b/src/zms.cpp @@ -43,9 +43,8 @@ bool ValidateAccess( User *user, int mon_id ) { allowed = false; } if ( !allowed ) { - Error( "Error, insufficient privileges for requested action user %d %s for monitor %d", - user->Id(), user->getUsername(), mon_id ); - exit( -1 ); + Error("Error, insufficient privileges for requested action user %d %s for monitor %d", + user->Id(), user->getUsername(), mon_id); } return allowed; } @@ -164,8 +163,7 @@ int main( int argc, const char *argv[] ) { strncpy( auth, value, sizeof(auth)-1 ); } else if ( !strcmp( name, "token" ) ) { jwt_token_str = value; - Debug(1,"ZMS: JWT token found: %s", jwt_token_str.c_str()); - + Debug(1, "ZMS: JWT token found: %s", jwt_token_str.c_str()); } else if ( !strcmp( name, "user" ) ) { username = UriDecode( value ); } else if ( !strcmp( name, "pass" ) ) { @@ -184,17 +182,15 @@ int main( int argc, const char *argv[] ) { } else { snprintf(log_id_string, sizeof(log_id_string), "zms_e%" PRIu64, event_id); } - logInit( log_id_string ); + logInit(log_id_string); if ( config.opt_use_auth ) { User *user = 0; - if (jwt_token_str != "") { + if ( jwt_token_str != "" ) { //user = zmLoadTokenUser(jwt_token_str, config.auth_hash_ips); user = zmLoadTokenUser(jwt_token_str, false); - - } - else if ( strcmp(config.auth_relay, "none") == 0 ) { + } else if ( strcmp(config.auth_relay, "none") == 0 ) { if ( checkUser(username.c_str()) ) { user = zmLoadUser(username.c_str()); } else { @@ -216,21 +212,27 @@ int main( int argc, const char *argv[] ) { } } if ( !user ) { + fprintf(stdout, "HTTP/1.0 401 Unauthorized\r\n"); Error("Unable to authenticate user"); logTerm(); zmDbClose(); return -1; } - ValidateAccess(user, monitor_id); + if ( !ValidateAccess(user, monitor_id) ) { + fprintf(stdout, "HTTP/1.0 403 Forbidden\r\n"); + logTerm(); + zmDbClose(); + return -1; + } } // end if config.opt_use_auth hwcaps_detect(); zmSetDefaultTermHandler(); zmSetDefaultDieHandler(); - setbuf( stdout, 0 ); + setbuf(stdout, 0); if ( nph ) { - fprintf( stdout, "HTTP/1.0 200 OK\r\n" ); + fprintf(stdout, "HTTP/1.0 200 OK\r\n"); } fprintf( stdout, "Server: ZoneMinder Video Server/%s\r\n", ZM_VERSION ); From 1e0c39c632e390c2d49ea965dc7f41176f0c27f0 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 19 Jul 2019 16:28:18 -0400 Subject: [PATCH 0021/1203] mostly spacing cleanups. Don't bother setting pkt_duration on resampled frame --- src/zm_videostore.cpp | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index 355578ffc..563c06be8 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -308,7 +308,6 @@ VideoStore::VideoStore( #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) audio_out_stream = avformat_new_stream(oc, NULL); - audio_out_stream->time_base = audio_in_stream->time_base; audio_out_ctx = avcodec_alloc_context3(audio_out_codec); if ( !audio_out_ctx ) { Error("could not allocate codec ctx for AAC"); @@ -1061,8 +1060,8 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { dumpPacket(audio_in_stream, ipkt, "input packet"); if ( audio_out_codec ) { - Debug(2, "Have output codec"); if ( ( ret = zm_receive_frame(audio_in_ctx, in_frame, *ipkt) ) < 0 ) { + Debug(3, "Not ready to receive frame"); return 0; } @@ -1100,10 +1099,11 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { } dumpPacket(audio_out_stream, &opkt, "raw opkt"); - opkt.duration = av_rescale_q( - opkt.duration, - audio_out_ctx->time_base, - audio_out_stream->time_base); + + opkt.duration = av_rescale_q( + opkt.duration, + audio_out_ctx->time_base, + audio_out_stream->time_base); // Scale the PTS of the outgoing packet to be the correct time base if ( ipkt->pts != AV_NOPTS_VALUE ) { if ( !audio_first_pts ) { @@ -1116,8 +1116,8 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { audio_out_ctx->time_base, audio_out_stream->time_base); opkt.pts -= audio_first_pts; - Debug(2, "audio opkt.pts = %" PRId64 " from ipkt->pts(%" PRId64 ") - first_pts(%" PRId64 ")", - opkt.pts, ipkt->pts, audio_first_pts); + Debug(2, "audio opkt.pts = %" PRId64 " from first_pts %" PRId64, + opkt.pts, audio_first_pts); } } else { Debug(2, "opkt.pts = undef"); @@ -1134,8 +1134,8 @@ int VideoStore::writeAudioFramePacket(AVPacket *ipkt) { audio_out_ctx->time_base, audio_out_stream->time_base); opkt.dts -= audio_first_dts; - Debug(2, "opkt.dts = %" PRId64 " from ipkt.dts(%" PRId64 ") - first_dts(%" PRId64 ")", - opkt.dts, ipkt->dts, audio_first_dts); + Debug(2, "opkt.dts = %" PRId64 " from first_dts %" PRId64, + opkt.dts, audio_first_dts); } audio_last_dts = opkt.dts; } else { @@ -1270,17 +1270,12 @@ int VideoStore::resample_audio() { // resampling changes the duration because the timebase is 1/samples // I think we should be dealing in codec timebases not stream if ( in_frame->pts != AV_NOPTS_VALUE ) { - // pkt_duration is in avstream timebase units - out_frame->pkt_duration = av_rescale_q( - in_frame->pkt_duration, - audio_in_stream->time_base, - audio_out_stream->time_base); out_frame->pts = av_rescale_q( in_frame->pts, audio_in_ctx->time_base, audio_out_ctx->time_base); - zm_dump_frame(out_frame, "Out frame after timestamp conversion"); } + zm_dump_frame(out_frame, "Out frame after timestamp conversion"); #else #if defined(HAVE_LIBAVRESAMPLE) ret = avresample_convert(resample_ctx, NULL, 0, 0, in_frame->data, From fe71a9abaa97af6d005a9d492b2b2b1c023fd970 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 19 Jul 2019 16:32:40 -0400 Subject: [PATCH 0022/1203] php_errormsg is deprecated --- web/includes/functions.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/web/includes/functions.php b/web/includes/functions.php index e1016bb82..867861ffe 100644 --- a/web/includes/functions.php +++ b/web/includes/functions.php @@ -2459,11 +2459,13 @@ function do_post_request($url, $data, $optional_headers = null) { $ctx = stream_context_create($params); $fp = @fopen($url, 'rb', false, $ctx); if ( !$fp ) { - throw new Exception("Problem with $url, $php_errormsg"); + throw new Exception("Problem with $url, " + .print_r(error_get_last(),true)); } $response = @stream_get_contents($fp); if ( $response === false ) { - throw new Exception("Problem reading data from $url, $php_errormsg"); + throw new Exception("Problem reading data from $url, data: ".print_r($params,true) + .print_r(error_get_last(),true)); } return $response; } From e821553265ed10a73b4f6d20d3162c2a83b07bed Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 23 Jul 2019 09:54:39 -0400 Subject: [PATCH 0023/1203] Split MoveTo into CopyTo and MoveTo. --- scripts/ZoneMinder/lib/ZoneMinder/Event.pm | 126 +++++++++++---------- 1 file changed, 69 insertions(+), 57 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm index 9c6fe3cd5..e1516f1f5 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm @@ -393,61 +393,66 @@ sub delete { sub delete_files { my $event = shift; - my $Storage = @_ ? $_[0] : new ZoneMinder::Storage($$event{StorageId}); - my $storage_path = $Storage->Path(); + foreach my $Storage ( + @_ ? ($_[0]) : ( + new ZoneMinder::Storage($$event{StorageId}), + ( $$event{SecondaryStorageId} ? new ZoneMinder::Storage($$event{SecondaryStorageId}) : () ), + ) ) { + my $storage_path = $Storage->Path(); - if ( ! $storage_path ) { - Error("Empty storage path when deleting files for event $$event{Id} with storage id $$event{StorageId}"); - return; - } - - if ( ! $$event{MonitorId} ) { - Error("No monitor id assigned to event $$event{Id}"); - return; - } - my $event_path = $event->RelativePath(); - Debug("Deleting files for Event $$event{Id} from $storage_path/$event_path, scheme is $$event{Scheme}."); - if ( $event_path ) { - ( $storage_path ) = ( $storage_path =~ /^(.*)$/ ); # De-taint - ( $event_path ) = ( $event_path =~ /^(.*)$/ ); # De-taint - - my $deleted = 0; - if ( $$Storage{Type} and ( $$Storage{Type} eq 's3fs' ) ) { - my ( $aws_id, $aws_secret, $aws_host, $aws_bucket ) = ( $$Storage{Url} =~ /^\s*([^:]+):([^@]+)@([^\/]*)\/(.+)\s*$/ ); - eval { - require Net::Amazon::S3; - my $s3 = Net::Amazon::S3->new( { - aws_access_key_id => $aws_id, - aws_secret_access_key => $aws_secret, - ( $aws_host ? ( host => $aws_host ) : () ), - }); - my $bucket = $s3->bucket($aws_bucket); - if ( ! $bucket ) { - Error("S3 bucket $bucket not found."); - die; - } - if ( $bucket->delete_key($event_path) ) { - $deleted = 1; - } else { - Error('Failed to delete from S3:'.$s3->err . ': ' . $s3->errstr); - } - }; - Error($@) if $@; + if ( ! $storage_path ) { + Error("Empty storage path when deleting files for event $$event{Id} with storage id $$event{StorageId}"); + return; } - if ( !$deleted ) { - my $command = "/bin/rm -rf $storage_path/$event_path"; - ZoneMinder::General::executeShellCommand($command); - } - } - if ( $event->Scheme() eq 'Deep' ) { - my $link_path = $event->LinkPath(); - Debug("Deleting link for Event $$event{Id} from $storage_path/$link_path."); - if ( $link_path ) { - ( $link_path ) = ( $link_path =~ /^(.*)$/ ); # De-taint + if ( ! $$event{MonitorId} ) { + Error("No monitor id assigned to event $$event{Id}"); + return; + } + my $event_path = $event->RelativePath(); + Debug("Deleting files for Event $$event{Id} from $storage_path/$event_path, scheme is $$event{Scheme}."); + if ( $event_path ) { + ( $storage_path ) = ( $storage_path =~ /^(.*)$/ ); # De-taint + ( $event_path ) = ( $event_path =~ /^(.*)$/ ); # De-taint + + my $deleted = 0; + if ( $$Storage{Type} and ( $$Storage{Type} eq 's3fs' ) ) { + my ( $aws_id, $aws_secret, $aws_host, $aws_bucket ) = ( $$Storage{Url} =~ /^\s*([^:]+):([^@]+)@([^\/]*)\/(.+)\s*$/ ); + eval { + require Net::Amazon::S3; + my $s3 = Net::Amazon::S3->new( { + aws_access_key_id => $aws_id, + aws_secret_access_key => $aws_secret, + ( $aws_host ? ( host => $aws_host ) : () ), + }); + my $bucket = $s3->bucket($aws_bucket); + if ( ! $bucket ) { + Error("S3 bucket $bucket not found."); + die; + } + if ( $bucket->delete_key($event_path) ) { + $deleted = 1; + } else { + Error('Failed to delete from S3:'.$s3->err . ': ' . $s3->errstr); + } + }; + Error($@) if $@; + } + if ( !$deleted ) { + my $command = "/bin/rm -rf $storage_path/$event_path"; + ZoneMinder::General::executeShellCommand($command); + } + } # end if event_path + + if ( $event->Scheme() eq 'Deep' ) { + my $link_path = $event->LinkPath(); + Debug("Deleting link for Event $$event{Id} from $storage_path/$link_path."); + if ( $link_path ) { + ( $link_path ) = ( $link_path =~ /^(.*)$/ ); # De-taint unlink($storage_path.'/'.$link_path) or Error("Unable to unlink '$storage_path/$link_path': $!"); - } - } + } + } # end if Scheme eq Deep + } # end foreach Storage } # end sub delete_files sub StorageId { @@ -519,7 +524,7 @@ sub DiskSpace { return $_[0]{DiskSpace}; } -sub MoveTo { +sub CopyTo { my ( $self, $NewStorage ) = @_; my $OldStorage = $self->Storage(undef); @@ -559,7 +564,7 @@ sub MoveTo { $ZoneMinder::Database::dbh->commit(); return "New path and old path are the same! $NewPath"; } - Debug("Moving event $$self{Id} from $OldPath to $NewPath"); + Debug("Copying event $$self{Id} from $OldPath to $NewPath"); my $moved = 0; @@ -650,7 +655,7 @@ Debug("Files to move @files"); last; } my $duration = time - $starttime; - Debug("Copied " . Number::Bytes::Human::format_bytes($size) . " in $duration seconds = " . ($duration?Number::Bytes::Human::format_bytes($size/$duration):'inf') . "/sec"); + Debug('Copied ' . Number::Bytes::Human::format_bytes($size) . " in $duration seconds = " . ($duration?Number::Bytes::Human::format_bytes($size/$duration):'inf') . '/sec'); } # end foreach file. } # end if ! moved @@ -658,6 +663,15 @@ Debug("Files to move @files"); $ZoneMinder::Database::dbh->commit(); return $error; } +} # end sub CopyTo + +sub MoveTo { + + my ( $self, $NewStorage ) = @_; + my $OldStorage = $self->Storage(undef); + + my $error = $self->CopyTo($NewStorage); + return $error if $error; # Succeeded in copying all files, so we may now update the Event. $$self{StorageId} = $$NewStorage{Id}; @@ -667,10 +681,8 @@ Debug("Files to move @files"); $ZoneMinder::Database::dbh->commit(); return $error; } -Debug("Committing"); $ZoneMinder::Database::dbh->commit(); - $self->delete_files( $OldStorage ); -Debug("Done deleting files, returning"); + $self->delete_files($OldStorage); return $error; } # end sub MoveTo From f9b5c8a1f4dcfeb2af820309113d45095db23eea Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 23 Jul 2019 09:55:14 -0400 Subject: [PATCH 0024/1203] If query is empty don't bother parsing it --- scripts/ZoneMinder/lib/ZoneMinder/Filter.pm | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm index 8383e43b7..1c0dd151b 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm @@ -132,6 +132,12 @@ sub Sql { my $self = shift; $$self{Sql} = shift if @_; if ( ! $$self{Sql} ) { + $self->{Sql} = ''; + if ( ! $self->{Query} ) { + Warning("No query in Filter!"); + return; + } + my $filter_expr = ZoneMinder::General::jsonDecode($self->{Query}); my $sql = 'SELECT E.*, unix_timestamp(E.StartTime) as Time, @@ -142,7 +148,6 @@ sub Sql { INNER JOIN Monitors as M on M.Id = E.MonitorId LEFT JOIN Storage as S on S.Id = E.StorageId '; - $self->{Sql} = ''; if ( $filter_expr->{terms} ) { foreach my $term ( @{$filter_expr->{terms}} ) { From fd95ab23e9a78b116b621bd4498a5701dc2b83fd Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 23 Jul 2019 09:55:27 -0400 Subject: [PATCH 0025/1203] Add AutoCopy support --- scripts/zmfilter.pl.in | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/scripts/zmfilter.pl.in b/scripts/zmfilter.pl.in index b572d40fa..0b8812c87 100644 --- a/scripts/zmfilter.pl.in +++ b/scripts/zmfilter.pl.in @@ -240,6 +240,7 @@ sub getFilters { or AutoDelete = 1 or UpdateDiskSpace = 1 or AutoMove = 1 + or AutoCopy = 1 ) ORDER BY Name'; my $sth = $dbh->prepare_cached($sql) or Fatal("Unable to prepare '$sql': ".$dbh->errstr()); @@ -283,6 +284,7 @@ sub checkFilter { ($filter->{AutoMessage}?'message':()), ($filter->{AutoExecute}?'execute':()), ($filter->{AutoMove}?'move':()), + ($filter->{AutoCopy}?'copy':()), ($filter->{UpdateDiskSpace}?'update disk space':()), ), 'returned' , scalar @Events , 'events', @@ -300,9 +302,9 @@ sub checkFilter { Info("Archiving event $Event->{Id}"); # Do it individually to avoid locking up the table for new events my $sql = 'UPDATE Events SET Archived = 1 WHERE Id = ?'; - my $sth = $dbh->prepare_cached( $sql ) + my $sth = $dbh->prepare_cached($sql) or Fatal("Unable to prepare '$sql': ".$dbh->errstr()); - my $res = $sth->execute( $Event->{Id} ) + my $res = $sth->execute($Event->{Id}) or Error("Unable to execute '$sql': ".$dbh->errstr()); } if ( $Config{ZM_OPT_FFMPEG} && $filter->{AutoVideo} ) { @@ -343,6 +345,11 @@ sub checkFilter { $_ = $Event->MoveTo($NewStorage); Error($_) if $_; } + if ( $filter->{AutoCopy} ) { + my $NewStorage = new ZoneMinder::Storage($filter->{AutoCopyTo}); + $_ = $Event->CopyTo($NewStorage); + Error($_) if $_; + } if ( $filter->{UpdateDiskSpace} ) { $ZoneMinder::Database::dbh->begin_work(); From b05aff1d5d5e4a339d5f6731dcffe2dfde679ce9 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 23 Jul 2019 09:57:16 -0400 Subject: [PATCH 0026/1203] Update Filter Object to extend ZM_Object. Rename Query to Query_json and implement a Query function to parse Query_json --- web/includes/Filter.php | 169 ++++++++++------------------------------ 1 file changed, 41 insertions(+), 128 deletions(-) diff --git a/web/includes/Filter.php b/web/includes/Filter.php index 8da602539..f8b06d4e5 100644 --- a/web/includes/Filter.php +++ b/web/includes/Filter.php @@ -1,9 +1,11 @@ null, 'Name' => '', 'AutoExecute' => 0, @@ -16,63 +18,44 @@ public $defaults = array( 'AutoMessage' => 0, 'AutoMove' => 0, 'AutoMoveTo' => 0, + 'AutoCopy' => 0, + 'AutoCopyTo' => 0, 'UpdateDiskSpace' => 0, 'Background' => 0, 'Concurrent' => 0, - 'limit' => 100, - 'Query' => array(), - 'sort_field' => ZM_WEB_EVENT_SORT_FIELD, - 'sort_asc' => ZM_WEB_EVENT_SORT_ORDER, -); + #'limit' => 100, + 'Query_json' => '', + #'sort_field' => ZM_WEB_EVENT_SORT_FIELD, + #'sort_asc' => ZM_WEB_EVENT_SORT_ORDER, + ); - public function __construct( $IdOrRow=NULL ) { - $row = NULL; - if ( $IdOrRow ) { - if ( is_integer($IdOrRow) or is_numeric($IdOrRow) ) { - $row = dbFetchOne('SELECT * FROM Filters WHERE Id=?', NULL, array($IdOrRow)); - if ( ! $row ) { - Error('Unable to load Filter record for Id=' . $IdOrRow); - } - } elseif ( is_array($IdOrRow) ) { - $row = $IdOrRow; - } else { - $backTrace = debug_backtrace(); - $file = $backTrace[1]['file']; - $line = $backTrace[1]['line']; - Error("Unknown argument passed to Filter Constructor from $file:$line)"); - Error("Unknown argument passed to Filter Constructor ($IdOrRow)"); - return; - } - } # end if isset($IdOrRow) + public function Query($new = -1) { + if ( $new and ( $new != -1 ) ) { + $this->{'Query'} = $new; + $this->{'Query_json'} = jsonEncode($new); + Logger::Debug("Setting Query to " . $this->{'Query_json'}); + } + if ( !array_key_exists('Query', $this) ) { + if ( array_key_exists('Query_json', $this) and $this->{'Query_json'} ) { + $this->{'Query'} = jsonDecode($this->{'Query_json'}); + Logger::Debug("Decoded Query already" . print_r($this->{'Query'}, true )); - if ( $row ) { - foreach ($row as $k => $v) { - $this->{$k} = $v; - } - if ( array_key_exists('Query', $this) and $this->{'Query'} ) { - $this->{'Query'} = jsonDecode($this->{'Query'}); } else { + Logger::Debug("No Have Query_json already"); $this->{'Query'} = array(); } - } - } // end function __construct - - public function __call( $fn, array $args ) { - if ( count( $args ) ) { - $this->{$fn} = $args[0]; - } - if ( array_key_exists( $fn, $this ) ) { - return $this->{$fn}; - } else if ( array_key_exists( $fn, $this->defaults ) ) { - $this->{$fn} = $this->defaults{$fn}; - return $this->{$fn}; } else { - - $backTrace = debug_backtrace(); - $file = $backTrace[1]['file']; - $line = $backTrace[1]['line']; - Warning( "Unknown function call Filter->$fn from $file:$line" ); + Logger::Debug("Have Query already" . print_r($this->{'Query'}, true )); } + return $this->{'Query'}; + } + + public static function find( $parameters = array(), $options = array() ) { + return ZM_Object::_find(get_class(), $parameters, $options); + } + + public static function find_one( $parameters = array(), $options = array() ) { + return ZM_Object::_find_one(get_class(), $parameters, $options); } public function terms( ) { @@ -93,101 +76,31 @@ public $defaults = array( if ( isset( $this->Query()['sort_field'] ) ) { return $this->{'Query'}['sort_field']; } - return $this->defaults{'sort_field'}; + return ZM_WEB_EVENT_SORT_FIELD; + #return $this->defaults{'sort_field'}; } + public function sort_asc( ) { if ( func_num_args( ) ) { - $this->{'Query'}['sort_asc'] = func_get_arg(0); + $this->Query()['sort_asc'] = func_get_arg(0); } if ( isset( $this->Query()['sort_asc'] ) ) { return $this->{'Query'}['sort_asc']; } - return $this->defaults{'sort_asc'}; + return ZM_WEB_EVENT_SORT_ORDER; + #return $this->defaults{'sort_asc'}; } + public function limit( ) { if ( func_num_args( ) ) { $this->{'Query'}['limit'] = func_get_arg(0); } if ( isset( $this->Query()['limit'] ) ) return $this->{'Query'}['limit']; - return $this->defaults{'limit'}; + return 100; + #return $this->defaults{'limit'}; } - public static function find( $parameters = null, $options = null ) { - $filters = array(); - $sql = 'SELECT * FROM Filters '; - $values = array(); - - if ( $parameters ) { - $fields = array(); - $sql .= 'WHERE '; - foreach ( $parameters as $field => $value ) { - if ( $value == null ) { - $fields[] = $field.' IS NULL'; - } else if ( is_array( $value ) ) { - $func = function(){return '?';}; - $fields[] = $field.' IN ('.implode(',', array_map($func, $value)). ')'; - $values += $value; - } else { - $fields[] = $field.'=?'; - $values[] = $value; - } - } - $sql .= implode(' AND ', $fields); - } - if ( $options ) { - if ( isset($options['order']) ) { - $sql .= ' ORDER BY ' . $options['order']; - } - if ( isset($options['limit']) ) { - if ( is_integer($options['limit']) or ctype_digit($options['limit']) ) { - $sql .= ' LIMIT ' . $options['limit']; - } else { - $backTrace = debug_backtrace(); - $file = $backTrace[1]['file']; - $line = $backTrace[1]['line']; - Error("Invalid value for limit(".$options['limit'].") passed to Filter::find from $file:$line"); - return array(); - } - } - } - $result = dbQuery($sql, $values); - $results = $result->fetchALL(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'Filter'); - foreach ( $results as $row => $obj ) { - $filters[] = $obj; - } - return $filters; - } # end find() - - public static function find_one( $parameters = array() ) { - $results = Filter::find($parameters, array('limit'=>1)); - if ( ! sizeof($results) ) { - return; - } - return $results[0]; - } # end find_one() - - public function delete() { - dbQuery('DELETE FROM Filters WHERE Id=?', array($this->{'Id'})); - } # end function delete() - - public function set( $data ) { - foreach ($data as $k => $v) { - if ( is_array( $v ) ) { - $this->{$k} = $v; - } else if ( is_string( $v ) ) { - $this->{$k} = trim( $v ); - } else if ( is_integer( $v ) ) { - $this->{$k} = $v; - } else if ( is_bool( $v ) ) { - $this->{$k} = $v; - } else { - Error( "Unknown type $k => $v of var " . gettype( $v ) ); - $this->{$k} = $v; - } - } - } # end function set - public function control($command, $server_id=null) { $Servers = $server_id ? Server::find(array('Id'=>$server_id)) : Server::find(); if ( !count($Servers) and !$server_id ) { From 7c52f8a4aea74f9541830a9402c7a702d901405a Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 23 Jul 2019 09:57:44 -0400 Subject: [PATCH 0027/1203] Fixes and add Objects_Indexed_By_Id --- web/includes/Object.php | 230 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 230 insertions(+) create mode 100644 web/includes/Object.php diff --git a/web/includes/Object.php b/web/includes/Object.php new file mode 100644 index 000000000..041b45bed --- /dev/null +++ b/web/includes/Object.php @@ -0,0 +1,230 @@ + $v) { + $this->{$k} = $v; + } + $cache[$row['Id']] = $this; + } else { + # Set defaults + foreach ( $this->defaults as $k => $v ) $this->{$k} = $v; + } + } + + public function __call($fn, array $args){ + if ( count($args) ) { + $this->{$fn} = $args[0]; + } + if ( array_key_exists($fn, $this) ) { + return $this->{$fn}; + } else { + if ( array_key_exists($fn, $this->defaults) ) { + return $this->defaults{$fn}; + } else { + $backTrace = debug_backtrace(); + Warning("Unknown function call Sensor->$fn from ".print_r($backTrace,true)); + } + } + } + + public static function _find($class, $parameters = null, $options = null ) { + $table = $class::$table; + $filters = array(); + $sql = "SELECT * FROM `$table` "; + $values = array(); + + if ( $parameters ) { + $fields = array(); + $sql .= 'WHERE '; + foreach ( $parameters as $field => $value ) { + if ( $value == null ) { + $fields[] = '`'.$field.'` IS NULL'; + } else if ( is_array($value) ) { + $func = function(){return '?';}; + $fields[] = '`'.$field.'` IN ('.implode(',', array_map($func, $value)). ')'; + $values += $value; + + } else { + $fields[] = '`'.$field.'`=?'; + $values[] = $value; + } + } + $sql .= implode(' AND ', $fields ); + } + if ( $options ) { + if ( isset($options['order']) ) { + $sql .= ' ORDER BY ' . $options['order']; + } + if ( isset($options['limit']) ) { + if ( is_integer($options['limit']) or ctype_digit($options['limit']) ) { + $sql .= ' LIMIT ' . $options['limit']; + } else { + Error('Invalid value for limit('.$options['limit'].') passed to '.get_class()."::find from ".print_r($backTrace,true)); + return array(); + } + } + } + $rows = dbFetchAll($sql, NULL, $values); + $results = array(); + if ( $rows ) { + foreach ( $rows as $row ) { + array_push($results , new $class($row)); + } + } + return $results; + } # end public function find() + + public static function _find_one($class, $parameters = array(), $options = array() ) { + global $object_cache; + if ( ! isset($object_cache[$class]) ) + $object_cache[$class] = array(); + $cache = $object_cache[$class]; + if ( + ( count($parameters) == 1 ) and + isset($parameters['Id']) and + isset($cache[$parameters['Id']]) ) { + return $cache[$parameters['Id']]; + } + $options['limit'] = 1; + $results = ZM_Object::_find($class, $parameters, $options); + if ( ! sizeof($results) ) { + return; + } + return $results[0]; + } + + public static function Objects_Indexed_By_Id($class) { + $results = array(); + foreach ( ZM_Object::_find($class, null, array('order'=>'lower(Name)')) as $Object ) { + $results[$Object->Id()] = $Object; + } + return $results; + } + + public function to_json() { + $json = array(); + foreach ($this->defaults as $key => $value) { + if ( is_callable(array($this, $key)) ) { + $json[$key] = $this->$key(); + } else if ( array_key_exists($key, $this) ) { + $json[$key] = $this->{$key}; + } else { + $json[$key] = $this->defaults{$key}; + } + } + return json_encode($json); + } + + public function set($data) { + foreach ( $data as $k => $v ) { + if ( method_exists($this, $k) ) { + $this->{$k}($v); + } else { + if ( is_array($v) ) { +# perhaps should turn into a comma-separated string + $this->{$k} = implode(',', $v); + } else if ( is_string($v) ) { + if ( $v == '' and array_key_exists($k, $this->defaults) ) { + $this->{$k} = $this->defaults[$k]; + } else { + $this->{$k} = trim($v); + } + } else if ( is_integer($v) ) { + $this->{$k} = $v; + } else if ( is_bool($v) ) { + $this->{$k} = $v; + } else if ( is_null($v) ) { + $this->{$k} = $v; + } else { + Error( "Unknown type $k => $v of var " . gettype( $v ) ); + $this->{$k} = $v; + } + } # end if method_exists + } # end foreach $data as $k=>$v + } + + public function changes( $new_values ) { + $changes = array(); + foreach ( $this->defaults as $field=>$default_value ) { + if ( array_key_exists($field, $new_values) ) { + Logger::Debug("Checking default $field => $default_value exists in new values :".$this->{$field} . " " .$new_values[$field]); + if ( (!array_key_exists($field, $this)) or ( $this->{$field} != $new_values[$field] ) ) { + Logger::Debug("Checking default $field => $default_value changes becaause" . $new_values[$field].' != '.$new_values[$field]); + $changes[$field] = $new_values[$field]; + #} else if { + Logger::Debug("Checking default $field => $default_value changes becaause " . $new_values[$field].' != '.$new_values[$field]); + #array_push( $changes, [$field=>$defaults[$field]] ); + } + } else { + Logger::Debug("Checking default $field => $default_value not in new_values"); + } + } # end foreach default + return $changes; + } # end public function changes + + public function save($new_values = null) { + $class = get_class($this); + $table = $class::$table; + + if ( $new_values ) { + Logger::Debug("New values" . print_r($new_values,true)); + $this->set($new_values); + } + + if ( $this->Id() ) { + $fields = array_keys($this->defaults); + $sql = 'UPDATE '.$table.' SET '.implode(', ', array_map(function($field) {return '`'.$field.'`=?';}, $fields )) . ' WHERE Id=?'; + $values = array_map(function($field){return $this->{$field};}, $fields); + $values[] = $this->{'Id'}; + if ( dbQuery($sql, $values) ) + return true; + } else { + $fields = $this->defaults; + unset($fields['Id']); + + $sql = 'INSERT INTO '.$table.' ('.implode(', ', array_map(function($field) {return '`'.$field.'`';}, array_keys($fields))).') VALUES ('.implode(', ', array_map(function($field){return '?';}, array_values($fields))).')'; + $values = array_map(function($field){return $this->{$field};}, array_keys($fields)); + if ( dbQuery($sql, $values) ) { + $this->{'Id'} = dbInsertId(); + return true; + } + } + return false; + } // end function save + + public function delete() { + $class = get_class($this); + $table = $class::$table; + dbQuery("DELETE FROM $table WHERE Id=?", array($this->{'Id'})); + if ( isset($object_cache[$class]) and isset($object_cache[$class][$this->{'Id'}]) ) + unset($object_cache[$class][$this->{'Id'}]); + } + +} # end class Sensor Action +?> From 35ec60ca031f5ee6e3a72d9d53ae2cfe2221b1a4 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 23 Jul 2019 09:58:05 -0400 Subject: [PATCH 0028/1203] Change Storage object to extend ZM_Object --- web/includes/Storage.php | 136 +++------------------------------------ 1 file changed, 9 insertions(+), 127 deletions(-) diff --git a/web/includes/Storage.php b/web/includes/Storage.php index b3caa3ffa..8607b52fe 100644 --- a/web/includes/Storage.php +++ b/web/includes/Storage.php @@ -2,10 +2,11 @@ namespace ZM; require_once('database.php'); require_once('Event.php'); +require_once('Object.php'); -$storage_cache = array(); -class Storage { - private $defaults = array( +class Storage extends ZM_Object { + protected static $table = 'Storage'; + protected $defaults = array( 'Id' => null, 'Path' => '', 'Name' => '', @@ -16,31 +17,12 @@ class Storage { 'ServerId' => 0, 'DoDelete' => 1, ); + public static function find($parameters = array(), $options = array() ) { + return ZM_Object::_find(get_class(), $parameters, $options); + } - public function __construct( $IdOrRow = NULL ) { - global $storage_cache; - - $row = NULL; - if ( $IdOrRow ) { - if ( is_integer($IdOrRow) or is_numeric($IdOrRow) ) { - $row = dbFetchOne('SELECT * FROM Storage WHERE Id=?', NULL, array($IdOrRow)); - if ( ! $row ) { - Error('Unable to load Storage record for Id=' . $IdOrRow); - } - } else if ( is_array($IdOrRow) ) { - $row = $IdOrRow; - } - } - if ( $row ) { - foreach ($row as $k => $v) { - $this->{$k} = $v; - } - $storage_cache[$row['Id']] = $this; - } else { - $this->{'Name'} = ''; - $this->{'Path'} = ''; - $this->{'Type'} = 'local'; - } + public static function find_one( $parameters = array(), $options = array() ) { + return ZM_Object::_find_one(get_class(), $parameters, $options); } public function Path() { @@ -66,93 +48,6 @@ class Storage { return $this->{'Name'}; } - public function __call( $fn, array $args= NULL ) { - if ( count($args) ) { - $this->{$fn} = $args[0]; - } - if ( array_key_exists($fn, $this) ) - return $this->{$fn}; - - if ( array_key_exists($fn, $this->defaults) ) - return $this->defaults{$fn}; - - $backTrace = debug_backtrace(); - $file = $backTrace[0]['file']; - $line = $backTrace[0]['line']; - Warning("Unknown function call Storage->$fn from $file:$line"); - $file = $backTrace[1]['file']; - $line = $backTrace[1]['line']; - Warning("Unknown function call Storage->$fn from $file:$line"); - } - - public static function find_one( $parameters = null, $options = null ) { - global $storage_cache; - if ( - ( count($parameters) == 1 ) and - isset($parameters['Id']) and - isset($storage_cache[$parameters['Id']]) ) { - return $storage_cache[$parameters['Id']]; - } - - $results = Storage::find($parameters, $options); - if ( count($results) > 1 ) { - Error('Storage Returned more than 1'); - return $results[0]; - } else if ( count($results) ) { - return $results[0]; - } else { - return null; - } - } - public static function find( $parameters = null, $options = null ) { - $sql = 'SELECT * FROM Storage '; - $values = array(); - - if ( $parameters ) { - $fields = array(); - $sql .= 'WHERE '; - foreach ( $parameters as $field => $value ) { - if ( $value == null ) { - $fields[] = $field.' IS NULL'; - } else if ( is_array($value) ) { - $func = function(){return '?';}; - $fields[] = $field.' IN ('.implode(',', array_map($func, $value)).')'; - $values += $value; - - } else { - $fields[] = $field.'=?'; - $values[] = $value; - } - } - $sql .= implode(' AND ', $fields); - } # end if parameters - if ( $options ) { - if ( isset($options['order']) ) { - $sql .= ' ORDER BY ' . $options['order']; - } # end if options - if ( isset($options['limit']) ) { - if ( is_integer($options['limit']) or ctype_digit($options['limit']) ) { - $sql .= ' LIMIT ' . $option['limit']; - } else { - $backTrace = debug_backtrace(); - $file = $backTrace[1]['file']; - $line = $backTrace[1]['line']; - Error("Invalid value for limit(".$options['limit'].") passed to Control::find from $file:$line"); - return array(); - } - } # end if limit - } # end if options - $storage_areas = array(); - $result = dbQuery($sql, $values); - if ( $result ) { - $results = $result->fetchALL(); - foreach ( $results as $row ) { - $storage_areas[] = new Storage($row); - } - } - return $storage_areas; - } # end find() - public function disk_usage_percent() { $path = $this->Path(); if ( ! $path ) { @@ -226,18 +121,5 @@ class Storage { return $this->{'Server'}; } - public function to_json() { - $json = array(); - foreach ($this->defaults as $key => $value) { - if ( is_callable(array($this, $key)) ) { - $json[$key] = $this->$key(); - } else if ( array_key_exists($key, $this) ) { - $json[$key] = $this->{$key}; - } else { - $json[$key] = $this->defaults{$key}; - } - } - return json_encode($json); - } } // end class Storage ?> From 9b6dedb35d4399bc7e8fc13083d103e1d1ad766f Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 23 Jul 2019 09:58:28 -0400 Subject: [PATCH 0029/1203] Update Filter saving action to use object set/save etc --- web/includes/actions/filter.php | 37 ++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/web/includes/actions/filter.php b/web/includes/actions/filter.php index 6b3019ee5..8548f21d1 100644 --- a/web/includes/actions/filter.php +++ b/web/includes/actions/filter.php @@ -51,11 +51,30 @@ if ( isset($_REQUEST['object']) and ( $_REQUEST['object'] == 'filter' ) ) { $_REQUEST['filter']['Query']['sort_asc'] = validStr($_REQUEST['filter']['Query']['sort_asc']); $_REQUEST['filter']['Query']['limit'] = validInt($_REQUEST['filter']['Query']['limit']); if ( $action == 'execute' ) { - $tempFilterName = '_TempFilter'.time(); - $sql .= ' Name = \''.$tempFilterName.'\''; - } else { - $sql .= ' Name = '.dbEscape($_REQUEST['filter']['Name']); + $_REQUEST['filter']['Name'] = '_TempFilter'.time(); + unset($_REQUEST['Id']); + #$tempFilterName = '_TempFilter'.time(); + #$sql .= ' Name = \''.$tempFilterName.'\''; + #} else { + #$sql .= ' Name = '.dbEscape($_REQUEST['filter']['Name']); } + + $_REQUEST['filter']['AutoCopy'] = empty($_REQUEST['filter']['AutoCopy']) ? 0 : 1; + $_REQUEST['filter']['AutoMove'] = empty($_REQUEST['filter']['AutoMove']) ? 0 : 1; + $_REQUEST['filter']['AutoArchive'] = empty($_REQUEST['filter']['AutoArchive']) ? 0 : 1; + $_REQUEST['filter']['AutoVideo'] = empty($_REQUEST['filter']['AutoVideo']) ? 0 : 1; + $_REQUEST['filter']['AutoUpload'] = empty($_REQUEST['filter']['AutoUpload']) ? 0 : 1; + $_REQUEST['filter']['AutoEmail'] = empty($_REQUEST['filter']['AutoEmail']) ? 0 : 1; + $_REQUEST['filter']['AutoMessage'] = empty($_REQUEST['filter']['AutoMessage']) ? 0 : 1; + $_REQUEST['filter']['AutoExecute'] = empty($_REQUEST['filter']['AutoExecute']) ? 0 : 1; + $_REQUEST['filter']['AutoDelete'] = empty($_REQUEST['filter']['AutoDelete']) ? 0 : 1; + $_REQUEST['filter']['UpdateDiskSpace'] = empty($_REQUEST['filter']['UpdateDiskSpace']) ? 0 : 1; + $_REQUEST['filter']['Background'] = empty($_REQUEST['filter']['Background']) ? 0 : 1; + $_REQUEST['filter']['Concurrent'] = empty($_REQUEST['filter']['Concurrent']) ? 0 : 1; + $changes = $filter->changes($_REQUEST['filter']); + ZM\Logger::Debug("Changes: " . print_r($changes,true)); + + if ( 0 ) { $sql .= ', Query = '.dbEscape(jsonEncode($_REQUEST['filter']['Query'])); $sql .= ', AutoArchive = '.(!empty($_REQUEST['filter']['AutoArchive']) ? 1 : 0); $sql .= ', AutoVideo = '. ( !empty($_REQUEST['filter']['AutoVideo']) ? 1 : 0); @@ -73,17 +92,25 @@ if ( isset($_REQUEST['object']) and ( $_REQUEST['object'] == 'filter' ) ) { $sql .= ', UpdateDiskSpace = '. ( !empty($_REQUEST['filter']['UpdateDiskSpace']) ? 1 : 0); $sql .= ', Background = '. ( !empty($_REQUEST['filter']['Background']) ? 1 : 0); $sql .= ', Concurrent = '. ( !empty($_REQUEST['filter']['Concurrent']) ? 1 : 0); + } if ( $_REQUEST['Id'] and ( $action == 'Save' ) ) { + if ( 0 ) { dbQuery('UPDATE Filters SET '.$sql.' WHERE Id=?', array($_REQUEST['Id'])); + } + $filter->save($changes); if ( $filter->Background() ) $filter->control('stop'); } else { + # COuld be execute + if ( 0 ) { dbQuery('INSERT INTO Filters SET'.$sql); $_REQUEST['Id'] = dbInsertId(); $filter = new ZM\Filter($_REQUEST['Id']); + } + $filter->save($changes); } - if ( !empty($_REQUEST['filter']['Background']) ) + if ( $filter->Background() ) $filter->control('start'); if ( $action == 'execute' ) { From 346933126d822e89d0c75d21545f2ac108a5364f Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 23 Jul 2019 09:59:49 -0400 Subject: [PATCH 0030/1203] Update filter view to use Filter::find --- web/skins/classic/views/filter.php | 61 ++++++++++++++++-------------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/web/skins/classic/views/filter.php b/web/skins/classic/views/filter.php index 1eb4b71c3..893d1ecbd 100644 --- a/web/skins/classic/views/filter.php +++ b/web/skins/classic/views/filter.php @@ -22,36 +22,35 @@ if ( !canView('Events') ) { $view = 'error'; return; } -require_once 'includes/Filter.php'; +require_once('includes/Object.php'); +require_once('includes/Storage.php'); +require_once('includes/Filter.php'); parseSort(); -$filterNames = array( ''=>translate('ChooseFilter') ); +$filterNames = array(''=>translate('ChooseFilter')); $filter = NULL; -foreach ( dbFetchAll('SELECT * FROM Filters ORDER BY Name') as $row ) { - $filterNames[$row['Id']] = $row['Id'] . ' ' . $row['Name']; - if ( $row['Background'] ) - $filterNames[$row['Id']] .= '*'; - if ( $row['Concurrent'] ) - $filterNames[$row['Id']] .= '&'; +foreach ( ZM\Filter::find(null,array('order'=>'lower(Name)')) as $Filter ) { + $filterNames[$Filter->Id()] = $Filter->Id() . ' ' . $Filter->Name(); + if ( $Filter->Background() ) + $filterNames[$Filter->Id()] .= '*'; + if ( $Filter->Concurrent() ) + $filterNames[$Filter->Id()] .= '&'; - if ( isset($_REQUEST['Id']) && $_REQUEST['Id'] == $row['Id'] ) { - $filter = new ZM\Filter($row); + if ( isset($_REQUEST['Id']) && ($_REQUEST['Id'] == $Filter->Id()) ) { + $filter = $Filter; } } -if ( ! $filter ) { +if ( !$filter ) { $filter = new ZM\Filter(); } -if ( isset($_REQUEST['sort_field']) && isset($_REQUEST['filter']) ) { - $_REQUEST['filter']['Query']['sort_field'] = $_REQUEST['sort_field']; - $_REQUEST['filter']['Query']['sort_asc'] = $_REQUEST['sort_asc']; - $_REQUEST['filter']['Query']['limit'] = $_REQUEST['limit']; -} +ZM\Logger::Debug("Query: " . $filter->Query_json()); +ZM\Logger::Debug("Query: " . print_r($filter->Query(), true)); if ( isset($_REQUEST['filter']) ) { - $filter->set($_REQUEST['filter']); # Update our filter object with whatever changes we have made before saving + #$filter->set($_REQUEST['filter']); } $conjunctionTypes = getFilterQueryConjunctionTypes(); @@ -97,12 +96,13 @@ $attrTypes = array( 'DiskPercent' => translate('AttrDiskPercent'), 'DiskSpace' => translate('AttrDiskSpace'), 'SystemLoad' => translate('AttrSystemLoad'), - 'StorageId' => translate('AttrStorageArea'), - 'ServerId' => translate('AttrMonitorServer'), + 'StorageId' => translate('AttrStorageArea'), + 'SecondaryStorageId' => translate('AttrSecondaryStorageArea'), + 'ServerId' => translate('AttrMonitorServer'), 'FilterServerId' => translate('AttrFilterServer'), 'MonitorServerId' => translate('AttrMonitorServer'), 'StorageServerId' => translate('AttrStorageServer'), - 'StateId' => translate('AttrStateId'), + 'StateId' => translate('AttrStateId'), ); $opTypes = array( @@ -127,27 +127,24 @@ $archiveTypes = array( $focusWindow = true; -$storageareas = array('' => 'All'); -//$storageareas[0] = 'Default ' . ZM_DIR_EVENTS; -foreach ( dbFetchAll('SELECT Id,Name FROM Storage ORDER BY lower(Name) ASC') as $storage ) { - $storageareas[$storage['Id']] = $storage['Name']; -} +$storageareas = array('' => 'All') + ZM\ZM_Object::Objects_Indexed_By_Id('ZM\Storage'); + $weekdays = array(); for ( $i = 0; $i < 7; $i++ ) { $weekdays[$i] = strftime('%A', mktime(12, 0, 0, 1, $i+1, 2001)); } $states = array(); -foreach ( dbFetchAll('SELECT Id, Name FROM States ORDER BY lower(Name) ASC') as $state_row ) { +foreach ( dbFetchAll('SELECT `Id`, `Name` FROM `States` ORDER BY lower(`Name`) ASC') as $state_row ) { $states[$state_row['Id']] = validHtmlStr($state_row['Name']); } $servers = array(); $servers['ZM_SERVER_ID'] = 'Current Server'; $servers['NULL'] = 'No Server'; -foreach ( dbFetchAll('SELECT Id, Name FROM Servers ORDER BY lower(Name) ASC') as $server ) { +foreach ( dbFetchAll('SELECT `Id`, `Name` FROM `Servers` ORDER BY lower(`Name`) ASC') as $server ) { $servers[$server['Id']] = validHtmlStr($server['Name']); } $monitors = array(); -foreach ( dbFetchAll('SELECT Id, Name FROM Monitors ORDER BY Name ASC') as $monitor ) { +foreach ( dbFetchAll('SELECT `Id`, `Name` FROM `Monitors` ORDER BY lower(`Name`) ASC') as $monitor ) { if ( visibleMonitor($monitor['Id']) ) { $monitors[$monitor['Name']] = validHtmlStr($monitor['Name']); } @@ -391,7 +388,13 @@ if ( ZM_OPT_MESSAGE ) {

+

+ + AutoCopy() ) { ?> checked="checked" data-on-click-this="click_autocopy"/> + AutoCopyTo(), $filter->AutoCopy() ? null : array('style'=>'display:none;')); ?> +

+

+ AutoMove() ) { ?> checked="checked" data-on-click-this="click_automove"/> AutoMoveTo(), $filter->AutoMove() ? null : array('style'=>'display:none;')); ?>

From 0e040fc2fcbafd57682f319f577ceea47e2bfea3 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 23 Jul 2019 10:00:05 -0400 Subject: [PATCH 0031/1203] Add click_autocopy function --- web/skins/classic/views/js/filter.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/web/skins/classic/views/js/filter.js b/web/skins/classic/views/js/filter.js index 49a882ee6..36d1e09d5 100644 --- a/web/skins/classic/views/js/filter.js +++ b/web/skins/classic/views/js/filter.js @@ -72,6 +72,15 @@ function click_automove(element) { } } +function click_autocopy(element) { + updateButtons(this); + if ( this.checked ) { + $j(this.form.elements['filter[AutoCopyTo]']).css('display', 'inline'); + } else { + this.form.elements['filter[AutoCopyTo]'].hide(); + } +} + function checkValue( element ) { var rows = $j(element).closest('tbody').children(); parseRows(rows); From df0aef89affbc8a0cb6e31bf5395a3f03892f255 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 23 Jul 2019 10:03:28 -0400 Subject: [PATCH 0032/1203] gracefully handle when window[fnName] doesn't exist --- web/skins/classic/js/skin.js | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/web/skins/classic/js/skin.js b/web/skins/classic/js/skin.js index d39062087..c29ef4a84 100644 --- a/web/skins/classic/js/skin.js +++ b/web/skins/classic/js/skin.js @@ -144,7 +144,7 @@ window.addEventListener("DOMContentLoaded", function onSkinDCL() { el.addEventListener("click", function onClick(evt) { var el = this; var url; - if (el.hasAttribute("href")) { + if ( el.hasAttribute("href") ) { // url = el.getAttribute("href"); } else { @@ -167,12 +167,20 @@ window.addEventListener("DOMContentLoaded", function onSkinDCL() { // 'data-on-click-this' calls the global function in the attribute value with the element when a click happens. document.querySelectorAll("a[data-on-click-this], button[data-on-click-this], input[data-on-click-this]").forEach(function attachOnClick(el) { var fnName = el.getAttribute("data-on-click-this"); + if ( !window[fnName] ) { + console.error("Nothing found to bind to " + fnName); + return; + } el.onclick = window[fnName].bind(el, el); }); // 'data-on-click' calls the global function in the attribute value with no arguments when a click happens. document.querySelectorAll("a[data-on-click], button[data-on-click], input[data-on-click]").forEach(function attachOnClick(el) { var fnName = el.getAttribute("data-on-click"); + if ( !window[fnName] ) { + console.error("Nothing found to bind to " + fnName); + return; + } el.onclick = function() { window[fnName](); }; @@ -181,6 +189,10 @@ window.addEventListener("DOMContentLoaded", function onSkinDCL() { // 'data-on-click-true' calls the global function in the attribute value with no arguments when a click happens. document.querySelectorAll("a[data-on-click-true], button[data-on-click-true], input[data-on-click-true]").forEach(function attachOnClick(el) { var fnName = el.getAttribute("data-on-click-true"); + if ( !window[fnName] ) { + console.error("Nothing found to bind to " + fnName); + return; + } el.onclick = function() { window[fnName](true); }; @@ -189,12 +201,20 @@ window.addEventListener("DOMContentLoaded", function onSkinDCL() { // 'data-on-change-this' calls the global function in the attribute value with the element when a change happens. document.querySelectorAll("select[data-on-change-this], input[data-on-change-this]").forEach(function attachOnChangeThis(el) { var fnName = el.getAttribute("data-on-change-this"); + if ( !window[fnName] ) { + console.error("Nothing found to bind to " + fnName); + return; + } el.onchange = window[fnName].bind(el, el); }); // 'data-on-change' adds an event listener for the global function in the attribute value when a change happens. document.querySelectorAll("select[data-on-change], input[data-on-change]").forEach(function attachOnChange(el) { var fnName = el.getAttribute("data-on-change"); + if ( !window[fnName] ) { + console.error("Nothing found to bind to " + fnName); + return; + } el.onchange = window[fnName]; }); }); From 88beb46c3e645491d93c157be5d6af10b37d16c8 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 23 Jul 2019 10:04:15 -0400 Subject: [PATCH 0033/1203] Add FilterCopyEvents --- web/lang/en_gb.php | 1 + 1 file changed, 1 insertion(+) diff --git a/web/lang/en_gb.php b/web/lang/en_gb.php index 4001c456e..526486921 100644 --- a/web/lang/en_gb.php +++ b/web/lang/en_gb.php @@ -356,6 +356,7 @@ $SLANG = array( 'FilterArchiveEvents' => 'Archive all matches', 'FilterUpdateDiskSpace' => 'Update used disk space', 'FilterDeleteEvents' => 'Delete all matches', + 'FilterCopyEvents' => 'Copy all matches', 'FilterMoveEvents' => 'Move all matches', 'FilterEmailEvents' => 'Email details of all matches', 'FilterExecuteEvents' => 'Execute command on all matches', From 49621bf6529173843261ac542e9306793f9f641d Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 23 Jul 2019 12:58:03 -0400 Subject: [PATCH 0034/1203] Only parse Sql if there is a Query in the filter --- scripts/ZoneMinder/lib/ZoneMinder/Filter.pm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm index 8383e43b7..fd6dd59dc 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm @@ -132,6 +132,10 @@ sub Sql { my $self = shift; $$self{Sql} = shift if @_; if ( ! $$self{Sql} ) { + if ( !$self->{Query} ) { + Warning('No Query in filter.'); + return; + } my $filter_expr = ZoneMinder::General::jsonDecode($self->{Query}); my $sql = 'SELECT E.*, unix_timestamp(E.StartTime) as Time, From bb653b172c59d37e63844494a933e853e7cc94ca Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 23 Jul 2019 14:34:26 -0400 Subject: [PATCH 0035/1203] Use hires time to give better bandwidth reporitng --- scripts/ZoneMinder/lib/ZoneMinder/Event.pm | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm index e1516f1f5..27c157359 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm @@ -41,6 +41,7 @@ require Number::Bytes::Human; require Date::Parse; require POSIX; use Date::Format qw(time2str); +use Time::HiRes qw(gettimeofday tv_interval); #our @ISA = qw(ZoneMinder::Object); use parent qw(ZoneMinder::Object); @@ -595,7 +596,7 @@ Debug("Files to move @files"); for my $file (@files) { next if $file =~ /^\./; ( $file ) = ( $file =~ /^(.*)$/ ); # De-taint - my $starttime = time; + my $starttime = [gettimeofday]; Debug("Moving file $file to $NewPath"); my $size = -s $file; if ( ! $size ) { @@ -607,10 +608,10 @@ Debug("Files to move @files"); } my $filename = $event_path.'/'.File::Basename::basename($file); - if ( ! $bucket->add_key( $filename, $file_contents ) ) { + if ( ! $bucket->add_key($filename, $file_contents) ) { die "Unable to add key for $filename"; } - my $duration = time - $starttime; + my $duration = tv_interval($starttime); Debug('PUT to S3 ' . Number::Bytes::Human::format_bytes($size) . " in $duration seconds = " . Number::Bytes::Human::format_bytes($duration?$size/$duration:$size) . '/sec'); } # end foreach file. @@ -621,13 +622,13 @@ Debug("Files to move @files"); } # end if s3 my $error = ''; - if ( ! $moved ) { - File::Path::make_path( $NewPath, {error => \my $err} ); + if ( !$moved ) { + File::Path::make_path($NewPath, {error => \my $err}); if ( @$err ) { for my $diag (@$err) { my ($file, $message) = %$diag; next if $message eq 'File exists'; - if ($file eq '') { + if ( $file eq '' ) { $error .= "general error: $message\n"; } else { $error .= "problem making $file: $message\n"; @@ -641,20 +642,20 @@ Debug("Files to move @files"); my @files = glob("$OldPath/*"); if ( ! @files ) { $ZoneMinder::Database::dbh->commit(); - return "No files to move."; + return 'No files to move.'; } for my $file (@files) { next if $file =~ /^\./; ( $file ) = ( $file =~ /^(.*)$/ ); # De-taint - my $starttime = time; + my $starttime = [gettimeofday]; Debug("Moving file $file to $NewPath"); my $size = -s $file; if ( ! File::Copy::copy( $file, $NewPath ) ) { $error .= "Copy failed: for $file to $NewPath: $!"; last; } - my $duration = time - $starttime; + my $duration = tv_interval($starttime); Debug('Copied ' . Number::Bytes::Human::format_bytes($size) . " in $duration seconds = " . ($duration?Number::Bytes::Human::format_bytes($size/$duration):'inf') . '/sec'); } # end foreach file. } # end if ! moved From 98922b6788dfdc1f1d58a608fa75c66ecfd0e2f2 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 24 Jul 2019 09:37:16 -0400 Subject: [PATCH 0036/1203] Add SecondaryStorageId to Event so that we can update it --- scripts/ZoneMinder/lib/ZoneMinder/Event.pm | 29 ++++++++++++---------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm index 27c157359..fd1c8297d 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm @@ -64,6 +64,7 @@ $serial = $primary_key = 'Id'; Id MonitorId StorageId + SecondaryStorageId Name Cause StartTime @@ -117,7 +118,7 @@ sub Time { } sub getPath { - return Path( @_ ); + return Path(@_); } sub Path { @@ -132,7 +133,7 @@ sub Path { if ( ! $$event{Path} ) { my $Storage = $event->Storage(); - $$event{Path} = join('/', $Storage->Path(), $event->RelativePath() ); + $$event{Path} = join('/', $Storage->Path(), $event->RelativePath()); } return $$event{Path}; } @@ -164,7 +165,8 @@ sub RelativePath { if ( $event->Time() ) { $$event{RelativePath} = join('/', $event->{MonitorId}, - POSIX::strftime( '%y/%m/%d/%H/%M/%S', + POSIX::strftime( + '%y/%m/%d/%H/%M/%S', localtime($event->Time()) ), ); @@ -204,7 +206,8 @@ sub LinkPath { if ( $event->Time() ) { $$event{LinkPath} = join('/', $event->{MonitorId}, - POSIX::strftime( '%y/%m/%d', + POSIX::strftime( + '%y/%m/%d', localtime($event->Time()) ), '.'.$$event{Id} @@ -256,8 +259,8 @@ sub createIdFile { sub GenerateVideo { my ( $self, $rate, $fps, $scale, $size, $overwrite, $format ) = @_; - my $event_path = $self->Path( ); - chdir( $event_path ); + my $event_path = $self->Path(); + chdir($event_path); ( my $video_name = $self->{Name} ) =~ s/\s/_/g; my @file_parts; @@ -283,10 +286,10 @@ sub GenerateVideo { $file_scale =~ s/_00//; $file_scale =~ s/(_\d+)0+$/$1/; $file_scale = 's'.$file_scale; - push( @file_parts, $file_scale ); + push @file_parts, $file_scale; } elsif ( $size ) { my $file_size = 'S'.$size; - push( @file_parts, $file_size ); + push @file_parts, $file_size; } my $video_file = join('-', $video_name, $file_parts[0], $file_parts[1] ).'.'.$format; if ( $overwrite || !-s $video_file ) { @@ -537,9 +540,9 @@ sub CopyTo { # We do this before bothering to lock the event my ( $NewPath ) = ( $NewStorage->Path() =~ /^(.*)$/ ); # De-taint if ( ! $$NewStorage{Id} ) { - return "New storage does not have an id. Moving will not happen."; + return 'New storage does not have an id. Moving will not happen.'; } elsif ( $$NewStorage{Id} == $$self{StorageId} ) { - return "Event is already located at " . $NewPath; + return 'Event is already located at ' . $NewPath; } elsif ( !$NewPath ) { return "New path ($NewPath) is empty."; } elsif ( ! -e $NewPath ) { @@ -551,7 +554,7 @@ sub CopyTo { # data is reloaded, so need to check that the move hasn't already happened. if ( $$self{StorageId} == $$NewStorage{Id} ) { $ZoneMinder::Database::dbh->commit(); - return "Event has already been moved by someone else."; + return 'Event has already been moved by someone else.'; } if ( $$OldStorage{Id} != $$self{StorageId} ) { @@ -586,13 +589,13 @@ sub CopyTo { } my $event_path = 'events/'.$self->RelativePath(); -Info("Making dir ectory $event_path/"); + Debug("Making directory $event_path/"); if ( ! $bucket->add_key( $event_path.'/','' ) ) { die "Unable to add key for $event_path/"; } my @files = glob("$OldPath/*"); -Debug("Files to move @files"); + Debug("Files to move @files"); for my $file (@files) { next if $file =~ /^\./; ( $file ) = ( $file =~ /^(.*)$/ ); # De-taint From 99f78c50af9a981276e29a6fab92c4b0a48f3dfb Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 24 Jul 2019 09:37:38 -0400 Subject: [PATCH 0037/1203] Add Updating SecondaryStorageId when using CopyTo --- scripts/zmfilter.pl.in | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/scripts/zmfilter.pl.in b/scripts/zmfilter.pl.in index 0b8812c87..b3da3bb68 100644 --- a/scripts/zmfilter.pl.in +++ b/scripts/zmfilter.pl.in @@ -346,10 +346,20 @@ sub checkFilter { Error($_) if $_; } if ( $filter->{AutoCopy} ) { - my $NewStorage = new ZoneMinder::Storage($filter->{AutoCopyTo}); - $_ = $Event->CopyTo($NewStorage); - Error($_) if $_; - } + # Copy To is different from MoveTo in that it JUST copies the files + # So we still need to update the Event object with the new SecondaryStorageId + my $NewStorage = ZoneMinder::Storage->find_one($filter->{AutoCopyTo}); + if ( $NewStorage ) { + $_ = $Event->CopyTo($NewStorage); + if ( $_ ) { + Error($_); + } else { + $Event->save({SecondaryStorageId=>$$NewStorage{Id}}); + } + } else { + Error("No storage area found for copy to operation. AutoCopyTo was $$filter{AutoCopyTo}"); + } + } # end if AutoCopy if ( $filter->{UpdateDiskSpace} ) { $ZoneMinder::Database::dbh->begin_work(); @@ -368,7 +378,7 @@ sub checkFilter { $ZoneMinder::Database::dbh->commit(); } # end if UpdateDiskSpace } # end foreach event -} +} # end sub checkFilter sub generateVideo { my $filter = shift; From 2d556e6402b9430ff156abd8e441f0b03add487e Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 24 Jul 2019 10:52:32 -0400 Subject: [PATCH 0038/1203] Add SecondaryStorageId to Events --- db/zm_create.sql.in | 1 + 1 file changed, 1 insertion(+) diff --git a/db/zm_create.sql.in b/db/zm_create.sql.in index d1952f66a..113d434ff 100644 --- a/db/zm_create.sql.in +++ b/db/zm_create.sql.in @@ -186,6 +186,7 @@ CREATE TABLE `Events` ( `Id` bigint unsigned NOT NULL auto_increment, `MonitorId` int(10) unsigned NOT NULL default '0', `StorageId` smallint(5) unsigned default 0, + `SecondaryStorageId` smallint(5) unsigned default 0, `Name` varchar(64) NOT NULL default '', `Cause` varchar(32) NOT NULL default '', `StartTime` datetime default NULL, From 57133691e91c8898bd3700c3a68908fdc02f1349 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 24 Jul 2019 10:53:23 -0400 Subject: [PATCH 0039/1203] Add update script for SecondaryStorageArea capability in Events and Filters --- db/zm_update-1.33.14.sql | 51 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 db/zm_update-1.33.14.sql diff --git a/db/zm_update-1.33.14.sql b/db/zm_update-1.33.14.sql new file mode 100644 index 000000000..83d0cfbba --- /dev/null +++ b/db/zm_update-1.33.14.sql @@ -0,0 +1,51 @@ +-- +-- Add CopyTo action to Filters +-- + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Filters' + AND column_name = 'AutoCopy' + ) > 0, +"SELECT 'Column AutoCopy already exists in Filters'", +"ALTER TABLE Filters ADD `AutoCopy` tinyint(3) unsigned NOT NULL default '0' AFTER `AutoMove`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Filters' + AND column_name = 'AutoCopyTo' + ) > 0, +"SELECT 'Column AutoCopyTo already exists in Filters'", +"ALTER TABLE Filters ADD `AutoCopyTo` smallint(5) unsigned NOT NULL default '0' AFTER `AutoCopy`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Filters' + AND column_name = 'Query_json' + ) > 0, +"SELECT 'Column Query_json already exists in Filters'", +"ALTER TABLE `Filters` Change `Query` `Query_json` text NOT NULL" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Events' + AND column_name = 'SecondaryStorageId' + ) > 0, +"SELECT 'Column SecondaryStorageId already exists in Events'", +"ALTER TABLE `Events` ADD `SecondaryStorageId` smallint(5) unsigned default 0 AFTER `StorageId`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; From afa02e436d6bddffa202fada5f74162d5dda4730 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 24 Jul 2019 10:53:56 -0400 Subject: [PATCH 0040/1203] Upgrade Storage perl object to use parent Object::find --- scripts/ZoneMinder/lib/ZoneMinder/Storage.pm | 47 +------------------- 1 file changed, 2 insertions(+), 45 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Storage.pm b/scripts/ZoneMinder/lib/ZoneMinder/Storage.pm index 1f7c1b9fe..17d196f92 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Storage.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Storage.pm @@ -46,56 +46,13 @@ use ZoneMinder::Database qw(:all); use POSIX; -use vars qw/ $table $primary_key /; +use vars qw/ $table $primary_key %fields/; $table = 'Storage'; $primary_key = 'Id'; #__PACKAGE__->table('Storage'); #__PACKAGE__->primary_key('Id'); +%fields = map { $_ => $_ } qw( Id Name Path DoDelete ServerId Type Url DiskSpace Scheme ); -sub find { - shift if $_[0] eq 'ZoneMinder::Storage'; - my %sql_filters = @_; - - my $sql = 'SELECT * FROM Storage'; - my @sql_filters; - my @sql_values; - - if ( exists $sql_filters{Id} ) { - push @sql_filters , ' Id=? '; - push @sql_values, $sql_filters{Id}; - } - if ( exists $sql_filters{Name} ) { - push @sql_filters , ' Name = ? '; - push @sql_values, $sql_filters{Name}; - } - if ( exists $sql_filters{ServerId} ) { - push @sql_filters, ' ServerId = ?'; - push @sql_values, $sql_filters{ServerId}; - } - - - $sql .= ' WHERE ' . join(' AND ', @sql_filters) if @sql_filters; - $sql .= ' LIMIT ' . $sql_filters{limit} if $sql_filters{limit}; - - my $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() ); - my $res = $sth->execute( @sql_values ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); - - my @results; - - while( my $db_filter = $sth->fetchrow_hashref() ) { - my $filter = new ZoneMinder::Storage( $$db_filter{Id}, $db_filter ); - push @results, $filter; - } # end while - Debug("SQL: $sql returned " . @results . ' results'); - return @results; -} - -sub find_one { - my @results = find(@_); - return $results[0] if @results; -} sub Path { if ( @_ > 1 ) { From 58851d23d2b4780f2da15f7e5f26cb033edeef30 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 24 Jul 2019 11:22:55 -0400 Subject: [PATCH 0041/1203] Add Secondary Storage support to the Event object --- web/includes/Event.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/web/includes/Event.php b/web/includes/Event.php index 1b996a839..dc7dd3575 100644 --- a/web/includes/Event.php +++ b/web/includes/Event.php @@ -12,6 +12,7 @@ class Event { 'Name', 'MonitorId', 'StorageId', +'SecondaryStorageId', 'Name', 'Cause', 'StartTime', @@ -85,6 +86,19 @@ class Event { return $this->{'Storage'}; } + public function SecondaryStorage( $new = null ) { + if ( $new ) { + $this->{'SecondaryStorage'} = $new; + } + if ( ! ( array_key_exists('SecondaryStorage', $this) and $this->{'SecondaryStorage'} ) ) { + if ( isset($this->{'SecondaryStorageId'}) and $this->{'SecondaryStorageId'} ) + $this->{'SecondaryStorage'} = Storage::find_one(array('Id'=>$this->{'SecondaryStorageId'})); + if ( ! ( array_key_exists('SecondaryStorage', $this) and $this->{'SecondaryStorage'} ) ) + $this->{'SecondaryStorage'} = new Storage(NULL); + } + return $this->{'SecondaryStorage'}; + } + public function Monitor() { if ( isset($this->{'MonitorId'}) ) { $Monitor = Monitor::find_one(array('Id'=>$this->{'MonitorId'})); From aff081ad41c127133737fb4d7feb7add9fb39539 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 24 Jul 2019 11:23:13 -0400 Subject: [PATCH 0042/1203] Must commit after COpyTo to release locks --- scripts/zmfilter.pl.in | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/zmfilter.pl.in b/scripts/zmfilter.pl.in index b3da3bb68..5cba8f1d4 100644 --- a/scripts/zmfilter.pl.in +++ b/scripts/zmfilter.pl.in @@ -348,13 +348,15 @@ sub checkFilter { if ( $filter->{AutoCopy} ) { # Copy To is different from MoveTo in that it JUST copies the files # So we still need to update the Event object with the new SecondaryStorageId - my $NewStorage = ZoneMinder::Storage->find_one($filter->{AutoCopyTo}); + my $NewStorage = ZoneMinder::Storage->find_one(Id=>$filter->{AutoCopyTo}); if ( $NewStorage ) { $_ = $Event->CopyTo($NewStorage); if ( $_ ) { + $ZoneMinder::Database::dbh->commit(); Error($_); } else { $Event->save({SecondaryStorageId=>$$NewStorage{Id}}); + $ZoneMinder::Database::dbh->commit(); } } else { Error("No storage area found for copy to operation. AutoCopyTo was $$filter{AutoCopyTo}"); From 341f4adbdfaedd093869a009754f46a0a2c6f58d Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 24 Jul 2019 11:23:38 -0400 Subject: [PATCH 0043/1203] Functions that change the Query must reset Query_json as well --- web/includes/Filter.php | 46 ++++++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/web/includes/Filter.php b/web/includes/Filter.php index f8b06d4e5..d59ecda7c 100644 --- a/web/includes/Filter.php +++ b/web/includes/Filter.php @@ -23,29 +23,33 @@ class Filter extends ZM_Object { 'UpdateDiskSpace' => 0, 'Background' => 0, 'Concurrent' => 0, - #'limit' => 100, 'Query_json' => '', - #'sort_field' => ZM_WEB_EVENT_SORT_FIELD, - #'sort_asc' => ZM_WEB_EVENT_SORT_ORDER, ); - public function Query($new = -1) { - if ( $new and ( $new != -1 ) ) { - $this->{'Query'} = $new; - $this->{'Query_json'} = jsonEncode($new); - Logger::Debug("Setting Query to " . $this->{'Query_json'}); + public function Query_json() { + if ( func_num_args( ) ) { + $this->{'Query_json'} = func_get_arg(0);; + $this->{'Query'} = jsonDecode($this->{'Query_json'}); + } + return $this->{'Query_json'}; + } + + public function Query() { + if ( func_num_args( ) ) { + $this->{'Query'} = func_get_arg(0);; + $this->{'Query_json'} = jsonEncode($this->{'Query'}); } if ( !array_key_exists('Query', $this) ) { if ( array_key_exists('Query_json', $this) and $this->{'Query_json'} ) { $this->{'Query'} = jsonDecode($this->{'Query_json'}); - Logger::Debug("Decoded Query already" . print_r($this->{'Query'}, true )); - } else { - Logger::Debug("No Have Query_json already"); $this->{'Query'} = array(); } } else { - Logger::Debug("Have Query already" . print_r($this->{'Query'}, true )); + if ( !is_array($this->{'Query'}) ) { + # Handle existence of both Query_json and Query in the row + $this->{'Query'} = jsonDecode($this->{'Query_json'}); + } } return $this->{'Query'}; } @@ -59,8 +63,10 @@ class Filter extends ZM_Object { } public function terms( ) { - if ( func_num_args( ) ) { - $this->Query()['terms'] = func_get_arg(0); + if ( func_num_args() ) { + $Query = $this->Query(); + $Query['terms'] = func_get_arg(0); + $this->Query($Query); } if ( isset( $this->Query()['terms'] ) ) { return $this->Query()['terms']; @@ -71,7 +77,9 @@ class Filter extends ZM_Object { // The following three fields are actually stored in the Query public function sort_field( ) { if ( func_num_args( ) ) { - $this->Query()['sort_field'] = func_get_arg(0); + $Query = $this->Query(); + $Query['sort_field'] = func_get_arg(0); + $this->Query($Query); } if ( isset( $this->Query()['sort_field'] ) ) { return $this->{'Query'}['sort_field']; @@ -82,7 +90,9 @@ class Filter extends ZM_Object { public function sort_asc( ) { if ( func_num_args( ) ) { - $this->Query()['sort_asc'] = func_get_arg(0); + $Query = $this->Query(); + $Query['sort_asc'] = func_get_arg(0); + $this->Query($Query); } if ( isset( $this->Query()['sort_asc'] ) ) { return $this->{'Query'}['sort_asc']; @@ -93,7 +103,9 @@ class Filter extends ZM_Object { public function limit( ) { if ( func_num_args( ) ) { - $this->{'Query'}['limit'] = func_get_arg(0); + $Query = $this->Query(); + $Query['limit'] = func_get_arg(0); + $this->Query($Query); } if ( isset( $this->Query()['limit'] ) ) return $this->{'Query'}['limit']; From e3a9d5d48875c6cf8e24f73dc9f0d880a49e0a08 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 24 Jul 2019 11:24:14 -0400 Subject: [PATCH 0044/1203] Rewrite changes to run through the keys of the passed in new values array, and handle object methods as well as basic values --- web/includes/Object.php | 46 +++++++++++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/web/includes/Object.php b/web/includes/Object.php index 041b45bed..2b58928d9 100644 --- a/web/includes/Object.php +++ b/web/includes/Object.php @@ -171,19 +171,43 @@ class ZM_Object { public function changes( $new_values ) { $changes = array(); - foreach ( $this->defaults as $field=>$default_value ) { - if ( array_key_exists($field, $new_values) ) { - Logger::Debug("Checking default $field => $default_value exists in new values :".$this->{$field} . " " .$new_values[$field]); - if ( (!array_key_exists($field, $this)) or ( $this->{$field} != $new_values[$field] ) ) { - Logger::Debug("Checking default $field => $default_value changes becaause" . $new_values[$field].' != '.$new_values[$field]); - $changes[$field] = $new_values[$field]; - #} else if { - Logger::Debug("Checking default $field => $default_value changes becaause " . $new_values[$field].' != '.$new_values[$field]); - #array_push( $changes, [$field=>$defaults[$field]] ); + foreach ( $new_values as $field => $value ) { + + if ( method_exists($this, $field) ) { + $old_value = $this->$field(); + Logger::Debug("Checking method $field () ".print_r($old_value,true)." => " . print_r($value,true)); + if ( is_array($old_value) ) { + $diff = array_recursive_diff($old_value, $value); + Logger::Debug("Checking method $field () diff is".print_r($diff,true)); + if ( count($diff) ) { + $changes[$field] = $value; + } + } else if ( $this->$field() != $value ) { + $changes[$field] = $value; + } + } else if ( array_key_exists($field, $this) ) { + Logger::Debug("Checking field $field => ".$this->{$field} . " ?= " .$value); + if ( $this->{$field} != $value ) { + $changes[$field] = $value; + } + } else if ( array_key_exists($field, $this->defaults) ) { + + Logger::Debug("Checking default $field => ".$this->defaults[$field] . " " .$value); + if ( $this->defaults[$field] != $value ) { + $changes[$field] = $value; } - } else { - Logger::Debug("Checking default $field => $default_value not in new_values"); } + + #if ( (!array_key_exists($field, $this)) or ( $this->{$field} != $new_values[$field] ) ) { + #Logger::Debug("Checking default $field => $default_value changes becaause" . $new_values[$field].' != '.$new_values[$field]); + #$changes[$field] = $new_values[$field]; + ##} else if { + #Logger::Debug("Checking default $field => $default_value changes becaause " . $new_values[$field].' != '.$new_values[$field]); + ##array_push( $changes, [$field=>$defaults[$field]] ); + #} + #} else { + #Logger::Debug("Checking default $field => $default_value not in new_values"); + #} } # end foreach default return $changes; } # end public function changes From 45afc2a534878b86ae10928fc4241c1f9acad36c Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 24 Jul 2019 11:24:37 -0400 Subject: [PATCH 0045/1203] introduce array_recursive_diff which we use to compare two arrays in Object::changes --- web/includes/functions.php | 41 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/web/includes/functions.php b/web/includes/functions.php index 867861ffe..9e0655933 100644 --- a/web/includes/functions.php +++ b/web/includes/functions.php @@ -2518,4 +2518,45 @@ function format_duration($time, $separator=':') { return sprintf('%02d%s%02d%s%02d', floor($time/3600), $separator, ($time/60)%60, $separator, $time%60); } +function array_recursive_diff($aArray1, $aArray2) { + $aReturn = array(); + + foreach ($aArray1 as $mKey => $mValue) { + if ( array_key_exists($mKey, $aArray2) ) { + if ( is_array($mValue) ) { + $aRecursiveDiff = array_recursive_diff($mValue, $aArray2[$mKey]); + if ( count($aRecursiveDiff) ) { + $aReturn[$mKey] = $aRecursiveDiff; + } + } else { + if ( $mValue != $aArray2[$mKey] ) { + $aReturn[$mKey] = $mValue; + } + } + } else { + $aReturn[$mKey] = $mValue; + } + } + # Now check for keys in array2 that are not in array1 + foreach ($aArray2 as $mKey => $mValue) { + if ( array_key_exists($mKey, $aArray1) ) { + # Already checked it... I think. + #if ( is_array($mValue) ) { + #$aRecursiveDiff = array_recursive_diff($mValue, $aArray2[$mKey]); + #if ( count($aRecursiveDiff) ) { + #$aReturn[$mKey] = $aRecursiveDiff; + #} + #} else { + #if ( $mValue != $aArray2[$mKey] ) { + #$aReturn[$mKey] = $mValue; + #} + #} + } else { + $aReturn[$mKey] = $mValue; + } + } + + return $aReturn; +} + ?> From 1254e8ab67d0c90ee0b6b98b3496e5ad70036c78 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 24 Jul 2019 11:24:50 -0400 Subject: [PATCH 0046/1203] Add AttrSecondaryStorageArea to lang --- web/lang/en_gb.php | 1 + 1 file changed, 1 insertion(+) diff --git a/web/lang/en_gb.php b/web/lang/en_gb.php index 526486921..0e575fd1f 100644 --- a/web/lang/en_gb.php +++ b/web/lang/en_gb.php @@ -132,6 +132,7 @@ $SLANG = array( 'AttrMaxScore' => 'Max. Score', 'AttrMonitorId' => 'Monitor Id', 'AttrMonitorName' => 'Monitor Name', + 'AttrSecondaryStorageArea' => 'Secondary Storage Area', 'AttrStorageArea' => 'Storage Area', 'AttrFilterServer' => 'Server Filter is Running On', 'AttrMonitorServer' => 'Server Monitor is Running On', From 1a0beab70336397109379197de3b253c01d2642c Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 24 Jul 2019 11:25:38 -0400 Subject: [PATCH 0047/1203] add Secondary Storage Area options. Storage array is now an array of Objects so use the Name key --- web/skins/classic/views/js/filter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/skins/classic/views/js/filter.js b/web/skins/classic/views/js/filter.js index 36d1e09d5..99e8a9c03 100644 --- a/web/skins/classic/views/js/filter.js +++ b/web/skins/classic/views/js/filter.js @@ -209,10 +209,10 @@ function parseRows(rows) { } var serverVal = inputTds.eq(4).children().val(); inputTds.eq(4).html(serverSelect).children().val(serverVal).chosen({width: "101%"}); - } else if ( attr == 'StorageId' ) { //Choose by storagearea + } else if ( (attr == 'StorageId') || (attr == 'SecondaryStorageId') ) { //Choose by storagearea var storageSelect = $j('').attr('name', queryPrefix + rowNum + '][val]').attr('id', queryPrefix + rowNum + '][val]'); for ( key in storageareas ) { - storageSelect.append(''); + storageSelect.append(''); } var storageVal = inputTds.eq(4).children().val(); inputTds.eq(4).html(storageSelect).children().val(storageVal).chosen({width: "101%"}); From 2d46f2adaba3747401d96cd8549916a739e8d696 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 24 Jul 2019 11:25:51 -0400 Subject: [PATCH 0048/1203] add Secondary Storage Area options. --- web/skins/classic/views/filter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/skins/classic/views/filter.php b/web/skins/classic/views/filter.php index 893d1ecbd..2f159230e 100644 --- a/web/skins/classic/views/filter.php +++ b/web/skins/classic/views/filter.php @@ -270,7 +270,7 @@ for ( $i=0; $i < count($terms); $i++ ) { From 39262d55f5914004a2ae41a273bb7631d96294ec Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 24 Jul 2019 11:26:07 -0400 Subject: [PATCH 0049/1203] Also show secondary storage area when viewing event --- web/skins/classic/views/event.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/web/skins/classic/views/event.php b/web/skins/classic/views/event.php index b09f88acc..f0a6e343c 100644 --- a/web/skins/classic/views/event.php +++ b/web/skins/classic/views/event.php @@ -134,7 +134,11 @@ if ( ! $Event->Id() ) { Length().'s' ?> Frames() ?>/AlarmFrames() ?> TotScore() ?>/AvgScore() ?>/MaxScore() ?> - DiskSpace(null)) . ' on ' . $Event->Storage()->Name() ?> + +DiskSpace(null)) . ' on ' . $Event->Storage()->Name(). + ( $Event->SecondaryStorageId() ? ', ' . $Event->SecondaryStorage()->Name() :'' ) +?>