From 8554ccd64982c8ff6ed101f7bc3182690565588c Mon Sep 17 00:00:00 2001 From: criadoperez Date: Wed, 29 Sep 2021 12:26:15 +0200 Subject: [PATCH 01/30] Added Debian 11 (bullseye) installation --- docs/installationguide/debian.rst | 86 +++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/docs/installationguide/debian.rst b/docs/installationguide/debian.rst index c6e0cebcf..15cc9660d 100644 --- a/docs/installationguide/debian.rst +++ b/docs/installationguide/debian.rst @@ -3,6 +3,92 @@ Debian .. contents:: +Easy Way: Debian 11 (Bullseye) +------------------------ + +This procedure will guide you through the installation of ZoneMinder on Debian 11 (Bullseye). + +**Step 1:** Setup Sudo (optional but recommended) + +By default Debian does not come with sudo, so you have to install it and configure it manually. +This step is optional but recommended and the following instructions assume that you have setup sudo. +If you prefer to setup ZoneMinder as root, do it at your own risk and adapt the following instructions accordingly. + +:: + + apt install sudo + usermod -a -G sudo + exit + +Now your terminal session is back under your normal user. You can check that +you are now part of the sudo group with the command ``groups``, "sudo" should +appear in the list. If not, run ``newgrp sudo`` and check again with ``groups``. + +**Step 2:** Update system and install zoneminder + +Run the following commands. + +:: + + sudo apt update + sudo apt upgrade + sudo apt install zoneminder + +**Step 3:** Initialize database + +Run the following commands. + +:: + + cat /usr/share/zoneminder/db/zm_create.sql | sudo mysql --defaults-file=/etc/mysql/debian.cnf + echo 'grant lock tables,alter,create,select,insert,update,delete,index on zm.* to 'zmuser'@localhost identified by "zmpass";' | sudo mysql --defaults-file=/etc/mysql/debian.cnf mysql + +**Step 4:** Setup permissions for zm.conf + +To make sure zoneminder can read the configuration file, run the following command. + +:: + + sudo chgrp -c www-data /etc/zm/zm.conf + +**Step 5:** Edit Timezone in PHP + +Automated way: +:: + + sudo sed -i "s/;date.timezone =/date.timezone = $(sed 's/\//\\\//' /etc/timezone)/g" /etc/php/7.*/apache2/php.ini + +Manual way +:: + + sudo nano /etc/php/7.*/apache2/php.ini + +Search for [Date] (Ctrl + w then type Date and press Enter) and change +date.timezone for your time zone. Don't forget to remove the ; from in front +of date.timezone. + +:: + + [Date] + ; Defines the default timezone used by the date functions + ; http://php.net/date.timezone + date.timezone = America/New_York + +CTRL+o then [Enter] to save + +CTRL+x to exit + +**Step 6:** Reload Apache and start zoneminder + +Run the following commands. + +:: + + sudo systemctl reload apache2 + sudo systectl start zoneminder + +Congratulations! You should now be able to access zoneminder at ``http://yourhostname/zm`` + Easy Way: Debian Buster ------------------------ From 9f683c0c91f7c5f37d10f1bf502d499016a7bb84 Mon Sep 17 00:00:00 2001 From: criadoperez Date: Wed, 29 Sep 2021 14:14:25 +0200 Subject: [PATCH 02/30] Removed default-file parameter that is deprecated --- docs/installationguide/debian.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/installationguide/debian.rst b/docs/installationguide/debian.rst index 15cc9660d..50173928f 100644 --- a/docs/installationguide/debian.rst +++ b/docs/installationguide/debian.rst @@ -40,8 +40,8 @@ Run the following commands. :: - cat /usr/share/zoneminder/db/zm_create.sql | sudo mysql --defaults-file=/etc/mysql/debian.cnf - echo 'grant lock tables,alter,create,select,insert,update,delete,index on zm.* to 'zmuser'@localhost identified by "zmpass";' | sudo mysql --defaults-file=/etc/mysql/debian.cnf mysql + cat /usr/share/zoneminder/db/zm_create.sql | sudo mariadb + echo 'grant lock tables,alter,create,select,insert,update,delete,index on zm.* to 'zmuser'@localhost identified by "zmpass";' | sudo mariadb **Step 4:** Setup permissions for zm.conf From e462c231151ecb6044d747e3a1ad29ef948af3dd Mon Sep 17 00:00:00 2001 From: criadoperez Date: Wed, 29 Sep 2021 16:46:04 +0200 Subject: [PATCH 03/30] updated dlandon repository name --- docs/installationguide/easydocker.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installationguide/easydocker.rst b/docs/installationguide/easydocker.rst index 899ce0e5d..a7d37a9e5 100644 --- a/docs/installationguide/easydocker.rst +++ b/docs/installationguide/easydocker.rst @@ -2,7 +2,7 @@ An Easy To Use Docker Image =========================== If you are interested in trying out ZoneMinder quickly, user Dan Landon maintains an easy to use docker image for ZoneMinder. With a few simple configuration changes, it also provides complete Event Notification Server and Machine Learning hook support. Please follow instructions in his repostory. He maintains two repositories: -* If you want to run the latest stable release, please use his `zoneminder repository `__. +* If you want to run the latest stable release, please use his `zoneminder machine learning repository `__. * If you want to run the latest zoneminder master, please use his `zoneminder master repository `__. In both cases, instructions are provided in the repo README files. From 1e71300a3bee9cf8844c4cee6a75f4e0b2d130bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Criado-P=C3=A9rez?= Date: Thu, 30 Sep 2021 01:15:45 +0200 Subject: [PATCH 04/30] added Debian installation instructions --- docs/installationguide/debian.rst | 48 ++----------------------------- 1 file changed, 3 insertions(+), 45 deletions(-) diff --git a/docs/installationguide/debian.rst b/docs/installationguide/debian.rst index 50173928f..f7325fe1f 100644 --- a/docs/installationguide/debian.rst +++ b/docs/installationguide/debian.rst @@ -32,18 +32,12 @@ Run the following commands. sudo apt update sudo apt upgrade + sudo apt install mariadb-server sudo apt install zoneminder -**Step 3:** Initialize database +When mariadb is installed for the first time, it doesn't add a password to the root user. Therefore, for security, it is recommended to run ``mysql secure installation``. -Run the following commands. - -:: - - cat /usr/share/zoneminder/db/zm_create.sql | sudo mariadb - echo 'grant lock tables,alter,create,select,insert,update,delete,index on zm.* to 'zmuser'@localhost identified by "zmpass";' | sudo mariadb - -**Step 4:** Setup permissions for zm.conf +**Step 3:** Setup permissions for zm.conf To make sure zoneminder can read the configuration file, run the following command. @@ -51,42 +45,6 @@ To make sure zoneminder can read the configuration file, run the following comma sudo chgrp -c www-data /etc/zm/zm.conf -**Step 5:** Edit Timezone in PHP - -Automated way: -:: - - sudo sed -i "s/;date.timezone =/date.timezone = $(sed 's/\//\\\//' /etc/timezone)/g" /etc/php/7.*/apache2/php.ini - -Manual way -:: - - sudo nano /etc/php/7.*/apache2/php.ini - -Search for [Date] (Ctrl + w then type Date and press Enter) and change -date.timezone for your time zone. Don't forget to remove the ; from in front -of date.timezone. - -:: - - [Date] - ; Defines the default timezone used by the date functions - ; http://php.net/date.timezone - date.timezone = America/New_York - -CTRL+o then [Enter] to save - -CTRL+x to exit - -**Step 6:** Reload Apache and start zoneminder - -Run the following commands. - -:: - - sudo systemctl reload apache2 - sudo systectl start zoneminder - Congratulations! You should now be able to access zoneminder at ``http://yourhostname/zm`` Easy Way: Debian Buster From 16d0e269dfe182bfb5807ec248542d1eb12984a3 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sat, 16 Oct 2021 18:30:55 -0400 Subject: [PATCH 05/30] WHen saving v4l settings redirect back to watch instead of console. --- web/includes/actions/settings.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/web/includes/actions/settings.php b/web/includes/actions/settings.php index a4aa1b489..710bfa641 100644 --- a/web/includes/actions/settings.php +++ b/web/includes/actions/settings.php @@ -20,18 +20,18 @@ // Monitor control actions, require a monitor id and control view permissions for that monitor -if ( empty($_REQUEST['mid']) ) { +if (empty($_REQUEST['mid'])) { ZM\Warning('Settings requires a monitor id'); return; } -if ( ! canView('Control', $_REQUEST['mid']) ) { +if (!canView('Control', $_REQUEST['mid'])) { ZM\Warning('Settings requires the Control permission'); return; } require_once('includes/Monitor.php'); $mid = validInt($_REQUEST['mid']); -if ( $action == 'settings' ) { +if ($action == 'settings') { $args = ' -m ' . escapeshellarg($mid); $args .= ' -B' . escapeshellarg($_REQUEST['newBrightness']); $args .= ' -C' . escapeshellarg($_REQUEST['newContrast']); @@ -45,5 +45,7 @@ if ( $action == 'settings' ) { dbQuery( 'UPDATE Monitors SET Brightness = ?, Contrast = ?, Hue = ?, Colour = ? WHERE Id = ?', array($brightness, $contrast, $hue, $colour, $mid)); + global $redirect; + $redirect = '?view=watch&mid='.$mid; } ?> From 6d7f752eb062387cbf70612dad54f297fac13055 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 19 Oct 2021 13:04:49 -0400 Subject: [PATCH 06/30] Only record when in modect or nodect. Linked monitors would cause a monitor in monitor mode to record --- src/zm_monitor.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index cd17f3fe0..e85330eb2 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -2040,8 +2040,7 @@ bool Monitor::Analyse() { } // end if ! event } // end if RECORDING - if (score) { - + if (score and (function == MODECT or function == NODECT)) { if ((state == IDLE) || (state == TAPE) || (state == PREALARM)) { // If we should end then previous continuous event and start a new non-continuous event if (event && event->Frames() From 52f0240334549342126b904fed333725ffac2872 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 19 Oct 2021 13:05:11 -0400 Subject: [PATCH 07/30] free and re-init opts as needed --- src/zm_videostore.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index 6f76f3521..2b9f662e6 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -151,6 +151,7 @@ bool VideoStore::open() { Debug(3, "Encoder Option %s=%s", e->key, e->value); } } + av_dict_free(&opts); if (video_in_stream) { zm_dump_codecpar(video_in_stream->codecpar); @@ -184,6 +185,7 @@ bool VideoStore::open() { } } // end if orientation + av_dict_parse_string(&opts, Options.c_str(), "=", ",#\n", 0); if (av_dict_get(opts, "new_extradata", nullptr, AV_DICT_MATCH_CASE)) { av_dict_set(&opts, "new_extradata", nullptr, 0); // Special flag to tell us to open a codec to get new extraflags to fix weird h265 @@ -224,14 +226,13 @@ bool VideoStore::open() { ); video_out_codec = nullptr; } - av_dict_free(&opts); - av_dict_parse_string(&opts, Options.c_str(), "=", ",#\n", 0); } // end if video_out_codec ret = avcodec_parameters_from_context(video_out_stream->codecpar, video_out_ctx); if (ret < 0) { Error("Could not initialize stream parameteres"); } + av_dict_free(&opts); } // end if extradata_entry } else if (monitor->GetOptVideoWriter() == Monitor::ENCODE) { int wanted_codec = monitor->OutputCodec(); @@ -486,6 +487,7 @@ bool VideoStore::open() { zm_dump_stream_format(oc, 0, 0, 1); if (audio_out_stream) zm_dump_stream_format(oc, 1, 0, 1); + av_dict_parse_string(&opts, Options.c_str(), "=", ",#\n", 0); const AVDictionaryEntry *movflags_entry = av_dict_get(opts, "movflags", nullptr, AV_DICT_MATCH_CASE); if (!movflags_entry) { Debug(1, "setting movflags to frag_keyframe+empty_moov"); From 4351f51929aa69989451373dcfd87477df2d3f28 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 20 Oct 2021 13:13:50 -0400 Subject: [PATCH 08/30] Fix logic ordering in 1.35.14 updated that moves columns from Monitors table to Monitor_Status --- db/zm_update-1.35.14.sql | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/db/zm_update-1.35.14.sql b/db/zm_update-1.35.14.sql index daa8239ff..f3c8bf779 100644 --- a/db/zm_update-1.35.14.sql +++ b/db/zm_update-1.35.14.sql @@ -28,8 +28,8 @@ SET @s = (SELECT IF( AND table_name = 'Monitors' AND column_name = 'TotalEvents' ) > 0, -"SELECT 'Column TotalEvents is already removed from Monitors'", -"ALTER TABLE `Monitors` DROP `TotalEvents`" +"ALTER TABLE `Monitors` DROP `TotalEvents`", +"SELECT 'Column TotalEvents is already removed from Monitors'" )); PREPARE stmt FROM @s; EXECUTE stmt; @@ -50,8 +50,8 @@ SET @s = (SELECT IF( AND table_name = 'Monitors' AND column_name = 'TotalEventDiskSpace' ) > 0, -"SELECT 'Column TotalEventDiskSpace is already removed from Monitors'", -"ALTER TABLE `Monitors` DROP `TotalEventDiskSpace`" +"ALTER TABLE `Monitors` DROP `TotalEventDiskSpace`", +"SELECT 'Column TotalEventDiskSpace is already removed from Monitors'" )); PREPARE stmt FROM @s; EXECUTE stmt; From c6eedd434c9a174b03bdd204931a2bc44ec357e7 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Fri, 22 Oct 2021 20:59:04 -0400 Subject: [PATCH 09/30] Make delete dialog disappear on success. Fixes #3377 --- web/skins/classic/views/js/event.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/web/skins/classic/views/js/event.js b/web/skins/classic/views/js/event.js index 727976e76..0cd770170 100644 --- a/web/skins/classic/views/js/event.js +++ b/web/skins/classic/views/js/event.js @@ -767,14 +767,15 @@ function handleClick(event) { // Manage the DELETE CONFIRMATION modal button function manageDelConfirmModalBtns() { document.getElementById("delConfirmBtn").addEventListener("click", function onDelConfirmClick(evt) { - if ( !canEdit.Events ) { + if (!canEdit.Events) { enoperm(); return; } evt.preventDefault(); - $j.getJSON(thisUrl + '?request=events&task=delete&eids[]='+eventData.Id) + $j.getJSON(thisUrl + '?request=event&task=delete&id='+eventData.Id) .done(function(data) { + $j('#deleteConfirm').modal('hide'); streamNext(true); }) .fail(logAjaxFail); From 9169da6963e3812eb5bd64c18298ec1d25084c4f Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 25 Oct 2021 11:09:06 -0400 Subject: [PATCH 10/30] Only start a transaction if we are not already in a transaction --- scripts/ZoneMinder/lib/ZoneMinder/Object.pm | 24 ++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Object.pm b/scripts/ZoneMinder/lib/ZoneMinder/Object.pm index f3d750338..e54bb15ce 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Object.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Object.pm @@ -218,7 +218,7 @@ sub save { my $serial = eval '$'.$type.'::serial'; my @identified_by = eval '@'.$type.'::identified_by'; - my $ac = ZoneMinder::Database::start_transaction( $local_dbh ); + my $ac = ZoneMinder::Database::start_transaction( $local_dbh ) if $local_dbh->{AutoCommit}; if ( ! $serial ) { my $insert = $force_insert; my %serial = eval '%'.$type.'::serial'; @@ -234,8 +234,8 @@ $log->debug("No serial") if $debug; if ( ! ( ( $_ = $local_dbh->prepare("DELETE FROM `$table` WHERE $where") ) and $_->execute( @$self{@identified_by} ) ) ) { $where =~ s/\?/\%s/g; $log->error("Error deleting: DELETE FROM $table WHERE " . sprintf($where, map { defined $_ ? $_ : 'undef' } ( @$self{@identified_by}) ).'):' . $local_dbh->errstr); - $local_dbh->rollback(); - ZoneMinder::Database::end_transaction( $local_dbh, $ac ); + $local_dbh->rollback() if $ac; + ZoneMinder::Database::end_transaction( $local_dbh, $ac ) if $ac; return $local_dbh->errstr; } elsif ( $debug ) { $log->debug("SQL succesful DELETE FROM $table WHERE $where"); @@ -267,8 +267,8 @@ $log->debug("No serial") if $debug; my $error = $local_dbh->errstr; $command =~ s/\?/\%s/g; $log->error('SQL statement execution failed: ('.sprintf($command, , map { defined $_ ? $_ : 'undef' } ( @sql{@keys}) ).'):' . $local_dbh->errstr); - $local_dbh->rollback(); - ZoneMinder::Database::end_transaction( $local_dbh, $ac ); + $local_dbh->rollback() if $ac; + ZoneMinder::Database::end_transaction( $local_dbh, $ac ) if $ac; return $error; } # end if if ( $debug or DEBUG_ALL ) { @@ -282,8 +282,8 @@ $log->debug("No serial") if $debug; my $error = $local_dbh->errstr; $command =~ s/\?/\%s/g; $log->error('SQL failed: ('.sprintf($command, , map { defined $_ ? $_ : 'undef' } ( @sql{@keys, @$fields{@identified_by}}) ).'):' . $local_dbh->errstr); - $local_dbh->rollback(); - ZoneMinder::Database::end_transaction( $local_dbh, $ac ); + $local_dbh->rollback() if $ac; + ZoneMinder::Database::end_transaction( $local_dbh, $ac ) if $ac; return $error; } # end if if ( $debug or DEBUG_ALL ) { @@ -321,8 +321,8 @@ $log->debug("No serial") if $debug; $command =~ s/\?/\%s/g; my $error = $local_dbh->errstr; $log->error('SQL failed: ('.sprintf($command, map { defined $_ ? $_ : 'undef' } ( @sql{@keys}) ).'):' . $error); - $local_dbh->rollback(); - ZoneMinder::Database::end_transaction( $local_dbh, $ac ); + $local_dbh->rollback() if $ac; + ZoneMinder::Database::end_transaction( $local_dbh, $ac ) if $ac; return $error; } # end if if ( $debug or DEBUG_ALL ) { @@ -340,8 +340,8 @@ $log->debug("No serial") if $debug; my $error = $local_dbh->errstr; $command =~ s/\?/\%s/g; $log->error('SQL failed: ('.sprintf($command, map { defined $_ ? $_ : 'undef' } ( @sql{@keys}, @sql{@$fields{@identified_by}} ) ).'):' . $error) if $log; - $local_dbh->rollback(); - ZoneMinder::Database::end_transaction( $local_dbh, $ac ); + $local_dbh->rollback() if $ac; + ZoneMinder::Database::end_transaction( $local_dbh, $ac ) if $ac; return $error; } # end if if ( $debug or DEBUG_ALL ) { @@ -350,7 +350,7 @@ $log->debug("No serial") if $debug; } # end if } # end if } # end if - ZoneMinder::Database::end_transaction( $local_dbh, $ac ); + ZoneMinder::Database::end_transaction( $local_dbh, $ac ) if $ac; #$self->load(); #if ( $$fields{id} ) { #if ( ! $ZoneMinder::Object::cache{$type}{$$self{id}} ) { From 5e21d4833a2aa9c2de0760ce5909c03e32044961 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 25 Oct 2021 11:09:29 -0400 Subject: [PATCH 11/30] Remove dead code, remove locking from CopyTo, put locking into MoveTo. --- scripts/ZoneMinder/lib/ZoneMinder/Event.pm | 76 +++++++--------------- 1 file changed, 24 insertions(+), 52 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm index 48544a911..cc5722679 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm @@ -584,6 +584,7 @@ sub DiskSpace { return $_[0]{DiskSpace}; } +# Icon: I removed the locking from this. So we now have an assumption that the Event object is up to date. sub CopyTo { my ( $self, $NewStorage ) = @_; @@ -614,16 +615,12 @@ sub CopyTo { Debug("$NewPath is good"); } - $ZoneMinder::Database::dbh->begin_work(); - $self->lock_and_load(); # 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.'; } if ( $$OldStorage{Id} != $$self{StorageId} ) { - $ZoneMinder::Database::dbh->commit(); return 'Old Storage path changed, Event has moved somewhere else.'; } @@ -661,39 +658,21 @@ sub CopyTo { } my $event_path = $subpath.$self->RelativePath(); - if ( 0 ) { # Not neccessary - Debug("Making directory $event_path/"); - if ( !$bucket->add_key($event_path.'/', '') ) { - Warning("Unable to add key for $event_path/ :". $s3->err . ': '. $s3->errstr()); - } - } my @files = glob("$OldPath/*"); Debug("Files to move @files"); - foreach my $file ( @files ) { + foreach my $file (@files) { next if $file =~ /^\./; - ( $file ) = ( $file =~ /^(.*)$/ ); # De-taint + ($file) = ($file =~ /^(.*)$/); # De-taint my $starttime = [gettimeofday]; Debug("Moving file $file to $NewPath"); my $size = -s $file; - if ( ! $size ) { + if (!$size) { Info('Not moving file with 0 size'); } - if ( 0 ) { - my $file_contents = File::Slurp::read_file($file); - if ( ! $file_contents ) { - die 'Loaded empty file, but it had a size. Giving up'; - } - - my $filename = $event_path.'/'.File::Basename::basename($file); - if ( ! $bucket->add_key($filename, $file_contents) ) { - die "Unable to add key for $filename : ".$s3->err . ': ' . $s3->errstr; - } - } else { - my $filename = $event_path.'/'.File::Basename::basename($file); - if ( ! $bucket->add_key_filename($filename, $file) ) { - die "Unable to add key for $filename " . $s3->err . ': '. $s3->errstr; - } + my $filename = $event_path.'/'.File::Basename::basename($file); + if (!$bucket->add_key_filename($filename, $file)) { + die "Unable to add key for $filename " . $s3->err . ': '. $s3->errstr; } my $duration = tv_interval($starttime); @@ -704,16 +683,15 @@ sub CopyTo { }; Error($@) if $@; } else { - Error("Unable to parse S3 Url into it's component parts."); + Error('Unable to parse S3 Url into it\'s component parts.'); } - #die $@ if $@; } # end if Url } # end if s3 my $error = ''; - if ( !$moved ) { + if (!$moved) { File::Path::make_path($NewPath, {error => \my $err}); - if ( @$err ) { + if (@$err) { for my $diag (@$err) { my ($file, $message) = %$diag; next if $message eq 'File exists'; @@ -724,23 +702,16 @@ sub CopyTo { } } } - if ( $error ) { - $ZoneMinder::Database::dbh->commit(); - return $error; - } + return $error if $error; my @files = glob("$OldPath/*"); - if ( ! @files ) { - $ZoneMinder::Database::dbh->commit(); - return 'No files to move.'; - } + return 'No files to move.' if !@files; for my $file (@files) { next if $file =~ /^\./; - ( $file ) = ( $file =~ /^(.*)$/ ); # De-taint + ($file) = ($file =~ /^(.*)$/); # De-taint my $starttime = [gettimeofday]; - Debug("Moving file $file to $NewPath"); my $size = -s $file; - if ( ! File::Copy::copy( $file, $NewPath ) ) { + if (!File::Copy::copy($file, $NewPath)) { $error .= "Copy failed: for $file to $NewPath: $!"; last; } @@ -749,20 +720,21 @@ sub CopyTo { } # end foreach file. } # end if ! moved - if ( $error ) { - $ZoneMinder::Database::dbh->commit(); - return $error; - } + return $error if $error; } # end sub CopyTo sub MoveTo { - my ( $self, $NewStorage ) = @_; + my ($self, $NewStorage) = @_; - if ( !$self->canEdit() ) { + if (!$self->canEdit()) { Warning('No permission to move event.'); return 'No permission to move event.'; } + 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 + my $OldStorage = $self->Storage(undef); my $error = $self->CopyTo($NewStorage); @@ -772,11 +744,11 @@ sub MoveTo { $$self{StorageId} = $$NewStorage{Id}; $self->Storage($NewStorage); $error .= $self->save(); - if ( $error ) { - $ZoneMinder::Database::dbh->commit(); + if ($error) { + $ZoneMinder::Database::dbh->commit() if !$was_in_transaction; return $error; } - $ZoneMinder::Database::dbh->commit(); + $ZoneMinder::Database::dbh->commit() if !$was_in_transaction; $self->delete_files($OldStorage); return $error; } # end sub MoveTo From 8afed25d18067a2c86b0f2ddc47efcfd20d02a45 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 25 Oct 2021 12:34:28 -0400 Subject: [PATCH 12/30] Use filter->sort_asc and sort_field which will use either the value specified in query, or defaults set in system. url params order and sort will override. --- web/ajax/events.php | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/web/ajax/events.php b/web/ajax/events.php index 562cce9c2..471f32664 100644 --- a/web/ajax/events.php +++ b/web/ajax/events.php @@ -6,28 +6,30 @@ $data = array(); // INITIALIZE AND CHECK SANITY // -if ( !canView('Events') ) $message = 'Insufficient permissions for user '.$user['Username']; +if (!canView('Events')) + $message = 'Insufficient permissions for user '.$user['Username'].'
'; -if ( empty($_REQUEST['task']) ) { - $message = 'Must specify a task'; +if (empty($_REQUEST['task'])) { + $message = 'Must specify a task
'; } else { $task = $_REQUEST['task']; } -if ( empty($_REQUEST['eids']) ) { - if ( isset($_REQUEST['task']) && $_REQUEST['task'] != 'query' ) $message = 'No event id(s) supplied'; +if (empty($_REQUEST['eids'])) { + if (isset($_REQUEST['task']) && $_REQUEST['task'] != 'query') + $message = 'No event id(s) supplied
'; } else { $eids = $_REQUEST['eids']; } -if ( $message ) { +if ($message) { ajaxError($message); return; } require_once('includes/Filter.php'); $filter = isset($_REQUEST['filter']) ? ZM\Filter::parse($_REQUEST['filter']) : new ZM\Filter(); -if ( $user['MonitorIds'] ) { +if ($user['MonitorIds']) { $filter = $filter->addTerm(array('cnj'=>'and', 'attr'=>'MonitorId', 'op'=>'IN', 'val'=>$user['MonitorIds'])); } @@ -39,10 +41,19 @@ $search = isset($_REQUEST['search']) ? $_REQUEST['search'] : ''; $advsearch = isset($_REQUEST['advsearch']) ? json_decode($_REQUEST['advsearch'], JSON_OBJECT_AS_ARRAY) : array(); // Order specifies the sort direction, either asc or desc -$order = (isset($_REQUEST['order']) and (strtolower($_REQUEST['order']) == 'asc')) ? 'ASC' : 'DESC'; +$order = $filter->sort_asc() ? 'ASC' : 'DESC'; +if (isset($_REQUEST['order'])) { + if (strtolower($_REQUEST['order']) == 'asc')) { + $order = 'ASC'; + } else if (strtolower($_REQUEST['order']) == 'desc')) { + $order = 'DESC'; + } else { + Warning("Invalid value for order " . $_REQUEST['order']); + } +} // Sort specifies the name of the column to sort on -$sort = 'StartDateTime'; +$sort = $filter->sort_field(); if (isset($_REQUEST['sort'])) { $sort = $_REQUEST['sort']; if ($sort == 'EndDateTime') { From 0c222f66ee8be7f8f9a566653d2960a22ae08f59 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 25 Oct 2021 12:34:51 -0400 Subject: [PATCH 13/30] Add sort_asc, sort_field and limit to the filter QueryString --- web/includes/Filter.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/web/includes/Filter.php b/web/includes/Filter.php index 3578a1d62..a155d97a8 100644 --- a/web/includes/Filter.php +++ b/web/includes/Filter.php @@ -60,6 +60,9 @@ class Filter extends ZM_Object { foreach ( $this->FilterTerms() as $term ) { $this->_querystring .= $term->querystring($objectname, $separator); } # end foreach term + $this->_querystring .= $separator.urlencode($objectname.'[Query][sort_asc]').'='.$this->sort_asc(); + $this->_querystring .= $separator.urlencode($objectname.'[Query][sort_field]').'='.$this->sort_field(); + $this->_querystring .= $separator.urlencode($objectname.'[Query][limit]').'='.$this->limit(); if ( $this->Id() ) { $this->_querystring .= $separator.$objectname.urlencode('[Id]').'='.$this->Id(); } From 51e77438fa9d0ad91925f95e9874a03f41b230cd Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 25 Oct 2021 12:35:46 -0400 Subject: [PATCH 14/30] Add sort-name and sort-order to bootstrap table options. Also set data-remember-order=false. This allows orderings set by filters to work. Fixes #3348 --- web/skins/classic/views/events.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/web/skins/classic/views/events.php b/web/skins/classic/views/events.php index 52cb5c3f0..1c93e0b92 100644 --- a/web/skins/classic/views/events.php +++ b/web/skins/classic/views/events.php @@ -79,11 +79,14 @@ getBodyTopHTML(); data-cookie-id-table="zmEventsTable" data-cookie-expire="2y" data-click-to-select="true" - data-remember-order="true" + data-remember-order="false" data-show-columns="true" data-show-export="true" data-uncheckAll="true" data-toolbar="#toolbar" + data-sort-name="sort_field() ?>" + data-sort-order="sort_asc() ? 'asc' : 'desc' ?>" + data-server-sort="true" data-show-fullscreen="true" data-click-to-select="true" data-maintain-meta-data="true" From 1e38cf8bb7721b0cce7e3c323dd7b81a732eb949 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 25 Oct 2021 16:40:21 -0400 Subject: [PATCH 15/30] Fix monitor type labels by adding an Unknown for entry 0. Implement Function_Strings. Fix decoding_enabled not being recalculated correctly because we havn't loaded savejpegs or videowriter yet. --- src/zm_monitor.cpp | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index e85330eb2..93ae2d026 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -86,6 +86,7 @@ std::string load_monitor_sql = "`SignalCheckPoints`, `SignalCheckColour`, `Importance`-1 FROM `Monitors`"; std::string CameraType_Strings[] = { + "Unknown", "Local", "Remote", "File", @@ -93,10 +94,21 @@ std::string CameraType_Strings[] = { "LibVLC", "NVSOCKET", "CURL", - "VNC", + "VNC" +}; + +std::string Function_Strings[] = { + "Unknown", + "None", + "Monitor", + "Modect", + "Record", + "Mocord", + "Nodect" }; std::string State_Strings[] = { + "Unknown", "IDLE", "PREALARM", "ALARM", @@ -474,16 +486,7 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) { function = (Function)atoi(dbrow[col]); col++; enabled = dbrow[col] ? atoi(dbrow[col]) : false; col++; decoding_enabled = dbrow[col] ? atoi(dbrow[col]) : false; col++; - decoding_enabled = !( - ( function == RECORD or function == NODECT ) - and - ( savejpegs == 0 ) - and - ( videowriter == PASSTHROUGH ) - and - !decoding_enabled - ); - Debug(1, "Decoding enabled: %d", decoding_enabled); + // See below after save_jpegs for a recalculation of decoding_enabled ReloadLinkedMonitors(dbrow[col]); col++; @@ -552,6 +555,17 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) { videowriter = (VideoWriter)atoi(dbrow[col]); col++; encoderparams = dbrow[col] ? dbrow[col] : ""; col++; + decoding_enabled = !( + ( function == RECORD or function == NODECT ) + and + ( savejpegs == 0 ) + and + ( videowriter == PASSTHROUGH ) + and + !decoding_enabled + ); + Debug(3, "Decoding enabled: %d function %d %s savejpegs %d videowriter %d", decoding_enabled, function, Function_Strings[function].c_str(), savejpegs, videowriter); + /*"`OutputCodec`, `Encoder`, `OutputContainer`, " */ output_codec = dbrow[col] ? atoi(dbrow[col]) : 0; col++; encoder = dbrow[col] ? dbrow[col] : ""; col++; From a55bd23bf65a1036c3770155513d3e7e2071dc0a Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Sun, 24 Oct 2021 15:51:58 -0400 Subject: [PATCH 16/30] Add some extra parenthesis to make sure the logic works right --- web/skins/classic/views/js/watch.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/skins/classic/views/js/watch.js b/web/skins/classic/views/js/watch.js index 8c8ea480d..aacad5126 100644 --- a/web/skins/classic/views/js/watch.js +++ b/web/skins/classic/views/js/watch.js @@ -945,7 +945,7 @@ function initPage() { }); // Only enable the settings button for local cameras - settingsBtn.prop('disabled', !(canView.Control && monitorType == 'Local')); + settingsBtn.prop('disabled', !(canView.Control && (monitorType == 'Local'))); // Init the bootstrap-table if (monitorType != 'WebSite') table.bootstrapTable({icons: icons}); From d56de57494e7e64dd49b4d80d460d46c6250d223 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 25 Oct 2021 16:55:20 -0400 Subject: [PATCH 17/30] set vertical-align:top on monitor edit labels --- web/skins/classic/css/base/views/monitor.css | 1 + 1 file changed, 1 insertion(+) diff --git a/web/skins/classic/css/base/views/monitor.css b/web/skins/classic/css/base/views/monitor.css index 7fca80aac..f827af5ca 100644 --- a/web/skins/classic/css/base/views/monitor.css +++ b/web/skins/classic/css/base/views/monitor.css @@ -50,6 +50,7 @@ select.chosen { } tr td:first-child { min-width: 300px; + vertical-align: top; } .OutputContainer { display: none; From cebe86feb249c4397d87ebe04f21d932e7a87144 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 26 Oct 2021 18:10:09 -0400 Subject: [PATCH 18/30] Make ZoneMinder::Control ingerit from Object. Make loadMonitor use MonitorId --- scripts/ZoneMinder/lib/ZoneMinder/Control.pm | 133 ++++++++++++++++--- 1 file changed, 113 insertions(+), 20 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control.pm index feb12a0ca..e4d052700 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control.pm @@ -29,6 +29,7 @@ use strict; use warnings; require ZoneMinder::Base; +require ZoneMinder::Object; require ZoneMinder::Monitor; our $VERSION = $ZoneMinder::Base::VERSION; @@ -42,24 +43,116 @@ our $VERSION = $ZoneMinder::Base::VERSION; use ZoneMinder::Logger qw(:all); use ZoneMinder::Database qw(:all); +use parent qw(ZoneMinder::Object); + +use vars qw/ $table $primary_key %fields $serial %defaults $debug/; +$table = 'Controls'; +$serial = $primary_key = 'Id'; +%fields = map { $_ => $_ } qw( + Id + Name + Type + Protocol + CanWake + CanSleep + CanReset + CanReboot + CanZoom + CanAutoZoom + CanZoomAbs + CanZoomRel + CanZoomCon + MinZoomRange + MaxZoomRange + MinZoomStep + MaxZoomStep + HasZoomSpeed + MinZoomSpeed + MaxZoomSpeed + CanFocus + CanAutoFocus + CanFocusAbs + CanFocusRel + CanFocusCon + MinFocusRange + MaxFocusRange + MinFocusStep + MaxFocusStep + HasFocusSpeed + MinFocusSpeed + MaxFocusSpeed + CanIris + CanAutoIris + CanIrisAbs + CanIrisRel + CanIrisCon + MinIrisRange + MaxIrisRange + MinIrisStep + MaxIrisStep + HasIrisSpeed + MinIrisSpeed + MaxIrisSpeed + CanGain + CanAutoGain + CanGainAbs + CanGainRel + CanGainCon + MinGainRange + MaxGainRange + MinGainStep + MaxGainStep + HasGainSpeed + MinGainSpeed + MaxGainSpeed + CanWhite + CanAutoWhite + CanWhiteAbs + CanWhiteRel + CanWhiteCon + MinWhiteRange + MaxWhiteRange + MinWhiteStep + MaxWhiteStep + HasWhiteSpeed + MinWhiteSpeed + MaxWhiteSpeed + HasPresets + NumPresets + HasHomePreset + CanSetPresets + CanMove + CanMoveDiag + CanMoveMap + CanMoveAbs + CanMoveRel + CanMoveCon + CanPan + MinPanRange + MaxPanRange + MinPanStep + MaxPanStep + HasPanSpeed + MinPanSpeed + MaxPanSpeed + HasTurboPan + TurboPanSpeed + CanTilt + MinTiltRange + MaxTiltRange + MinTiltStep + MaxTiltStep + HasTiltSpeed + MinTiltSpeed + MaxTiltSpeed + HasTurboTilt + TurboTiltSpeed + CanAutoScan + NumScanPaths + ); + our $AUTOLOAD; -sub new { - my $class = shift; - my $id = shift; - if ( !defined($id) ) { - Fatal('No monitor defined when invoking protocol '.$class); - } - my $self = {}; - $self->{name} = $class; - $self->{id} = $id; - bless($self, $class); - return $self; -} - -sub DESTROY { -} - sub AUTOLOAD { my $self = shift; my $class = ref($self); @@ -79,24 +172,24 @@ sub AUTOLOAD { sub getKey { my $self = shift; - return $self->{id}; + return $self->{Id}; } sub open { my $self = shift; - Fatal('No open method defined for protocol '.$self->{name}); + Fatal('No open method defined for protocol '.$self->{Protocol}); } sub close { my $self = shift; $self->{state} = 'closed'; - Debug('No close method defined for protocol '.$self->{name}); + Debug('No close method defined for protocol '.$self->{Protocol}); } sub loadMonitor { my $self = shift; if ( !$self->{Monitor} ) { - if ( !($self->{Monitor} = ZoneMinder::Monitor->find_one(Id=>$self->{id})) ) { + if ( !($self->{Monitor} = ZoneMinder::Monitor->find_one(Id=>$self->{MonitorId})) ) { Fatal('Monitor id '.$self->{id}.' not found'); } if ( defined($self->{Monitor}->{AutoStopTimeout}) ) { From c864d0765b0c32a0b30f5a8a6d7dfccf40b7d4fd Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 26 Oct 2021 18:10:39 -0400 Subject: [PATCH 19/30] Update, add url parsing to match other control scripts. Add getConfig and setConfig --- .../lib/ZoneMinder/Control/Vivotek_ePTZ.pm | 185 ++++++++++-------- 1 file changed, 99 insertions(+), 86 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/Vivotek_ePTZ.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/Vivotek_ePTZ.pm index 58ebe4c63..bcf1905c5 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/Vivotek_ePTZ.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/Vivotek_ePTZ.pm @@ -41,120 +41,133 @@ our @ISA = qw(ZoneMinder::Control); use ZoneMinder::Logger qw(:all); use ZoneMinder::Config qw(:all); +use ZoneMinder::General qw(:all); use Time::HiRes qw( usleep ); +use URI::Encode qw(uri_encode); -sub open -{ - my $self = shift; +our $REALM = ''; +our $PROTOCOL = 'http://'; +our $USERNAME = 'admin'; +our $PASSWORD = ''; +our $ADDRESS = ''; +our $BASE_URL = ''; - $self->loadMonitor(); - Debug( "Camera open" ); - use LWP::UserAgent; - $self->{ua} = LWP::UserAgent->new; - $self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION ); +sub open { + my $self = shift; + $self->loadMonitor(); - $self->{state} = 'open'; + if (($self->{Monitor}->{ControlAddress} =~ /^(?https?:\/\/)?(?[^:@]+)?:?(?[^\/@]+)?@?(?
.*)$/)) { + $PROTOCOL = $+{PROTOCOL} if $+{PROTOCOL}; + $USERNAME = $+{USERNAME} if $+{USERNAME}; + $PASSWORD = $+{PASSWORD} if $+{PASSWORD}; + $ADDRESS = $+{ADDRESS} if $+{ADDRESS}; + } else { + Error('Failed to parse auth from address ' . $self->{Monitor}->{ControlAddress}); + $ADDRESS = $self->{Monitor}->{ControlAddress}; + } + if ( !($ADDRESS =~ /:/) ) { + Error('You generally need to also specify the port. I will append :80'); + $ADDRESS .= ':80'; + } + $BASE_URL = $PROTOCOL.($USERNAME?$USERNAME.':'.$PASSWORD.'@':'').$ADDRESS; + + use LWP::UserAgent; + $self->{ua} = LWP::UserAgent->new; + $self->{ua}->agent( 'ZoneMinder Control Agent/'.ZoneMinder::Base::ZM_VERSION ); + $self->{state} = 'open'; } -sub close -{ - my $self = shift; - $self->{state} = 'closed'; +sub close { + my $self = shift; + $self->{state} = 'closed'; } -sub printMsg -{ - my $msg = shift; - my $msg_len = length($msg); +sub sendCmd { + my ($self, $cmd, $speedcmd) = @_; - Debug( $msg."[".$msg_len."]" ); + $self->printMsg( $speedcmd, 'Tx' ); + $self->printMsg( $cmd, 'Tx' ); + + my $req = HTTP::Request->new( GET => $BASE_URL."/cgi-bin/camctrl/eCamCtrl.cgi?stream=0&$speedcmd&$cmd"); + my $res = $self->{ua}->request($req); + + if (!$res->is_success) { + Error('Request failed: '.$res->status_line().' (URI: '.$req->as_string().')'); + } + return $res->is_success; } -sub sendCmd -{ - my ($self, $cmd, $speedcmd) = @_; - - my $result = undef; - - printMsg( $speedcmd, "Tx" ); - printMsg( $cmd, "Tx" ); - - my $req = HTTP::Request->new( GET => "http://" . $self->{Monitor}->{ControlAddress} . "/cgi-bin/camctrl/eCamCtrl.cgi?stream=0&$speedcmd&$cmd" ); - my $res = $self->{ua}->request($req); - - if ( $res->is_success ) - { - $result = !undef; - } - else - { - Error( "Request failed: '" . $res->status_line() . "' (URI: '" . $req->as_string() . "')" ); - } - - return( $result ); +sub moveConUp { + my ($self, $params) = @_; + my $speed = 'speedtilt=' . ($params->{tiltspeed} - 6); + $self->sendCmd( 'move=up', $speed ); } -sub moveConUp -{ - my ($self, $params) = @_; - my $speed = 'speedtilt=' . ($params->{tiltspeed} - 6); - Debug( "Move Up" ); - $self->sendCmd( 'move=up', $speed ); +sub moveConDown { + my ($self, $params) = @_; + my $speed = 'speedtilt=' . ($params->{tiltspeed} - 6); + $self->sendCmd( 'move=down', $speed ); } -sub moveConDown -{ - my ($self, $params) = @_; - my $speed = 'speedtilt=' . ($params->{tiltspeed} - 6); - Debug( "Move Down" ); - $self->sendCmd( 'move=down', $speed ); +sub moveConLeft { + my ($self, $params) = @_; + my $speed = 'speedpan=-' . $params->{panspeed}; + $self->sendCmd( 'move=left', $speed ); } -sub moveConLeft -{ - my ($self, $params) = @_; - my $speed = 'speedpan=-' . $params->{panspeed}; - Debug( "Move Left" ); - $self->sendCmd( 'move=left', $speed ); +sub moveConRight { + my ($self, $params) = @_; + my $speed = 'speedpan=' . ($params->{panspeed} - 6); + $self->sendCmd( 'move=right', $speed ); } -sub moveConRight -{ - my ($self, $params) = @_; - my $speed = 'speedpan=' . ($params->{panspeed} - 6); - Debug( "Move Right" ); - $self->sendCmd( 'move=right', $speed ); +sub moveStop { + my $self = shift; + Debug( "Move Stop: not implemented" ); + # not implemented } -sub moveStop -{ - my $self = shift; - Debug( "Move Stop" ); - # not implemented +sub zoomConTele { + my ($self, $params) = @_; + my $speed = 'speedzoom=' . ($params->{speed} - 6); + $self->sendCmd( 'zoom=tele', $speed ); } -sub zoomConTele -{ - my ($self, $params) = @_; - my $speed = 'speedzoom=' . ($params->{speed} - 6); - Debug( "Zoom In" ); - $self->sendCmd( 'zoom=tele', $speed ); +sub zoomConWide { + my ($self, $params) = @_; + my $speed = 'speedzoom=' . ($params->{speed} - 6); + $self->sendCmd( 'zoom=wide', $speed ); } -sub zoomConWide -{ - my ($self, $params) = @_; - my $speed = 'speedzoom=' . ($params->{speed} - 6); - Debug( "Zoom Out" ); - $self->sendCmd( 'zoom=wide', $speed ); +sub reset { + my $self = shift; + $self->sendCmd( 'move=home' ); } -sub reset -{ - my $self = shift; - Debug( "Camera Reset" ); - $self->sendCmd( 'move=home' ); +sub get_config { + my $self = shift; + + my $url = $BASE_URL.'/cgi-bin/admin/lsctrl.cgi?cmd=queryStatus&retType=javascript'; + my $req = new HTTP::Request(GET => $url); + my $response = $self->{ua}->request($req); + if ( $response->is_success() ) { + my $resp = $response->decoded_content; + return ZoneMinder::General::parseNameEqualsValueToHash($resp); + } + Warn("Failed to get config from $url: " . $response->status_line()); + return; +} # end sub get_config + +sub set_config { + my $self = shift; + my $diff = shift; + + my $url = $BASE_URL.'/cgi-bin/'.$USERNAME.'/setparam.cgi?'. + join('&', map { $_.'='.uri_encode($$diff{$_}) } keys %$diff); + my $response = $self->{ua}->get($url); + Debug($response->content); + return $response->is_success(); } 1; From d62d88a0a3aaa639b551be50a91a292a45646124 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 26 Oct 2021 18:11:34 -0400 Subject: [PATCH 20/30] add utility functions parseNameEqualsValueToHash and hash_diff --- scripts/ZoneMinder/lib/ZoneMinder/General.pm | 40 ++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/General.pm b/scripts/ZoneMinder/lib/ZoneMinder/General.pm index d68967fa9..b14b08aae 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/General.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/General.pm @@ -31,6 +31,8 @@ our %EXPORT_TAGS = ( systemStatus packageControl daemonControl + parseNameEqualsValueToHash + hash_diff ) ] ); push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; @@ -534,6 +536,42 @@ sub jsonDecode { return $result; } +sub parseNameEqualsValueToHash { + my %settings; + foreach my $line ( split ( /\r?\n/, $_[0] ) ) { + next if ! $line; + next if ! ( $line =~ /=/ ); + my ($name, $value ) = split('=', $line); + $value =~ s/^'//; + $value =~ s/'$//; + $settings{$name} = defined $value ? $value : ''; + } + return %settings; +} + +sub hash_diff { + # assumes keys of second hash are all in the first hash + my ( $settings, $defaults ) = @_; + my %updates; + + foreach my $setting ( keys %{$settings} ) { + next if ! exists $$defaults{$setting}; + if ( + ($$settings{$setting} and ! $$defaults{$setting}) + or + (!$$settings{$setting} and $$defaults{$setting}) + or + ( + ($$settings{$setting} and $$defaults{$setting} and ( + $$settings{$setting} ne $$defaults{$setting})) + ) + ) { + $updates{$setting} = $$defaults{$setting}; + } + } # end foreach setting + return %updates; +} + sub packageControl { my $command = shift; my $string = $Config{ZM_PATH_BIN}.'/zmpkg.pl '.$command; @@ -598,6 +636,8 @@ of the ZoneMinder scripts packageControl daemonControl systemStatus + parseNameEqualsValueToHash + hash_diff ) ] From a2ec18be5ae44a3ec1d83b270133ff95e07bae97 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 26 Oct 2021 18:15:03 -0400 Subject: [PATCH 21/30] Implement Control() function to implement the Protocol Specific Control object --- scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm b/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm index d01ef3455..899426164 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm @@ -36,6 +36,7 @@ require ZoneMinder::Server; require ZoneMinder::Memory; require ZoneMinder::Monitor_Status; require ZoneMinder::Zone; +use ZoneMinder::Logger qw(:all); #our @ISA = qw(Exporter ZoneMinder::Base); use parent qw(ZoneMinder::Object); @@ -313,6 +314,25 @@ sub resumeMotionDetection { return 1; } +sub Control { + my $self = shift; + if ( ! exists $$self{Control}) { + require ZoneMinder::Control; + my $Control = ZoneMinder::Control->find_one(Id=>$$self{ControlId}); + if ($Control) { + 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"); + return undef; + } + bless $Control, 'ZoneMinder::Control::'.$$Control{Protocol}; + $$Control{MonitorId} = $$self{Id}; + $$self{Control} = $Control; + } + } + return $$self{Control}; +} + 1; __END__ From a74b785d08f1333e64cd0b100b67bf241fc13fbd Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 26 Oct 2021 18:15:22 -0400 Subject: [PATCH 22/30] Move the protocol loading into the Monitor Object. --- scripts/zmcontrol.pl.in | 33 ++++++--------------------------- 1 file changed, 6 insertions(+), 27 deletions(-) diff --git a/scripts/zmcontrol.pl.in b/scripts/zmcontrol.pl.in index 8a93a19a4..a9f26ac80 100644 --- a/scripts/zmcontrol.pl.in +++ b/scripts/zmcontrol.pl.in @@ -30,7 +30,6 @@ use autouse 'Pod::Usage'=>qw(pod2usage); use POSIX qw/strftime EPIPE EINTR/; use Socket; use Data::Dumper; -use Module::Load::Conditional qw{can_load}; use constant MAX_CONNECT_DELAY => 15; use constant MAX_COMMAND_WAIT => 1800; @@ -102,40 +101,21 @@ if ($options{command}) { } } else { # The server isn't there - my $monitor = zmDbGetMonitorAndControl($id); + require ZoneMinder::Monitor; + + my $monitor = ZoneMinder::Monitor->find_one(Id=>$id); Fatal("Unable to load control data for monitor $id") if !$monitor; - my $protocol = $monitor->{Protocol}; + my $control = $monitor->Control(); + + my $protocol = $control->{Protocol}; if (!$protocol) { Fatal('No protocol is set in monitor. Please edit the monitor, edit control type, select the control capability and fill in the Protocol field'); } - if (-x $protocol) { - # Protocol is actually a script! - # Holdover from previous versions - my $command .= $protocol.' '.$arg_string; - Debug($command); - - my $output = qx($command); - my $status = $? >> 8; - if ($status || logDebugging()) { - chomp($output); - Debug("Output: $output"); - } - if ($status) { - Error("Command '$command' exited with status: $status"); - exit($status); - } - exit(0); - } - Info("Starting control server $id/$protocol"); close(CLIENT); - if (!can_load(modules => {'ZoneMinder::Control::'.$protocol => undef})) { - Fatal("Can't load ZoneMinder::Control::$protocol\n$Module::Load::Conditional::ERROR"); - } - my $zm_terminate = 0; sub TermHandler { Info('Received TERM, exiting'); @@ -150,7 +130,6 @@ if ($options{command}) { $0 = $0.' --id '.$id; - my $control = ('ZoneMinder::Control::'.$protocol)->new($id); my $control_key = $control->getKey(); $control->loadMonitor(); From 6478999ecf4e936c3e614727b119205019288705 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 27 Oct 2021 12:21:03 -0400 Subject: [PATCH 23/30] Fix Event count subsitutions in emails because they are no longer in Monitor_Status. They are in Event_Summaries. So create a new object for them and use it. --- .../lib/ZoneMinder/Event_Summary.pm | 99 +++++++++++++++++++ scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm | 10 ++ .../lib/ZoneMinder/Monitor_Status.pm | 24 ----- scripts/zmfilter.pl.in | 18 ++-- 4 files changed, 118 insertions(+), 33 deletions(-) create mode 100644 scripts/ZoneMinder/lib/ZoneMinder/Event_Summary.pm diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Event_Summary.pm b/scripts/ZoneMinder/lib/ZoneMinder/Event_Summary.pm new file mode 100644 index 000000000..0086b5bac --- /dev/null +++ b/scripts/ZoneMinder/lib/ZoneMinder/Event_Summary.pm @@ -0,0 +1,99 @@ +# ========================================================================== +# +# ZoneMinder Event_Summary Module +# Copyright (C) 2020 ZoneMinder +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# ========================================================================== +# +# This module contains the common definitions and functions used by the rest +# of the ZoneMinder scripts +# +package ZoneMinder::Event_Summary; + +use 5.006; +use strict; +use warnings; + +require ZoneMinder::Base; +require ZoneMinder::Object; + +#our @ISA = qw(Exporter ZoneMinder::Base); +use parent qw(ZoneMinder::Object); + +use vars qw/ $table $primary_key %fields $serial %defaults $debug/; +$table = 'Event_Summaries'; +$serial = $primary_key = 'MonitorId'; +%fields = map { $_ => $_ } qw( + MonitorId + TotalEvents + TotalEventDiskSpace + HourEvents + HourEventDiskSpace + DayEvents + DayEventDiskSpace + WeekEvents + WeekEventDiskSpace + MonthEvents + MonthEventDiskSpace + ArchivedEvents + ArchivedEventDiskSpace + ); + +%defaults = ( + TotalEvents => undef, + TotalEventDiskSpace => undef, + HourEvents => undef, + HourEventDiskSpace => undef, + DayEvents => undef, + DayEventDiskSpace => undef, + WeekEvents => undef, + WeekEventDiskSpace => undef, + MonthEvents => undef, + MonthEventDiskSpace => undef, + ArchivedEvents => undef, + ArchivedEventDiskSpace => undef, + ); + +sub Monitor { + return new ZoneMinder::Monitor( $_[0]{MonitorId} ); +} # end sub Monitor + +1; +__END__ + +=head1 NAME + +ZoneMinder::Event_Summary - Perl Class for Event Summaries + +=head1 SYNOPSIS + +use ZoneMinder::Event_Summary; + +=head1 AUTHOR + +Isaac Connor, Eisaac@zoneminder.comE + +=head1 COPYRIGHT AND LICENSE + +Copyright (C) 2001-2017 ZoneMinder LLC + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself, either Perl version 5.8.3 or, +at your option, any later version of Perl 5 you may have available. + + +=cut diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm b/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm index 899426164..c5e09c137 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm @@ -35,6 +35,7 @@ require ZoneMinder::Storage; require ZoneMinder::Server; require ZoneMinder::Memory; require ZoneMinder::Monitor_Status; +require ZoneMinder::Event_Summary; require ZoneMinder::Zone; use ZoneMinder::Logger qw(:all); @@ -267,6 +268,15 @@ sub Status { return $$self{Status}; } +sub Event_Summary { + my $self = shift; + $$self{Event_Summary} = shift if @_; + if ( ! $$self{Event_Summary} ) { + $$self{Event_Summary} = ZoneMinder::Event_Summary->find_one(MonitorId=>$$self{Id}); + } + return $$self{Event_Summary}; +} + sub connect { my $self = shift; return ZoneMinder::Memory::zmMemVerify($self); diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Monitor_Status.pm b/scripts/ZoneMinder/lib/ZoneMinder/Monitor_Status.pm index 9a9077653..1fafd3b0b 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Monitor_Status.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Monitor_Status.pm @@ -43,18 +43,6 @@ $serial = $primary_key = 'MonitorId'; CaptureFPS AnalysisFPS CaptureBandwidth - TotalEvents - TotalEventDiskSpace - HourEvents - HourEventDiskSpace - DayEvents - DayEventDiskSpace - WeekEvents - WeekEventDiskSpace - MonthEvents - MonthEventDiskSpace - ArchivedEvents - ArchivedEventDiskSpace ); %defaults = ( @@ -62,18 +50,6 @@ $serial = $primary_key = 'MonitorId'; CaptureFPS => undef, AnalysisFPS => undef, CaptureBandwidth => undef, - TotalEvents => undef, - TotalEventDiskSpace => undef, - HourEvents => undef, - HourEventDiskSpace => undef, - DayEvents => undef, - DayEventDiskSpace => undef, - WeekEvents => undef, - WeekEventDiskSpace => undef, - MonthEvents => undef, - MonthEventDiskSpace => undef, - ArchivedEvents => undef, - ArchivedEventDiskSpace => undef, ); sub Monitor { diff --git a/scripts/zmfilter.pl.in b/scripts/zmfilter.pl.in index 1e62cb229..61922771b 100644 --- a/scripts/zmfilter.pl.in +++ b/scripts/zmfilter.pl.in @@ -665,10 +665,10 @@ sub substituteTags { # We have a filter and an event, do we need any more # monitor information? my $need_monitor = $text =~ /%(?:MN|MET|MEH|MED|MEW|MEN|MEA)%/; - my $need_status = $text =~ /%(?:MET|MEH|MED|MEW|MEN|MEA)%/; + my $need_summary = $text =~ /%(?:MET|MEH|MED|MEW|MEN|MEA)%/; my $Monitor = $Event->Monitor() if $need_monitor; - my $Status = $Monitor->Status() if $need_status; + my $Summary = $Monitor->Event_Summary() if $need_summary; # Do we need the image information too? my $need_images = $text =~ /%(?:EPI1|EPIM|EI1|EIM|EI1A|EIMA|EIMOD|EIMODG)%/; @@ -692,19 +692,19 @@ sub substituteTags { } $rows ++; } - Debug("Frames: rows: $rows first alarm frame: $first_alarm_frame max_alaarm_frame: $max_alarm_frame, score: $max_alarm_score"); + Debug("Frames: rows: $rows first alarm frame: $first_alarm_frame max_alarm_frame: $max_alarm_frame, score: $max_alarm_score"); $sth->finish(); } my $url = $Config{ZM_URL}; $text =~ s/%ZP%/$url/g; $text =~ s/%MN%/$Monitor->{Name}/g; - $text =~ s/%MET%/$Status->{TotalEvents}/g; - $text =~ s/%MEH%/$Status->{HourEvents}/g; - $text =~ s/%MED%/$Status->{DayEvents}/g; - $text =~ s/%MEW%/$Status->{WeekEvents}/g; - $text =~ s/%MEM%/$Status->{MonthEvents}/g; - $text =~ s/%MEA%/$Status->{ArchivedEvents}/g; + $text =~ s/%MET%/$Summary->{TotalEvents}/g; + $text =~ s/%MEH%/$Summary->{HourEvents}/g; + $text =~ s/%MED%/$Summary->{DayEvents}/g; + $text =~ s/%MEW%/$Summary->{WeekEvents}/g; + $text =~ s/%MEM%/$Summary->{MonthEvents}/g; + $text =~ s/%MEA%/$Summary->{ArchivedEvents}/g; $text =~ s/%MP%/$url?view=watch&mid=$Event->{MonitorId}/g; $text =~ s/%MPS%/$url?view=watch&mid=$Event->{MonitorId}&mode=stream/g; $text =~ s/%MPI%/$url?view=watch&mid=$Event->{MonitorId}&mode=still/g; From 2bcad7bf5e7cb60b74037c837a6c856acc2bf93e Mon Sep 17 00:00:00 2001 From: SzymekCRX <59235537+SzymekCRX@users.noreply.github.com> Date: Thu, 28 Oct 2021 07:43:44 +0200 Subject: [PATCH 24/30] Critical bug in events.php Two extra brackets causes fatal error in Ajax request causing 500 HTTP error and problems with listing / deleting events --- web/ajax/events.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/ajax/events.php b/web/ajax/events.php index 471f32664..6d87da869 100644 --- a/web/ajax/events.php +++ b/web/ajax/events.php @@ -43,9 +43,9 @@ $advsearch = isset($_REQUEST['advsearch']) ? json_decode($_REQUEST['advsearch'], // Order specifies the sort direction, either asc or desc $order = $filter->sort_asc() ? 'ASC' : 'DESC'; if (isset($_REQUEST['order'])) { - if (strtolower($_REQUEST['order']) == 'asc')) { + if (strtolower($_REQUEST['order']) == 'asc') { $order = 'ASC'; - } else if (strtolower($_REQUEST['order']) == 'desc')) { + } else if (strtolower($_REQUEST['order']) == 'desc') { $order = 'DESC'; } else { Warning("Invalid value for order " . $_REQUEST['order']); From 5955e4b410601571d422f420bc3cfffdb91972c0 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 28 Oct 2021 10:20:00 -0400 Subject: [PATCH 25/30] add licensing info for fontawesome. --- web/fonts/license.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/web/fonts/license.md b/web/fonts/license.md index 197f20b13..b56a5654f 100644 --- a/web/fonts/license.md +++ b/web/fonts/license.md @@ -1,6 +1,12 @@ ZoneMinder uses certain 3rd party media assets/libraries for UI display purposes. Their licenses are listed in this file +### Font Awesome icons + +Origin: http://fontawesome.io + +License: Font: SIL OFL 1.1, CSS: MIT License (http://fontawesome.io/license) + ### Material Design icons Origin: https://github.com/google/material-design-icons From 9fe2762bc898212376de551db3b51551d0da1f86 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 28 Oct 2021 10:43:24 -0400 Subject: [PATCH 26/30] Add mp4 as an option for generated video and make it the default instead of avi --- scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in index ff802dd15..48d494614 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in @@ -1064,7 +1064,7 @@ our @options = ( }, { name => 'ZM_FFMPEG_FORMATS', - default => 'mpg mpeg wmv asf avi* mov swf 3gp**', + default => 'mp4* mpg mpeg wmv asf avi mov swf 3gp**', description => 'Formats to allow for ffmpeg video generation', help => q` Ffmpeg can generate video in many different formats. This From c2bd2dc129dd8d12aacf24b65b401981376e4c65 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 28 Oct 2021 10:44:46 -0400 Subject: [PATCH 27/30] Add a warning if db queue is larger than 10 --- src/zm_db.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/zm_db.cpp b/src/zm_db.cpp index c6cc452b9..f659bb361 100644 --- a/src/zm_db.cpp +++ b/src/zm_db.cpp @@ -251,6 +251,13 @@ void zmDbQueue::process() { mCondition.wait(lock); } while (!mQueue.empty()) { + if (mQueue.size() > 10) { + Logger *log = Logger::fetch(); + Logger::Level db_level = log->databaseLevel(); + log->databaseLevel(Logger::NOLOG); + Warning("db queue size has grown larger than 10 entries"); + log->databaseLevel(db_level); + } std::string sql = mQueue.front(); mQueue.pop(); // My idea for leaving the locking around each sql statement is to allow From 2a160f045d35e238f34e4f9f05bfc1b02e4e9142 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 28 Oct 2021 10:46:07 -0400 Subject: [PATCH 28/30] Put back generate video button --- web/skins/classic/views/event.php | 1 + 1 file changed, 1 insertion(+) diff --git a/web/skins/classic/views/event.php b/web/skins/classic/views/event.php index a07c95042..81d4e97be 100644 --- a/web/skins/classic/views/event.php +++ b/web/skins/classic/views/event.php @@ -153,6 +153,7 @@ if ( $Event->Id() and !file_exists($Event->Path()) ) download DefaultVideo() ? '' : 'style="display:none;"' ?> > + From 0c157904e6e44fdd43dee5604dde74e1e5b1d5e6 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 28 Oct 2021 10:46:26 -0400 Subject: [PATCH 29/30] add js to manage the generate video button --- web/skins/classic/views/js/event.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/web/skins/classic/views/js/event.js b/web/skins/classic/views/js/event.js index 0cd770170..050a78d59 100644 --- a/web/skins/classic/views/js/event.js +++ b/web/skins/classic/views/js/event.js @@ -708,7 +708,7 @@ function renameEvent() { } function exportEvent() { - window.location.assign('?view=export&eid='+eventData.Id); + window.location.assign('?view=export&eids[]='+eventData.Id); } function showEventFrames() { @@ -1016,7 +1016,13 @@ function initPage() { // Manage the EXPORT button bindButton('#exportBtn', 'click', null, function onExportClick(evt) { evt.preventDefault(); - window.location.assign('?view=export&eids[]='+eventData.Id); + exportEvent(); + }); + + // Manage the generateVideo button + bindButton('#videoBtn', 'click', null, function onExportClick(evt) { + evt.preventDefault(); + videoEvent(); }); // Manage the Event STATISTICS Button From fee69d3bc2e20585c47320cf17b5c63fb9ec7824 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 28 Oct 2021 10:46:42 -0400 Subject: [PATCH 30/30] is no longer in existence --- 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 a4c01273f..597897936 100644 --- a/web/skins/classic/views/video.php +++ b/web/skins/classic/views/video.php @@ -102,7 +102,7 @@ $focusWindow = true; xhtmlHeaders(__FILE__, translate('Video')); ?> - +