From 7914583a9e982a7623aea7f0c01bf02481ac07e7 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Mon, 24 Jun 2019 11:50:58 -0400 Subject: [PATCH 01/50] Add support for specifying PPA --- utils/do_debian_package.sh | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/utils/do_debian_package.sh b/utils/do_debian_package.sh index 0b9e18ee7..fc1eaaeff 100755 --- a/utils/do_debian_package.sh +++ b/utils/do_debian_package.sh @@ -30,6 +30,10 @@ case $i in INTERACTIVE="${i#*=}" shift # past argument=value ;; + -p=*|--ppa=*) + PPA="${i#*=}" + shift # past argument=value + ;; -r=*|--release=*) RELEASE="${i#*=}" shift @@ -120,20 +124,21 @@ else fi; fi -PPA=""; -if [ "$RELEASE" != "" ]; then - # We need to use our official tarball for the original source, so grab it and overwrite our generated one. - IFS='.' read -r -a VERSION <<< "$RELEASE" - if [ "${VERSION[0]}.${VERSION[1]}" == "1.30" ]; then - PPA="ppa:iconnor/zoneminder-stable" +if [ "$PPA" == "" ]; then + if [ "$RELEASE" != "" ]; then + # We need to use our official tarball for the original source, so grab it and overwrite our generated one. + IFS='.' read -r -a VERSION <<< "$RELEASE" + if [ "${VERSION[0]}.${VERSION[1]}" == "1.30" ]; then + PPA="ppa:iconnor/zoneminder-stable" + else + PPA="ppa:iconnor/zoneminder-${VERSION[0]}.${VERSION[1]}" + fi; else - PPA="ppa:iconnor/zoneminder-${VERSION[0]}.${VERSION[1]}" - fi; -else - if [ "$BRANCH" == "" ]; then - PPA="ppa:iconnor/zoneminder-master"; - else - PPA="ppa:iconnor/zoneminder-$BRANCH"; + if [ "$BRANCH" == "" ]; then + PPA="ppa:iconnor/zoneminder-master"; + else + PPA="ppa:iconnor/zoneminder-$BRANCH"; + fi; fi; fi; @@ -327,6 +332,7 @@ EOF dput $PPA $SC fi; else + echo "dputting to $PPA"; dput $PPA $SC fi; fi; From e821553265ed10a73b4f6d20d3162c2a83b07bed Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 23 Jul 2019 09:54:39 -0400 Subject: [PATCH 02/50] Split MoveTo into CopyTo and MoveTo. --- scripts/ZoneMinder/lib/ZoneMinder/Event.pm | 126 +++++++++++---------- 1 file changed, 69 insertions(+), 57 deletions(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm index 9c6fe3cd5..e1516f1f5 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm @@ -393,61 +393,66 @@ sub delete { sub delete_files { my $event = shift; - my $Storage = @_ ? $_[0] : new ZoneMinder::Storage($$event{StorageId}); - my $storage_path = $Storage->Path(); + foreach my $Storage ( + @_ ? ($_[0]) : ( + new ZoneMinder::Storage($$event{StorageId}), + ( $$event{SecondaryStorageId} ? new ZoneMinder::Storage($$event{SecondaryStorageId}) : () ), + ) ) { + my $storage_path = $Storage->Path(); - if ( ! $storage_path ) { - Error("Empty storage path when deleting files for event $$event{Id} with storage id $$event{StorageId}"); - return; - } - - if ( ! $$event{MonitorId} ) { - Error("No monitor id assigned to event $$event{Id}"); - return; - } - my $event_path = $event->RelativePath(); - Debug("Deleting files for Event $$event{Id} from $storage_path/$event_path, scheme is $$event{Scheme}."); - if ( $event_path ) { - ( $storage_path ) = ( $storage_path =~ /^(.*)$/ ); # De-taint - ( $event_path ) = ( $event_path =~ /^(.*)$/ ); # De-taint - - my $deleted = 0; - if ( $$Storage{Type} and ( $$Storage{Type} eq 's3fs' ) ) { - my ( $aws_id, $aws_secret, $aws_host, $aws_bucket ) = ( $$Storage{Url} =~ /^\s*([^:]+):([^@]+)@([^\/]*)\/(.+)\s*$/ ); - eval { - require Net::Amazon::S3; - my $s3 = Net::Amazon::S3->new( { - aws_access_key_id => $aws_id, - aws_secret_access_key => $aws_secret, - ( $aws_host ? ( host => $aws_host ) : () ), - }); - my $bucket = $s3->bucket($aws_bucket); - if ( ! $bucket ) { - Error("S3 bucket $bucket not found."); - die; - } - if ( $bucket->delete_key($event_path) ) { - $deleted = 1; - } else { - Error('Failed to delete from S3:'.$s3->err . ': ' . $s3->errstr); - } - }; - Error($@) if $@; + if ( ! $storage_path ) { + Error("Empty storage path when deleting files for event $$event{Id} with storage id $$event{StorageId}"); + return; } - if ( !$deleted ) { - my $command = "/bin/rm -rf $storage_path/$event_path"; - ZoneMinder::General::executeShellCommand($command); - } - } - if ( $event->Scheme() eq 'Deep' ) { - my $link_path = $event->LinkPath(); - Debug("Deleting link for Event $$event{Id} from $storage_path/$link_path."); - if ( $link_path ) { - ( $link_path ) = ( $link_path =~ /^(.*)$/ ); # De-taint + if ( ! $$event{MonitorId} ) { + Error("No monitor id assigned to event $$event{Id}"); + return; + } + my $event_path = $event->RelativePath(); + Debug("Deleting files for Event $$event{Id} from $storage_path/$event_path, scheme is $$event{Scheme}."); + if ( $event_path ) { + ( $storage_path ) = ( $storage_path =~ /^(.*)$/ ); # De-taint + ( $event_path ) = ( $event_path =~ /^(.*)$/ ); # De-taint + + my $deleted = 0; + if ( $$Storage{Type} and ( $$Storage{Type} eq 's3fs' ) ) { + my ( $aws_id, $aws_secret, $aws_host, $aws_bucket ) = ( $$Storage{Url} =~ /^\s*([^:]+):([^@]+)@([^\/]*)\/(.+)\s*$/ ); + eval { + require Net::Amazon::S3; + my $s3 = Net::Amazon::S3->new( { + aws_access_key_id => $aws_id, + aws_secret_access_key => $aws_secret, + ( $aws_host ? ( host => $aws_host ) : () ), + }); + my $bucket = $s3->bucket($aws_bucket); + if ( ! $bucket ) { + Error("S3 bucket $bucket not found."); + die; + } + if ( $bucket->delete_key($event_path) ) { + $deleted = 1; + } else { + Error('Failed to delete from S3:'.$s3->err . ': ' . $s3->errstr); + } + }; + Error($@) if $@; + } + if ( !$deleted ) { + my $command = "/bin/rm -rf $storage_path/$event_path"; + ZoneMinder::General::executeShellCommand($command); + } + } # end if event_path + + if ( $event->Scheme() eq 'Deep' ) { + my $link_path = $event->LinkPath(); + Debug("Deleting link for Event $$event{Id} from $storage_path/$link_path."); + if ( $link_path ) { + ( $link_path ) = ( $link_path =~ /^(.*)$/ ); # De-taint unlink($storage_path.'/'.$link_path) or Error("Unable to unlink '$storage_path/$link_path': $!"); - } - } + } + } # end if Scheme eq Deep + } # end foreach Storage } # end sub delete_files sub StorageId { @@ -519,7 +524,7 @@ sub DiskSpace { return $_[0]{DiskSpace}; } -sub MoveTo { +sub CopyTo { my ( $self, $NewStorage ) = @_; my $OldStorage = $self->Storage(undef); @@ -559,7 +564,7 @@ sub MoveTo { $ZoneMinder::Database::dbh->commit(); return "New path and old path are the same! $NewPath"; } - Debug("Moving event $$self{Id} from $OldPath to $NewPath"); + Debug("Copying event $$self{Id} from $OldPath to $NewPath"); my $moved = 0; @@ -650,7 +655,7 @@ Debug("Files to move @files"); last; } my $duration = time - $starttime; - Debug("Copied " . Number::Bytes::Human::format_bytes($size) . " in $duration seconds = " . ($duration?Number::Bytes::Human::format_bytes($size/$duration):'inf') . "/sec"); + Debug('Copied ' . Number::Bytes::Human::format_bytes($size) . " in $duration seconds = " . ($duration?Number::Bytes::Human::format_bytes($size/$duration):'inf') . '/sec'); } # end foreach file. } # end if ! moved @@ -658,6 +663,15 @@ Debug("Files to move @files"); $ZoneMinder::Database::dbh->commit(); return $error; } +} # end sub CopyTo + +sub MoveTo { + + my ( $self, $NewStorage ) = @_; + my $OldStorage = $self->Storage(undef); + + my $error = $self->CopyTo($NewStorage); + return $error if $error; # Succeeded in copying all files, so we may now update the Event. $$self{StorageId} = $$NewStorage{Id}; @@ -667,10 +681,8 @@ Debug("Files to move @files"); $ZoneMinder::Database::dbh->commit(); return $error; } -Debug("Committing"); $ZoneMinder::Database::dbh->commit(); - $self->delete_files( $OldStorage ); -Debug("Done deleting files, returning"); + $self->delete_files($OldStorage); return $error; } # end sub MoveTo From f9b5c8a1f4dcfeb2af820309113d45095db23eea Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 23 Jul 2019 09:55:14 -0400 Subject: [PATCH 03/50] If query is empty don't bother parsing it --- scripts/ZoneMinder/lib/ZoneMinder/Filter.pm | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm index 8383e43b7..1c0dd151b 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm @@ -132,6 +132,12 @@ sub Sql { my $self = shift; $$self{Sql} = shift if @_; if ( ! $$self{Sql} ) { + $self->{Sql} = ''; + if ( ! $self->{Query} ) { + Warning("No query in Filter!"); + return; + } + my $filter_expr = ZoneMinder::General::jsonDecode($self->{Query}); my $sql = 'SELECT E.*, unix_timestamp(E.StartTime) as Time, @@ -142,7 +148,6 @@ sub Sql { INNER JOIN Monitors as M on M.Id = E.MonitorId LEFT JOIN Storage as S on S.Id = E.StorageId '; - $self->{Sql} = ''; if ( $filter_expr->{terms} ) { foreach my $term ( @{$filter_expr->{terms}} ) { From fd95ab23e9a78b116b621bd4498a5701dc2b83fd Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 23 Jul 2019 09:55:27 -0400 Subject: [PATCH 04/50] Add AutoCopy support --- scripts/zmfilter.pl.in | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/scripts/zmfilter.pl.in b/scripts/zmfilter.pl.in index b572d40fa..0b8812c87 100644 --- a/scripts/zmfilter.pl.in +++ b/scripts/zmfilter.pl.in @@ -240,6 +240,7 @@ sub getFilters { or AutoDelete = 1 or UpdateDiskSpace = 1 or AutoMove = 1 + or AutoCopy = 1 ) ORDER BY Name'; my $sth = $dbh->prepare_cached($sql) or Fatal("Unable to prepare '$sql': ".$dbh->errstr()); @@ -283,6 +284,7 @@ sub checkFilter { ($filter->{AutoMessage}?'message':()), ($filter->{AutoExecute}?'execute':()), ($filter->{AutoMove}?'move':()), + ($filter->{AutoCopy}?'copy':()), ($filter->{UpdateDiskSpace}?'update disk space':()), ), 'returned' , scalar @Events , 'events', @@ -300,9 +302,9 @@ sub checkFilter { Info("Archiving event $Event->{Id}"); # Do it individually to avoid locking up the table for new events my $sql = 'UPDATE Events SET Archived = 1 WHERE Id = ?'; - my $sth = $dbh->prepare_cached( $sql ) + my $sth = $dbh->prepare_cached($sql) or Fatal("Unable to prepare '$sql': ".$dbh->errstr()); - my $res = $sth->execute( $Event->{Id} ) + my $res = $sth->execute($Event->{Id}) or Error("Unable to execute '$sql': ".$dbh->errstr()); } if ( $Config{ZM_OPT_FFMPEG} && $filter->{AutoVideo} ) { @@ -343,6 +345,11 @@ sub checkFilter { $_ = $Event->MoveTo($NewStorage); Error($_) if $_; } + if ( $filter->{AutoCopy} ) { + my $NewStorage = new ZoneMinder::Storage($filter->{AutoCopyTo}); + $_ = $Event->CopyTo($NewStorage); + Error($_) if $_; + } if ( $filter->{UpdateDiskSpace} ) { $ZoneMinder::Database::dbh->begin_work(); From b05aff1d5d5e4a339d5f6731dcffe2dfde679ce9 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 23 Jul 2019 09:57:16 -0400 Subject: [PATCH 05/50] Update Filter Object to extend ZM_Object. Rename Query to Query_json and implement a Query function to parse Query_json --- web/includes/Filter.php | 169 ++++++++++------------------------------ 1 file changed, 41 insertions(+), 128 deletions(-) diff --git a/web/includes/Filter.php b/web/includes/Filter.php index 8da602539..f8b06d4e5 100644 --- a/web/includes/Filter.php +++ b/web/includes/Filter.php @@ -1,9 +1,11 @@ null, 'Name' => '', 'AutoExecute' => 0, @@ -16,63 +18,44 @@ public $defaults = array( 'AutoMessage' => 0, 'AutoMove' => 0, 'AutoMoveTo' => 0, + 'AutoCopy' => 0, + 'AutoCopyTo' => 0, 'UpdateDiskSpace' => 0, 'Background' => 0, 'Concurrent' => 0, - 'limit' => 100, - 'Query' => array(), - 'sort_field' => ZM_WEB_EVENT_SORT_FIELD, - 'sort_asc' => ZM_WEB_EVENT_SORT_ORDER, -); + #'limit' => 100, + 'Query_json' => '', + #'sort_field' => ZM_WEB_EVENT_SORT_FIELD, + #'sort_asc' => ZM_WEB_EVENT_SORT_ORDER, + ); - public function __construct( $IdOrRow=NULL ) { - $row = NULL; - if ( $IdOrRow ) { - if ( is_integer($IdOrRow) or is_numeric($IdOrRow) ) { - $row = dbFetchOne('SELECT * FROM Filters WHERE Id=?', NULL, array($IdOrRow)); - if ( ! $row ) { - Error('Unable to load Filter record for Id=' . $IdOrRow); - } - } elseif ( is_array($IdOrRow) ) { - $row = $IdOrRow; - } else { - $backTrace = debug_backtrace(); - $file = $backTrace[1]['file']; - $line = $backTrace[1]['line']; - Error("Unknown argument passed to Filter Constructor from $file:$line)"); - Error("Unknown argument passed to Filter Constructor ($IdOrRow)"); - return; - } - } # end if isset($IdOrRow) + public function Query($new = -1) { + if ( $new and ( $new != -1 ) ) { + $this->{'Query'} = $new; + $this->{'Query_json'} = jsonEncode($new); + Logger::Debug("Setting Query to " . $this->{'Query_json'}); + } + if ( !array_key_exists('Query', $this) ) { + if ( array_key_exists('Query_json', $this) and $this->{'Query_json'} ) { + $this->{'Query'} = jsonDecode($this->{'Query_json'}); + Logger::Debug("Decoded Query already" . print_r($this->{'Query'}, true )); - if ( $row ) { - foreach ($row as $k => $v) { - $this->{$k} = $v; - } - if ( array_key_exists('Query', $this) and $this->{'Query'} ) { - $this->{'Query'} = jsonDecode($this->{'Query'}); } else { + Logger::Debug("No Have Query_json already"); $this->{'Query'} = array(); } - } - } // end function __construct - - public function __call( $fn, array $args ) { - if ( count( $args ) ) { - $this->{$fn} = $args[0]; - } - if ( array_key_exists( $fn, $this ) ) { - return $this->{$fn}; - } else if ( array_key_exists( $fn, $this->defaults ) ) { - $this->{$fn} = $this->defaults{$fn}; - return $this->{$fn}; } else { - - $backTrace = debug_backtrace(); - $file = $backTrace[1]['file']; - $line = $backTrace[1]['line']; - Warning( "Unknown function call Filter->$fn from $file:$line" ); + Logger::Debug("Have Query already" . print_r($this->{'Query'}, true )); } + return $this->{'Query'}; + } + + public static function find( $parameters = array(), $options = array() ) { + return ZM_Object::_find(get_class(), $parameters, $options); + } + + public static function find_one( $parameters = array(), $options = array() ) { + return ZM_Object::_find_one(get_class(), $parameters, $options); } public function terms( ) { @@ -93,101 +76,31 @@ public $defaults = array( if ( isset( $this->Query()['sort_field'] ) ) { return $this->{'Query'}['sort_field']; } - return $this->defaults{'sort_field'}; + return ZM_WEB_EVENT_SORT_FIELD; + #return $this->defaults{'sort_field'}; } + public function sort_asc( ) { if ( func_num_args( ) ) { - $this->{'Query'}['sort_asc'] = func_get_arg(0); + $this->Query()['sort_asc'] = func_get_arg(0); } if ( isset( $this->Query()['sort_asc'] ) ) { return $this->{'Query'}['sort_asc']; } - return $this->defaults{'sort_asc'}; + return ZM_WEB_EVENT_SORT_ORDER; + #return $this->defaults{'sort_asc'}; } + public function limit( ) { if ( func_num_args( ) ) { $this->{'Query'}['limit'] = func_get_arg(0); } if ( isset( $this->Query()['limit'] ) ) return $this->{'Query'}['limit']; - return $this->defaults{'limit'}; + return 100; + #return $this->defaults{'limit'}; } - public static function find( $parameters = null, $options = null ) { - $filters = array(); - $sql = 'SELECT * FROM Filters '; - $values = array(); - - if ( $parameters ) { - $fields = array(); - $sql .= 'WHERE '; - foreach ( $parameters as $field => $value ) { - if ( $value == null ) { - $fields[] = $field.' IS NULL'; - } else if ( is_array( $value ) ) { - $func = function(){return '?';}; - $fields[] = $field.' IN ('.implode(',', array_map($func, $value)). ')'; - $values += $value; - } else { - $fields[] = $field.'=?'; - $values[] = $value; - } - } - $sql .= implode(' AND ', $fields); - } - if ( $options ) { - if ( isset($options['order']) ) { - $sql .= ' ORDER BY ' . $options['order']; - } - if ( isset($options['limit']) ) { - if ( is_integer($options['limit']) or ctype_digit($options['limit']) ) { - $sql .= ' LIMIT ' . $options['limit']; - } else { - $backTrace = debug_backtrace(); - $file = $backTrace[1]['file']; - $line = $backTrace[1]['line']; - Error("Invalid value for limit(".$options['limit'].") passed to Filter::find from $file:$line"); - return array(); - } - } - } - $result = dbQuery($sql, $values); - $results = $result->fetchALL(PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE, 'Filter'); - foreach ( $results as $row => $obj ) { - $filters[] = $obj; - } - return $filters; - } # end find() - - public static function find_one( $parameters = array() ) { - $results = Filter::find($parameters, array('limit'=>1)); - if ( ! sizeof($results) ) { - return; - } - return $results[0]; - } # end find_one() - - public function delete() { - dbQuery('DELETE FROM Filters WHERE Id=?', array($this->{'Id'})); - } # end function delete() - - public function set( $data ) { - foreach ($data as $k => $v) { - if ( is_array( $v ) ) { - $this->{$k} = $v; - } else if ( is_string( $v ) ) { - $this->{$k} = trim( $v ); - } else if ( is_integer( $v ) ) { - $this->{$k} = $v; - } else if ( is_bool( $v ) ) { - $this->{$k} = $v; - } else { - Error( "Unknown type $k => $v of var " . gettype( $v ) ); - $this->{$k} = $v; - } - } - } # end function set - public function control($command, $server_id=null) { $Servers = $server_id ? Server::find(array('Id'=>$server_id)) : Server::find(); if ( !count($Servers) and !$server_id ) { From 7c52f8a4aea74f9541830a9402c7a702d901405a Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 23 Jul 2019 09:57:44 -0400 Subject: [PATCH 06/50] Fixes and add Objects_Indexed_By_Id --- web/includes/Object.php | 230 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 230 insertions(+) create mode 100644 web/includes/Object.php diff --git a/web/includes/Object.php b/web/includes/Object.php new file mode 100644 index 000000000..041b45bed --- /dev/null +++ b/web/includes/Object.php @@ -0,0 +1,230 @@ + $v) { + $this->{$k} = $v; + } + $cache[$row['Id']] = $this; + } else { + # Set defaults + foreach ( $this->defaults as $k => $v ) $this->{$k} = $v; + } + } + + public function __call($fn, array $args){ + if ( count($args) ) { + $this->{$fn} = $args[0]; + } + if ( array_key_exists($fn, $this) ) { + return $this->{$fn}; + } else { + if ( array_key_exists($fn, $this->defaults) ) { + return $this->defaults{$fn}; + } else { + $backTrace = debug_backtrace(); + Warning("Unknown function call Sensor->$fn from ".print_r($backTrace,true)); + } + } + } + + public static function _find($class, $parameters = null, $options = null ) { + $table = $class::$table; + $filters = array(); + $sql = "SELECT * FROM `$table` "; + $values = array(); + + if ( $parameters ) { + $fields = array(); + $sql .= 'WHERE '; + foreach ( $parameters as $field => $value ) { + if ( $value == null ) { + $fields[] = '`'.$field.'` IS NULL'; + } else if ( is_array($value) ) { + $func = function(){return '?';}; + $fields[] = '`'.$field.'` IN ('.implode(',', array_map($func, $value)). ')'; + $values += $value; + + } else { + $fields[] = '`'.$field.'`=?'; + $values[] = $value; + } + } + $sql .= implode(' AND ', $fields ); + } + if ( $options ) { + if ( isset($options['order']) ) { + $sql .= ' ORDER BY ' . $options['order']; + } + if ( isset($options['limit']) ) { + if ( is_integer($options['limit']) or ctype_digit($options['limit']) ) { + $sql .= ' LIMIT ' . $options['limit']; + } else { + Error('Invalid value for limit('.$options['limit'].') passed to '.get_class()."::find from ".print_r($backTrace,true)); + return array(); + } + } + } + $rows = dbFetchAll($sql, NULL, $values); + $results = array(); + if ( $rows ) { + foreach ( $rows as $row ) { + array_push($results , new $class($row)); + } + } + return $results; + } # end public function find() + + public static function _find_one($class, $parameters = array(), $options = array() ) { + global $object_cache; + if ( ! isset($object_cache[$class]) ) + $object_cache[$class] = array(); + $cache = $object_cache[$class]; + if ( + ( count($parameters) == 1 ) and + isset($parameters['Id']) and + isset($cache[$parameters['Id']]) ) { + return $cache[$parameters['Id']]; + } + $options['limit'] = 1; + $results = ZM_Object::_find($class, $parameters, $options); + if ( ! sizeof($results) ) { + return; + } + return $results[0]; + } + + public static function Objects_Indexed_By_Id($class) { + $results = array(); + foreach ( ZM_Object::_find($class, null, array('order'=>'lower(Name)')) as $Object ) { + $results[$Object->Id()] = $Object; + } + return $results; + } + + public function to_json() { + $json = array(); + foreach ($this->defaults as $key => $value) { + if ( is_callable(array($this, $key)) ) { + $json[$key] = $this->$key(); + } else if ( array_key_exists($key, $this) ) { + $json[$key] = $this->{$key}; + } else { + $json[$key] = $this->defaults{$key}; + } + } + return json_encode($json); + } + + public function set($data) { + foreach ( $data as $k => $v ) { + if ( method_exists($this, $k) ) { + $this->{$k}($v); + } else { + if ( is_array($v) ) { +# perhaps should turn into a comma-separated string + $this->{$k} = implode(',', $v); + } else if ( is_string($v) ) { + if ( $v == '' and array_key_exists($k, $this->defaults) ) { + $this->{$k} = $this->defaults[$k]; + } else { + $this->{$k} = trim($v); + } + } else if ( is_integer($v) ) { + $this->{$k} = $v; + } else if ( is_bool($v) ) { + $this->{$k} = $v; + } else if ( is_null($v) ) { + $this->{$k} = $v; + } else { + Error( "Unknown type $k => $v of var " . gettype( $v ) ); + $this->{$k} = $v; + } + } # end if method_exists + } # end foreach $data as $k=>$v + } + + public function changes( $new_values ) { + $changes = array(); + foreach ( $this->defaults as $field=>$default_value ) { + if ( array_key_exists($field, $new_values) ) { + Logger::Debug("Checking default $field => $default_value exists in new values :".$this->{$field} . " " .$new_values[$field]); + if ( (!array_key_exists($field, $this)) or ( $this->{$field} != $new_values[$field] ) ) { + Logger::Debug("Checking default $field => $default_value changes becaause" . $new_values[$field].' != '.$new_values[$field]); + $changes[$field] = $new_values[$field]; + #} else if { + Logger::Debug("Checking default $field => $default_value changes becaause " . $new_values[$field].' != '.$new_values[$field]); + #array_push( $changes, [$field=>$defaults[$field]] ); + } + } else { + Logger::Debug("Checking default $field => $default_value not in new_values"); + } + } # end foreach default + return $changes; + } # end public function changes + + public function save($new_values = null) { + $class = get_class($this); + $table = $class::$table; + + if ( $new_values ) { + Logger::Debug("New values" . print_r($new_values,true)); + $this->set($new_values); + } + + if ( $this->Id() ) { + $fields = array_keys($this->defaults); + $sql = 'UPDATE '.$table.' SET '.implode(', ', array_map(function($field) {return '`'.$field.'`=?';}, $fields )) . ' WHERE Id=?'; + $values = array_map(function($field){return $this->{$field};}, $fields); + $values[] = $this->{'Id'}; + if ( dbQuery($sql, $values) ) + return true; + } else { + $fields = $this->defaults; + unset($fields['Id']); + + $sql = 'INSERT INTO '.$table.' ('.implode(', ', array_map(function($field) {return '`'.$field.'`';}, array_keys($fields))).') VALUES ('.implode(', ', array_map(function($field){return '?';}, array_values($fields))).')'; + $values = array_map(function($field){return $this->{$field};}, array_keys($fields)); + if ( dbQuery($sql, $values) ) { + $this->{'Id'} = dbInsertId(); + return true; + } + } + return false; + } // end function save + + public function delete() { + $class = get_class($this); + $table = $class::$table; + dbQuery("DELETE FROM $table WHERE Id=?", array($this->{'Id'})); + if ( isset($object_cache[$class]) and isset($object_cache[$class][$this->{'Id'}]) ) + unset($object_cache[$class][$this->{'Id'}]); + } + +} # end class Sensor Action +?> From 35ec60ca031f5ee6e3a72d9d53ae2cfe2221b1a4 Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 23 Jul 2019 09:58:05 -0400 Subject: [PATCH 07/50] Change Storage object to extend ZM_Object --- web/includes/Storage.php | 136 +++------------------------------------ 1 file changed, 9 insertions(+), 127 deletions(-) diff --git a/web/includes/Storage.php b/web/includes/Storage.php index b3caa3ffa..8607b52fe 100644 --- a/web/includes/Storage.php +++ b/web/includes/Storage.php @@ -2,10 +2,11 @@ namespace ZM; require_once('database.php'); require_once('Event.php'); +require_once('Object.php'); -$storage_cache = array(); -class Storage { - private $defaults = array( +class Storage extends ZM_Object { + protected static $table = 'Storage'; + protected $defaults = array( 'Id' => null, 'Path' => '', 'Name' => '', @@ -16,31 +17,12 @@ class Storage { 'ServerId' => 0, 'DoDelete' => 1, ); + public static function find($parameters = array(), $options = array() ) { + return ZM_Object::_find(get_class(), $parameters, $options); + } - public function __construct( $IdOrRow = NULL ) { - global $storage_cache; - - $row = NULL; - if ( $IdOrRow ) { - if ( is_integer($IdOrRow) or is_numeric($IdOrRow) ) { - $row = dbFetchOne('SELECT * FROM Storage WHERE Id=?', NULL, array($IdOrRow)); - if ( ! $row ) { - Error('Unable to load Storage record for Id=' . $IdOrRow); - } - } else if ( is_array($IdOrRow) ) { - $row = $IdOrRow; - } - } - if ( $row ) { - foreach ($row as $k => $v) { - $this->{$k} = $v; - } - $storage_cache[$row['Id']] = $this; - } else { - $this->{'Name'} = ''; - $this->{'Path'} = ''; - $this->{'Type'} = 'local'; - } + public static function find_one( $parameters = array(), $options = array() ) { + return ZM_Object::_find_one(get_class(), $parameters, $options); } public function Path() { @@ -66,93 +48,6 @@ class Storage { return $this->{'Name'}; } - public function __call( $fn, array $args= NULL ) { - if ( count($args) ) { - $this->{$fn} = $args[0]; - } - if ( array_key_exists($fn, $this) ) - return $this->{$fn}; - - if ( array_key_exists($fn, $this->defaults) ) - return $this->defaults{$fn}; - - $backTrace = debug_backtrace(); - $file = $backTrace[0]['file']; - $line = $backTrace[0]['line']; - Warning("Unknown function call Storage->$fn from $file:$line"); - $file = $backTrace[1]['file']; - $line = $backTrace[1]['line']; - Warning("Unknown function call Storage->$fn from $file:$line"); - } - - public static function find_one( $parameters = null, $options = null ) { - global $storage_cache; - if ( - ( count($parameters) == 1 ) and - isset($parameters['Id']) and - isset($storage_cache[$parameters['Id']]) ) { - return $storage_cache[$parameters['Id']]; - } - - $results = Storage::find($parameters, $options); - if ( count($results) > 1 ) { - Error('Storage Returned more than 1'); - return $results[0]; - } else if ( count($results) ) { - return $results[0]; - } else { - return null; - } - } - public static function find( $parameters = null, $options = null ) { - $sql = 'SELECT * FROM Storage '; - $values = array(); - - if ( $parameters ) { - $fields = array(); - $sql .= 'WHERE '; - foreach ( $parameters as $field => $value ) { - if ( $value == null ) { - $fields[] = $field.' IS NULL'; - } else if ( is_array($value) ) { - $func = function(){return '?';}; - $fields[] = $field.' IN ('.implode(',', array_map($func, $value)).')'; - $values += $value; - - } else { - $fields[] = $field.'=?'; - $values[] = $value; - } - } - $sql .= implode(' AND ', $fields); - } # end if parameters - if ( $options ) { - if ( isset($options['order']) ) { - $sql .= ' ORDER BY ' . $options['order']; - } # end if options - if ( isset($options['limit']) ) { - if ( is_integer($options['limit']) or ctype_digit($options['limit']) ) { - $sql .= ' LIMIT ' . $option['limit']; - } else { - $backTrace = debug_backtrace(); - $file = $backTrace[1]['file']; - $line = $backTrace[1]['line']; - Error("Invalid value for limit(".$options['limit'].") passed to Control::find from $file:$line"); - return array(); - } - } # end if limit - } # end if options - $storage_areas = array(); - $result = dbQuery($sql, $values); - if ( $result ) { - $results = $result->fetchALL(); - foreach ( $results as $row ) { - $storage_areas[] = new Storage($row); - } - } - return $storage_areas; - } # end find() - public function disk_usage_percent() { $path = $this->Path(); if ( ! $path ) { @@ -226,18 +121,5 @@ class Storage { return $this->{'Server'}; } - public function to_json() { - $json = array(); - foreach ($this->defaults as $key => $value) { - if ( is_callable(array($this, $key)) ) { - $json[$key] = $this->$key(); - } else if ( array_key_exists($key, $this) ) { - $json[$key] = $this->{$key}; - } else { - $json[$key] = $this->defaults{$key}; - } - } - return json_encode($json); - } } // end class Storage ?> From 9b6dedb35d4399bc7e8fc13083d103e1d1ad766f Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 23 Jul 2019 09:58:28 -0400 Subject: [PATCH 08/50] Update Filter saving action to use object set/save etc --- web/includes/actions/filter.php | 37 ++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/web/includes/actions/filter.php b/web/includes/actions/filter.php index 6b3019ee5..8548f21d1 100644 --- a/web/includes/actions/filter.php +++ b/web/includes/actions/filter.php @@ -51,11 +51,30 @@ if ( isset($_REQUEST['object']) and ( $_REQUEST['object'] == 'filter' ) ) { $_REQUEST['filter']['Query']['sort_asc'] = validStr($_REQUEST['filter']['Query']['sort_asc']); $_REQUEST['filter']['Query']['limit'] = validInt($_REQUEST['filter']['Query']['limit']); if ( $action == 'execute' ) { - $tempFilterName = '_TempFilter'.time(); - $sql .= ' Name = \''.$tempFilterName.'\''; - } else { - $sql .= ' Name = '.dbEscape($_REQUEST['filter']['Name']); + $_REQUEST['filter']['Name'] = '_TempFilter'.time(); + unset($_REQUEST['Id']); + #$tempFilterName = '_TempFilter'.time(); + #$sql .= ' Name = \''.$tempFilterName.'\''; + #} else { + #$sql .= ' Name = '.dbEscape($_REQUEST['filter']['Name']); } + + $_REQUEST['filter']['AutoCopy'] = empty($_REQUEST['filter']['AutoCopy']) ? 0 : 1; + $_REQUEST['filter']['AutoMove'] = empty($_REQUEST['filter']['AutoMove']) ? 0 : 1; + $_REQUEST['filter']['AutoArchive'] = empty($_REQUEST['filter']['AutoArchive']) ? 0 : 1; + $_REQUEST['filter']['AutoVideo'] = empty($_REQUEST['filter']['AutoVideo']) ? 0 : 1; + $_REQUEST['filter']['AutoUpload'] = empty($_REQUEST['filter']['AutoUpload']) ? 0 : 1; + $_REQUEST['filter']['AutoEmail'] = empty($_REQUEST['filter']['AutoEmail']) ? 0 : 1; + $_REQUEST['filter']['AutoMessage'] = empty($_REQUEST['filter']['AutoMessage']) ? 0 : 1; + $_REQUEST['filter']['AutoExecute'] = empty($_REQUEST['filter']['AutoExecute']) ? 0 : 1; + $_REQUEST['filter']['AutoDelete'] = empty($_REQUEST['filter']['AutoDelete']) ? 0 : 1; + $_REQUEST['filter']['UpdateDiskSpace'] = empty($_REQUEST['filter']['UpdateDiskSpace']) ? 0 : 1; + $_REQUEST['filter']['Background'] = empty($_REQUEST['filter']['Background']) ? 0 : 1; + $_REQUEST['filter']['Concurrent'] = empty($_REQUEST['filter']['Concurrent']) ? 0 : 1; + $changes = $filter->changes($_REQUEST['filter']); + ZM\Logger::Debug("Changes: " . print_r($changes,true)); + + if ( 0 ) { $sql .= ', Query = '.dbEscape(jsonEncode($_REQUEST['filter']['Query'])); $sql .= ', AutoArchive = '.(!empty($_REQUEST['filter']['AutoArchive']) ? 1 : 0); $sql .= ', AutoVideo = '. ( !empty($_REQUEST['filter']['AutoVideo']) ? 1 : 0); @@ -73,17 +92,25 @@ if ( isset($_REQUEST['object']) and ( $_REQUEST['object'] == 'filter' ) ) { $sql .= ', UpdateDiskSpace = '. ( !empty($_REQUEST['filter']['UpdateDiskSpace']) ? 1 : 0); $sql .= ', Background = '. ( !empty($_REQUEST['filter']['Background']) ? 1 : 0); $sql .= ', Concurrent = '. ( !empty($_REQUEST['filter']['Concurrent']) ? 1 : 0); + } if ( $_REQUEST['Id'] and ( $action == 'Save' ) ) { + if ( 0 ) { dbQuery('UPDATE Filters SET '.$sql.' WHERE Id=?', array($_REQUEST['Id'])); + } + $filter->save($changes); if ( $filter->Background() ) $filter->control('stop'); } else { + # COuld be execute + if ( 0 ) { dbQuery('INSERT INTO Filters SET'.$sql); $_REQUEST['Id'] = dbInsertId(); $filter = new ZM\Filter($_REQUEST['Id']); + } + $filter->save($changes); } - if ( !empty($_REQUEST['filter']['Background']) ) + if ( $filter->Background() ) $filter->control('start'); if ( $action == 'execute' ) { From 346933126d822e89d0c75d21545f2ac108a5364f Mon Sep 17 00:00:00 2001 From: Isaac Connor Date: Tue, 23 Jul 2019 09:59:49 -0400 Subject: [PATCH 09/50] Update filter view to use Filter::find --- web/skins/classic/views/filter.php | 61 ++++++++++++++++-------------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/web/skins/classic/views/filter.php b/web/skins/classic/views/filter.php index 1eb4b71c3..893d1ecbd 100644 --- a/web/skins/classic/views/filter.php +++ b/web/skins/classic/views/filter.php @@ -22,36 +22,35 @@ if ( !canView('Events') ) { $view = 'error'; return; } -require_once 'includes/Filter.php'; +require_once('includes/Object.php'); +require_once('includes/Storage.php'); +require_once('includes/Filter.php'); parseSort(); -$filterNames = array( ''=>translate('ChooseFilter') ); +$filterNames = array(''=>translate('ChooseFilter')); $filter = NULL; -foreach ( dbFetchAll('SELECT * FROM Filters ORDER BY Name') as $row ) { - $filterNames[$row['Id']] = $row['Id'] . ' ' . $row['Name']; - if ( $row['Background'] ) - $filterNames[$row['Id']] .= '*'; - if ( $row['Concurrent'] ) - $filterNames[$row['Id']] .= '&'; +foreach ( ZM\Filter::find(null,array('order'=>'lower(Name)')) as $Filter ) { + $filterNames[$Filter->Id()] = $Filter->Id() . ' ' . $Filter->Name(); + if ( $Filter->Background() ) + $filterNames[$Filter->Id()] .= '*'; + if ( $Filter->Concurrent() ) + $filterNames[$Filter->Id()] .= '&'; - if ( isset($_REQUEST['Id']) && $_REQUEST['Id'] == $row['Id'] ) { - $filter = new ZM\Filter($row); + if ( isset($_REQUEST['Id']) && ($_REQUEST['Id'] == $Filter->Id()) ) { + $filter = $Filter; } } -if ( ! $filter ) { +if ( !$filter ) { $filter = new ZM\Filter(); } -if ( isset($_REQUEST['sort_field']) && isset($_REQUEST['filter']) ) { - $_REQUEST['filter']['Query']['sort_field'] = $_REQUEST['sort_field']; - $_REQUEST['filter']['Query']['sort_asc'] = $_REQUEST['sort_asc']; - $_REQUEST['filter']['Query']['limit'] = $_REQUEST['limit']; -} +ZM\Logger::Debug("Query: " . $filter->Query_json()); +ZM\Logger::Debug("Query: " . print_r($filter->Query(), true)); if ( isset($_REQUEST['filter']) ) { - $filter->set($_REQUEST['filter']); # Update our filter object with whatever changes we have made before saving + #$filter->set($_REQUEST['filter']); } $conjunctionTypes = getFilterQueryConjunctionTypes(); @@ -97,12 +96,13 @@ $attrTypes = array( 'DiskPercent' => translate('AttrDiskPercent'), 'DiskSpace' => translate('AttrDiskSpace'), 'SystemLoad' => translate('AttrSystemLoad'), - 'StorageId' => translate('AttrStorageArea'), - 'ServerId' => translate('AttrMonitorServer'), + 'StorageId' => translate('AttrStorageArea'), + 'SecondaryStorageId' => translate('AttrSecondaryStorageArea'), + 'ServerId' => translate('AttrMonitorServer'), 'FilterServerId' => translate('AttrFilterServer'), 'MonitorServerId' => translate('AttrMonitorServer'), 'StorageServerId' => translate('AttrStorageServer'), - 'StateId' => translate('AttrStateId'), + 'StateId' => translate('AttrStateId'), ); $opTypes = array( @@ -127,27 +127,24 @@ $archiveTypes = array( $focusWindow = true; -$storageareas = array('' => 'All'); -//$storageareas[0] = 'Default ' . ZM_DIR_EVENTS; -foreach ( dbFetchAll('SELECT Id,Name FROM Storage ORDER BY lower(Name) ASC') as $storage ) { - $storageareas[$storage['Id']] = $storage['Name']; -} +$storageareas = array('' => 'All') + ZM\ZM_Object::Objects_Indexed_By_Id('ZM\Storage'); + $weekdays = array(); for ( $i = 0; $i < 7; $i++ ) { $weekdays[$i] = strftime('%A', mktime(12, 0, 0, 1, $i+1, 2001)); } $states = array(); -foreach ( dbFetchAll('SELECT Id, Name FROM States ORDER BY lower(Name) ASC') as $state_row ) { +foreach ( dbFetchAll('SELECT `Id`, `Name` FROM `States` ORDER BY lower(`Name`) ASC') as $state_row ) { $states[$state_row['Id']] = validHtmlStr($state_row['Name']); } $servers = array(); $servers['ZM_SERVER_ID'] = 'Current Server'; $servers['NULL'] = 'No Server'; -foreach ( dbFetchAll('SELECT Id, Name FROM Servers ORDER BY lower(Name) ASC') as $server ) { +foreach ( dbFetchAll('SELECT `Id`, `Name` FROM `Servers` ORDER BY lower(`Name`) ASC') as $server ) { $servers[$server['Id']] = validHtmlStr($server['Name']); } $monitors = array(); -foreach ( dbFetchAll('SELECT Id, Name FROM Monitors ORDER BY Name ASC') as $monitor ) { +foreach ( dbFetchAll('SELECT `Id`, `Name` FROM `Monitors` ORDER BY lower(`Name`) ASC') as $monitor ) { if ( visibleMonitor($monitor['Id']) ) { $monitors[$monitor['Name']] = validHtmlStr($monitor['Name']); } @@ -391,7 +388,13 @@ if ( ZM_OPT_MESSAGE ) { AutoDelete() ) { ?> checked="checked" data-on-click-this="updateButtons"/>

-

+

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

+

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

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