From 5f27c124c371f58ae09d12c2dc44219c05d57b37 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 2 Nov 2021 18:00:18 -0400 Subject: [PATCH 01/52] Ignore bootstrap-4.5.0.js --- .eslintignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.eslintignore b/.eslintignore index 4682851df..91b0fd196 100644 --- a/.eslintignore +++ b/.eslintignore @@ -4,7 +4,7 @@ web/api/lib web/includes/csrf/ web/js/videojs.zoomrotate.js -web/skins/classic/js/bootstrap.js +web/skins/classic/js/bootstrap-4.5.0.js web/skins/classic/js/chosen web/skins/classic/js/dateTimePicker web/skins/classic/js/jquery-*.js From ab9c538c370e3d2817dd13448017bea788315c1a Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 16 Nov 2021 14:59:18 -0500 Subject: [PATCH 02/52] Allow NOW or CURRENT for PACKAGE_VERSION similar to snapshot --- utils/do_debian_package.sh | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/utils/do_debian_package.sh b/utils/do_debian_package.sh index 6c8c06c00..a77702fca 100755 --- a/utils/do_debian_package.sh +++ b/utils/do_debian_package.sh @@ -146,6 +146,14 @@ else fi; fi +if [ "$PACKAGE_VERSION" == "NOW" ]; then + PACKAGE_VERSION=`date +%Y%m%d%H%M%S`; +else + if [ "$PACKAGE_VERSION" == "CURRENT" ]; then + PACKAGE_VERSION="`date +%Y%m%d.`$(git rev-list ${versionhash}..HEAD --count)" + fi; +fi; + IFS='.' read -r -a VERSION_PARTS <<< "$RELEASE" if [ "$PPA" == "" ]; then if [ "$RELEASE" != "" ]; then From 40e7f607f5d3dd73fcd714acc3cd7d73e06d9188 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 22 Nov 2021 11:38:40 -0500 Subject: [PATCH 03/52] If no protocol defined, fall back to the name of the Control --- scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm b/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm index da3a56373..5646fb897 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm @@ -326,17 +326,23 @@ sub resumeMotionDetection { sub Control { my $self = shift; - if ( ! exists $$self{Control}) { + if (!exists $$self{Control}) { if ($$self{ControlId}) { require ZoneMinder::Control; my $Control = ZoneMinder::Control->find_one(Id=>$$self{ControlId}); if ($Control) { + my $Protocol = $$Control{Protocol}; + + if (!$Protocol) { + Error("No protocol set in control $$Control{Id}, trying Name $$Control{Name}"); + $Protocol = $$Control{Name}; + } require Module::Load::Conditional; - if (!Module::Load::Conditional::can_load(modules => {'ZoneMinder::Control::'.$$Control{Protocol} => undef})) { - Error("Can't load ZoneMinder::Control::$$Control{Protocol}\n$Module::Load::Conditional::ERROR"); + if (!Module::Load::Conditional::can_load(modules => {'ZoneMinder::Control::'.$Protocol => undef})) { + Error("Can't load ZoneMinder::Control::$Protocol\n$Module::Load::Conditional::ERROR"); return undef; } - bless $Control, 'ZoneMinder::Control::'.$$Control{Protocol}; + bless $Control, 'ZoneMinder::Control::'.$Protocol; $$Control{MonitorId} = $$self{Id}; $$self{Control} = $Control; } else { From cc65c99791ee2ce5c9617dae41d9f7cae8f9a100 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 22 Nov 2021 12:53:51 -0500 Subject: [PATCH 04/52] Move init of ctx up before we setup the monitors. I think in some cases we can calls functions that assume ctx has a value. Uncaught%20TypeError%3A%20Cannot%20read%20properties%20of%20undefined%20(reading%20'getImageData') --- web/skins/classic/views/js/montagereview.js | 25 ++++++++++--------- .../classic/views/js/montagereview.js.php | 2 +- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/web/skins/classic/views/js/montagereview.js b/web/skins/classic/views/js/montagereview.js index 787a910d0..da3301d4d 100644 --- a/web/skins/classic/views/js/montagereview.js +++ b/web/skins/classic/views/js/montagereview.js @@ -985,6 +985,19 @@ function initPage() { }); }); + if ( !liveMode ) { + canvas = document.getElementById('timeline'); + + canvas.addEventListener('mousemove', mmove, false); + canvas.addEventListener('touchmove', tmove, false); + canvas.addEventListener('mousedown', mdown, false); + canvas.addEventListener('mouseup', mup, false); + canvas.addEventListener('mouseout', mout, false); + + ctx = canvas.getContext('2d'); + drawGraph(); + } + for ( var i = 0, len = monitorPtr.length; i < len; i += 1 ) { var monId = monitorPtr[i]; if ( !monId ) continue; @@ -1006,18 +1019,6 @@ function initPage() { } } // end foreach monitor - if ( !liveMode ) { - canvas = document.getElementById('timeline'); - - canvas.addEventListener('mousemove', mmove, false); - canvas.addEventListener('touchmove', tmove, false); - canvas.addEventListener('mousedown', mdown, false); - canvas.addEventListener('mouseup', mup, false); - canvas.addEventListener('mouseout', mout, false); - - ctx = canvas.getContext('2d'); - drawGraph(); - } setSpeed(speedIndex); //setFit(fitMode); // will redraw //setLive(liveMode); // will redraw diff --git a/web/skins/classic/views/js/montagereview.js.php b/web/skins/classic/views/js/montagereview.js.php index d82f103ed..f4b6222f4 100644 --- a/web/skins/classic/views/js/montagereview.js.php +++ b/web/skins/classic/views/js/montagereview.js.php @@ -239,6 +239,6 @@ echo "];\n"; var cWidth; // save canvas width var cHeight; // save canvas height var canvas; // global canvas definition so we don't have to keep looking it up -var ctx; +var ctx = null; var underSlider; // use this to hold what is hidden by the slider var underSliderX; // Where the above was taken from (left side, Y is zero) From 1f75b017ccb4688bfda4726c3ac4bdb3a64bd996 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 22 Nov 2021 14:17:50 -0500 Subject: [PATCH 05/52] kill the background timer when switching to history so that we don't cause a javascript error. comment out debugging and use native javascript instead of jquery. --- web/skins/classic/views/js/montagereview.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/web/skins/classic/views/js/montagereview.js b/web/skins/classic/views/js/montagereview.js index da3301d4d..60856f4ca 100644 --- a/web/skins/classic/views/js/montagereview.js +++ b/web/skins/classic/views/js/montagereview.js @@ -57,7 +57,7 @@ function getFrame(monId, time, last_Frame) { var events_for_monitor = events_by_monitor_id[monId]; if ( !events_for_monitor ) { - console.log("No events for monitor " + monId); + //console.log("No events for monitor " + monId); return; } @@ -648,8 +648,11 @@ function setSpeed(speed_index) { } function setLive(value) { + // When we submit the context etc goes away but we may still be trying to update + // So kill the timer. + clearInterval(timerObj); liveMode = value; - var form = $j('#montagereview_form')[0]; + var form = document.getElementById('montagereview_form'); form.elements['live'].value = value; form.submit(); return false; From 46a835b28ab80ec2128d5d4acc2f109f1fb29890 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 22 Nov 2021 14:26:48 -0500 Subject: [PATCH 06/52] fix error when no monitors defined and we are adding one.Fixes #3385 --- web/skins/classic/views/monitor.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/skins/classic/views/monitor.php b/web/skins/classic/views/monitor.php index 7d599904a..3dc6937e8 100644 --- a/web/skins/classic/views/monitor.php +++ b/web/skins/classic/views/monitor.php @@ -453,7 +453,7 @@ foreach ( $tabs as $name=>$value ) { switch ( $name ) { case 'general' : { - if (!$monitor->Id()) { + if (!$monitor->Id() and count($monitors)) { $monitor_ids = array(); foreach ($monitors as $m) { $monitor_ids[] = $m['Id']; } $available_monitor_ids = array_diff(range(min($monitor_ids),max($monitor_ids)), $monitor_ids); @@ -470,7 +470,7 @@ if (count($available_monitor_ids)) { Id() + } # end if ! $monitor->Id() and count($monitors) ?> From ffdb0f98249819c5a75c123da4709e995deeee80 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 23 Nov 2021 11:05:39 -0500 Subject: [PATCH 07/52] If we are starting a process that is waiting to term, mark it to get started by the reaper. Fixes case where zmdc thought the process was still running and so didn't start it. We never noticed because zmwatch would eventually notice. The result is instant restart. --- scripts/zmdc.pl.in | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/scripts/zmdc.pl.in b/scripts/zmdc.pl.in index 5cf866e56..793049479 100644 --- a/scripts/zmdc.pl.in +++ b/scripts/zmdc.pl.in @@ -429,10 +429,20 @@ sub start { # It's not running, or at least it's not been started by us $process = { daemon=>$daemon, args=>\@args, command=>$command, keepalive=>!undef }; } elsif ( $process->{pid} && $pid_hash{$process->{pid}} ) { - dPrint(ZoneMinder::Logger::INFO, "'$process->{command}' already running at " + if ($process->{term_sent_at}) { + dPrint(ZoneMinder::Logger::INFO, "'$process->{command}' was told to term at " + .strftime('%y/%m/%d %H:%M:%S', localtime($process->{term_sent_at})) + .", pid = $process->{pid}\n" + ); + $process->{keepalive} = !undef; + $process->{delay} = 0; + delete $terminating_processes{$command}; + } else { + dPrint(ZoneMinder::Logger::INFO, "'$process->{command}' already running at " .strftime('%y/%m/%d %H:%M:%S', localtime($process->{started})) .", pid = $process->{pid}\n" - ); + ); + } return; } @@ -523,7 +533,7 @@ sub send_stop { ."\n" ); sigprocmask(SIG_UNBLOCK, $blockset) or die "dying at unblock...\n"; - return(); + return (); } my $pid = $process->{pid}; @@ -586,7 +596,7 @@ sub check_for_processes_to_kill { sub stop { my ( $daemon, @args ) = @_; - my $command = join(' ', $daemon, @args ); + my $command = join(' ', $daemon, @args); my $process = $cmd_hash{$command}; if ( !$process ) { dPrint(ZoneMinder::Logger::WARNING, "Can't find process with command of '$command'"); From 81ffc6df4e763474035d56c098089c7ca6339f8d Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 24 Nov 2021 12:06:13 -0500 Subject: [PATCH 08/52] Remove text-nowrap from cause/notes column --- web/skins/classic/views/js/events.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/skins/classic/views/js/events.js b/web/skins/classic/views/js/events.js index 6139ba3e4..bafd16763 100644 --- a/web/skins/classic/views/js/events.js +++ b/web/skins/classic/views/js/events.js @@ -63,13 +63,13 @@ function processRows(rows) { row.Id = '' + eid + ''; row.Name = '' + row.Name + '' + - '
' + archived + emailed + '
'; + '
' + archived + emailed + '
'; if ( canEdit.Monitors ) row.Monitor = '' + row.Monitor + ''; if ( canEdit.Events ) row.Cause = '' + row.Cause + ''; if ( row.Notes.indexOf('detected:') >= 0 ) { - row.Cause = row.Cause + '
' + row.Notes + '
'; + row.Cause = row.Cause + '
' + row.Notes + '
'; } else if ( row.Notes != 'Forced Web: ' ) { - row.Cause = row.Cause + '
' + row.Notes + '
'; + row.Cause = row.Cause + '
' + row.Notes + '
'; } row.Frames = '' + row.Frames + ''; row.AlarmFrames = '' + row.AlarmFrames + ''; From 4be9c6cdd28c87fdaecec89342b758a6876ee6d7 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 24 Nov 2021 12:42:31 -0500 Subject: [PATCH 09/52] Code comments and make warning when the first packet in queue is locked. --- src/zm_packetqueue.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/zm_packetqueue.cpp b/src/zm_packetqueue.cpp index 509a25ee6..8b831ec2d 100644 --- a/src/zm_packetqueue.cpp +++ b/src/zm_packetqueue.cpp @@ -116,14 +116,15 @@ bool PacketQueue::queuePacket(std::shared_ptr add_packet) { , max_video_packet_count); for ( - auto it = ++pktQueue.begin(); - it != pktQueue.end() and *it != add_packet; + auto it = ++pktQueue.begin(); + it != pktQueue.end() and *it != add_packet; + // iterator is incremented by erase ) { std::shared_ptr zm_packet = *it; ZMLockedPacket *lp = new ZMLockedPacket(zm_packet); if (!lp->trylock()) { - Debug(1, "Found locked packet when trying to free up video packets. Skipping to next one"); + Warning("Found locked packet when trying to free up video packets. This basically means that decoding is not keeping up."); delete lp; ++it; continue; @@ -312,7 +313,6 @@ void PacketQueue::clearPackets(const std::shared_ptr &add_packet) { pktQueue.size()); pktQueue.pop_front(); packet_counts[zm_packet->packet.stream_index] -= 1; - //delete zm_packet; } } // end if have at least max_video_packet_count video packets remaining // We signal on every packet because someday we may analyze sound From 67556430c661a4e801d306b819442ebc2588844d Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 24 Nov 2021 12:44:07 -0500 Subject: [PATCH 10/52] Add option ZM_NO_PCRE to disable testing for libpcre3. debian wants to remove it so this allows us to test building without it. Remove libpcre3 from depends and set ZM_NO_PCRE=ON in debian build config --- CMakeLists.txt | 34 ++++++++++++++++++++-------------- distros/ubuntu2004/control | 2 -- distros/ubuntu2004/rules | 1 + 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index c34a9f808..3b789e4c8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -167,6 +167,8 @@ set(ZM_NO_X10 "OFF" CACHE BOOL set(ZM_ONVIF "ON" CACHE BOOL "Set to ON to enable basic ONVIF support. This is EXPERIMENTAL and may not work with all cameras claiming to be ONVIF compliant. default: ON") +set(ZM_NO_PCRE "OFF" CACHE BOOL + "Set to ON to skip libpcre3 checks and force building ZM without libpcre3. default: OFF") set(ZM_NO_RTSPSERVER "OFF" CACHE BOOL "Set to ON to skip building ZM with rtsp server support. default: OFF") set(ZM_PERL_MM_PARMS INSTALLDIRS=vendor NO_PACKLIST=1 NO_PERLLOCAL=1 CACHE STRING @@ -407,21 +409,24 @@ else() message(FATAL_ERROR "ZoneMinder requires pthread but it was not found on your system") endif() -# pcre (using find_library and find_path) -find_library(PCRE_LIBRARIES pcre) -if(PCRE_LIBRARIES) - set(HAVE_LIBPCRE 1) - list(APPEND ZM_BIN_LIBS "${PCRE_LIBRARIES}") - find_path(PCRE_INCLUDE_DIR pcre.h) - if(PCRE_INCLUDE_DIR) - include_directories("${PCRE_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${PCRE_INCLUDE_DIR}") +# Do not check for cURL if ZM_NO_CURL is on +if(NOT ZM_NO_PRCE) + # pcre (using find_library and find_path) + find_library(PCRE_LIBRARIES pcre) + if(PCRE_LIBRARIES) + set(HAVE_LIBPCRE 1) + list(APPEND ZM_BIN_LIBS "${PCRE_LIBRARIES}") + find_path(PCRE_INCLUDE_DIR pcre.h) + if(PCRE_INCLUDE_DIR) + include_directories("${PCRE_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${PCRE_INCLUDE_DIR}") + endif() + mark_as_advanced(FORCE PCRE_LIBRARIES PCRE_INCLUDE_DIR) + check_include_file("pcre.h" HAVE_PCRE_H) + set(optlibsfound "${optlibsfound} PCRE") + else() + set(optlibsnotfound "${optlibsnotfound} PCRE") endif() - mark_as_advanced(FORCE PCRE_LIBRARIES PCRE_INCLUDE_DIR) - check_include_file("pcre.h" HAVE_PCRE_H) - set(optlibsfound "${optlibsfound} PCRE") -else() - set(optlibsnotfound "${optlibsnotfound} PCRE") endif() # mysqlclient (using find_library and find_path) @@ -540,6 +545,7 @@ set(ZM_PCRE 0) if(HAVE_LIBPCRE AND HAVE_PCRE_H) set(ZM_PCRE 1) endif() + # Check for mmap and enable in all components set(ZM_MEM_MAPPED 0) set(ENABLE_MMAP no) diff --git a/distros/ubuntu2004/control b/distros/ubuntu2004/control index a4683bfde..d14c3fb52 100644 --- a/distros/ubuntu2004/control +++ b/distros/ubuntu2004/control @@ -16,7 +16,6 @@ Build-Depends: debhelper (>= 11), sphinx-doc, python3-sphinx, dh-linktree, dh-ap ,libjpeg-turbo8-dev | libjpeg62-turbo-dev | libjpeg8-dev | libjpeg9-dev ,libturbojpeg0-dev ,default-libmysqlclient-dev | libmysqlclient-dev | libmariadbclient-dev-compat - ,libpcre3-dev ,libpolkit-gobject-1-dev ,libv4l-dev [!hurd-any] ,libvlc-dev @@ -70,7 +69,6 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} ,policykit-1 ,rsyslog | system-log-daemon ,zip - ,libpcre3 ,libcrypt-eksblowfish-perl ,libdata-entropy-perl ,libvncclient1|libvncclient0 diff --git a/distros/ubuntu2004/rules b/distros/ubuntu2004/rules index c137a9da2..af75a409a 100755 --- a/distros/ubuntu2004/rules +++ b/distros/ubuntu2004/rules @@ -19,6 +19,7 @@ override_dh_auto_configure: -DCMAKE_VERBOSE_MAKEFILE=ON \ -DCMAKE_BUILD_TYPE=Release \ -DBUILD_MAN=0 \ + -DZM_NO_PCRE=ON \ -DZM_CONFIG_DIR="/etc/zm" \ -DZM_CONFIG_SUBDIR="/etc/zm/conf.d" \ -DZM_RUNDIR="/run/zm" \ From 77d3109152ab6a238589ec82853d864403af4833 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 24 Nov 2021 13:44:45 -0500 Subject: [PATCH 11/52] Increase to 20 before warning about db queue size. Put lock in it's own scope so that we unlock before notifying --- src/zm_db.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/zm_db.cpp b/src/zm_db.cpp index e3b737c07..f0b13d538 100644 --- a/src/zm_db.cpp +++ b/src/zm_db.cpp @@ -251,11 +251,11 @@ void zmDbQueue::process() { mCondition.wait(lock); } while (!mQueue.empty()) { - if (mQueue.size() > 10) { + if (mQueue.size() > 20) { Logger *log = Logger::fetch(); Logger::Level db_level = log->databaseLevel(); log->databaseLevel(Logger::NOLOG); - Warning("db queue size has grown larger %zu than 10 entries", mQueue.size()); + Warning("db queue size has grown larger %zu than 20 entries", mQueue.size()); log->databaseLevel(db_level); } std::string sql = mQueue.front(); @@ -271,8 +271,10 @@ void zmDbQueue::process() { void zmDbQueue::push(std::string &&sql) { if (mTerminate) return; - std::unique_lock lock(mMutex); - mQueue.push(std::move(sql)); + { + std::unique_lock lock(mMutex); + mQueue.push(std::move(sql)); + } mCondition.notify_all(); } From e061f3b34a6e230850120c99bdecdafe7be4ac4b Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 24 Nov 2021 14:28:15 -0500 Subject: [PATCH 12/52] typo --- src/zm_monitor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index d808ddba0..d9f932d75 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -1873,7 +1873,7 @@ bool Monitor::Analyse() { if (snap->image) { // decoder may not have been able to provide an image if (!ref_image.Buffer()) { - Debug(1, "Assigning instead of Dectecting"); + Debug(1, "Assigning instead of Detecting"); ref_image.Assign(*(snap->image)); } else { Debug(1, "Detecting motion on image %d, image %p", snap->image_index, snap->image); From e8bb095730a7ba7430ffe27703926c9cd71a7a43 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 24 Nov 2021 14:28:31 -0500 Subject: [PATCH 13/52] include monitor dimensions when logging about zone mismatch --- src/zm_zone.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/zm_zone.cpp b/src/zm_zone.cpp index f0c09ec78..4fc1bf61b 100644 --- a/src/zm_zone.cpp +++ b/src/zm_zone.cpp @@ -878,16 +878,23 @@ std::vector Zone::Load(Monitor *monitor) { continue; } - if (polygon.Extent().Lo().x_ < 0 || polygon.Extent().Hi().x_ > static_cast(monitor->Width()) - || polygon.Extent().Lo().y_ < 0 || polygon.Extent().Hi().y_ > static_cast(monitor->Height())) { - Error("Zone %d/%s for monitor %s extends outside of image dimensions, (%d,%d), (%d,%d), fixing", + if (polygon.Extent().Lo().x_ < 0 + || + polygon.Extent().Hi().x_ > static_cast(monitor->Width()) + || + polygon.Extent().Lo().y_ < 0 + || + polygon.Extent().Hi().y_ > static_cast(monitor->Height())) { + Error("Zone %d/%s for monitor %s extends outside of image dimensions, (%d,%d), (%d,%d) != (%d,%d), fixing", Id, Name, monitor->Name(), polygon.Extent().Lo().x_, polygon.Extent().Lo().y_, polygon.Extent().Hi().x_, - polygon.Extent().Hi().y_); + polygon.Extent().Hi().y_, + monitor->Width(), + monitor->Height()); polygon.Clip(Box( {0, 0}, From af5436d009732c4a2656eb32bbe3b6d3d12036a7 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 26 Nov 2021 10:31:54 -0500 Subject: [PATCH 14/52] Handle bug where a value of '' will prevent special case handling. Allow '' to mean NULL when specifying Storage Area --- scripts/ZoneMinder/lib/ZoneMinder/Filter.pm | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm index 3a3308938..0db46ff0d 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm @@ -230,8 +230,8 @@ sub Sql { # PostCondition, so no further SQL } else { ( my $stripped_value = $value ) =~ s/^["\']+?(.+)["\']+?$/$1/; - foreach my $temp_value ( split( /["'\s]*?,["'\s]*?/, $stripped_value ) ) { - + # Empty value will result in () from split + foreach my $temp_value ( $stripped_value ? split( /["'\s]*?,["'\s]*?/, $stripped_value ) : $stripped_value ) { if ( $term->{attr} eq 'AlarmedZoneId' ) { $value = '(SELECT * FROM Stats WHERE EventId=E.Id AND Score > 0 AND ZoneId='.$value.')'; } elsif ( $term->{attr} =~ /^MonitorName/ ) { @@ -250,7 +250,8 @@ sub Sql { $$self{Server} = new ZoneMinder::Server($temp_value); } } elsif ( $term->{attr} eq 'StorageId' ) { - $value = "'$temp_value'"; + # Empty means NULL, otherwise must be an integer + $value = $temp_value ne '' ? int($temp_value) : 'NULL'; $$self{Storage} = new ZoneMinder::Storage($temp_value); } elsif ( $term->{attr} eq 'Name' || $term->{attr} eq 'Cause' From a1bf8f7f5bf2b271ab7d1e598f572206a5bd54de Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 26 Nov 2021 10:35:15 -0500 Subject: [PATCH 15/52] Fix NULL and add special 0 case for Storage area specification in filter --- 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 b425f7896..b441db349 100644 --- a/web/skins/classic/views/filter.php +++ b/web/skins/classic/views/filter.php @@ -152,7 +152,7 @@ $booleanValues = array( $focusWindow = true; -$storageareas = array('' => 'All') + ZM\ZM_Object::Objects_Indexed_By_Id('ZM\Storage'); +$storageareas = array('' => array('Name'=>'NULL Unspecified'), '0' => array('Name'=>'Zero')) + ZM\ZM_Object::Objects_Indexed_By_Id('ZM\Storage'); $weekdays = array(); for ( $i = 0; $i < 7; $i++ ) { From f9f2615d485381dcacc04977860d9f6fa31d9b61 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 26 Nov 2021 15:26:38 -0500 Subject: [PATCH 16/52] Return if unable to lock the event record. Improve code around CopyTo call. --- scripts/ZoneMinder/lib/ZoneMinder/Event.pm | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm index 0276af097..c5e3994a7 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm @@ -733,19 +733,22 @@ sub MoveTo { my $was_in_transaction = !$ZoneMinder::Database::dbh->{AutoCommit}; $ZoneMinder::Database::dbh->begin_work() if !$was_in_transaction; - $self->lock_and_load(); # The fact that we are in a transaction might not imply locking + if (!$self->lock_and_load()) { + Warning('Unable to lock event record '.$$self{Id}); # The fact that we are in a transaction might not imply locking + $ZoneMinder::Database::dbh->commit() if !$was_in_transaction; + return 'Unable to lock event record'; + } my $OldStorage = $self->Storage(undef); - my $error = $self->CopyTo($NewStorage); - return $error if $error; + if (!$error) { + # Succeeded in copying all files, so we may now update the Event. + $$self{StorageId} = $$NewStorage{Id}; + $self->Storage($NewStorage); + $error .= $self->save(); - # Succeeded in copying all files, so we may now update the Event. - $$self{StorageId} = $$NewStorage{Id}; - $self->Storage($NewStorage); - $error .= $self->save(); - - # Going to leave it to upper layer as to whether we rollback or not + # Going to leave it to upper layer as to whether we rollback or not + } $ZoneMinder::Database::dbh->commit() if !$was_in_transaction; return $error if $error; From d51eb63947d66619cae917663f6a0601f5b0f92b Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 26 Nov 2021 19:21:24 -0500 Subject: [PATCH 17/52] Add EventStartCommand and EventEndCommand to monitors table --- db/zm_create.sql.in | 2 ++ db/zm_update-1.37.5.sql | 31 +++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 db/zm_update-1.37.5.sql diff --git a/db/zm_create.sql.in b/db/zm_create.sql.in index fe4d93dde..bf598b646 100644 --- a/db/zm_create.sql.in +++ b/db/zm_create.sql.in @@ -456,6 +456,8 @@ CREATE TABLE `Monitors` ( `DecodingEnabled` tinyint(3) unsigned NOT NULL default '1', `LinkedMonitors` varchar(255), `Triggers` set('X10') NOT NULL default '', + `EventStartCommand` VARCHAR(255) NOT NULL DEFAULT '', + `EventEndCommand` VARCHAR(255) NOT NULL DEFAULT '', `ONVIF_URL` VARCHAR(255) NOT NULL DEFAULT '', `ONVIF_Username` VARCHAR(64) NOT NULL DEFAULT '', `ONVIF_Password` VARCHAR(64) NOT NULL DEFAULT '', diff --git a/db/zm_update-1.37.5.sql b/db/zm_update-1.37.5.sql new file mode 100644 index 000000000..1f40eb923 --- /dev/null +++ b/db/zm_update-1.37.5.sql @@ -0,0 +1,31 @@ +-- +-- This update adds EventStartCommand and EventEndCommand +-- + +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Monitors' + AND table_schema = DATABASE() + AND column_name = 'AlarmEndCommand' + ) > 0, +"SELECT 'Column EventEndCommand already exists in Monitors'", +"ALTER TABLE `Monitors` ADD COLUMN `EventEndCommand` VARCHAR(255) NOT NULL DEFAULT '' AFTER `Triggers`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +SET @s = (SELECT IF( + (SELECT COUNT(*) + FROM INFORMATION_SCHEMA.COLUMNS + WHERE table_name = 'Monitors' + AND table_schema = DATABASE() + AND column_name = 'AlarmStartCommand' + ) > 0, +"SELECT 'Column EventStartCommand already exists in Monitors'", +"ALTER TABLE `Monitors` ADD COLUMN `EventStartCommand` VARCHAR(255) NOT NULL DEFAULT '' AFTER `Triggers`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; From b626de50dca26a2d5e9ce28977d24aed9a91a5de Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 26 Nov 2021 19:21:51 -0500 Subject: [PATCH 18/52] Add code to fork and exec EventStartCommand and EventEndCommand --- src/zm_monitor.cpp | 32 +++++++++++++++++++++++++++++--- src/zm_monitor.h | 2 ++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index d9f932d75..d50e4e6eb 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -70,7 +70,7 @@ // It will be used whereever a Monitor dbrow is needed. WHERE conditions can be appended std::string load_monitor_sql = "SELECT `Id`, `Name`, `ServerId`, `StorageId`, `Type`, `Function`+0, `Enabled`, `DecodingEnabled`, " -"`LinkedMonitors`, `AnalysisFPSLimit`, `AnalysisUpdateDelay`, `MaxFPS`, `AlarmMaxFPS`," +"`LinkedMonitors`, `EventStartCommand`, `EventEndCommand`, `AnalysisFPSLimit`, `AnalysisUpdateDelay`, `MaxFPS`, `AlarmMaxFPS`," "`Device`, `Channel`, `Format`, `V4LMultiBuffer`, `V4LCapturesPerFrame`, " // V4L Settings "`Protocol`, `Method`, `Options`, `User`, `Pass`, `Host`, `Port`, `Path`, `SecondPath`, `Width`, `Height`, `Colours`, `Palette`, `Orientation`+0, `Deinterlacing`, " "`DecoderHWAccelName`, `DecoderHWAccelDevice`, `RTSPDescribe`, " @@ -435,7 +435,7 @@ Monitor::Monitor() /* std::string load_monitor_sql = - "SELECT Id, Name, ServerId, StorageId, Type, Function+0, Enabled, DecodingEnabled, LinkedMonitors, " + "SELECT Id, Name, ServerId, StorageId, Type, Function+0, Enabled, DecodingEnabled, LinkedMonitors, `EventStartCommand`, `EventEndCommand`, " "AnalysisFPSLimit, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS," "Device, Channel, Format, V4LMultiBuffer, V4LCapturesPerFrame, " // V4L Settings "Protocol, Method, Options, User, Pass, Host, Port, Path, SecondPath, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, RTSPDescribe, " @@ -489,6 +489,8 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) { // See below after save_jpegs for a recalculation of decoding_enabled ReloadLinkedMonitors(dbrow[col]); col++; + event_start_command = dbrow[col] ? dbrow[col] : ""; col++; + event_end_command = dbrow[col] ? dbrow[col] : ""; col++; /* "AnalysisFPSLimit, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS," */ analysis_fps_limit = dbrow[col] ? strtod(dbrow[col], nullptr) : 0.0; col++; @@ -1994,6 +1996,12 @@ bool Monitor::Analyse() { alarm_cause = cause+" Continuous "+alarm_cause; strncpy(shared_data->alarm_cause, alarm_cause.c_str(), sizeof(shared_data->alarm_cause)-1); SetVideoWriterStartTime(event->StartTime()); + if (!event_start_command.empty()) { + if (fork() == 0) { + execlp(event_start_command.c_str(), event_start_command.c_str(), std::to_string(event->Id()).c_str(), nullptr); + Error("Error execing %s", event_start_command.c_str()); + } + } Info("%s: %03d - Opened new event %" PRIu64 ", section start", name.c_str(), analysis_image_count, event->Id()); @@ -2084,6 +2092,13 @@ bool Monitor::Analyse() { delete start_it; start_it = nullptr; + if (!event_start_command.empty()) { + if (fork() == 0) { + execlp(event_start_command.c_str(), event_start_command.c_str(), std::to_string(event->Id()).c_str(), nullptr); + Error("Error execing %s", event_start_command.c_str()); + } + } + Info("%s: %03d - Opening new event %" PRIu64 ", alarm start", name.c_str(), analysis_image_count, event->Id()); } else { shared_data->state = state = ALARM; @@ -2786,7 +2801,18 @@ void Monitor::closeEvent() { Debug(1, "close event thread is not joinable"); } Debug(1, "Starting thread to close event"); - close_event_thread = std::thread([](Event *e){ delete e; }, event); + close_event_thread = std::thread([](Event *e, const std::string &command){ + int64_t event_id = e->Id(); + delete e; + + if (!command.empty()) { + if (fork() == 0) { + execlp(command.c_str(), command.c_str(), std::to_string(event_id).c_str(), nullptr); + Error("Error execing %s", command.c_str()); + } + } + + }, event, event_end_command); Debug(1, "Nulling event"); event = nullptr; if (shared_data) video_store_data->recording = {}; diff --git a/src/zm_monitor.h b/src/zm_monitor.h index 5b9a2ce46..d670911bb 100644 --- a/src/zm_monitor.h +++ b/src/zm_monitor.h @@ -404,6 +404,8 @@ protected: int n_linked_monitors; MonitorLink **linked_monitors; + std::string event_start_command; + std::string event_end_command; std::vector groups; From 21218491f7e1f94a5e7ff5bd520b79e57a4211a8 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 26 Nov 2021 19:22:07 -0500 Subject: [PATCH 19/52] Add EventStartCommand and EventEndCommand to monitors ui --- web/includes/Monitor.php | 2 ++ web/skins/classic/views/monitor.php | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/web/includes/Monitor.php b/web/includes/Monitor.php index 6423af11f..b5e2ef853 100644 --- a/web/includes/Monitor.php +++ b/web/includes/Monitor.php @@ -57,6 +57,8 @@ class Monitor extends ZM_Object { 'DecodingEnabled' => array('type'=>'boolean','default'=>1), 'LinkedMonitors' => array('type'=>'set', 'default'=>null), 'Triggers' => array('type'=>'set','default'=>''), + 'EventStartCommand' => '', + 'EventEndCommand' => '', 'ONVIF_URL' => '', 'ONVIF_Username' => '', 'ONVIF_Password' => '', diff --git a/web/skins/classic/views/monitor.php b/web/skins/classic/views/monitor.php index 3dc6937e8..657c2ce8c 100644 --- a/web/skins/classic/views/monitor.php +++ b/web/skins/classic/views/monitor.php @@ -675,6 +675,14 @@ if (count($available_monitor_ids)) { } ?> + + + + + + + + Date: Fri, 26 Nov 2021 19:22:53 -0500 Subject: [PATCH 20/52] bump version 1.37.5 --- distros/redhat/zoneminder.spec | 2 +- version | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/distros/redhat/zoneminder.spec b/distros/redhat/zoneminder.spec index 0c38e7d30..8d49c0d7f 100644 --- a/distros/redhat/zoneminder.spec +++ b/distros/redhat/zoneminder.spec @@ -36,7 +36,7 @@ %global _hardened_build 1 Name: zoneminder -Version: 1.37.3 +Version: 1.37.5 Release: 1%{?dist} Summary: A camera monitoring and analysis tool Group: System Environment/Daemons diff --git a/version b/version index d2829d87d..e2ba07049 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.37.3 +1.37.5 From 74a227667122a8dbe6ed0a1eac69046575fac660 Mon Sep 17 00:00:00 2001 From: pkubaj Date: Mon, 29 Nov 2021 00:03:45 +0000 Subject: [PATCH 21/52] Fix build on FreeBSD/armv7 1. FreeBSD uses elf_aux_info instead of getauxval. 2. FreeBSD uses HWCAP_NEON macro for Neon. --- src/zm_utils.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/zm_utils.cpp b/src/zm_utils.cpp index 5da5509ff..409429667 100644 --- a/src/zm_utils.cpp +++ b/src/zm_utils.cpp @@ -252,8 +252,15 @@ void HwCapsDetect() { #elif defined(__arm__) // ARM processor in 32bit mode // To see if it supports NEON, we need to get that information from the kernel + #ifdef __linux__ unsigned long auxval = getauxval(AT_HWCAP); if (auxval & HWCAP_ARM_NEON) { + #elif defined(__FreeBSD__) + unsigned long auxval = 0; + elf_aux_info(AT_HWCAP, &auxval, sizeof(auxval)); + if (auxval & HWCAP_NEON) { + #error Unsupported OS. + #endif Debug(1,"Detected ARM (AArch32) processor with Neon"); neonversion = 1; } else { From b47e96d7cfde58a0c84553d88116736e5fffa423 Mon Sep 17 00:00:00 2001 From: maddios Date: Mon, 29 Nov 2021 00:20:25 -0500 Subject: [PATCH 22/52] Fix Copy/Move to Default Storage When moving from a 2nd storage to Default it fails with "New storage does not have an id. Moving will not happen" because the default ID is 0. --- scripts/ZoneMinder/lib/ZoneMinder/Event.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm index c5e3994a7..026ff6cea 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm @@ -43,6 +43,7 @@ require Date::Parse; require POSIX; use Date::Format qw(time2str); use Time::HiRes qw(gettimeofday tv_interval stat); +use Scalar::Util qw(looks_like_number); #our @ISA = qw(ZoneMinder::Object); use parent qw(ZoneMinder::Object); @@ -601,7 +602,7 @@ sub CopyTo { # First determine if we can move it to the dest. # We do this before bothering to lock the event my ( $NewPath ) = ( $NewStorage->Path() =~ /^(.*)$/ ); # De-taint - if ( ! $$NewStorage{Id} ) { + if ( ! looks_like_number($$NewStorage{Id}) ) { return 'New storage does not have an id. Moving will not happen.'; } elsif ( $$NewStorage{Id} == $$self{StorageId} ) { return 'Event is already located at ' . $NewPath; From 01f4aee45034cea6e027dc6be031e0277a794133 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sat, 27 Nov 2021 19:10:09 -0500 Subject: [PATCH 23/52] Fix underline --- docs/installationguide/debian.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installationguide/debian.rst b/docs/installationguide/debian.rst index f7325fe1f..e6a3404df 100644 --- a/docs/installationguide/debian.rst +++ b/docs/installationguide/debian.rst @@ -4,7 +4,7 @@ Debian .. contents:: Easy Way: Debian 11 (Bullseye) ------------------------- +------------------------------ This procedure will guide you through the installation of ZoneMinder on Debian 11 (Bullseye). From 1f19ad7c9d9b483a92679f225c4efbafd9f3e290 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sat, 27 Nov 2021 19:16:32 -0500 Subject: [PATCH 24/52] fix by removing code block --- docs/installationguide/debian.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installationguide/debian.rst b/docs/installationguide/debian.rst index e6a3404df..b92ff267a 100644 --- a/docs/installationguide/debian.rst +++ b/docs/installationguide/debian.rst @@ -104,7 +104,7 @@ Add the following to the /etc/apt/sources.list.d/zoneminder.list file You can do this using: -.. code-block:: +:: echo "deb https://zmrepo.zoneminder.com/debian/release-1.36 buster/" | sudo tee /etc/apt/sources.list.d/zoneminder.list From ea6a84ae66ea3a3d7ae72f6ba5047b815bb26db7 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 29 Nov 2021 12:53:44 -0500 Subject: [PATCH 25/52] Fix AlarmEndCommand => EventEndCommand --- db/zm_update-1.37.5.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/zm_update-1.37.5.sql b/db/zm_update-1.37.5.sql index 1f40eb923..0f205ff77 100644 --- a/db/zm_update-1.37.5.sql +++ b/db/zm_update-1.37.5.sql @@ -7,7 +7,7 @@ SET @s = (SELECT IF( FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = 'Monitors' AND table_schema = DATABASE() - AND column_name = 'AlarmEndCommand' + AND column_name = 'EventEndCommand' ) > 0, "SELECT 'Column EventEndCommand already exists in Monitors'", "ALTER TABLE `Monitors` ADD COLUMN `EventEndCommand` VARCHAR(255) NOT NULL DEFAULT '' AFTER `Triggers`" From 072d181f79c41889ba44107ade0f48254157c2ae Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 29 Nov 2021 12:54:46 -0500 Subject: [PATCH 26/52] Fix AlarmStartCommand => EventStartCommand --- db/zm_update-1.37.5.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/zm_update-1.37.5.sql b/db/zm_update-1.37.5.sql index 0f205ff77..035a73a1a 100644 --- a/db/zm_update-1.37.5.sql +++ b/db/zm_update-1.37.5.sql @@ -21,7 +21,7 @@ SET @s = (SELECT IF( FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = 'Monitors' AND table_schema = DATABASE() - AND column_name = 'AlarmStartCommand' + AND column_name = 'EventStartCommand' ) > 0, "SELECT 'Column EventStartCommand already exists in Monitors'", "ALTER TABLE `Monitors` ADD COLUMN `EventStartCommand` VARCHAR(255) NOT NULL DEFAULT '' AFTER `Triggers`" From 82a4cbaec520cc46ad2e75aba5e433013da80f46 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 29 Nov 2021 13:48:44 -0500 Subject: [PATCH 27/52] Fix task=>action so that deleting works. Pause streaming before delete to prevent errors being logged due to missing files --- web/skins/classic/views/js/event.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/web/skins/classic/views/js/event.js b/web/skins/classic/views/js/event.js index 05914f911..94e132cc1 100644 --- a/web/skins/classic/views/js/event.js +++ b/web/skins/classic/views/js/event.js @@ -773,8 +773,9 @@ function manageDelConfirmModalBtns() { return; } + pauseClicked(); evt.preventDefault(); - $j.getJSON(thisUrl + '?request=event&task=delete&id='+eventData.Id) + $j.getJSON(thisUrl + '?request=event&action=delete&id='+eventData.Id) .done(function(data) { $j('#deleteConfirm').modal('hide'); streamNext(true); From 089563d1cecb5d04c8b7876f585e050390af5d73 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 29 Nov 2021 14:14:56 -0500 Subject: [PATCH 28/52] rework do_debian_package to properly support the CURRENT style of snapshots and make the code a little easier to read --- utils/do_debian_package.sh | 122 ++++++++++++++++++++----------------- 1 file changed, 65 insertions(+), 57 deletions(-) diff --git a/utils/do_debian_package.sh b/utils/do_debian_package.sh index 6c8c06c00..91ecf520b 100755 --- a/utils/do_debian_package.sh +++ b/utils/do_debian_package.sh @@ -116,52 +116,6 @@ else echo "Defaulting to ZoneMinder upstream git" GITHUB_FORK="ZoneMinder" fi; - if [ "$SNAPSHOT" == "stable" ]; then - if [ "$BRANCH" == "" ]; then - #REV=$(git rev-list --tags --max-count=1) - BRANCH=`git describe --tags $(git rev-list --tags --max-count=1)`; - if [ -z "$BRANCH" ]; then - # This should only happen in CI environments where tag info isn't available - BRANCH=`cat version` - echo "Building branch $BRANCH" - fi - if [ "$BRANCH" == "" ]; then - echo "Unable to determine latest stable branch!" - exit 0; - fi - echo "Latest stable branch is $BRANCH"; - fi; - else - if [ "$BRANCH" == "" ]; then - echo "Defaulting to master branch"; - BRANCH="master"; - fi; - if [ "$SNAPSHOT" == "NOW" ]; then - SNAPSHOT=`date +%Y%m%d%H%M%S`; - else - if [ "$SNAPSHOT" == "CURRENT" ]; then - SNAPSHOT="`date +%Y%m%d.`$(git rev-list ${versionhash}..HEAD --count)" - fi; - fi; - fi; -fi - -IFS='.' read -r -a VERSION_PARTS <<< "$RELEASE" -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. - if [ "${VERSION_PARTS[0]}.${VERSION_PARTS[1]}" == "1.30" ]; then - PPA="ppa:iconnor/zoneminder-stable" - else - PPA="ppa:iconnor/zoneminder-${VERSION_PARTS[0]}.${VERSION_PARTS[1]}" - fi; - else - if [ "$BRANCH" == "" ]; then - PPA="ppa:iconnor/zoneminder-master"; - else - PPA="ppa:iconnor/zoneminder-$BRANCH"; - fi; - fi; fi; # Instead of cloning from github each time, if we have a fork lying around, update it and pull from there instead. @@ -171,15 +125,8 @@ if [ ! -d "${GITHUB_FORK}_zoneminder_release" ]; then cd "${GITHUB_FORK}_ZoneMinder.git" echo "git fetch..." git fetch - echo "git checkout $BRANCH" - git checkout $BRANCH - if [ $? -ne 0 ]; then - echo "Failed to switch to branch." - exit 1; - fi; - echo "git pull..." - git pull cd ../ + echo "git clone ${GITHUB_FORK}_ZoneMinder.git ${GITHUB_FORK}_zoneminder_release" git clone "${GITHUB_FORK}_ZoneMinder.git" "${GITHUB_FORK}_zoneminder_release" else @@ -192,14 +139,59 @@ else fi; cd "${GITHUB_FORK}_zoneminder_release" - git checkout $BRANCH -cd ../ -VERSION=`cat ${GITHUB_FORK}_zoneminder_release/version` +if [ "$SNAPSHOT" == "stable" ]; then + if [ "$BRANCH" == "" ]; then + #REV=$(git rev-list --tags --max-count=1) + BRANCH=`git describe --tags $(git rev-list --tags --max-count=1)`; + if [ -z "$BRANCH" ]; then + # This should only happen in CI environments where tag info isn't available + BRANCH=`cat version` + echo "Building branch $BRANCH" + fi + if [ "$BRANCH" == "" ]; then + echo "Unable to determine latest stable branch!" + exit 0; + fi + echo "Latest stable branch is $BRANCH"; + fi; +else + if [ "$BRANCH" == "" ]; then + echo "Defaulting to master branch"; + BRANCH="master"; + fi; + if [ "$SNAPSHOT" == "NOW" ]; then + SNAPSHOT=`date +%Y%m%d%H%M%S`; + else + if [ "$SNAPSHOT" == "CURRENT" ]; then + # git the latest (short) commit hash of the version file + versionhash=$(git log -n1 --pretty=format:%h version) + # Number of commits since the version file was last changed + numcommits=$(git rev-list ${versionhash}..HEAD --count) + SNAPSHOT="`date +%Y%m%d.`$(git rev-list ${versionhash}..HEAD --count)" + fi; + fi; +fi; + + +echo "git checkout $BRANCH" +git checkout $BRANCH +if [ $? -ne 0 ]; then + echo "Failed to switch to branch." + exit 1; +fi; +echo "git pull..." +git pull +# Grab the ZoneMinder version from the contents of the version file +VERSION=$(cat version) if [ -z "$VERSION" ]; then exit 1; fi; +IFS='.' read -r -a VERSION_PARTS <<< "$VERSION" + +cd ../ + if [ "$SNAPSHOT" != "stable" ] && [ "$SNAPSHOT" != "" ]; then VERSION="$VERSION~$SNAPSHOT"; fi; @@ -357,6 +349,22 @@ EOF fi; else SC="zoneminder_${VERSION}-${DISTRO}${PACKAGE_VERSION}_source.changes"; + 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. + if [ "${VERSION_PARTS[0]}.${VERSION_PARTS[1]}" == "1.30" ]; then + PPA="ppa:iconnor/zoneminder-stable" + else + PPA="ppa:iconnor/zoneminder-${VERSION_PARTS[0]}.${VERSION_PARTS[1]}" + fi; + else + if [ "$BRANCH" == "" ]; then + PPA="ppa:iconnor/zoneminder-master"; + else + PPA="ppa:iconnor/zoneminder-$BRANCH"; + fi; + fi; + fi; dput="Y"; if [ "$INTERACTIVE" != "no" ]; then From 7b9c86111c094662e485a94417755a7e230de8b2 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 29 Nov 2021 16:21:34 -0500 Subject: [PATCH 29/52] Move Cleanup and framebuffer freeing into Close() so that we don't crash on Reload --- src/zm_libvnc_camera.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/zm_libvnc_camera.cpp b/src/zm_libvnc_camera.cpp index 6fb414686..3ff7804b6 100644 --- a/src/zm_libvnc_camera.cpp +++ b/src/zm_libvnc_camera.cpp @@ -23,7 +23,7 @@ void bind_libvnc_symbols() { libvnc_lib = dlopen("libvncclient.so", RTLD_LAZY | RTLD_GLOBAL); if (!libvnc_lib) { - Error("Error loading libvncclient: %s", dlerror()); + Error("Error loading libvncclient.so: %s", dlerror()); return; } @@ -135,11 +135,6 @@ VncCamera::VncCamera( } VncCamera::~VncCamera() { - if (capture and mRfb) { - if (mRfb->frameBuffer) - free(mRfb->frameBuffer); - (*rfbClientCleanup_f)(mRfb); - } if (libvnc_lib) { dlclose(libvnc_lib); libvnc_lib = nullptr; @@ -253,6 +248,12 @@ int VncCamera::PostCapture() { } int VncCamera::Close() { + if (capture and mRfb) { + if (mRfb->frameBuffer) + free(mRfb->frameBuffer); + (*rfbClientCleanup_f)(mRfb); + mRfb = nullptr; + } return 1; } #endif From c927ef4b52b3ede8835e65f1e0c9aa7d62ca59df Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 29 Nov 2021 18:32:48 -0500 Subject: [PATCH 30/52] Aim to do db updates every 5 seconds instead of 1 second --- src/zm_event.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zm_event.cpp b/src/zm_event.cpp index 9f1124e7b..2d95ec23a 100644 --- a/src/zm_event.cpp +++ b/src/zm_event.cpp @@ -546,7 +546,7 @@ void Event::AddFrame(Image *image, or (frame_type == BULK) or - (fps and (frame_data.size() > fps))) { + (fps and (frame_data.size() > 5*fps))) { Debug(1, "Adding %zu frames to DB because write_to_db:%d or frames > analysis fps %f or BULK(%d)", frame_data.size(), write_to_db, fps, (frame_type == BULK)); WriteDbFrames(); From 1cddac4efd45cd6ea965d8d7e90c3815060332d8 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 29 Nov 2021 18:34:06 -0500 Subject: [PATCH 31/52] remove remaining signal blocking cruft, add ignoring sigchld so that anything we spawn doesn't become a zombie. --- src/zmc.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/zmc.cpp b/src/zmc.cpp index 68c6227af..c2f14e254 100644 --- a/src/zmc.cpp +++ b/src/zmc.cpp @@ -220,12 +220,13 @@ int main(int argc, char *argv[]) { zmSetDefaultTermHandler(); zmSetDefaultDieHandler(); - sigset_t block_set; - sigemptyset(&block_set); - - sigaddset(&block_set, SIGHUP); - sigaddset(&block_set, SIGUSR1); - sigaddset(&block_set, SIGUSR2); + struct sigaction sa; + sa.sa_handler = SIG_IGN; //handle signal by ignoring + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + if (sigaction(SIGCHLD, &sa, 0) == -1) { + Error("Unable to set SIGCHLD to ignore. There may be zombies."); + } int result = 0; int prime_capture_log_count = 0; @@ -286,7 +287,6 @@ int main(int argc, char *argv[]) { Microseconds sleep_time = Microseconds(0); while (!zm_terminate) { - //sigprocmask(SIG_BLOCK, &block_set, 0); for (size_t i = 0; i < monitors.size(); i++) { monitors[i]->CheckAction(); From 37069cb6fc4cc2650ebaa8c0e5ed547a66a8443d Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 29 Nov 2021 18:59:44 -0500 Subject: [PATCH 32/52] add cmake module to find libFmt --- cmake/Modules/FindFmt.cmake | 100 ++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 cmake/Modules/FindFmt.cmake diff --git a/cmake/Modules/FindFmt.cmake b/cmake/Modules/FindFmt.cmake new file mode 100644 index 000000000..b426d8c77 --- /dev/null +++ b/cmake/Modules/FindFmt.cmake @@ -0,0 +1,100 @@ +# FindFmt +# ------- +# Finds the Fmt library +# +# This will define the following variables:: +# +# FMT_FOUND - system has Fmt +# FMT_INCLUDE_DIRS - the Fmt include directory +# FMT_LIBRARIES - the Fmt libraries +# +# and the following imported targets:: +# +# Fmt::Fmt - The Fmt library + +if(ENABLE_INTERNAL_FMT) + include(ExternalProject) + file(STRINGS ${CMAKE_SOURCE_DIR}/tools/depends/target/libfmt/Makefile VER REGEX "^[ ]*VERSION[ ]*=.+$") + string(REGEX REPLACE "^[ ]*VERSION[ ]*=[ ]*" "" FMT_VERSION "${VER}") + + # allow user to override the download URL with a local tarball + # needed for offline build envs + if(FMT_URL) + get_filename_component(FMT_URL "${FMT_URL}" ABSOLUTE) + else() + set(FMT_URL http://mirrors.kodi.tv/build-deps/sources/fmt-${FMT_VERSION}.tar.gz) + endif() + if(VERBOSE) + message(STATUS "FMT_URL: ${FMT_URL}") + endif() + + if(APPLE) + set(EXTRA_ARGS "-DCMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES}") + endif() + + set(FMT_LIBRARY ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/lib/libfmt.a) + set(FMT_INCLUDE_DIR ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/include) + externalproject_add(fmt + URL ${FMT_URL} + DOWNLOAD_DIR ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/download + PREFIX ${CORE_BUILD_DIR}/fmt + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR} + -DCMAKE_CXX_EXTENSIONS=${CMAKE_CXX_EXTENSIONS} + -DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD} + -DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE} + -DCMAKE_INSTALL_LIBDIR=lib + -DFMT_DOC=OFF + -DFMT_TEST=OFF + "${EXTRA_ARGS}" + BUILD_BYPRODUCTS ${FMT_LIBRARY}) + set_target_properties(fmt PROPERTIES FOLDER "External Projects") + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Fmt + REQUIRED_VARS FMT_LIBRARY FMT_INCLUDE_DIR + VERSION_VAR FMT_VERSION) + + set(FMT_LIBRARIES ${FMT_LIBRARY}) + set(FMT_INCLUDE_DIRS ${FMT_INCLUDE_DIR}) + +else() + +find_package(FMT 6.1.2 CONFIG REQUIRED QUIET) + +if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_FMT libfmt QUIET) + if(PC_FMT_VERSION AND NOT FMT_VERSION) + set(FMT_VERSION ${PC_FMT_VERSION}) + endif() +endif() + +find_path(FMT_INCLUDE_DIR NAMES fmt/format.h + PATHS ${PC_FMT_INCLUDEDIR}) + +find_library(FMT_LIBRARY_RELEASE NAMES fmt + PATHS ${PC_FMT_LIBDIR}) +find_library(FMT_LIBRARY_DEBUG NAMES fmtd + PATHS ${PC_FMT_LIBDIR}) + +include(SelectLibraryConfigurations) +select_library_configurations(FMT) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Fmt + REQUIRED_VARS FMT_LIBRARY FMT_INCLUDE_DIR FMT_VERSION + VERSION_VAR FMT_VERSION) + +if(FMT_FOUND) + set(FMT_LIBRARIES ${FMT_LIBRARY}) + set(FMT_INCLUDE_DIRS ${FMT_INCLUDE_DIR}) + + if(NOT TARGET fmt) + add_library(fmt UNKNOWN IMPORTED) + set_target_properties(fmt PROPERTIES + IMPORTED_LOCATION "${FMT_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${FMT_INCLUDE_DIR}") + endif() +endif() + +endif() +mark_as_advanced(FMT_INCLUDE_DIR FMT_LIBRARY) From 5647c224d1d6407b6a44288c91a8c9384c9f2f08 Mon Sep 17 00:00:00 2001 From: Petter Reinholdtsen Date: Thu, 2 Dec 2021 15:18:04 +0100 Subject: [PATCH 33/52] Make config file comment on unix socket option a bit clearer Initially I took the comment for granted, and the 'unix_socket' string as a magic string to tell zoneminder to use the mysql default socket. Alas, this do not work, as the setting really need to point to the path of the socket. Rewrite the comment to make this more clear, and less confusing for the future users. --- zm.conf.in | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zm.conf.in b/zm.conf.in index 312c9aeae..d48dfce56 100644 --- a/zm.conf.in +++ b/zm.conf.in @@ -37,7 +37,8 @@ ZM_WEB_GROUP=@WEB_GROUP@ ZM_DB_TYPE=@ZM_DB_TYPE@ # ZoneMinder database hostname or ip address and optionally port or unix socket -# Acceptable formats include hostname[:port], ip_address[:port], or localhost:unix_socket +# Acceptable formats include hostname[:port], ip_address[:port], or +# localhost:/path/to/unix_socket ZM_DB_HOST=@ZM_DB_HOST@ # ZoneMinder database name From 1277e752783ed8d2dc061e1ae633a3670cc4b617 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 3 Dec 2021 12:03:23 -0500 Subject: [PATCH 34/52] Detect group hierarchy loops and break them. --- web/includes/Group.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/web/includes/Group.php b/web/includes/Group.php index 026d1f7f4..34db0e869 100644 --- a/web/includes/Group.php +++ b/web/includes/Group.php @@ -167,9 +167,15 @@ class Group extends ZM_Object { public function Parents() { $Parents = array(); $Parent = $this->Parent(); - while( $Parent ) { + $seen_parents = array(); + while ($Parent) { + $seen_parents[$Parent->Id()] = $Parent; array_unshift($Parents, $Parent); $Parent = $Parent->Parent(); + if ($Parent and isset($seen_parents[$Parent->Id()])) { + Warning("Detected hierarchy loop in group {$Parent->Name()}"); + break; + } } return $Parents; } @@ -189,6 +195,9 @@ class Group extends ZM_Object { public function canView($u=null) { global $user; if (!$u) $u = $user; + if (!count($this->Monitors()) and !count($this->Children())) { + return true; + } # Can view if we can view any of the monitors in it. foreach ($this->Monitors() as $monitor) { if ($monitor->canView($u)) return true; From eef172379c11c60a68236d3768495e962bef398f Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 3 Dec 2021 13:25:21 -0500 Subject: [PATCH 35/52] Move all the opening of events code into one function called openEvent. --- src/zm_monitor.cpp | 208 ++++++++++++++++----------------------------- 1 file changed, 75 insertions(+), 133 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index d50e4e6eb..0410d39bc 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -1882,12 +1882,20 @@ bool Monitor::Analyse() { // Get new score. int motion_score = DetectMotion(*(snap->image), zoneSet); + // lets construct alarm cause. It will contain cause + names of zones alarmed + std::string alarm_cause; snap->zone_stats.reserve(zones.size()); for (const Zone &zone : zones) { const ZoneStats &stats = zone.GetStats(); stats.DumpToLog("After detect motion"); snap->zone_stats.push_back(stats); + if (zone.Alarmed()) { + if (!alarm_cause.empty()) alarm_cause += ","; + alarm_cause += std::string(zone.Label()); + } } + if (!alarm_cause.empty()) + cause = cause+" "+alarm_cause; Debug(3, "After motion detection, score:%d last_motion_score(%d), new motion score(%d)", score, last_motion_score, motion_score); @@ -1913,6 +1921,7 @@ bool Monitor::Analyse() { ); } // end if active and doing motion detection + if (function == RECORD or function == MOCORD) { // If doing record, check to see if we need to close the event or not. if (event) { @@ -1935,75 +1944,9 @@ bool Monitor::Analyse() { } // end if event if (!event) { - Debug(2, "Creating continuous event"); - if (!snap->keyframe and (videowriter == PASSTHROUGH)) { - // Must start on a keyframe so rewind. Only for passthrough though I guess. - // FIXME this iterator is not protected from invalidation - packetqueue_iterator *start_it = packetqueue.get_event_start_packet_it( - *analysis_it, 0 /* pre_event_count */ - ); + event = openEvent(snap, cause.empty() ? "Continuous" : cause, noteSetMap); - // This gets a lock on the starting packet - - ZMLockedPacket *starting_packet_lock = nullptr; - std::shared_ptr starting_packet = nullptr; - if (*start_it != *analysis_it) { - starting_packet_lock = packetqueue.get_packet(start_it); - if (!starting_packet_lock) { - Warning("Unable to get starting packet lock"); - delete packet_lock; - return false; - } - starting_packet = starting_packet_lock->packet_; - } else { - starting_packet = snap; - } - - event = new Event(this, starting_packet->timestamp, "Continuous", noteSetMap); - // Write out starting packets, do not modify packetqueue it will garbage collect itself - while (starting_packet and ((*start_it) != *analysis_it)) { - event->AddPacket(starting_packet); - // Have added the packet, don't want to unlock it until we have locked the next - - packetqueue.increment_it(start_it); - if ((*start_it) == *analysis_it) { - if (starting_packet_lock) delete starting_packet_lock; - break; - } - ZMLockedPacket *lp = packetqueue.get_packet(start_it); - delete starting_packet_lock; - if (!lp) return false; - starting_packet_lock = lp; - starting_packet = lp->packet_; - } - packetqueue.free_it(start_it); - delete start_it; - start_it = nullptr; - } else { - // Create event from current snap - event = new Event(this, timestamp, "Continuous", noteSetMap); - } - shared_data->last_event_id = event->Id(); - - // lets construct alarm cause. It will contain cause + names of zones alarmed - std::string alarm_cause; - for (const Zone &zone : zones) { - if (zone.Alarmed()) { - if (!alarm_cause.empty()) alarm_cause += ","; - alarm_cause += std::string(zone.Label()); - } - } - alarm_cause = cause+" Continuous "+alarm_cause; - strncpy(shared_data->alarm_cause, alarm_cause.c_str(), sizeof(shared_data->alarm_cause)-1); - SetVideoWriterStartTime(event->StartTime()); - if (!event_start_command.empty()) { - if (fork() == 0) { - execlp(event_start_command.c_str(), event_start_command.c_str(), std::to_string(event->Id()).c_str(), nullptr); - Error("Error execing %s", event_start_command.c_str()); - } - } - - Info("%s: %03d - Opened new event %" PRIu64 ", section start", + Info("%s: %03d - Opened new event %" PRIu64 ", continuous section start", name.c_str(), analysis_image_count, event->Id()); /* To prevent cancelling out an existing alert\prealarm\alarm state */ if (state == IDLE) { @@ -2035,70 +1978,12 @@ bool Monitor::Analyse() { (event_close_mode == CLOSE_ALARM)); } if ((!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count-1)) { - // lets construct alarm cause. It will contain cause + names of zones alarmed - std::string alarm_cause = ""; - for (const Zone &zone : zones) { - if (zone.Alarmed()) { - alarm_cause = alarm_cause + "," + std::string(zone.Label()); - } - } - if (!alarm_cause.empty()) alarm_cause[0] = ' '; - alarm_cause = cause + alarm_cause; - strncpy(shared_data->alarm_cause, alarm_cause.c_str(), sizeof(shared_data->alarm_cause)-1); Info("%s: %03d - Gone into alarm state PreAlarmCount: %u > AlarmFrameCount:%u Cause:%s", - name.c_str(), image_count, Event::PreAlarmCount(), alarm_frame_count, shared_data->alarm_cause); + name.c_str(), image_count, Event::PreAlarmCount(), alarm_frame_count, cause.c_str()); if (!event) { - packetqueue_iterator *start_it = packetqueue.get_event_start_packet_it( - *analysis_it, - (pre_event_count > alarm_frame_count ? pre_event_count : alarm_frame_count) - ); - ZMLockedPacket *starting_packet_lock = nullptr; - std::shared_ptr starting_packet = nullptr; - if (*start_it != *analysis_it) { - starting_packet_lock = packetqueue.get_packet(start_it); - if (!starting_packet_lock) return false; - starting_packet = starting_packet_lock->packet_; - } else { - starting_packet = snap; - } - - event = new Event(this, starting_packet->timestamp, cause, noteSetMap); - shared_data->last_event_id = event->Id(); - snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "%s", event->getEventFile()); - SetVideoWriterStartTime(event->StartTime()); + event = openEvent(snap, cause, noteSetMap); shared_data->state = state = ALARM; - - // Write out starting packets, do not modify packetqueue it will garbage collect itself - while (*start_it != *analysis_it) { - event->AddPacket(starting_packet); - - packetqueue.increment_it(start_it); - if ( (*start_it) == (*analysis_it) ) { - if (starting_packet_lock) delete starting_packet_lock; - break; - } - ZMLockedPacket *lp = packetqueue.get_packet(start_it); - delete starting_packet_lock; - if (!lp) { - // Shutting down event will be closed by ~Monitor() - // Perhaps we shouldn't do this. - return false; - } - starting_packet_lock = lp; - starting_packet = lp->packet_; - } - packetqueue.free_it(start_it); - delete start_it; - start_it = nullptr; - - if (!event_start_command.empty()) { - if (fork() == 0) { - execlp(event_start_command.c_str(), event_start_command.c_str(), std::to_string(event->Id()).c_str(), nullptr); - Error("Error execing %s", event_start_command.c_str()); - } - } - Info("%s: %03d - Opening new event %" PRIu64 ", alarm start", name.c_str(), analysis_image_count, event->Id()); } else { shared_data->state = state = ALARM; @@ -2208,11 +2093,7 @@ bool Monitor::Analyse() { static_cast(std::chrono::duration_cast(timestamp - GetVideoWriterStartTime()).count()), static_cast(Seconds(section_length).count())); closeEvent(); - event = new Event(this, timestamp, cause, noteSetMap); - shared_data->last_event_id = event->Id(); - //set up video store data - snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "%s", event->getEventFile()); - SetVideoWriterStartTime(event->StartTime()); + event = openEvent(snap, cause, noteSetMap); } } else { Error("ALARM but no event"); @@ -2791,6 +2672,67 @@ void Monitor::TimestampImage(Image *ts_image, SystemTimePoint ts_time) const { Debug(2, "done annotating %s", label_text); } // end void Monitor::TimestampImage +Event * Monitor::openEvent( + const std::shared_ptr &snap, + const std::string &cause, + const Event::StringSetMap noteSetMap) { + + // FIXME this iterator is not protected from invalidation + packetqueue_iterator *start_it = packetqueue.get_event_start_packet_it( + *analysis_it, + (cause == "Continuous" ? 0 : (pre_event_count > alarm_frame_count ? pre_event_count : alarm_frame_count)) + ); + + // This gets a lock on the starting packet + + ZMLockedPacket *starting_packet_lock = nullptr; + std::shared_ptr starting_packet = nullptr; + if (*start_it != *analysis_it) { + starting_packet_lock = packetqueue.get_packet(start_it); + if (!starting_packet_lock) { + Warning("Unable to get starting packet lock"); + return nullptr; + } + starting_packet = starting_packet_lock->packet_; + } else { + starting_packet = snap; + } + + event = new Event(this, starting_packet->timestamp, cause, noteSetMap); + + shared_data->last_event_id = event->Id(); + strncpy(shared_data->alarm_cause, cause.c_str(), sizeof(shared_data->alarm_cause)-1); + + if (!event_start_command.empty()) { + if (fork() == 0) { + execlp(event_start_command.c_str(), event_start_command.c_str(), std::to_string(event->Id()).c_str(), nullptr); + Error("Error execing %s", event_start_command.c_str()); + } + } + + // Write out starting packets, do not modify packetqueue it will garbage collect itself + while (starting_packet and ((*start_it) != *analysis_it)) { + event->AddPacket(starting_packet); + // Have added the packet, don't want to unlock it until we have locked the next + + packetqueue.increment_it(start_it); + if ((*start_it) == *analysis_it) { + if (starting_packet_lock) delete starting_packet_lock; + break; + } + ZMLockedPacket *lp = packetqueue.get_packet(start_it); + delete starting_packet_lock; + if (!lp) return nullptr; // only on terminate FIXME + starting_packet_lock = lp; + starting_packet = lp->packet_; + } + packetqueue.free_it(start_it); + delete start_it; + start_it = nullptr; + + return event; +} + void Monitor::closeEvent() { if (!event) return; From 27e87fc21ff305a062416dd508d6d208146b0809 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 3 Dec 2021 13:25:36 -0500 Subject: [PATCH 36/52] Move all the opening of events code into one function called openEvent. --- src/zm_monitor.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/zm_monitor.h b/src/zm_monitor.h index d670911bb..8d6bbd95e 100644 --- a/src/zm_monitor.h +++ b/src/zm_monitor.h @@ -593,6 +593,10 @@ public: bool Decode(); void DumpImage( Image *dump_image ) const; void TimestampImage(Image *ts_image, SystemTimePoint ts_time) const; + Event *openEvent( + const std::shared_ptr &snap, + const std::string &cause, + const Event::StringSetMap noteSetMap); void closeEvent(); void Reload(); From 45559123afc511218f38736ea8356e67b6d50909 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sun, 5 Dec 2021 17:45:26 -0500 Subject: [PATCH 37/52] Add numCoords, Coords, Area, AlarmRGB to Zone object. Also add Points(), AreaCoords, svg_polygon utility functions to it. --- web/includes/Zone.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/web/includes/Zone.php b/web/includes/Zone.php index 9f3850bb1..eb216565a 100644 --- a/web/includes/Zone.php +++ b/web/includes/Zone.php @@ -13,6 +13,10 @@ class Zone extends ZM_Object { 'Name' => '', 'Type' => 'Active', 'Units' => 'Pixels', + 'NumCoords' => '0', + 'Coords' => 0, + 'Area' => '0', + 'AlarmRGB' => '0', 'CheckMethod' => 'Blobs', 'MinPixelThreshold' => null, 'MaxPixelThreshold' => null, @@ -46,5 +50,17 @@ class Zone extends ZM_Object { return new Monitor(); } + public function Points() { + return coordsToPoints($this->Coords()); + } + + public function AreaCoords() { + return preg_replace('/\s+/', ',', $this->Coords()); + } + + public function svg_polygon() { + return ''; + } + } # end class Zone ?> From e6a12d20c6ad8279d0a02ce6727b41a4c37eb6a4 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sun, 5 Dec 2021 17:45:54 -0500 Subject: [PATCH 38/52] Add svg syles and rename imageFeed to videoFeed --- web/skins/classic/css/base/views/event.css | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/web/skins/classic/css/base/views/event.css b/web/skins/classic/css/base/views/event.css index f392cb99c..da59db6aa 100644 --- a/web/skins/classic/css/base/views/event.css +++ b/web/skins/classic/css/base/views/event.css @@ -77,7 +77,7 @@ height: 100%; position: relative; } -#imageFeed { +#videoFeed { display: inline-block; position: relative; text-align: center; @@ -263,3 +263,17 @@ height: 100%; height: 100%; background-color: #999999; } +svg.zones { + position:absolute; + top: 0; + left: 0; + background: none; + width: 100%; + /* + height: 100%; + */ +} +#videoobj { + width: 100%; + height: 100%; +} From 76c4560c25e5d21aee2c17f12ceee2ee84a3a93c Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sun, 5 Dec 2021 17:46:25 -0500 Subject: [PATCH 39/52] put svg zone styles in one files that can be included where needed --- web/skins/classic/css/base/zones.css | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 web/skins/classic/css/base/zones.css diff --git a/web/skins/classic/css/base/zones.css b/web/skins/classic/css/base/zones.css new file mode 100644 index 000000000..82c96e6b2 --- /dev/null +++ b/web/skins/classic/css/base/zones.css @@ -0,0 +1,19 @@ +.zones polygon { + fill-opacity: 0.25; +} +.Active { + stroke: #ff0000; + fill: #ff0000; +} +.Inclusive { + stroke: #FFA500; + fill: #FFA500; +} +.Exclusive { + stroke: #800080; + fill: #800080; +} +.Preclusive { + stroke: #0000FF; + fill: #0000FF; +} From 089c6044f1e3f42382f115bc04b55135489ee50d Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sun, 5 Dec 2021 17:48:49 -0500 Subject: [PATCH 40/52] Add layers toggle button, cleanup code by using ->canView, rename Monitor to monitor, add svg zones layer --- web/skins/classic/views/event.php | 107 ++++++++++++++---------- web/skins/classic/views/js/event.js | 32 +++++-- web/skins/classic/views/js/event.js.php | 10 ++- 3 files changed, 98 insertions(+), 51 deletions(-) diff --git a/web/skins/classic/views/event.php b/web/skins/classic/views/event.php index e352f305b..b096fda97 100644 --- a/web/skins/classic/views/event.php +++ b/web/skins/classic/views/event.php @@ -25,47 +25,57 @@ if ( !canView('Events') ) { require_once('includes/Event.php'); require_once('includes/Filter.php'); +require_once('includes/Zone.php'); $eid = validInt($_REQUEST['eid']); $fid = !empty($_REQUEST['fid']) ? validInt($_REQUEST['fid']) : 1; $Event = new ZM\Event($eid); -if ( $user['MonitorIds'] ) { - $monitor_ids = explode(',', $user['MonitorIds']); - if ( count($monitor_ids) and ! in_array($Event->MonitorId(), $monitor_ids) ) { - $view = 'error'; - return; - } -} -$Monitor = $Event->Monitor(); +$monitor = $Event->Monitor(); -if ( isset($_REQUEST['rate']) ) { +if (!$monitor->canView()) { + $view = 'error'; + return; +} + +zm_session_start(); +if (isset($_REQUEST['rate']) ) { $rate = validInt($_REQUEST['rate']); -} else if ( isset($_COOKIE['zmEventRate']) ) { +} else if (isset($_COOKIE['zmEventRate'])) { $rate = $_COOKIE['zmEventRate']; } else { - $rate = reScale(RATE_BASE, $Monitor->DefaultRate(), ZM_WEB_DEFAULT_RATE); + $rate = reScale(RATE_BASE, $monitor->DefaultRate(), ZM_WEB_DEFAULT_RATE); } -if ( isset($_REQUEST['scale']) ) { +if (isset($_REQUEST['scale'])) { $scale = validInt($_REQUEST['scale']); -} else if ( isset($_COOKIE['zmEventScale'.$Event->MonitorId()]) ) { +} else if (isset($_COOKIE['zmEventScale'.$Event->MonitorId()])) { $scale = $_COOKIE['zmEventScale'.$Event->MonitorId()]; } else { - $scale = $Monitor->DefaultScale(); + $scale = $monitor->DefaultScale(); +} + +$showZones = false; +if (isset($_REQUEST['showZones'])) { + $showZones = $_REQUEST['showZones'] == 1; + $_SESSION['zmEventShowZones'.$monitor->Id()] = $showZones; +} else if (isset($_COOKIE['zmEventShowZones'.$monitor->Id()])) { + $showZones = $_COOKIE['zmEventShowZones'.$monitor->Id()] == 1; +} else if (isset($_SESSION['zmEventShowZones'.$monitor->Id()]) ) { + $showZones = $_SESSION['zmEventShowZones'.$monitor->Id()]; } $codec = 'auto'; -if ( isset($_REQUEST['codec']) ) { +if (isset($_REQUEST['codec'])) { $codec = $_REQUEST['codec']; - zm_session_start(); $_SESSION['zmEventCodec'.$Event->MonitorId()] = $codec; - session_write_close(); } else if ( isset($_SESSION['zmEventCodec'.$Event->MonitorId()]) ) { $codec = $_SESSION['zmEventCodec'.$Event->MonitorId()]; } else { - $codec = $Monitor->DefaultCodec(); + $codec = $monitor->DefaultCodec(); } +session_write_close(); + $codecs = array( 'auto' => translate('Auto'), 'MP4' => translate('MP4'), @@ -79,32 +89,30 @@ $replayModes = array( 'gapless' => translate('ReplayGapless'), ); -if ( isset($_REQUEST['streamMode']) ) +if (isset($_REQUEST['streamMode'])) $streamMode = validHtmlStr($_REQUEST['streamMode']); else $streamMode = 'video'; $replayMode = ''; -if ( isset($_REQUEST['replayMode']) ) +if (isset($_REQUEST['replayMode'])) $replayMode = validHtmlStr($_REQUEST['replayMode']); -if ( isset($_COOKIE['replayMode']) && preg_match('#^[a-z]+$#', $_COOKIE['replayMode']) ) +if (isset($_COOKIE['replayMode']) && preg_match('#^[a-z]+$#', $_COOKIE['replayMode'])) $replayMode = validHtmlStr($_COOKIE['replayMode']); -if ( ( !$replayMode ) or ( !$replayModes[$replayMode] ) ) { +if ((!$replayMode) or !$replayModes[$replayMode]) { $replayMode = 'none'; } -$video_tag = false; -if ( $Event->DefaultVideo() and ( $codec == 'MP4' or $codec == 'auto' ) ) { - $video_tag = true; -} +$video_tag = ($Event->DefaultVideo() and ($codec == 'MP4' or $codec == 'auto')); + // videojs zoomrotate only when direct recording $Zoom = 1; $Rotation = 0; -if ( $Monitor->VideoWriter() == '2' ) { +if ($monitor->VideoWriter() == '2') { # Passthrough $Rotation = $Event->Orientation(); - if ( in_array($Event->Orientation(),array('90','270')) ) + if (in_array($Event->Orientation(),array('90','270'))) $Zoom = $Event->Height()/$Event->Width(); } @@ -143,7 +151,7 @@ if ( $Event->Id() and !file_exists($Event->Path()) )
-Id() ) { ?> +Id()) { ?> @@ -158,7 +166,13 @@ if ( $Event->Id() and !file_exists($Event->Path()) ) -Id ?> + +

Id() ?>

@@ -190,10 +204,10 @@ if ( $Event->Id() and !file_exists($Event->Path()) )
-
+ -
-
-getStreamSrc(array('mode'=>'mpeg', 'scale'=>$scale, 'rate'=>$rate, 'bitrate'=>ZM_WEB_VIDEO_BITRATE, 'maxfps'=>ZM_WEB_VIDEO_MAXFPS, 'format'=>ZM_MPEG_REPLAY_FORMAT, 'replay'=>$replayMode),'&'); outputVideoStream('evtStream', $streamSrc, reScale( $Event->Width(), $scale ).'px', reScale( $Event->Height(), $scale ).'px', ZM_MPEG_LIVE_FORMAT ); } else { $streamSrc = $Event->getStreamSrc(array('mode'=>'jpeg', 'frame'=>$fid, 'scale'=>$scale, 'rate'=>$rate, 'maxfps'=>ZM_WEB_VIDEO_MAXFPS, 'replay'=>$replayMode),'&'); if ( canStreamNative() ) { - outputImageStream('evtStream', $streamSrc, reScale($Event->Width(), $scale).'px', reScale($Event->Height(), $scale).'px', validHtmlStr($Event->Name())); + outputImageStream('evtStream', $streamSrc, '100%', '100%', validHtmlStr($Event->Name())); } else { - outputHelperStream('evtStream', $streamSrc, reScale($Event->Width(), $scale).'px', reScale($Event->Height(), $scale).'px' ); + outputHelperStream('evtStream', $streamSrc, '100%', '100%'); } } // end if stream method ?> @@ -231,10 +241,18 @@ if ( (ZM_WEB_STREAM_METHOD == 'mpeg') && ZM_MPEG_LIVE_FORMAT ) {
-
+ +$monitor->Id()), array('order'=>'Area DESC')) as $zone) { + echo $zone->svg_polygon(); + } // end foreach zone +?> + Sorry, your browser does not support inline SVG + +

- + diff --git a/web/skins/classic/views/js/event.js b/web/skins/classic/views/js/event.js index 94e132cc1..b252c292c 100644 --- a/web/skins/classic/views/js/event.js +++ b/web/skins/classic/views/js/event.js @@ -177,7 +177,7 @@ function changeScale() { var newWidth; var newHeight; var autoScale; - var eventViewer= $j(vid ? '#videoobj' : '#evtStream'); + var eventViewer= $j(vid ? '#videoobj' : '#videoFeed'); var alarmCue = $j('div.alarmCue'); var bottomEl = $j('#replayStatus'); @@ -910,12 +910,12 @@ function initPage() { progressBarNav(); streamCmdTimer = setTimeout(streamQuery, 500); if (canStreamNative) { - if (!$j('#imageFeed')) { - console.log('No element with id tag imageFeed found.'); + if (!$j('#videoFeed')) { + console.log('No element with id tag videoFeed found.'); } else { - var streamImg = $j('#imageFeed img'); + var streamImg = $j('#videoFeed img'); if (!streamImg) { - streamImg = $j('#imageFeed object'); + streamImg = $j('#videoFeed object'); } $j(streamImg).click(function(event) { handleClick(event); @@ -1071,5 +1071,27 @@ function initPage() { }); } // end initPage +document.getElementById('toggleZonesButton').addEventListener('click', toggleZones); + +function toggleZones(e) { + const zones = $j('#zones'+eventData.MonitorId); + const button = document.getElementById('toggleZonesButton'); + if (zones.length) { + if (zones.is(":visible")) { + zones.hide(); + button.setAttribute('title', showZonesString); + button.innerHTML = 'layers'; + setCookie('zmEventShowZones'+eventData.MonitorId, '0', 3600); + } else { + zones.show(); + button.setAttribute('title', hideZonesString); + button.innerHTML = 'layers_clear'; + setCookie('zmEventShowZones'+eventData.MonitorId, '1', 3600); + } + } else { + console.error("Zones svg not found"); + } +} + // Kick everything off $j(document).ready(initPage); diff --git a/web/skins/classic/views/js/event.js.php b/web/skins/classic/views/js/event.js.php index cdd4e9041..5d4d57f7c 100644 --- a/web/skins/classic/views/js/event.js.php +++ b/web/skins/classic/views/js/event.js.php @@ -1,7 +1,7 @@ Id() ?>', Name: 'Name() ?>', MonitorId: 'MonitorId() ?>', - MonitorName: 'Name()) ?>', + MonitorName: 'Name()) ?>', Cause: 'Cause()) ?>', + Notes: 'Notes()?>', Width: 'Width() ?>', Height: 'Height() ?>', Length: 'Length() ?>', @@ -72,6 +73,7 @@ var eventDataStrings = { MonitorId: '', MonitorName: '', Cause: '', + Notes: '', StartDateTimeShort: '', Length: '', Frames: '', @@ -93,7 +95,7 @@ var sortQuery = '; var rate = ''; // really only used when setting up initial playback rate. var scale = ""; -var LabelFormat = "LabelFormat())?>"; +var LabelFormat = "LabelFormat())?>"; var streamTimeout = ; @@ -105,6 +107,8 @@ var streamMode = ''; // var deleteString = ""; var causeString = ""; +var showZonesString = ""; +var hideZonesString = ""; var WEB_LIST_THUMB_WIDTH = ''; var WEB_LIST_THUMB_HEIGHT = ''; var popup = ''; From 7b66d751d80a9f0722ded2154b1514f72ab24105 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sun, 5 Dec 2021 17:49:24 -0500 Subject: [PATCH 41/52] cleanup, spacing, use zone object methods to clean up svg zone layers --- web/skins/classic/views/montage.php | 43 +++++++++++++++-------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/web/skins/classic/views/montage.php b/web/skins/classic/views/montage.php index b0b10f190..deaddcaf3 100644 --- a/web/skins/classic/views/montage.php +++ b/web/skins/classic/views/montage.php @@ -24,6 +24,7 @@ if ( !canView('Stream') ) { } require_once('includes/MontageLayout.php'); +require_once('includes/Zone.php'); $showControl = false; $showZones = false; @@ -49,7 +50,6 @@ $heights = array( '1080' => '1080px', ); - $layouts = ZM\MontageLayout::find(NULL, array('order'=>"lower('Name')")); $layoutsById = array(); foreach ( $layouts as $l ) { @@ -149,7 +149,7 @@ echo getNavBarHTML(); $html .= '