From cd6c44db47b7df91f889c54303d8858e32ac61e2 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 11 Jan 2022 17:01:04 -0500 Subject: [PATCH 01/12] Revert "Remove sleeping from Analysis thread. It is uneccessary and results in us starting off way behind the capture thread. It also limits us to 30fps." This reverts commit 6c4156a4830da04d6128a9159c32c3bb0be607b1. --- src/zm_analysis_thread.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/zm_analysis_thread.cpp b/src/zm_analysis_thread.cpp index 325d8de59..7d803c16b 100644 --- a/src/zm_analysis_thread.cpp +++ b/src/zm_analysis_thread.cpp @@ -23,6 +23,14 @@ void AnalysisThread::Start() { void AnalysisThread::Run() { while (!(terminate_ or zm_terminate)) { - monitor_->Analyse(); + // Some periodic updates are required for variable capturing framerate + Debug(2, "Analyzing"); + if (!monitor_->Analyse()) { + if (!(terminate_ or zm_terminate)) { + Microseconds sleep_for = monitor_->Active() ? Microseconds(ZM_SAMPLE_RATE) : Microseconds(ZM_SUSPENDED_RATE); + Debug(2, "Sleeping for %" PRId64 "us", int64(sleep_for.count())); + std::this_thread::sleep_for(sleep_for); + } + } } } From ff91ac62fbb35c719fcc3493f16da5cc1c430c03 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 11 Jan 2022 17:02:05 -0500 Subject: [PATCH 02/12] add comment documenting why we sleep --- src/zm_analysis_thread.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zm_analysis_thread.cpp b/src/zm_analysis_thread.cpp index 7d803c16b..a6f1afcc2 100644 --- a/src/zm_analysis_thread.cpp +++ b/src/zm_analysis_thread.cpp @@ -24,9 +24,9 @@ void AnalysisThread::Start() { void AnalysisThread::Run() { while (!(terminate_ or zm_terminate)) { // Some periodic updates are required for variable capturing framerate - Debug(2, "Analyzing"); if (!monitor_->Analyse()) { if (!(terminate_ or zm_terminate)) { + // We only sleep when Analyse returns false because it is an error condition and we will spin like mad if it persists. Microseconds sleep_for = monitor_->Active() ? Microseconds(ZM_SAMPLE_RATE) : Microseconds(ZM_SUSPENDED_RATE); Debug(2, "Sleeping for %" PRId64 "us", int64(sleep_for.count())); std::this_thread::sleep_for(sleep_for); From c27b0aff977d8e410a8c96b02c954bbbd84a8ce3 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 11 Jan 2022 22:11:28 -0500 Subject: [PATCH 03/12] Move analysis image assignment up to where motion detection is done do remove duplication and simplify logic. Free analysis images as well as captured images after analysis. remove extra copy of image to analysis_image. Replae a packetqueue.unlock with just deleting the locked packet. No one waits for analysis. Re-introduce sleeping in the analysis thread on error to prevent spinning --- src/zm_monitor.cpp | 70 ++++++++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 40 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 4feea00fe..77607d716 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -1849,12 +1849,10 @@ bool Monitor::Analyse() { // Need to guard around event creation/deletion from Reload() std::lock_guard lck(event_mutex); - Debug(3, "Have event lock"); // if we have been told to be OFF, then we are off and don't do any processing. if (trigger_data->trigger_state != TriggerState::TRIGGER_OFF) { Debug(4, "Trigger not OFF state is (%d)", int(trigger_data->trigger_state)); - int score = 0; // Ready means that we have captured the warmup # of frames if (!Ready()) { Debug(3, "Not ready?"); @@ -1862,6 +1860,7 @@ bool Monitor::Analyse() { return false; } + int score = 0; std::string cause; Event::StringSetMap noteSetMap; @@ -1886,6 +1885,8 @@ bool Monitor::Analyse() { score += trigger_data->trigger_score; Debug(1, "Triggered on score += %d => %d", trigger_data->trigger_score, score); if (!event) { + if (!cause.empty()) + cause += ", "; cause += trigger_data->trigger_cause; } Event::StringSet noteSet; @@ -1904,7 +1905,7 @@ bool Monitor::Analyse() { } } else if (function == MOCORD or function == RECORD) { if (!event) { - if (cause.length()) cause += ", "; + if (!cause.empty()) cause += ", "; cause += SIGNAL_CAUSE + std::string(": Reacquired"); } else { event->addNote(SIGNAL_CAUSE, "Reacquired"); @@ -1963,7 +1964,7 @@ bool Monitor::Analyse() { packetqueue.unlock(packet_lock); // This will delete packet_lock and notify_all packetqueue.wait(); // Everything may have changed, just return and start again. This needs to be more RAII - return false; + return true; } // end while ! decoded } // end if decoding enabled @@ -1981,17 +1982,19 @@ bool Monitor::Analyse() { } if (snap->image) { - alarm_image.Assign(*(snap->image)); // decoder may not have been able to provide an image if (!ref_image.Buffer()) { Debug(1, "Assigning instead of Detecting"); ref_image.Assign(*(snap->image)); + alarm_image.Assign(*(snap->image)); } else if (!(analysis_image_count % (motion_frame_skip+1))) { Debug(1, "Detecting motion on image %d, image %p", snap->image_index, snap->image); // Get new score. int motion_score = DetectMotion(*(snap->image), zoneSet); + if (!snap->analysis_image) + snap->analysis_image = new Image(*(snap->image)); // lets construct alarm cause. It will contain cause + names of zones alarmed snap->zone_stats.reserve(zones.size()); for (const Zone &zone : zones) { @@ -2001,8 +2004,11 @@ bool Monitor::Analyse() { if (zone.Alarmed()) { if (!snap->alarm_cause.empty()) snap->alarm_cause += ","; snap->alarm_cause += std::string(zone.Label()); + if (zone.AlarmImage()) + snap->analysis_image->Overlay(*(zone.AlarmImage())); } } + alarm_image.Assign(*(snap->analysis_image)); Debug(3, "After motion detection, score:%d last_motion_score(%d), new motion score(%d)", score, last_motion_score, motion_score); motion_frame_count += 1; @@ -2015,13 +2021,14 @@ bool Monitor::Analyse() { } // end if motion_score } else { Debug(1, "Skipped motion detection last motion score was %d", last_motion_score); + alarm_image.Assign(*(snap->image)); } } else { Debug(1, "no image so skipping motion detection"); } // end if has image score += last_motion_score; } else { - Debug(1, "Not Active(%d) enabled %d active %d doing motion detection: %d", + Debug(1, "Not Active(%d) enabled %d shared->active %d doing motion detection: %d", Active(), enabled, shared_data->active, (function == MODECT or function == MOCORD) ); @@ -2167,34 +2174,9 @@ bool Monitor::Analyse() { snap->score = score; if (state == PREALARM) { - // Generate analysis images if necessary - if (snap->image) { - for (const Zone &zone : zones) { - if (zone.Alarmed() and zone.AlarmImage()) { - if (!snap->analysis_image) - snap->analysis_image = new Image(*(snap->image)); - snap->analysis_image->Overlay(*(zone.AlarmImage())); - } // end if zone is alarmed - } // end foreach zone - if (snap->analysis_image != nullptr) - alarm_image.Assign(*(snap->analysis_image)); - } // end if image. - // incremement pre alarm image count Event::AddPreAlarmFrame(snap->image, timestamp, score, nullptr); } else if (state == ALARM) { - if (snap->image) { - for (const Zone &zone : zones) { - if (zone.Alarmed() and zone.AlarmImage()) { - if (!snap->analysis_image) - snap->analysis_image = new Image(*(snap->image)); - snap->analysis_image->Overlay(*(zone.AlarmImage())); - } // end if zone is alarmed - } // end foreach zone - if (snap->analysis_image != nullptr) - alarm_image.Assign(*(snap->analysis_image)); - } - if (event) { if (noteSetMap.size() > 0) event->updateNotes(noteSetMap); @@ -2211,15 +2193,15 @@ bool Monitor::Analyse() { } else { Error("ALARM but no event"); } - } else if ( state == ALERT ) { + } else if (state == ALERT) { // Alert means this frame has no motion, but we were alarmed and are still recording. if ((noteSetMap.size() > 0) and event) event->updateNotes(noteSetMap); - } else if ( state == TAPE ) { + } else if (state == TAPE) { // bulk frame code moved to event. } // end if state machine - if ( (function == MODECT or function == MOCORD) and snap->image ) { + if ((function == MODECT or function == MOCORD) and snap->image) { if (!ref_image.Buffer()) { Debug(1, "Assigning"); ref_image.Assign(*(snap->image)); @@ -2246,11 +2228,18 @@ bool Monitor::Analyse() { // In the case where people have pre-alarm frames, the web ui will generate the frame images // from the mp4. So no one will notice anyways. - if (snap->image and (videowriter == PASSTHROUGH) and !savejpegs) { - Debug(1, "Deleting image data for %d", snap->image_index); - // Don't need raw images anymore - delete snap->image; - snap->image = nullptr; + if (snap->image and (videowriter == PASSTHROUGH)) { + if (!savejpegs) { + Debug(1, "Deleting image data for %d", snap->image_index); + // Don't need raw images anymore + delete snap->image; + snap->image = nullptr; + } + if (snap->analysis_image and !(savejpegs & 2)) { + Debug(1, "Deleting analysis image data for %d", snap->image_index); + delete snap->analysis_image; + snap->analysis_image = nullptr; + } } packetqueue.clearPackets(snap); @@ -2261,7 +2250,8 @@ bool Monitor::Analyse() { analysis_image_count++; } packetqueue.increment_it(analysis_it); - packetqueue.unlock(packet_lock); + delete packet_lock; + //packetqueue.unlock(packet_lock); shared_data->last_read_time = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); return true; From 134d80a91d65f699d54cbefabddd41545b870b49 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 12 Jan 2022 11:32:08 -0500 Subject: [PATCH 04/12] Replace Requires with BindsTo and add comment explaining how to override --- distros/ubuntu2004/zoneminder.service | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/distros/ubuntu2004/zoneminder.service b/distros/ubuntu2004/zoneminder.service index cb2d6791e..6645fea04 100644 --- a/distros/ubuntu2004/zoneminder.service +++ b/distros/ubuntu2004/zoneminder.service @@ -5,7 +5,8 @@ Description=ZoneMinder CCTV recording and surveillance system After=network.target mysql.service # Remarked out so that it will start ZM on machines that don't have mysql installed -#Requires=mysql.service +# Override it by placing an override.conf in /etc/systemd/system/zoneminder.service.d +#BindsTo=mysql.service [Service] #User=www-data From a9f7b257ea0444f1d5c685bca3188cf3a73d5bd2 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 12 Jan 2022 17:16:49 -0500 Subject: [PATCH 05/12] Rough in a queue and a thread into the event to process packets. We do this so that the event creator can get back to analysis as fast as possible so as to avoid the packetqueue filling up. --- src/zm_event.cpp | 205 +++++++++++++++++++++++++++-------------------- src/zm_event.h | 20 +++++ 2 files changed, 138 insertions(+), 87 deletions(-) diff --git a/src/zm_event.cpp b/src/zm_event.cpp index 7db8f84f3..f1dce36d3 100644 --- a/src/zm_event.cpp +++ b/src/zm_event.cpp @@ -65,7 +65,8 @@ Event::Event( last_db_frame(0), have_video_keyframe(false), //scheme - save_jpegs(0) + save_jpegs(0), + terminate_(false) { std::string notes; createNotes(notes); @@ -133,98 +134,17 @@ Event::Event( ); id = zmDbDoInsert(sql); - if (!SetPath(storage)) { - // Try another - Warning("Failed creating event dir at %s", storage->Path()); - sql = stringtf("SELECT `Id` FROM `Storage` WHERE `Id` != %u", storage->Id()); - if (monitor->ServerId()) - sql += stringtf(" AND ServerId=%u", monitor->ServerId()); - - storage = nullptr; - - MYSQL_RES *result = zmDbFetch(sql); - if (result) { - for (int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++) { - storage = new Storage(atoi(dbrow[0])); - if (SetPath(storage)) - break; - delete storage; - storage = nullptr; - } // end foreach row of Storage - mysql_free_result(result); - result = nullptr; - } - if (!storage) { - Info("No valid local storage area found. Trying all other areas."); - // Try remote - sql = "SELECT `Id` FROM `Storage` WHERE ServerId IS NULL"; - if (monitor->ServerId()) - sql += stringtf(" OR ServerId != %u", monitor->ServerId()); - - result = zmDbFetch(sql); - if (result) { - for ( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++ ) { - storage = new Storage(atoi(dbrow[0])); - if (SetPath(storage)) - break; - delete storage; - storage = nullptr; - } // end foreach row of Storage - mysql_free_result(result); - result = nullptr; - } - } - if (!storage) { - storage = new Storage(); - Warning("Failed to find a storage area to save events."); - } - sql = stringtf("UPDATE Events SET StorageId = '%d' WHERE Id=%" PRIu64, storage->Id(), id); - zmDbDo(sql); - } // end if ! setPath(Storage) - Debug(1, "Using storage area at %s", path.c_str()); - - snapshot_file = path + "/snapshot.jpg"; - alarm_file = path + "/alarm.jpg"; - - video_incomplete_path = path + "/" + video_incomplete_file; - - if (monitor->GetOptVideoWriter() != 0) { - /* Save as video */ - - videoStore = new VideoStore( - video_incomplete_path.c_str(), - container.c_str(), - monitor->GetVideoStream(), - monitor->GetVideoCodecContext(), - ( monitor->RecordAudio() ? monitor->GetAudioStream() : nullptr ), - ( monitor->RecordAudio() ? monitor->GetAudioCodecContext() : nullptr ), - monitor ); - - if ( !videoStore->open() ) { - Warning("Failed to open videostore, turning on jpegs"); - delete videoStore; - videoStore = nullptr; - if ( ! ( save_jpegs & 1 ) ) { - save_jpegs |= 1; // Turn on jpeg storage - sql = stringtf("UPDATE Events SET SaveJpegs=%d WHERE Id=%" PRIu64, save_jpegs, id); - zmDbDo(sql); - } - } else { - std::string codec = videoStore->get_codec(); - video_file = stringtf("%" PRIu64 "-%s.%s.%s", id, "video", codec.c_str(), container.c_str()); - video_path = path + "/" + video_file; - Debug(1, "Video file is %s", video_file.c_str()); - } - } // end if GetOptVideoWriter - if (storage != monitor->getStorage()) - delete storage; + thread_ = std::thread(&Event::Run, this); } Event::~Event() { - // We close the videowriter first, because if we finish the event, we might try to view the file, but we aren't done writing it yet. + + Stop(); + if (thread_.joinable()) thread_.join(); /* Close the video file */ + // We close the videowriter first, because if we finish the event, we might try to view the file, but we aren't done writing it yet. if (videoStore != nullptr) { Debug(4, "Deleting video store"); delete videoStore; @@ -377,6 +297,12 @@ void Event::updateNotes(const StringSetMap &newNoteSetMap) { } // void Event::updateNotes(const StringSetMap &newNoteSetMap) void Event::AddPacket(const std::shared_ptr&packet) { + std::unique_lock lck(packet_queue_mutex); + packet_queue.push(packet); + packet_queue_condition.notify_one(); +} + +void Event::AddPacket_(const std::shared_ptr&packet) { have_video_keyframe = have_video_keyframe || ( ( packet->codec_type == AVMEDIA_TYPE_VIDEO ) && ( packet->keyframe || monitor->GetOptVideoWriter() == Monitor::ENCODE) ); @@ -655,3 +581,108 @@ bool Event::SetPath(Storage *storage) { } // deep storage or not return true; } // end bool Event::SetPath + +void Event::Run() { + Storage *storage = monitor->getStorage(); + if (!SetPath(storage)) { + // Try another + Warning("Failed creating event dir at %s", storage->Path()); + + std::string sql = stringtf("SELECT `Id` FROM `Storage` WHERE `Id` != %u", storage->Id()); + if (monitor->ServerId()) + sql += stringtf(" AND ServerId=%u", monitor->ServerId()); + + storage = nullptr; + + MYSQL_RES *result = zmDbFetch(sql); + if (result) { + for (int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++) { + storage = new Storage(atoi(dbrow[0])); + if (SetPath(storage)) + break; + delete storage; + storage = nullptr; + } // end foreach row of Storage + mysql_free_result(result); + result = nullptr; + } + if (!storage) { + Info("No valid local storage area found. Trying all other areas."); + // Try remote + sql = "SELECT `Id` FROM `Storage` WHERE ServerId IS NULL"; + if (monitor->ServerId()) + sql += stringtf(" OR ServerId != %u", monitor->ServerId()); + + result = zmDbFetch(sql); + if (result) { + for ( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++ ) { + storage = new Storage(atoi(dbrow[0])); + if (SetPath(storage)) + break; + delete storage; + storage = nullptr; + } // end foreach row of Storage + mysql_free_result(result); + result = nullptr; + } + } + if (!storage) { + storage = new Storage(); + Warning("Failed to find a storage area to save events."); + } + sql = stringtf("UPDATE Events SET StorageId = '%d' WHERE Id=%" PRIu64, storage->Id(), id); + zmDbDo(sql); + } // end if ! setPath(Storage) + Debug(1, "Using storage area at %s", path.c_str()); + + snapshot_file = path + "/snapshot.jpg"; + alarm_file = path + "/alarm.jpg"; + + video_incomplete_path = path + "/" + video_incomplete_file; + + if (monitor->GetOptVideoWriter() != 0) { + /* Save as video */ + + videoStore = new VideoStore( + video_incomplete_path.c_str(), + container.c_str(), + monitor->GetVideoStream(), + monitor->GetVideoCodecContext(), + ( monitor->RecordAudio() ? monitor->GetAudioStream() : nullptr ), + ( monitor->RecordAudio() ? monitor->GetAudioCodecContext() : nullptr ), + monitor ); + + if ( !videoStore->open() ) { + Warning("Failed to open videostore, turning on jpegs"); + delete videoStore; + videoStore = nullptr; + if ( ! ( save_jpegs & 1 ) ) { + save_jpegs |= 1; // Turn on jpeg storage + zmDbDo(stringtf("UPDATE Events SET SaveJpegs=%d WHERE Id=%" PRIu64, save_jpegs, id)); + } + } else { + std::string codec = videoStore->get_codec(); + video_file = stringtf("%" PRIu64 "-%s.%s.%s", id, "video", codec.c_str(), container.c_str()); + video_path = path + "/" + video_file; + Debug(1, "Video file is %s", video_file.c_str()); + } + } // end if GetOptVideoWriter + if (storage != monitor->getStorage()) + delete storage; + + + std::unique_lock lck(packet_queue_mutex); + + // The idea is to process the queue no matter what so that all packets get processed. + // We only break if the queue is empty + while (true) { + if (!packet_queue.empty()) { + this->AddPacket_(packet_queue.front()); + packet_queue.pop(); + } else { + if (terminate_ or zm_terminate) + break; + packet_queue_condition.wait(lck); + } + } +} diff --git a/src/zm_event.h b/src/zm_event.h index 4fb5c7469..447bdeafc 100644 --- a/src/zm_event.h +++ b/src/zm_event.h @@ -22,14 +22,21 @@ #include "zm_config.h" #include "zm_define.h" +#include "zm_packet.h" #include "zm_storage.h" #include "zm_time.h" #include "zm_utils.h" #include "zm_zone.h" +#include +#include #include +#include +#include #include #include +#include + class EventStream; class Frame; @@ -98,6 +105,15 @@ class Event { void createNotes(std::string ¬es); + std::queue> packet_queue; + std::mutex packet_queue_mutex; + std::condition_variable packet_queue_condition; + + void Run(); + + std::atomic terminate_; + std::thread thread_; + public: static bool OpenFrameSocket(int); static bool ValidateFrameSocket(int); @@ -118,6 +134,7 @@ class Event { SystemTimePoint EndTime() const { return end_time; } void AddPacket(const std::shared_ptr &p); + void AddPacket_(const std::shared_ptr &p); bool WritePacket(const std::shared_ptr &p); bool SendFrameImage(const Image *image, bool alarm_frame=false); bool WriteFrameImage(Image *image, SystemTimePoint timestamp, const char *event_file, bool alarm_frame = false) const; @@ -130,6 +147,9 @@ class Event { int score = 0, Image *alarm_image = nullptr); + void Stop() { terminate_ = true; } + bool Stopped() const { return terminate_; } + private: void WriteDbFrames(); bool SetPath(Storage *storage); From 8d061750242b35dc4429ed5cb69771d9cf83d301 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 12 Jan 2022 23:07:48 -0500 Subject: [PATCH 06/12] Rework to use ZoneMinder::Monitor class. Simplify loadMonitors and get rid of loadMonitor. Add in ServerId change handling. --- scripts/zmtrigger.pl.in | 62 ++++++++++++++++------------------------- 1 file changed, 24 insertions(+), 38 deletions(-) diff --git a/scripts/zmtrigger.pl.in b/scripts/zmtrigger.pl.in index 83fcf29f5..94f59e001 100644 --- a/scripts/zmtrigger.pl.in +++ b/scripts/zmtrigger.pl.in @@ -42,6 +42,7 @@ use constant SELECT_TIMEOUT => 0.25; @EXTRA_PERL_LIB@ use ZoneMinder; +use ZoneMinder::Monitor; use ZoneMinder::Trigger::Channel::Inet; use ZoneMinder::Trigger::Channel::Unix; use ZoneMinder::Trigger::Channel::Serial; @@ -203,14 +204,10 @@ while (!$zm_terminate) { # Check for alarms that might have happened my @out_messages; foreach my $monitor ( values %monitors ) { - if ($$monitor{Function} eq 'None') { - $monitor_reload_time = 0; - next; - } - - if (!zmMemVerify($monitor)) { + if (!$monitor->connect()) { # Our attempt to verify the memory handle failed. We should reload the monitors. # Don't need to zmMemInvalidate because the monitor reload will do it. + Debug("Failed connect, putting on reloads"); push @needsReload, $monitor; next; } @@ -249,6 +246,7 @@ while (!$zm_terminate) { } $monitor->{LastState} = $state; $monitor->{LastEvent} = $last_event; + $monitor->disconnect(); } # end foreach monitor foreach my $connection ( @out_connections ) { @@ -298,15 +296,22 @@ while (!$zm_terminate) { # Reload all monitors from the dB every MONITOR_RELOAD_INTERVAL if ( (time() - $monitor_reload_time) > MONITOR_RELOAD_INTERVAL ) { - foreach my $monitor ( values(%monitors) ) { - zmMemInvalidate( $monitor ); # Free up any used memory handle - } loadMonitors(); @needsReload = (); # We just reloaded all monitors so no need reload a specific monitor # If we have NOT just reloaded all monitors, reload a specific monitor if its shared mem changed - } elsif ( @needsReload ) { - foreach my $monitor ( @needsReload ) { - loadMonitor($monitor); + } elsif (@needsReload) { + foreach my $monitor (@needsReload) { + $monitor = $monitors{$monitor->Id()} = ZoneMinder::Monitor->find_one(Id=>$monitor->Id()); + if ( $$monitor{Function} eq 'None' ) { + delete $monitors{$monitor->Id()}; + } elsif ( $Config{ZM_SERVER_ID} and ($$monitor{ServerId} != $Config{ZM_SERVER_ID})) { + delete $monitors{$monitor->Id()}; + } else { + if ($monitor->connect()) { + $monitor->{LastState} = zmGetMonitorState($monitor); + $monitor->{LastEvent} = zmGetLastEvent($monitor); + } + } } @needsReload = (); } @@ -317,40 +322,21 @@ while (!$zm_terminate) { Info('Trigger daemon exiting'); exit; -sub loadMonitor { - my $monitor = shift; - - Debug('Loading monitor '.$monitor); - zmMemInvalidate($monitor); - - if ( zmMemVerify($monitor) ) { # This will re-init shared memory - $monitor->{LastState} = zmGetMonitorState($monitor); - $monitor->{LastEvent} = zmGetLastEvent($monitor); - } -} # end sub loadMonitor - sub loadMonitors { $monitor_reload_time = time(); - my %new_monitors = (); + %monitors = (); - my $sql = 'SELECT * FROM `Monitors` - WHERE find_in_set( `Function`, \'Modect,Mocord,Nodect,Record\' )'. - ( $Config{ZM_SERVER_ID} ? ' AND `ServerId`=?' : '' ) - ; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $Config{ZM_SERVER_ID} ? $Config{ZM_SERVER_ID} : () ) - or Fatal( "Can't execute: ".$sth->errstr() ); - - while ( my $monitor = $sth->fetchrow_hashref() ) { - if ( zmMemVerify($monitor) ) { # This will re-init shared memory + foreach my $monitor ( ZoneMinder::Monitor->find( + Function=>['Modect','Mocord','Nodect','Record'], + ($Config{ZM_SERVER_ID} ? (ServerId=>$Config{ZM_SERVER_ID}) : ()), + )) { + if ($monitor->connect()) { # This will re-init shared memory $monitor->{LastState} = zmGetMonitorState($monitor); $monitor->{LastEvent} = zmGetLastEvent($monitor); } - $new_monitors{$monitor->{Id}} = $monitor; + $monitors{$monitor->{Id}} = $monitor; } # end while fetchrow - %monitors = %new_monitors; } # end sub loadMonitors sub handleMessage { From e4693c251c598d92be0c54f54e8b41e69893fdb2 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 12 Jan 2022 23:08:29 -0500 Subject: [PATCH 07/12] add backticks around field names because some like Function are reserved --- scripts/ZoneMinder/lib/ZoneMinder/Object.pm | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Object.pm b/scripts/ZoneMinder/lib/ZoneMinder/Object.pm index e54bb15ce..d64ee7e8a 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Object.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Object.pm @@ -639,9 +639,9 @@ $log->debug("Have array for $k $$search{$k}") if DEBUG_ALL; if ( ! ( $db_field =~ /\?/ ) ) { if ( @{$$search{$k}} != 1 ) { - push @where, $db_field .' IN ('.join(',', map {'?'} @{$$search{$k}} ) . ')'; + push @where, '`'.$db_field .'` IN ('.join(',', map {'?'} @{$$search{$k}} ) . ')'; } else { - push @where, $db_field.'=?'; + push @where, '`'.$db_field.'`=?'; } # end if } else { $log->debug("Have question ? for $k $$search{$k} $db_field") if DEBUG_ALL; @@ -656,10 +656,10 @@ $log->debug("Have question ? for $k $$search{$k} $db_field") if DEBUG_ALL; foreach my $p_k ( keys %{$$search{$k}} ) { my $v = $$search{$k}{$p_k}; if ( ref $v eq 'ARRAY' ) { - push @where, $db_field.' IN ('.join(',', map {'?'} @{$v} ) . ')'; + push @where, '`'.$db_field.'` IN ('.join(',', map {'?'} @{$v} ) . ')'; push @values, $p_k, @{$v}; } else { - push @where, $db_field.'=?'; + push @where, '`'.$db_field.'`=?'; push @values, $p_k, $v; } # end if } # end foreach p_k @@ -667,7 +667,7 @@ $log->debug("Have question ? for $k $$search{$k} $db_field") if DEBUG_ALL; push @where, $db_field.' IS NULL'; } else { if ( ! ( $db_field =~ /\?/ ) ) { - push @where, $db_field .'=?'; + push @where, '`'.$db_field .'`=?'; } else { push @where, $db_field; } From 3d042284857a69496b5404089546458494386b7e Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 12 Jan 2022 23:10:17 -0500 Subject: [PATCH 08/12] rework ::Analyse. The state engine will only go into alarm on actual motion, not on a skipped frame. TRIGGERS and ONVIF events will immediately create events. --- src/zm_monitor.cpp | 78 ++++++++++++++++++++++++++-------------------- 1 file changed, 44 insertions(+), 34 deletions(-) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 958410efe..587c49f33 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -1870,11 +1870,18 @@ bool Monitor::Analyse() { score += 9; Debug(1, "Triggered on ONVIF"); if (!event) { + Event::StringSet noteSet; + noteSet.insert("ONVIF2"); + noteSetMap[MOTION_CAUSE] = noteSet; + + event = openEvent(snap, "ONVIF", noteSetMap); cause += "ONVIF"; + } else { + event->addNote(MOTION_CAUSE, "ONVIF2"); +// Add to cause } - Event::StringSet noteSet; - noteSet.insert("ONVIF2"); - noteSetMap[MOTION_CAUSE] = noteSet; + // Regardless of previous state, we go to ALARM + shared_data->state = state = ALARM; //If the camera isn't going to send an event close, we need to close it here, but only after it has actually triggered an alarm. if (!ONVIF_Closes_Event && state == ALARM) ONVIF_Trigger_State = FALSE; @@ -1886,11 +1893,16 @@ bool Monitor::Analyse() { score += trigger_data->trigger_score; Debug(1, "Triggered on score += %d => %d", trigger_data->trigger_score, score); if (!event) { - cause += trigger_data->trigger_cause; + Event::StringSet noteSet; + noteSet.insert(trigger_data->trigger_text); + noteSetMap[trigger_data->trigger_cause] = noteSet; + event = openEvent(snap, trigger_data->trigger_cause, noteSetMap); + Info("%s: %03d - Opening new event %" PRIu64 ", alarm start", name.c_str(), analysis_image_count, event->Id()); + } else { + event->addNote(trigger_data->trigger_cause, trigger_data->trigger_text); + // Need to know if we should end the previous and start a new one, or just add the data } - Event::StringSet noteSet; - noteSet.insert(trigger_data->trigger_text); - noteSetMap[trigger_data->trigger_cause] = noteSet; + shared_data->state = state = ALARM; } // end if trigger_on // FIXME this snap might not be the one that caused the signal change. Need to store that in the packet. @@ -1967,8 +1979,6 @@ bool Monitor::Analyse() { } // end while ! decoded } // end if decoding enabled - SystemTimePoint timestamp = snap->timestamp; - if (Active() and (function == MODECT or function == MOCORD)) { Debug(3, "signal and active and modect"); Event::StringSet zoneSet; @@ -1990,7 +2000,7 @@ bool Monitor::Analyse() { } else if (!(analysis_image_count % (motion_frame_skip+1))) { Debug(1, "Detecting motion on image %d, image %p", snap->image_index, snap->image); // Get new score. - int motion_score = DetectMotion(*(snap->image), zoneSet); + snap->score = DetectMotion(*(snap->image), zoneSet); // lets construct alarm cause. It will contain cause + names of zones alarmed snap->zone_stats.reserve(zones.size()); @@ -2004,11 +2014,11 @@ bool Monitor::Analyse() { } } Debug(3, "After motion detection, score:%d last_motion_score(%d), new motion score(%d)", - score, last_motion_score, motion_score); + score, last_motion_score, snap->score); motion_frame_count += 1; - last_motion_score = motion_score; + last_motion_score = snap->score; - if (motion_score) { + if (snap->score) { if (cause.length()) cause += ", "; cause += MOTION_CAUSE+std::string(":")+snap->alarm_cause; noteSetMap[MOTION_CAUSE] = zoneSet; @@ -2019,7 +2029,7 @@ bool Monitor::Analyse() { } else { Debug(1, "no image so skipping motion detection"); } // end if has image - score += last_motion_score; +//score += last_motion_score; } else { Debug(1, "Not Active(%d) enabled %d active %d doing motion detection: %d", Active(), enabled, shared_data->active, @@ -2032,17 +2042,17 @@ bool Monitor::Analyse() { if (event) { Debug(2, "Have event %" PRIu64 " in record", event->Id()); - if (section_length != Seconds(0) && (timestamp - event->StartTime() >= section_length) + if (section_length != Seconds(0) && (snap->timestamp - event->StartTime() >= section_length) && ((function == MOCORD && event_close_mode != CLOSE_TIME) || (function == RECORD && event_close_mode == CLOSE_TIME) - || std::chrono::duration_cast(timestamp.time_since_epoch()) % section_length == Seconds(0))) { + || std::chrono::duration_cast(snap->timestamp.time_since_epoch()) % section_length == Seconds(0))) { Info("%s: %03d - Closing event %" PRIu64 ", section end forced %" PRIi64 " - %" PRIi64 " = %" PRIi64 " >= %" PRIi64 , name.c_str(), image_count, event->Id(), - static_cast(std::chrono::duration_cast(timestamp.time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(snap->timestamp.time_since_epoch()).count()), static_cast(std::chrono::duration_cast(event->StartTime().time_since_epoch()).count()), - static_cast(std::chrono::duration_cast(timestamp - event->StartTime()).count()), + static_cast(std::chrono::duration_cast(snap->timestamp - event->StartTime()).count()), static_cast(Seconds(section_length).count())); closeEvent(); } // end if section_length @@ -2060,13 +2070,14 @@ bool Monitor::Analyse() { } // end if ! event } // end if RECORDING - if (score and (function != MONITOR)) { + if ((snap->score > 0) and (function != MONITOR)) { 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() && !event->AlarmFrames() && (event_close_mode == CLOSE_ALARM) - && ((timestamp - event->StartTime()) >= min_section_length) + // FIXME since we won't be including this snap in the event if we close it, we should be looking at event->duration() instead + && ((snap->timestamp - event->StartTime()) >= min_section_length) && ((!pre_event_count) || (Event::PreAlarmCount() >= alarm_frame_count - 1))) { Info("%s: %03d - Closing event %" PRIu64 ", continuous end, alarm begins", name.c_str(), image_count, event->Id()); @@ -2078,7 +2089,7 @@ bool Monitor::Analyse() { Event::PreAlarmCount(), pre_event_count, event->Frames(), event->AlarmFrames(), - static_cast(std::chrono::duration_cast(timestamp - event->StartTime()).count()), + static_cast(std::chrono::duration_cast(snap->timestamp - event->StartTime()).count()), static_cast(Seconds(min_section_length).count()), (event_close_mode == CLOSE_ALARM)); } @@ -2088,12 +2099,10 @@ bool Monitor::Analyse() { if (!event) { event = openEvent(snap, cause, noteSetMap); - shared_data->state = state = ALARM; Info("%s: %03d - Opening new event %" PRIu64 ", alarm start", name.c_str(), analysis_image_count, event->Id()); - } else { - shared_data->state = state = ALARM; } // end if no event, so start it - if ( alarm_frame_count ) { + shared_data->state = state = ALARM; + if (alarm_frame_count) { Debug(1, "alarm frame count so SavePreAlarmFrames"); event->SavePreAlarmFrames(); } @@ -2115,13 +2124,13 @@ bool Monitor::Analyse() { Debug(1, "Was in TAPE, going into ALARM"); } else { Debug(1, "Staying in %s", State_Strings[state].c_str()); - } if (state == ALARM) { last_alarm_count = analysis_image_count; } // This is needed so post_event_count counts after last alarmed frames while in ALARM not single alarmed frames while ALERT - } else { // no score? + } else if (!score) { // no snap->score? alert_to_alarm_frame_count = alarm_frame_count; // load same value configured for alarm_frame_count + if (state == ALARM) { Info("%s: %03d - Gone into alert state", name.c_str(), analysis_image_count); shared_data->state = state = ALERT; @@ -2129,7 +2138,7 @@ bool Monitor::Analyse() { if ( ((analysis_image_count - last_alarm_count) > post_event_count) && - ((timestamp - event->StartTime()) >= min_section_length)) { + ((snap->timestamp - event->StartTime()) >= min_section_length)) { Info("%s: %03d - Left alarm state (%" PRIu64 ") - %d(%d) images", name.c_str(), analysis_image_count, event->Id(), event->Frames(), event->AlarmFrames()); if ( @@ -2156,7 +2165,7 @@ bool Monitor::Analyse() { analysis_image_count, last_alarm_count, post_event_count, - static_cast(std::chrono::duration_cast(timestamp.time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(snap->timestamp.time_since_epoch()).count()), static_cast(std::chrono::duration_cast(GetVideoWriterStartTime().time_since_epoch()).count()), static_cast(Seconds(min_section_length).count())); } @@ -2164,7 +2173,8 @@ bool Monitor::Analyse() { Event::EmptyPreAlarmFrames(); } // end if score or not - snap->score = score; + if (score > snap->score) + snap->score = score; if (state == PREALARM) { // Generate analysis images if necessary @@ -2181,7 +2191,7 @@ bool Monitor::Analyse() { } // end if image. // incremement pre alarm image count - Event::AddPreAlarmFrame(snap->image, timestamp, score, nullptr); + Event::AddPreAlarmFrame(snap->image, snap->timestamp, score, nullptr); } else if (state == ALARM) { if (snap->image) { for (const Zone &zone : zones) { @@ -2198,12 +2208,12 @@ bool Monitor::Analyse() { if (event) { if (noteSetMap.size() > 0) event->updateNotes(noteSetMap); - if (section_length != Seconds(0) && (timestamp - event->StartTime() >= section_length)) { + if (section_length != Seconds(0) && (snap->timestamp - event->StartTime() >= section_length)) { Warning("%s: %03d - event %" PRIu64 ", has exceeded desired section length. %" PRIi64 " - %" PRIi64 " = %" PRIi64 " >= %" PRIi64, name.c_str(), analysis_image_count, event->Id(), - static_cast(std::chrono::duration_cast(timestamp.time_since_epoch()).count()), + static_cast(std::chrono::duration_cast(snap->timestamp.time_since_epoch()).count()), static_cast(std::chrono::duration_cast(GetVideoWriterStartTime().time_since_epoch()).count()), - static_cast(std::chrono::duration_cast(timestamp - GetVideoWriterStartTime()).count()), + static_cast(std::chrono::duration_cast(snap->timestamp - GetVideoWriterStartTime()).count()), static_cast(Seconds(section_length).count())); closeEvent(); event = openEvent(snap, cause, noteSetMap); From f764cfbc329612d7dee4a822ab2af8d557f2a911 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Wed, 12 Jan 2022 23:11:19 -0500 Subject: [PATCH 09/12] Fix deadlock on deleting event. Cannot race for the lock, joining will do the job --- src/zm_event.cpp | 19 ++++++++++++++----- src/zm_event.h | 5 ++++- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/zm_event.cpp b/src/zm_event.cpp index f1dce36d3..e14c1a343 100644 --- a/src/zm_event.cpp +++ b/src/zm_event.cpp @@ -134,14 +134,19 @@ Event::Event( ); id = zmDbDoInsert(sql); - thread_ = std::thread(&Event::Run, this); } Event::~Event() { - + Debug(1, "Deleting event, calling stop"); Stop(); - if (thread_.joinable()) thread_.join(); + if (thread_.joinable()) { + // Should be. Issuing the stop and then getting the lock + Debug(1, "Joinable"); + thread_.join(); + } else { + Debug(1, "Not Joinable"); + } /* Close the video file */ // We close the videowriter first, because if we finish the event, we might try to view the file, but we aren't done writing it yet. @@ -670,19 +675,23 @@ void Event::Run() { if (storage != monitor->getStorage()) delete storage; - std::unique_lock lck(packet_queue_mutex); // The idea is to process the queue no matter what so that all packets get processed. // We only break if the queue is empty while (true) { if (!packet_queue.empty()) { + Debug(1, "adding packet"); this->AddPacket_(packet_queue.front()); packet_queue.pop(); } else { - if (terminate_ or zm_terminate) + if (terminate_ or zm_terminate) { +Debug(1, "terminating"); break; + } +Debug(1, "waiting"); packet_queue_condition.wait(lck); +Debug(1, "wakeing"); } } } diff --git a/src/zm_event.h b/src/zm_event.h index 447bdeafc..be6d922de 100644 --- a/src/zm_event.h +++ b/src/zm_event.h @@ -147,7 +147,10 @@ class Event { int score = 0, Image *alarm_image = nullptr); - void Stop() { terminate_ = true; } + void Stop() { + terminate_ = true; + packet_queue_condition.notify_all(); + } bool Stopped() const { return terminate_; } private: From 3dad3a5200dc62844fbb6d12c3421ae5482283d3 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 13 Jan 2022 09:37:54 -0500 Subject: [PATCH 10/12] update description to reflect that zmupdate.pl now does all the things it will someday do. Meantion how -c works better. When in interactive mode, check once and print out the result instead of daemonising. Fix formatting osf usage --- scripts/zmupdate.pl.in | 51 ++++++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/scripts/zmupdate.pl.in b/scripts/zmupdate.pl.in index 0253fadc2..095218940 100644 --- a/scripts/zmupdate.pl.in +++ b/scripts/zmupdate.pl.in @@ -2,7 +2,7 @@ # # ========================================================================== # -# ZoneMinder Update Script, $Date$, $Revision$ +# ZoneMinder Update Script # Copyright (C) 2001-2008 Philip Coombes # # This program is free software; you can redistribute it and/or @@ -31,29 +31,29 @@ zmupdate.pl -c,--check | -f,--freshen | -v,--version= [-u , --version= - Force upgrade to the current version from --u , --user= - Alternate DB user with privileges to alter DB --p , --pass= - Password of alternate DB user with privileges to alter DB --s, --super - Use system maintenance account on debian based systems instead of unprivileged account --d , --dir= - Directory containing update files if not in default build location --interactive - interact with the user --nointeractive - do not interact with the user + -c, --check - Check for updated versions of ZoneMinder. + If not interactive zmupdate.pl will stay running, checking every hour. + If interactive will try once, print out result and quit. + -f, --freshen - Freshen the configuration in the database. Equivalent of old zmconfig.pl -noi + --migrate-events - Update database structures as per USE_DEEP_STORAGE setting. + -v , --version= - Force upgrade to the current version from + -u , --user= - Alternate DB user with privileges to alter DB + -p , --pass= - Password of alternate DB user with privileges to alter DB + -s, --super - Use system maintenance account on debian based systems instead of unprivileged account + -d , --dir= - Directory containing update files if not in default build location + -interactive - interact with the user + -nointeractive - do not interact with the user =cut use strict; use bytes; use version; -use Crypt::Eksblowfish::Bcrypt; -use Data::Entropy::Algorithms qw(rand_bits); # ========================================================================== # @@ -122,9 +122,8 @@ GetOptions( ) or pod2usage(-exitstatus => -1); my $dbh = zmDbConnect(undef, { mysql_multi_statements=>1 } ); -if ( !$dbh ) { - die "Unable to connect to db\n"; -} +die "Unable to connect to db\n" if !$dbh; + $Config{ZM_DB_USER} = $dbUser; $Config{ZM_DB_PASS} = $dbPass; # we escape dbpass with single quotes so that $ in the password has no effect, but dbpass could have a ' in it. @@ -181,6 +180,7 @@ if ( $check && $Config{ZM_CHECK_FOR_UPDATES} ) { Info('Got version: '.$lastVersion); + my $lv_sql = 'UPDATE Config SET Value = ? WHERE Name = \'ZM_DYN_LAST_VERSION\''; my $lv_sth = $dbh->prepare_cached($lv_sql) or die("Can't prepare '$lv_sql': ".$dbh->errstr()); my $lv_res = $lv_sth->execute($lastVersion) or die("Can't execute: ".$lv_sth->errstr()); @@ -190,6 +190,11 @@ if ( $check && $Config{ZM_CHECK_FOR_UPDATES} ) { my $lc_sth = $dbh->prepare_cached($lc_sql) or die("Can't prepare '$lc_sql': ".$dbh->errstr()); my $lc_res = $lc_sth->execute($lastCheck) or die("Can't execute: ".$lc_sth->errstr()); $lc_sth->finish(); + if ($interactive) { +print("Hello"); + print("Latest version $lastVersion, our version " . ZM_VERSION."\n"); + exit(0); + } } else { Error('Error check failed: \''.$res->status_line().'\''); } @@ -1044,14 +1049,16 @@ sub patchDB { } # end sub patchDB sub migratePasswords { - print ("Migratings passwords, if any...\n"); + use Crypt::Eksblowfish::Bcrypt; + use Data::Entropy::Algorithms qw(rand_bits); + print("Migratings passwords, if any...\n"); my $sql = 'SELECT * FROM `Users`'; my $sth = $dbh->prepare_cached($sql) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute() or die("Can't execute: ".$sth->errstr()); - while( my $user = $sth->fetchrow_hashref() ) { + while ( my $user = $sth->fetchrow_hashref() ) { my $scheme = substr($user->{Password}, 0, 1); if ($scheme eq '*') { - print ('-->'.$user->{Username}." password will be migrated\n"); + print('-->'.$user->{Username}." password will be migrated\n"); my $salt = Crypt::Eksblowfish::Bcrypt::en_base64(rand_bits(16*8)); my $settings = '$2a$10$'.$salt; my $pass_hash = Crypt::Eksblowfish::Bcrypt::bcrypt($user->{Password},$settings); From 3ec6c7e32fc17e2025966ba5efe3655b9630a6cf Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 13 Jan 2022 09:54:36 -0500 Subject: [PATCH 11/12] Fix behaviour of update check to support interactive mode. Use zmDbDo functions to simplify code. When interactive print out lastVersion, latestVersion and currentVersion --- scripts/zmupdate.pl.in | 38 +++++++++++++++----------------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/scripts/zmupdate.pl.in b/scripts/zmupdate.pl.in index 095218940..9ad5a1a02 100644 --- a/scripts/zmupdate.pl.in +++ b/scripts/zmupdate.pl.in @@ -52,6 +52,7 @@ It can also apply and configure upgrades etc, including on the fly upgrades. =cut use strict; +use warnings; use bytes; use version; @@ -143,8 +144,10 @@ if ( ($check + $freshen + $rename + $zoneFix + $migrateEvents + ($version?1:0)) pod2usage(-exitstatus => -1); } -if ( $check && $Config{ZM_CHECK_FOR_UPDATES} ) { - print('Update agent starting at '.strftime('%y/%m/%d %H:%M:%S', localtime() )."\n"); +if ($check and ($Config{ZM_CHECK_FOR_UPDATES} or $interactive) ) { + if (!$interactive) { + Info('Update agent starting at '.strftime('%y/%m/%d %H:%M:%S', localtime() )."\n"); + } my $currVersion = $Config{ZM_DYN_CURR_VERSION}; my $lastVersion = $Config{ZM_DYN_LAST_VERSION}; @@ -152,16 +155,14 @@ if ( $check && $Config{ZM_CHECK_FOR_UPDATES} ) { if ( !$currVersion ) { $currVersion = $Config{ZM_VERSION}; - - my $sql = "update Config set Value = ? where Name = 'ZM_DYN_CURR_VERSION'"; - my $sth = $dbh->prepare_cached($sql) or die("Can't prepare '$sql': ".$dbh->errstr()); - my $res = $sth->execute($currVersion) or die("Can't execute: ".$sth->errstr()); - $sth->finish(); + zmDbDo("UPDATE `Config` SET `Value` = ? WHERE `Name` = 'ZM_DYN_CURR_VERSION'", $currVersion); } while ( 1 ) { my $now = time(); - if ( !$lastVersion || !$lastCheck || (($now-$lastCheck) > CHECK_INTERVAL) ) { + if ( !$interactive and $lastVersion and $lastCheck and (($now-$lastCheck) <= CHECK_INTERVAL) ) { + Debug("Not checking for updates since we already have less than " . CHECK_INTERVAL . " seconds ago."); + } else { Info('Checking for updates'); use LWP::UserAgent; @@ -174,25 +175,16 @@ if ( $check && $Config{ZM_CHECK_FOR_UPDATES} ) { my $res = $ua->request($req); if ( $res->is_success ) { - $lastVersion = $res->content; - chomp($lastVersion); + my $latestVersion = $res->content; + chomp($latestVersion); $lastCheck = $now; - Info('Got version: '.$lastVersion); + Info('Got version: '.$latestVersion); - - my $lv_sql = 'UPDATE Config SET Value = ? WHERE Name = \'ZM_DYN_LAST_VERSION\''; - my $lv_sth = $dbh->prepare_cached($lv_sql) or die("Can't prepare '$lv_sql': ".$dbh->errstr()); - my $lv_res = $lv_sth->execute($lastVersion) or die("Can't execute: ".$lv_sth->errstr()); - $lv_sth->finish(); - - my $lc_sql = 'UPDATE Config SET Value = ? WHERE Name = \'ZM_DYN_LAST_CHECK\''; - my $lc_sth = $dbh->prepare_cached($lc_sql) or die("Can't prepare '$lc_sql': ".$dbh->errstr()); - my $lc_res = $lc_sth->execute($lastCheck) or die("Can't execute: ".$lc_sth->errstr()); - $lc_sth->finish(); + zmDbDo('UPDATE Config SET Value = ? WHERE Name = \'ZM_DYN_LAST_VERSION\'', $latestVersion); + zmDbDo('UPDATE Config SET Value = ? WHERE Name = \'ZM_DYN_LAST_CHECK\'', $lastCheck); if ($interactive) { -print("Hello"); - print("Latest version $lastVersion, our version " . ZM_VERSION."\n"); + print("Last version $lastVersion, Latest version $latestVersion, our version " . ZM_VERSION."\n"); exit(0); } } else { From 58bd09d83d674e5920c3fe781087035bf814fe6a Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Thu, 13 Jan 2022 09:54:47 -0500 Subject: [PATCH 12/12] Fix debug output from zmDbDo --- dep/RtspServer | 2 +- scripts/ZoneMinder/lib/ZoneMinder/Database.pm | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/dep/RtspServer b/dep/RtspServer index 1b40f1661..cd7fd49be 160000 --- a/dep/RtspServer +++ b/dep/RtspServer @@ -1 +1 @@ -Subproject commit 1b40f1661f93f50fd5805f239d1e466a3bcf888f +Subproject commit cd7fd49becad6010a1b8466bfebbd93999a39878 diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Database.pm b/scripts/ZoneMinder/lib/ZoneMinder/Database.pm index 35d2dc6fa..ae1259814 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Database.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Database.pm @@ -273,6 +273,9 @@ sub zmDbDo { if ( ! defined $rows ) { $sql =~ s/\?/'%s'/; Error(sprintf("Failed $sql :", @_).$dbh->errstr()); + } elsif ( ZoneMinder::Logger::logLevel() > INFO ) { + $sql =~ s/\?/'%s'/; + Debug(sprintf("Succeeded $sql : $rows rows affected", @_)); } return $rows; }