Implement rmrecover script. Flush out Object code to support find, find_one, find_sql, improve to_string, etc.
This commit is contained in:
parent
4b24bf4e36
commit
451c42ddf5
|
@ -10,6 +10,7 @@ configure_file(zmdc.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmdc.pl" @ONLY)
|
|||
configure_file(zmfilter.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmfilter.pl" @ONLY)
|
||||
configure_file(zmonvif-probe.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmonvif-probe.pl" @ONLY)
|
||||
configure_file(zmpkg.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmpkg.pl" @ONLY)
|
||||
configure_file(zmrecover.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmrecover.pl" @ONLY)
|
||||
configure_file(zmtrack.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmtrack.pl" @ONLY)
|
||||
configure_file(zmtrigger.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmtrigger.pl" @ONLY)
|
||||
configure_file(zmupdate.pl.in "${CMAKE_CURRENT_BINARY_DIR}/zmupdate.pl" @ONLY)
|
||||
|
@ -35,7 +36,7 @@ FOREACH(PERLSCRIPT ${perlscripts})
|
|||
ENDFOREACH(PERLSCRIPT ${perlscripts})
|
||||
|
||||
# Install the perl scripts
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zmaudit.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmcontrol.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmdc.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmfilter.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmonvif-probe.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmpkg.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmtrack.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmtrigger.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmupdate.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmvideo.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmwatch.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmcamtool.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmtelemetry.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmstats.pl" DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zmaudit.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmcontrol.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmdc.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmfilter.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmonvif-probe.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmpkg.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmrecover.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmtrack.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmtrigger.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmupdate.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmvideo.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmwatch.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmcamtool.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmtelemetry.pl" "${CMAKE_CURRENT_BINARY_DIR}/zmstats.pl" DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
|
||||
if(NOT ZM_NO_X10)
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/zmx10.pl" DESTINATION "${CMAKE_INSTALL_FULL_BINDIR}" PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
|
||||
endif(NOT ZM_NO_X10)
|
||||
|
|
|
@ -52,7 +52,7 @@ use ZoneMinder::Logger qw(:all);
|
|||
use ZoneMinder::Database qw(:all);
|
||||
require Date::Parse;
|
||||
|
||||
use vars qw/ $table $primary_key %fields $serial @identified_by/;
|
||||
use vars qw/ $table $primary_key %fields $serial @identified_by %defaults/;
|
||||
$table = 'Events';
|
||||
@identified_by = ('Id');
|
||||
$serial = $primary_key = 'Id';
|
||||
|
@ -84,6 +84,16 @@ $serial = $primary_key = 'Id';
|
|||
Orientation
|
||||
DiskSpace
|
||||
);
|
||||
%defaults = (
|
||||
Cause => q`'Unknown'`,
|
||||
TotScore => '0',
|
||||
Archived => '0',
|
||||
Videoed => '0',
|
||||
Uploaded => '0',
|
||||
Emailed => '0',
|
||||
Messaged => '0',
|
||||
Executed => '0',
|
||||
);
|
||||
|
||||
use POSIX;
|
||||
|
||||
|
@ -99,56 +109,10 @@ sub Time {
|
|||
return $_[0]{Time};
|
||||
}
|
||||
|
||||
sub Name {
|
||||
if ( @_ > 1 ) {
|
||||
$_[0]{Name} = $_[1];
|
||||
}
|
||||
return $_[0]{Name};
|
||||
} # end sub Name
|
||||
|
||||
sub find {
|
||||
shift if $_[0] eq 'ZoneMinder::Event';
|
||||
my %sql_filters = @_;
|
||||
|
||||
my $sql = 'SELECT * FROM Events';
|
||||
my @sql_filters;
|
||||
my @sql_values;
|
||||
|
||||
if ( exists $sql_filters{Name} ) {
|
||||
push @sql_filters , ' Name = ? ';
|
||||
push @sql_values, $sql_filters{Name};
|
||||
}
|
||||
if ( exists $sql_filters{Id} ) {
|
||||
push @sql_filters , ' Id = ? ';
|
||||
push @sql_values, $sql_filters{Id};
|
||||
}
|
||||
|
||||
$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::Event( $$db_filter{Id}, $db_filter );
|
||||
push @results, $filter;
|
||||
} # end while
|
||||
$sth->finish();
|
||||
return @results;
|
||||
}
|
||||
|
||||
sub find_one {
|
||||
my @results = find(@_);
|
||||
return $results[0] if @results;
|
||||
}
|
||||
|
||||
sub getPath {
|
||||
return Path( @_ );
|
||||
}
|
||||
|
||||
sub Path {
|
||||
my $event = shift;
|
||||
|
||||
|
@ -168,6 +132,9 @@ sub Path {
|
|||
|
||||
sub Scheme {
|
||||
my $self = shift;
|
||||
|
||||
$$self{Scheme} = shift if @_;
|
||||
|
||||
if ( ! $$self{Scheme} ) {
|
||||
if ( $$self{RelativePath} ) {
|
||||
if ( $$self{RelativePath} =~ /^\d+\/\d{4}\-\d{2}\-\d{2}\/\d+$/ ) {
|
||||
|
@ -182,9 +149,8 @@ sub Scheme {
|
|||
|
||||
sub RelativePath {
|
||||
my $event = shift;
|
||||
if ( @_ ) {
|
||||
$$event{RelativePath} = $_[0];
|
||||
}
|
||||
|
||||
$$event{RelativePath} = shift if @_;
|
||||
|
||||
if ( ! $$event{RelativePath} ) {
|
||||
if ( $$event{Scheme} eq 'Deep' ) {
|
||||
|
@ -203,7 +169,7 @@ sub RelativePath {
|
|||
if ( $event->Time() ) {
|
||||
$$event{RelativePath} = join('/',
|
||||
$event->{MonitorId},
|
||||
strftime( '%Y-%m-%d', localtime($event->Time())),
|
||||
strftime('%Y-%m-%d', localtime($event->Time())),
|
||||
$event->{Id},
|
||||
);
|
||||
} else {
|
||||
|
@ -223,9 +189,8 @@ sub RelativePath {
|
|||
|
||||
sub LinkPath {
|
||||
my $event = shift;
|
||||
if ( @_ ) {
|
||||
$$event{LinkPath} = $_[0];
|
||||
}
|
||||
|
||||
$$event{LinkPath} = shift if @_;
|
||||
|
||||
if ( ! $$event{LinkPath} ) {
|
||||
if ( $$event{Scheme} eq 'Deep' ) {
|
||||
|
@ -351,19 +316,19 @@ sub GenerateVideo {
|
|||
.$Config{ZM_FFMPEG_OUTPUT_OPTIONS}
|
||||
." '$video_file' > ffmpeg.log 2>&1"
|
||||
;
|
||||
Debug( $command."\n" );
|
||||
Debug($command);
|
||||
my $output = qx($command);
|
||||
|
||||
my $status = $? >> 8;
|
||||
if ( $status ) {
|
||||
Error( "Unable to generate video, check $event_path/ffmpeg.log for details");
|
||||
Error("Unable to generate video, check $event_path/ffmpeg.log for details");
|
||||
return;
|
||||
}
|
||||
|
||||
Info( "Finished $video_file\n" );
|
||||
Info("Finished $video_file");
|
||||
return $event_path.'/'.$video_file;
|
||||
} else {
|
||||
Info( "Video file $video_file already exists for event $self->{Id}\n" );
|
||||
Info("Video file $video_file already exists for event $self->{Id}");
|
||||
return $event_path.'/'.$video_file;
|
||||
}
|
||||
return;
|
||||
|
@ -373,14 +338,14 @@ sub delete {
|
|||
my $event = $_[0];
|
||||
if ( ! ( $event->{Id} and $event->{MonitorId} and $event->{StartTime} ) ) {
|
||||
my ( $caller, undef, $line ) = caller;
|
||||
Warning("Can't Delete event $event->{Id} from Monitor $event->{MonitorId} StartTime:$event->{StartTime} from $caller:$line\n");
|
||||
Warning("Can't delete event $event->{Id} from Monitor $event->{MonitorId} StartTime:$event->{StartTime} from $caller:$line");
|
||||
return;
|
||||
}
|
||||
if ( ! -e $event->Storage()->Path() ) {
|
||||
Warning("Not deleting event because storage path doesn't exist");
|
||||
return;
|
||||
}
|
||||
Info("Deleting event $event->{Id} from Monitor $event->{MonitorId} StartTime:$event->{StartTime}\n");
|
||||
Info("Deleting event $event->{Id} from Monitor $event->{MonitorId} StartTime:$event->{StartTime}");
|
||||
$ZoneMinder::Database::dbh->ping();
|
||||
|
||||
$ZoneMinder::Database::dbh->begin_work();
|
||||
|
@ -697,6 +662,63 @@ Debug("Done deleting files, returning");
|
|||
return $error;
|
||||
} # end sub MoveTo
|
||||
|
||||
# Assumes $path is absolute
|
||||
#
|
||||
sub recover_timestamps {
|
||||
my ( $Event, $path ) = @_;
|
||||
$path = $Event->Path() if ! $path;
|
||||
|
||||
if ( ! opendir(DIR, $path) ) {
|
||||
Error("Can't open directory '$path': $!");
|
||||
next;
|
||||
}
|
||||
my @contents = readdir(DIR);
|
||||
Debug('Have ' . @contents . " files in $path");
|
||||
closedir(DIR);
|
||||
|
||||
my @mp4_files = grep( /^\d+\-video\.mp4$/, @contents);
|
||||
my @capture_jpgs = grep( /^\d+\-capture\.jpg$/, @contents);
|
||||
|
||||
if ( @capture_jpgs ) {
|
||||
# can get start and end times from stat'ing first and last jpg
|
||||
@capture_jpgs = sort { $a cmp $b } @capture_jpgs;
|
||||
my $first_file = "$path/$capture_jpgs[0]";
|
||||
( $first_file ) = $first_file =~ /^(.*)$/;
|
||||
my $first_timestamp = (stat($first_file))[9];
|
||||
|
||||
my $last_file = "$path/$capture_jpgs[@capture_jpgs-1]";
|
||||
( $last_file ) = $last_file =~ /^(.*)$/;
|
||||
my $last_timestamp = (stat($last_file))[9];
|
||||
|
||||
my $duration = $last_timestamp - $first_timestamp;
|
||||
$Event->Length($duration);
|
||||
$Event->StartTime( Date::Format::time2str('%Y-%m-%d %H:%M:%S', $first_timestamp) );
|
||||
$Event->EndTime( Date::Format::time2str('%Y-%m-%d %H:%M:%S', $last_timestamp) );
|
||||
Debug("From capture Jpegs have duration $duration = $last_timestamp - $first_timestamp : $$Event{StartTime} to $$Event{EndTime}");
|
||||
} elsif ( @mp4_files ) {
|
||||
my $file = "$path/$mp4_files[0]";
|
||||
( $file ) = $file =~ /^(.*)$/;
|
||||
|
||||
my $first_timestamp = (stat($file))[9];
|
||||
my $output = `ffprobe $file 2>&1`;
|
||||
my ($duration) = $output =~ /Duration: [:\.0-9]+/gm;
|
||||
Debug("From mp4 have duration $duration, start: $first_timestamp");
|
||||
|
||||
my ( $h, $m, $s, $u );
|
||||
if ( $duration =~ m/(\d+):(\d+):(\d+)\.(\d+)/ ) {
|
||||
( $h, $m, $s, $u ) = ($1, $2, $3, $4 );
|
||||
Debug("( $h, $m, $s, $u ) from /^(\\d{2}):(\\d{2}):(\\d{2})\.(\\d+)/");
|
||||
}
|
||||
my $seconds = ($h*60*60)+($m*60)+$s;
|
||||
$Event->Length($seconds.'.'.$u);
|
||||
$Event->StartTime( Date::Format::time2str('%Y-%m-%d %H:%M:%S', $first_timestamp) );
|
||||
$Event->EndTime( Date::Format::time2str('%Y-%m-%d %H:%M:%S', $first_timestamp+$seconds) );
|
||||
}
|
||||
if ( @mp4_files ) {
|
||||
$Event->DefaultVideo($mp4_files[0]);
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
|
|
|
@ -36,17 +36,6 @@ require ZoneMinder::Server;
|
|||
#our @ISA = qw(Exporter ZoneMinder::Base);
|
||||
use parent qw(ZoneMinder::Object);
|
||||
|
||||
# ==========================================================================
|
||||
#
|
||||
# General Utility Functions
|
||||
#
|
||||
# ==========================================================================
|
||||
|
||||
use ZoneMinder::Config qw(:all);
|
||||
use ZoneMinder::Logger qw(:all);
|
||||
use ZoneMinder::Database qw(:all);
|
||||
|
||||
use POSIX;
|
||||
use vars qw/ $table $primary_key /;
|
||||
$table = 'Monitors';
|
||||
$primary_key = 'Id';
|
||||
|
|
|
@ -27,6 +27,8 @@ package ZoneMinder::Object;
|
|||
use 5.006;
|
||||
use strict;
|
||||
use warnings;
|
||||
use Time::HiRes qw{ gettimeofday tv_interval };
|
||||
use Carp qw( cluck );
|
||||
|
||||
require ZoneMinder::Base;
|
||||
|
||||
|
@ -49,7 +51,7 @@ use vars qw/ $AUTOLOAD $log $dbh %cache $no_cache/;
|
|||
|
||||
my $debug = 0;
|
||||
$no_cache = 0;
|
||||
use constant DEBUG_ALL=>0;
|
||||
use constant DEBUG_ALL=>1;
|
||||
|
||||
sub init_cache {
|
||||
$no_cache = 0;
|
||||
|
@ -167,17 +169,6 @@ sub lock_and_load {
|
|||
} # end sub lock_and_load
|
||||
|
||||
|
||||
sub AUTOLOAD {
|
||||
my ( $self, $newvalue ) = @_;
|
||||
my $type = ref($_[0]);
|
||||
my $name = $AUTOLOAD;
|
||||
$name =~ s/.*://;
|
||||
if ( @_ > 1 ) {
|
||||
return $_[0]{$name} = $_[1];
|
||||
}
|
||||
return $_[0]{$name};
|
||||
}
|
||||
|
||||
sub save {
|
||||
my ( $self, $data, $force_insert ) = @_;
|
||||
|
||||
|
@ -187,7 +178,12 @@ sub save {
|
|||
$log->error("No type in Object::save. self:$self from $caller:$line");
|
||||
}
|
||||
my $local_dbh = eval '$'.$type.'::dbh';
|
||||
$local_dbh = $ZoneMinder::Database::dbh if ! $local_dbh;
|
||||
if ( ! $local_dbh ) {
|
||||
$local_dbh = $ZoneMinder::Database::dbh;
|
||||
if ( $debug or DEBUG_ALL ) {
|
||||
$log->debug("Using global dbh");
|
||||
}
|
||||
}
|
||||
$self->set( $data ? $data : {} );
|
||||
if ( $debug or DEBUG_ALL ) {
|
||||
if ( $data ) {
|
||||
|
@ -196,7 +192,6 @@ sub save {
|
|||
}
|
||||
}
|
||||
}
|
||||
#$debug = 0;
|
||||
|
||||
my $table = eval '$'.$type.'::table';
|
||||
my $fields = eval '\%'.$type.'::fields';
|
||||
|
@ -297,6 +292,7 @@ $log->debug("No serial") if $debug;
|
|||
|
||||
if ( $need_serial ) {
|
||||
if ( $serial ) {
|
||||
$log->debug("Getting auto_increments");
|
||||
my $s = qq{SELECT `auto_increment` FROM INFORMATION_SCHEMA.TABLES WHERE table_name = '$table'};
|
||||
@$self{@identified_by} = @sql{@$fields{@identified_by}} = $local_dbh->selectrow_array( $s );
|
||||
#@$self{@identified_by} = @sql{@$fields{@identified_by}} = $local_dbh->selectrow_array( q{SELECT nextval('} . $serial . q{')} );
|
||||
|
@ -368,6 +364,7 @@ sub set {
|
|||
$log->warn("ZoneMinder::Object::set called on an object ($type) with no fields".$@);
|
||||
} # end if
|
||||
my %defaults = eval('%'.$type.'::defaults');
|
||||
|
||||
if ( ref $params ne 'HASH' ) {
|
||||
my ( $caller, undef, $line ) = caller;
|
||||
$log->error("$type -> set called with non-hash params from $caller $line");
|
||||
|
@ -456,7 +453,420 @@ sub transform {
|
|||
sub to_string {
|
||||
my $type = ref($_[0]);
|
||||
my $fields = eval '\%'.$type.'::fields';
|
||||
return $type . ': '. join(' ' , map { $_[0]{$_} ? "$_ => $_[0]{$_}" : () } keys %$fields );
|
||||
if ( $fields and %{$fields} ) {
|
||||
return $type . ': '. join(' ', map { $_[0]{$_} ? "$_ => $_[0]{$_}" : () } sort { $a cmp $b } keys %$fields );
|
||||
}
|
||||
return $type . ': '. join(' ', map { $_ .' => '.(defined $_[0]{$_} ? $_[0]{$_} : 'undef') } sort { $a cmp $b } keys %{$_[0]} );
|
||||
}
|
||||
|
||||
# We make this a separate function so that we can use it to generate the sql statements for each value in an OR
|
||||
sub find_operators {
|
||||
my ( $field, $type, $operator, $value ) = @_;
|
||||
$log->debug("find_operators: field($field) type($type) op($operator) value($value)") if DEBUG_ALL;
|
||||
|
||||
my $add_placeholder = ( ! ( $field =~ /\?/ ) ) ? 1 : 0;
|
||||
|
||||
if ( sets::isin( $operator, [ '=', '!=', '<', '>', '<=', '>=', '<<=' ] ) ) {
|
||||
return ( $field.$type.' ' . $operator . ( $add_placeholder ? ' ?' : '' ), $value );
|
||||
} elsif ( $operator eq 'not' ) {
|
||||
return ( '( NOT ' . $field.$type.')', $value );
|
||||
} elsif ( sets::isin( $operator, [ '&&', '<@', '@>' ] ) ) {
|
||||
if ( ref $value eq 'ARRAY' ) {
|
||||
if ( $field =~ /^\(/ ) {
|
||||
return ( 'ARRAY('.$field.$type.') ' . $operator . ' ?', $value );
|
||||
} else {
|
||||
return ( $field.$type.' ' . $operator . ' ?', $value );
|
||||
} # emd of
|
||||
} else {
|
||||
return ( $field.$type.' ' . $operator . ' ?', [ $value ] );
|
||||
} # end if
|
||||
} elsif ( $operator eq 'exists' ) {
|
||||
return ( $value ? '' : 'NOT ' ) . 'EXISTS ' . $field.$type;
|
||||
} elsif ( sets::isin( $operator, [ 'in', 'not in' ] ) ) {
|
||||
if ( ref $value eq 'ARRAY' ) {
|
||||
return ( $field.$type.' ' . $operator . ' ('. join(',', map { '?' } @{$value} ) . ')', @{$value} );
|
||||
} else {
|
||||
return ( $field.$type.' ' . $operator . ' (?)', $value );
|
||||
} # end if
|
||||
} elsif ( $operator eq 'contains' ) {
|
||||
return ( '? IN '.$field.$type, $value );
|
||||
} elsif ( $operator eq 'does not contain' ) {
|
||||
return ( '? NOT IN '.$field.$type, $value );
|
||||
} elsif ( sets::isin( $operator, [ 'like','ilike' ] ) ) {
|
||||
return $field.'::text ' . $operator . ' ?', $value;
|
||||
} elsif ( $operator eq 'null_or_<=' ) {
|
||||
return '('.$field.$type.' IS NULL OR '.$field.$type.' <= ?)', $value;
|
||||
} elsif ( $operator eq 'is null or <=' ) {
|
||||
return '('.$field.$type.' IS NULL OR '.$field.$type.' <= ?)', $value;
|
||||
} elsif ( $operator eq 'null_or_>=' ) {
|
||||
return '('.$field.$type.' IS NULL OR '.$field.$type.' >= ?)', $value;
|
||||
} elsif ( $operator eq 'is null or >=' ) {
|
||||
return '('.$field.$type.' IS NULL OR '.$field.$type.' >= ?)', $value;
|
||||
} elsif ( $operator eq 'null_or_>' or $operator eq 'is null or >' ) {
|
||||
return '('.$field.$type.' IS NULL OR '.$field.$type.' > ?)', $value;
|
||||
} elsif ( $operator eq 'null_or_<' or $operator eq 'is null or <' ) {
|
||||
return '('.$field.$type.' IS NULL OR '.$field.$type.' < ?)', $value;
|
||||
} elsif ( $operator eq 'null_or_=' or $operator eq 'is null or =' ) {
|
||||
return '('.$field.$type.' IS NULL OR '.$field.$type.' = ?)', $value;
|
||||
} elsif ( $operator eq 'null or in' or $operator eq 'is null or in' ) {
|
||||
return '('.$field.$type.' IS NULL OR '.$field.$type.' IN ('.join(',', map { '?' } @{$value} ) . '))', @{$value};
|
||||
} elsif ( $operator eq 'null or not in' ) {
|
||||
return '('.$field.$type.' IS NULL OR '.$field.$type.' NOT IN ('.join(',', map { '?' } @{$value} ) . '))', @{$value};
|
||||
} elsif ( $operator eq 'exists' ) {
|
||||
return ( $value ? ' EXISTS ' : 'NOT EXISTS ' ).$field;
|
||||
} elsif ( $operator eq 'lc' ) {
|
||||
return 'lower('.$field.$type.') = ?', $value;
|
||||
} elsif ( $operator eq 'uc' ) {
|
||||
return 'upper('.$field.$type.') = ?', $value;
|
||||
} elsif ( $operator eq 'trunc' ) {
|
||||
return 'trunc('.$field.$type.') = ?', $value;
|
||||
} elsif ( $operator eq 'any' ) {
|
||||
if ( ref $value eq 'ARRAY' ) {
|
||||
return '(' . join(',', map { '?' } @{$value} ).") = ANY($field)", @{$value};
|
||||
} else {
|
||||
return "? = ANY($field)", $value;
|
||||
} # end if
|
||||
} elsif ( $operator eq 'not any' ) {
|
||||
if ( ref $value eq 'ARRAY' ) {
|
||||
return '(' . join(',', map { '?' } @{$value} ).") != ANY($field)", @{$value};
|
||||
} else {
|
||||
return "? != ANY($field)", $value;
|
||||
} # end if
|
||||
} elsif ( $operator eq 'is null' ) {
|
||||
if ( $value ) {
|
||||
return $field.$type. ' is null';
|
||||
} else {
|
||||
return $field.$type. ' is not null';
|
||||
} # end if
|
||||
} elsif ( $operator eq 'is not null' ) {
|
||||
if ( $value ) {
|
||||
return $field.$type. ' is not null';
|
||||
} else {
|
||||
return $field.$type. ' is null';
|
||||
} # end if
|
||||
} else {
|
||||
$log->warn("find_operators: op not found field($field) type($type) op($operator) value($value)");
|
||||
} # end if
|
||||
return;
|
||||
} # end sub find_operators
|
||||
|
||||
sub get_fields_values {
|
||||
my ( $object_type, $search, $param_keys ) = @_;
|
||||
|
||||
my @used_fields;
|
||||
my @where;
|
||||
my @values;
|
||||
no strict 'refs';
|
||||
|
||||
foreach my $k ( @$param_keys ) {
|
||||
if ( $k eq 'or' ) {
|
||||
my $or_ref = ref $$search{or};
|
||||
|
||||
if ( $or_ref eq 'HASH' ) {
|
||||
my @keys = keys %{$$search{or}};
|
||||
if ( @keys ) {
|
||||
my ( $where, $values, $used_fields ) = get_fields_values( $object_type, $$search{or}, \@keys );
|
||||
|
||||
push @where, '('.join(' OR ', @{$where} ).')';
|
||||
push @values, @{$values};
|
||||
} else {
|
||||
$log->error("No keys in or");
|
||||
}
|
||||
|
||||
} elsif ( $or_ref eq 'ARRAY' ) {
|
||||
my %s = @{$$search{or}};
|
||||
my ( $where, $values, $used_fields ) = get_fields_values( $object_type, \%s, [ keys %s ] );
|
||||
push @where, '('.join(' OR ', @{$where} ).')';
|
||||
push @values, @{$values};
|
||||
|
||||
} else {
|
||||
$log->error("Deprecated use of or $or_ref for $$search{or}");
|
||||
} # end if
|
||||
push @used_fields, $k;
|
||||
next;
|
||||
} elsif ( $k eq 'and' ) {
|
||||
my $and_ref = ref $$search{and};
|
||||
if ( $and_ref eq 'HASH' ) {
|
||||
my @keys = keys %{$$search{and}};
|
||||
if ( @keys ) {
|
||||
my ( $where, $values, $used_fields ) = get_fields_values( $object_type, $$search{and}, \@keys );
|
||||
|
||||
push @where, '('.join(' AND ', @{$where} ).')';
|
||||
push @values, @{$values};
|
||||
} else {
|
||||
$log->error("No keys in and");
|
||||
}
|
||||
} elsif ( $and_ref eq 'ARRAY' and @{$$search{and}} ) {
|
||||
my @sub_where;
|
||||
|
||||
for( my $p_index = 0; $p_index < @{$$search{and}}; $p_index += 2 ) {
|
||||
my %p = ( $$search{and}[$p_index], $$search{and}[$p_index+1] );
|
||||
|
||||
my ( $where, $values, $used_fields ) = get_fields_values( $object_type, \%p, [ keys %p ] );
|
||||
push @sub_where, @{$where};
|
||||
push @values, @{$values};
|
||||
}
|
||||
push @where, '('.join(' AND ', @sub_where ).')';
|
||||
} else {
|
||||
$log->error("incorrect ref of and $and_ref");
|
||||
}
|
||||
push @used_fields, $k;
|
||||
next;
|
||||
}
|
||||
my ( $field, $type, $function ) = $k =~ /^([_\+\w\-]+)(::\w+\[?\]?)?[\s_]*(.*)?$/;
|
||||
$type = '' if ! defined $type;
|
||||
$log->debug("$object_type param $field($type) func($function) " . ( ref $$search{$k} eq 'ARRAY' ? join(',',@{$$search{$k}}) : $$search{$k} ) ) if DEBUG_ALL;
|
||||
|
||||
foreach ( 'find_fields', 'fields' ) {
|
||||
my $fields = \%{$object_type.'::'.$_};
|
||||
if ( ! $fields ) {
|
||||
$log->debug("No $fields in $object_type") if DEBUG_ALL;
|
||||
next;
|
||||
} # end if
|
||||
|
||||
if ( ! $$fields{$field} ) {
|
||||
#$log->debug("No $field in $_ for $object_type") if DEBUG_ALL;
|
||||
next;
|
||||
} # end if
|
||||
|
||||
# This allows mainly for find_fields to reference multiple values, opinion in Project, value
|
||||
foreach my $db_field ( ref $$fields{$field} eq 'ARRAY' ? @{$$fields{$field}} : $$fields{$field} ) {
|
||||
if ( ! $function ) {
|
||||
$db_field .= $type;
|
||||
|
||||
if ( ref $$search{$k} eq 'ARRAY' ) {
|
||||
$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}} ) . ')';
|
||||
} else {
|
||||
push @where, $db_field.'=?';
|
||||
} # end if
|
||||
} else {
|
||||
$log->debug("Have question ? for $k $$search{$k} $db_field") if DEBUG_ALL;
|
||||
|
||||
$db_field =~ s/=/IN/g;
|
||||
my $question_replacement = '('.join(',', map {'?'} @{$$search{$k}} ) . ')';
|
||||
$db_field =~ s/\?/$question_replacement/;
|
||||
push @where, $db_field;
|
||||
}
|
||||
push @values, @{$$search{$k}};
|
||||
} elsif ( ref $$search{$k} eq 'HASH' ) {
|
||||
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 @values, $p_k, @{$v};
|
||||
} else {
|
||||
push @where, $db_field.'=?';
|
||||
push @values, $p_k, $v;
|
||||
} # end if
|
||||
} # end foreach p_k
|
||||
} elsif ( ! defined $$search{$k} ) {
|
||||
push @where, $db_field.' IS NULL';
|
||||
} else {
|
||||
if ( ! ( $db_field =~ /\?/ ) ) {
|
||||
push @where, $db_field .'=?';
|
||||
} else {
|
||||
push @where, $db_field;
|
||||
}
|
||||
push @values, $$search{$k};
|
||||
} # end if
|
||||
push @used_fields, $k;
|
||||
} else {
|
||||
#my @w =
|
||||
#ref $search{$k} eq 'ARRAY' ?
|
||||
#map { find_operators( $field, $type, $function, $_ ); } @{$search{$k}} :
|
||||
my ( $w, @v ) = find_operators( $db_field, $type, $function, $$search{$k} );
|
||||
if ( $w ) {
|
||||
#push @where, '(' . join(' OR ', @w ) . ')';
|
||||
push @where, $w;
|
||||
push @values, @v if @v;
|
||||
push @used_fields, $k;
|
||||
} # end if @w
|
||||
} # end if has function or not
|
||||
} # end foreach db_field
|
||||
} # end foreach find_field
|
||||
} # end foreach k
|
||||
return ( \@where, \@values, \@used_fields );
|
||||
}
|
||||
|
||||
sub find {
|
||||
no strict 'refs';
|
||||
my $object_type = shift;
|
||||
my $debug = ${$object_type.'::debug'};
|
||||
$debug = DEBUG_ALL if ! $debug;
|
||||
|
||||
my $starttime = [gettimeofday] if $debug;
|
||||
my $params;
|
||||
if ( @_ == 1 ) {
|
||||
$params = $_[0];
|
||||
if ( ref $params ne 'HASH' ) {
|
||||
$log->error("params $params was not a has");
|
||||
} # end if
|
||||
} else {
|
||||
$params = { @_ };
|
||||
} # end if
|
||||
|
||||
my $local_dbh = ${$object_type.'::dbh'};
|
||||
if ( $$params{dbh} ) {
|
||||
$local_dbh = $$params{dbh};
|
||||
delete $$params{dbh};
|
||||
} elsif ( ! $local_dbh ) {
|
||||
$local_dbh = $dbh if ! $local_dbh;
|
||||
} # end if
|
||||
|
||||
my $sql = find_sql( $object_type, $params);
|
||||
|
||||
my $do_cache = $$sql{columns} ne '*' ? 0 : 1;
|
||||
|
||||
#$log->debug( 'find prepare: ' . sprintf('%.4f', tv_interval($starttime)*1000) ." useconds") if $debug;
|
||||
my $data = $local_dbh->selectall_arrayref($$sql{sql}, { Slice => {} }, @{$$sql{values}});
|
||||
if ( ! $data ) {
|
||||
$log->error('Error ' . $local_dbh->errstr() . " loading $object_type ($$sql{sql}) (". join(',', map { ref $_ eq 'ARRAY' ? 'ARRAY('.join(',',@$_).')' : $_ } @{$$sql{values}} ) . ')' );
|
||||
return ();
|
||||
#} elsif ( ( ! @$data ) and $debug ) {
|
||||
#$log->debug("No $type ($sql) (@values) " );
|
||||
} elsif ( $debug ) {
|
||||
$log->debug("Loading Debug:$debug $object_type ($$sql{sql}) (".join(',', map { ref $_ eq 'ARRAY' ? join(',', @{$_}) : $_ } @{$$sql{values}}).') # of results:' . @$data . ' in ' . sprintf('%.4f', tv_interval($starttime)*1000) .' useconds' );
|
||||
} # end if
|
||||
|
||||
my $fields = \%{$object_type.'::fields'};
|
||||
my $primary_key = ${$object_type.'::primary_key'};
|
||||
if ( ! $primary_key ) {
|
||||
Error( 'NO primary_key for type ' . $object_type );
|
||||
return;
|
||||
} # end if
|
||||
if ( ! ($fields and keys %{$fields}) ) {
|
||||
return map { new($object_type, $$_{$primary_key}, $_ ) } @$data;
|
||||
} elsif ( $$fields{$primary_key} ) {
|
||||
return map { new($object_type, $_->{$$fields{$primary_key}}, $_) } @$data;
|
||||
} else {
|
||||
my @identified_by = eval '@'.$object_type.'::identified_by';
|
||||
if ( ! @identified_by ) {
|
||||
$log->debug("Multi key object $object_type but no identified by $fields") if $debug;
|
||||
} # end if
|
||||
return map { new($object_type, \@identified_by, $_, !$do_cache) } @$data;
|
||||
} # end if
|
||||
} # end sub find
|
||||
|
||||
sub find_one {
|
||||
my $object_type = shift;
|
||||
my $params;
|
||||
if ( @_ == 1 ) {
|
||||
$params = $_[0];
|
||||
} else {
|
||||
%{$params} = @_;
|
||||
} # end if
|
||||
$$params{limit}=1;
|
||||
my @Results = $object_type->find(%$params);
|
||||
my ( $caller, undef, $line ) = caller;
|
||||
$log->debug("returning to $caller:$line from find_one") if DEBUG_ALL;
|
||||
return $Results[0] if @Results;
|
||||
} # end sub find_one
|
||||
|
||||
sub find_sql {
|
||||
no strict 'refs';
|
||||
my $object_type = shift;
|
||||
|
||||
my $debug = ${$object_type.'::debug'};
|
||||
$debug = DEBUG_ALL if ! $debug;
|
||||
|
||||
my $params;
|
||||
if ( @_ == 1 ) {
|
||||
$params = $_[0];
|
||||
if ( ref $params ne 'HASH' ) {
|
||||
$log->error("params $params was not a has");
|
||||
} # end if
|
||||
} else {
|
||||
$params = { @_ };
|
||||
} # end if
|
||||
|
||||
my %sql = (
|
||||
( distinct => ( exists $$params{distinct} ? 1:0 ) ),
|
||||
( columns => ( exists $$params{columns} ? $$params{columns} : '*' ) ),
|
||||
( table => ( exists $$params{table} ? $$params{table} : ${$object_type.'::table'} )),
|
||||
'group by'=> $$params{'group by'},
|
||||
limit => $$params{limit},
|
||||
offset => $$params{offset},
|
||||
);
|
||||
if ( exists $$params{order} ) {
|
||||
$sql{order} = $$params{order};
|
||||
} else {
|
||||
my $order = eval '$'.$object_type.'::default_sort';
|
||||
#$log->debug("default sort: $object_type :: default_sort = $order") if DEBUG_ALL;
|
||||
$sql{order} = $order if $order;
|
||||
} # end if
|
||||
delete @$params{'distinct','columns','table','group by','limit','offset','order'};
|
||||
|
||||
my @where;
|
||||
my @values;
|
||||
if ( exists $$params{custom} ) {
|
||||
push @where, '(' . (shift @{$$params{custom}}) . ')';
|
||||
push @values, @{$$params{custom}};
|
||||
delete $$params{custom};
|
||||
} # end if
|
||||
|
||||
my @param_keys = keys %$params;
|
||||
|
||||
# no operators, just which fields are being searched on. Mostly just useful for detetion of the deleted field.
|
||||
my %used_fields;
|
||||
|
||||
# We use this search hash so that we can mash it up and leave the params hash alone
|
||||
my %search;
|
||||
@search{@param_keys} = @$params{@param_keys};
|
||||
|
||||
my ( $where, $values, $used_fields ) = get_fields_values( $object_type, \%search, \@param_keys );
|
||||
delete @search{@{$used_fields}};
|
||||
@used_fields{ @{$used_fields} } = @{$used_fields};
|
||||
push @where, @{$where};
|
||||
push @values, @{$values};
|
||||
|
||||
my $fields = \%{$object_type.'::fields'};
|
||||
|
||||
#optimise this
|
||||
if ( $$fields{deleted} and ! $used_fields{deleted} ) {
|
||||
push @where, 'deleted=?';
|
||||
push @values, 0;
|
||||
} # end if
|
||||
$sql{where} = \@where;
|
||||
$sql{values} = \@values;
|
||||
$sql{used_fields} = \%used_fields;
|
||||
|
||||
foreach my $k ( keys %search ) {
|
||||
$log->error("Extra parameters in $object_type ::find $k => $search{$k}");
|
||||
Carp::cluck("Extra parameters in $object_type ::find $k => $search{$k}");
|
||||
} # end foreach
|
||||
|
||||
$sql{sql} = join( ' ',
|
||||
( 'SELECT', ( $sql{distinct} ? ('DISTINCT') : () ) ),
|
||||
( $sql{columns}, 'FROM', $sql{table} ),
|
||||
( @{$sql{where}} ? ('WHERE', join(' AND ', @{$sql{where}})) : () ),
|
||||
( $sql{order} ? ( 'ORDER BY', $sql{order} ) : () ),
|
||||
( $sql{'group by'} ? ( 'GROUP BY', $sql{'group by'} ) : () ),
|
||||
( $sql{limit} ? ( 'LIMIT', $sql{limit}) : () ),
|
||||
( $sql{offset} ? ( 'OFFSET', $sql{offset} ) : () ),
|
||||
);
|
||||
#$log->debug("Loading Debug:$debug $object_type ($sql) (".join(',', map { ref $_ eq 'ARRAY' ? join(',', @{$_}) : $_ } @values).')' ) if $debug;
|
||||
return \%sql;
|
||||
} # end sub find_sql
|
||||
|
||||
sub AUTOLOAD {
|
||||
my $type = ref($_[0]);
|
||||
Carp::cluck("No type in autoload") if ! $type;
|
||||
if ( DEBUG_ALL ) {
|
||||
Carp::cluck("Using AUTOLOAD $AUTOLOAD");
|
||||
}
|
||||
my $name = $AUTOLOAD;
|
||||
$name =~ s/.*://;
|
||||
if ( @_ > 1 ) {
|
||||
return $_[0]{$name} = $_[1];
|
||||
}
|
||||
return $_[0]{$name};
|
||||
}
|
||||
|
||||
sub DESTROY {
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
|
@ -0,0 +1,475 @@
|
|||
#!/usr/bin/perl -wT
|
||||
|
||||
use strict;
|
||||
use bytes;
|
||||
|
||||
# ==========================================================================
|
||||
#
|
||||
# These are the elements you can edit to suit your installation
|
||||
#
|
||||
# ==========================================================================
|
||||
|
||||
use constant RECOVER_TAG => '(r)'; # Tag to append to event name when recovered
|
||||
use constant RECOVER_TEXT => 'Recovered.'; # Text to append to event notes when recovered
|
||||
|
||||
# ==========================================================================
|
||||
#
|
||||
# You shouldn't need to change anything from here downwards
|
||||
#
|
||||
# ==========================================================================
|
||||
|
||||
@EXTRA_PERL_LIB@
|
||||
use ZoneMinder;
|
||||
use DBI;
|
||||
use POSIX;
|
||||
use File::Find;
|
||||
use Time::HiRes qw/gettimeofday/;
|
||||
use Getopt::Long;
|
||||
use Date::Format;
|
||||
use autouse 'Pod::Usage'=>qw(pod2usage);
|
||||
|
||||
use constant ZM_RECOVER_PID => '@ZM_RUNDIR@/zmrecover.pid';
|
||||
|
||||
|
||||
$ENV{PATH} = '/bin:/usr/bin:/usr/local/bin';
|
||||
$ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL};
|
||||
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
|
||||
|
||||
my $report = 0;
|
||||
my $interactive = 1;
|
||||
my $monitor_id = 0;
|
||||
my $version;
|
||||
my $force = 0;
|
||||
my $server_id = undef;
|
||||
my $storage_id = undef;
|
||||
|
||||
logInit();
|
||||
|
||||
GetOptions(
|
||||
force =>\$force,
|
||||
interactive =>\$interactive,
|
||||
'monitor_id=i' =>\$monitor_id,
|
||||
report =>\$report,
|
||||
'server_id=i' =>\$server_id,
|
||||
'storage_id=i' =>\$storage_id,
|
||||
version =>\$version
|
||||
) or pod2usage(-exitstatus => -1);
|
||||
|
||||
if ( $version ) {
|
||||
print( ZoneMinder::Base::ZM_VERSION . "\n");
|
||||
exit(0);
|
||||
}
|
||||
if ( ($report + $interactive) > 1 ) {
|
||||
print( STDERR "Error, only one option may be specified\n" );
|
||||
pod2usage(-exitstatus => -1);
|
||||
}
|
||||
|
||||
if ( -e ZM_RECOVER_PID ) {
|
||||
local $/ = undef;
|
||||
open FILE, ZM_RECOVER_PID or die "Couldn't open file: $!";
|
||||
binmode FILE;
|
||||
my $pid = <FILE>;
|
||||
close FILE;
|
||||
if ( $force ) {
|
||||
Error("zmrecover.pl appears to already be running at pid $pid. Continuing." );
|
||||
} else {
|
||||
Fatal("zmrecover.pl appears to already be running at pid $pid. If not, please delete " .
|
||||
ZM_RECOVER_PID . " or use the --force command line option." );
|
||||
}
|
||||
} # end if ZM_RECOVER_PID exists
|
||||
|
||||
if ( open(my $PID, '>', ZM_RECOVER_PID) ) {
|
||||
print($PID $$);
|
||||
close($PID);
|
||||
} else {
|
||||
Error( "Can't open pid file at " . ZM_PID );
|
||||
}
|
||||
|
||||
sub HupHandler {
|
||||
Info('Received HUP, reloading');
|
||||
&ZoneMinder::Logger::logHupHandler();
|
||||
}
|
||||
sub TermHandler {
|
||||
Info('Received TERM, exiting');
|
||||
Term();
|
||||
}
|
||||
sub Term {
|
||||
unlink ZM_RECOVER_PID;
|
||||
exit(0);
|
||||
}
|
||||
$SIG{HUP} = \&HupHandler;
|
||||
$SIG{TERM} = \&TermHandler;
|
||||
$SIG{INT} = \&TermHandler;
|
||||
|
||||
my $dbh = zmDbConnect();
|
||||
if ( ! $dbh ) {
|
||||
Error('Unable to connect to database');
|
||||
Term();
|
||||
} # end if
|
||||
|
||||
$| = 1;
|
||||
|
||||
require ZoneMinder::Monitor;
|
||||
require ZoneMinder::Storage;
|
||||
require ZoneMinder::Event;
|
||||
|
||||
|
||||
my @Storage_Areas;
|
||||
if ( defined $storage_id ) {
|
||||
@Storage_Areas = ZoneMinder::Storage->find( Id=>$storage_id );
|
||||
if ( !@Storage_Areas ) {
|
||||
Error("No Storage Area found with Id $storage_id");
|
||||
Term();
|
||||
}
|
||||
Info("Auditing Storage Area $Storage_Areas[0]{Id} $Storage_Areas[0]{Name} at $Storage_Areas[0]{Path}");
|
||||
} elsif ( $server_id ) {
|
||||
@Storage_Areas = ZoneMinder::Storage->find( ServerId => $server_id );
|
||||
if ( ! @Storage_Areas ) {
|
||||
Error("No Storage Area found with ServerId =" . $server_id);
|
||||
Term();
|
||||
}
|
||||
foreach my $Storage ( @Storage_Areas ) {
|
||||
Info('Auditing ' . $Storage->Name() . ' at ' . $Storage->Path() . ' on ' . $Storage->Server()->Name() );
|
||||
}
|
||||
} else {
|
||||
@Storage_Areas = ZoneMinder::Storage->find();
|
||||
Info("Auditing All Storage Areas");
|
||||
}
|
||||
|
||||
my @Monitors = ZoneMinder::Monitor->find();
|
||||
Debug("@Monitors");
|
||||
foreach my $Monitor ( @Monitors ) {
|
||||
Debug("Monitor " . $Monitor->to_string() )
|
||||
}
|
||||
my %Monitors = map { $$_{Id} => $_ } @Monitors;
|
||||
#ZoneMinder::Monitor->find(
|
||||
|
||||
# ($monitor_id ? ( Id=>$monitor_id ) : () ),
|
||||
|
||||
#);
|
||||
Debug("Found " . (keys %Monitors) . " monitors");
|
||||
foreach my $id ( keys %Monitors ) {
|
||||
Debug("Monitor $id $Monitors{$id}{Name}");
|
||||
}
|
||||
|
||||
foreach my $Storage ( @Storage_Areas ) {
|
||||
Debug('Checking events in ' . $Storage->Path() );
|
||||
if ( ! chdir($Storage->Path()) ) {
|
||||
Error('Unable to change dir to ' . $Storage->Path());
|
||||
next;
|
||||
} # end if
|
||||
|
||||
# Please note that this glob will take all files beginning with a digit.
|
||||
foreach my $monitor ( glob('[0-9]*') ) {
|
||||
if ( $monitor =~ /\D/ ) {
|
||||
Debug("Weird non digit characters in $monitor");
|
||||
next;
|
||||
}
|
||||
# De-taint
|
||||
( my $monitor_dir ) = ( $monitor =~ /^(\d+)$/ );
|
||||
if ( $monitor_id and ( $monitor_id != $monitor_dir ) ) {
|
||||
Debug("Skipping monitor $monitor_dir because we are only interested in monitor $monitor_id");
|
||||
next;
|
||||
}
|
||||
if ( ! $Monitors{$monitor_dir} ) {
|
||||
Warning("There is no monitor in the database for $$Storage{Path}/$monitor_dir. Skipping it.");
|
||||
next;
|
||||
}
|
||||
my $Monitor = $Monitors{$monitor_dir};
|
||||
|
||||
Debug("Found filesystem monitor '$monitor_dir'");
|
||||
|
||||
{
|
||||
my @day_dirs = glob("$monitor_dir/[0-9][0-9]/[0-9][0-9]/[0-9][0-9]");
|
||||
Debug(qq`Checking for Deep Events under $$Storage{Path} using glob("$monitor_dir/[0-9][0-9]/[0-9][0-9]/[0-9][0-9]") returned `. scalar @day_dirs . ' days with events');
|
||||
foreach my $day_dir ( @day_dirs ) {
|
||||
Debug("Checking day dir $day_dir");
|
||||
( $day_dir ) = ( $day_dir =~ /^(.*)$/ ); # De-taint
|
||||
if ( !chdir($day_dir) ) {
|
||||
Error("Can't chdir to '$$Storage{Path}/$day_dir': $!");
|
||||
next;
|
||||
}
|
||||
if ( ! opendir(DIR, '.') ) {
|
||||
Error("Can't open directory '$$Storage{Path}/$day_dir': $!");
|
||||
next;
|
||||
}
|
||||
my %event_ids_by_path;
|
||||
|
||||
my @event_links = sort { $b <=> $a } grep { -l $_ } readdir( DIR );
|
||||
Debug('Have ' . (scalar @event_links) . ' event links');
|
||||
closedir(DIR);
|
||||
|
||||
my $count = 0;
|
||||
foreach my $event_link ( @event_links ) {
|
||||
# Event links start with a period and consist of the digits of the event id.
|
||||
# Anything else is not an event link
|
||||
my ($event_id) = $event_link =~ /^\.(\d+)$/;
|
||||
if ( !$event_id ) {
|
||||
Warning("Non-event link found $event_link in $day_dir, skipping");
|
||||
next;
|
||||
}
|
||||
Debug("Checking link $event_link");
|
||||
#Event path is hour/minute/sec
|
||||
my $event_path = readlink($event_link);
|
||||
|
||||
if ( !($event_path and -e $event_path) ) {
|
||||
Warning("Event link $day_dir/$event_link does not point to valid target at $event_path");
|
||||
next;
|
||||
}
|
||||
if ( ! ZoneMinder::Event->find_one(Id=>$event_id) ) {
|
||||
Info("Event not found in db for event data found at $$Storage{Path}/$day_dir/$event_path with Id=$event_id");
|
||||
if ( confirm() ) {
|
||||
my $Event = new ZoneMinder::Event();
|
||||
$$Event{Id} = $event_id;
|
||||
$$Event{Path} = join('/', $Storage->Path(), $day_dir, $event_path);
|
||||
$$Event{RelativePath} = join('/', $day_dir, $event_path);
|
||||
$$Event{Scheme} = 'Deep';
|
||||
$$Event{Name} = "Event $event_id recovered";
|
||||
$Event->MonitorId( $monitor_dir );
|
||||
$Event->StorageId( $Storage->Id() );
|
||||
$Event->DiskSpace( undef );
|
||||
$Event->Width( $Monitor->Width() );
|
||||
$Event->Height( $Monitor->Height() );
|
||||
$Event->Orientation( $Monitor->Orientation() );
|
||||
|
||||
$Event->recover_timestamps();
|
||||
|
||||
$Event->save({}, 1);
|
||||
Debug("Event resurrected as " . $Event->to_string() );
|
||||
next;
|
||||
} # end if resurrection
|
||||
} # event path exists
|
||||
} # end foreach event_link
|
||||
|
||||
# Now check for events that have lost their link. We can determine event Id from .mp4
|
||||
|
||||
my @time_dirs = glob('[0-9][0-9]/[0-9][0-9]/[0-9][0-9]');
|
||||
foreach my $event_dir ( @time_dirs ) {
|
||||
Debug("Checking time dir $event_dir");
|
||||
( $event_dir ) = ( $event_dir =~ /^(.*)$/ ); # De-taint
|
||||
|
||||
my $event_id = undef;
|
||||
|
||||
if ( ! opendir(DIR, $event_dir) ) {
|
||||
Error("Can't open directory '$$Storage{Path}/$day_dir': $!");
|
||||
next;
|
||||
}
|
||||
my @contents = readdir( DIR );
|
||||
Debug('Have ' . @contents . " files in $day_dir/$event_dir");
|
||||
closedir(DIR);
|
||||
|
||||
my @mp4_files = grep( /^\d+\-video.mp4$/, @contents);
|
||||
foreach my $mp4_file ( @mp4_files ) {
|
||||
my ( $id ) = $mp4_file =~ /^([0-9]+)\-video\.mp4$/;
|
||||
if ( $id ) {
|
||||
$event_id = $id;
|
||||
Debug("Got event id from mp4 file $mp4_file => $event_id");
|
||||
last;
|
||||
}
|
||||
} # end foreach mp4
|
||||
|
||||
if ( ! $event_id ) {
|
||||
# Look for .id file
|
||||
my @hidden_files = grep( /^\.\d+$/, @contents);
|
||||
Debug('Have ' . @hidden_files . ' hidden files');
|
||||
if ( @hidden_files ) {
|
||||
( $event_id ) = $hidden_files[0] =~ /^.(\d+)$/;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $event_id and ! ZoneMinder::Event->find_one(Id=>$event_id) ) {
|
||||
Info("Event not found in db for event data found at $$Storage{Path}/$monitor_dir/$day_dir/$event_dir");
|
||||
if ( confirm() ) {
|
||||
my $Event = new ZoneMinder::Event();
|
||||
$$Event{Id} = $event_id;
|
||||
$$Event{Path} = join('/', $Storage->Path(), $day_dir, $event_dir);
|
||||
$$Event{RelativePath} = join('/', $day_dir, $event_dir);
|
||||
$$Event{Scheme} = 'Deep';
|
||||
$$Event{Name} = "Event $event_id recovered";
|
||||
$Event->MonitorId( $monitor_dir );
|
||||
$Event->Width( $Monitor->Width() );
|
||||
$Event->Height( $Monitor->Height() );
|
||||
$Event->Orientation( $Monitor->Orientation() );
|
||||
$Event->StorageId( $Storage->Id() );
|
||||
$Event->DiskSpace( undef );
|
||||
$Event->recover_timestamps();
|
||||
$Event->save({}, 1);
|
||||
Debug("Event resurrected as " . $Event->to_string() );
|
||||
next;
|
||||
}
|
||||
} # end if event found
|
||||
|
||||
# Search in db for given timestamp?
|
||||
|
||||
my ( undef, $year, $month, $day ) = split('/', $day_dir);
|
||||
$year += 2000;
|
||||
my ( $hour, $minute, $second ) = split('/', $event_dir);
|
||||
my $StartTime =sprintf('%.4d-%.2d-%.2d %.2d:%.2d:%.2d', $year, $month, $day, $hour, $minute, $second);
|
||||
my $Event = ZoneMinder::Event->find_one(
|
||||
MonitorId=>$monitor_dir,
|
||||
StartTime=>$StartTime,
|
||||
);
|
||||
if ( $Event ) {
|
||||
Debug("Found event matching starttime on monitor $monitor_dir at $StartTime: " . $Event->to_string());
|
||||
next;
|
||||
}
|
||||
|
||||
} # end foreach event_dir without link
|
||||
chdir( $Storage->Path() );
|
||||
} # end foreach day dir
|
||||
}
|
||||
|
||||
Debug("Checking for Medium Scheme Events under $$Storage{Path}/$monitor_dir");
|
||||
{
|
||||
my @event_dirs = glob("$monitor_dir/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/*");
|
||||
Debug(qq`glob("$monitor_dir/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/*") returned ` . scalar @event_dirs . " entries." );
|
||||
foreach my $event_dir ( @event_dirs ) {
|
||||
if ( ! -d $event_dir ) {
|
||||
Debug("$event_dir is not a dir. Skipping");
|
||||
next;
|
||||
}
|
||||
my ( $date, $event_id ) = $event_dir =~ /^$monitor_dir\/(\d{4}\-\d{2}\-\d{2})\/(\d+)$/;
|
||||
if ( !$event_id ) {
|
||||
Debug("Unable to parse date/event_id from $event_dir");
|
||||
next;
|
||||
}
|
||||
|
||||
my $Event = ZoneMinder::Event->find_one(Id=>$event_id);
|
||||
if ( $Event ) {
|
||||
Debug('Found event in the db, moving on.');
|
||||
next;
|
||||
}
|
||||
$Event = new ZoneMinder::Event();
|
||||
$$Event{Id} = $event_id;
|
||||
$$Event{Path} = join('/', $Storage->Path(), $event_dir );
|
||||
Debug("Have event $$Event{Id} at $$Event{Path}");
|
||||
$$Event{Scheme} = 'Medium';
|
||||
$$Event{RelativePath} = $event_dir;
|
||||
$$Event{Name} = "Event $event_id recovered";
|
||||
$Event->MonitorId( $monitor_dir );
|
||||
$Event->Width( $Monitor->Width() );
|
||||
$Event->Height( $Monitor->Height() );
|
||||
$Event->Orientation( $Monitor->Orientation() );
|
||||
$Event->StorageId( $Storage->Id() );
|
||||
$Event->recover_timestamps();
|
||||
if ( confirm() ) {
|
||||
$Event->save({}, 1);
|
||||
Debug("Event resurrected as " . $Event->to_string() );
|
||||
}
|
||||
} # end foreach event
|
||||
} # end search for Medium
|
||||
|
||||
# Shallow
|
||||
Debug("Checking for ShallowScheme Events under $$Storage{Path}/$monitor_dir");
|
||||
if ( ! chdir($monitor_dir) ) {
|
||||
Error("Can't chdir directory '$$Storage{Path}/$monitor_dir': $!");
|
||||
next;
|
||||
}
|
||||
if ( ! opendir(DIR, '.') ) {
|
||||
Error("Can't open directory '$$Storage{Path}/$monitor_dir': $!");
|
||||
next;
|
||||
}
|
||||
my @temp_events = sort { $b <=> $a } grep { -d $_ && $_ =~ /^\d+$/ } readdir( DIR );
|
||||
closedir(DIR);
|
||||
foreach my $event ( @temp_events ) {
|
||||
my $Event = ZoneMinder::Event->find_one(Id=>$event);
|
||||
if ( $Event ) {
|
||||
Debug("Found an event in db for $event");
|
||||
next;
|
||||
}
|
||||
$$Event{Id} = $event;
|
||||
$$Event{Path} = join('/', $Storage->Path(), $event );
|
||||
Debug("Have event $$Event{Id} at $$Event{Path}");
|
||||
$$Event{Scheme} = 'Shallow';
|
||||
$$Event{Name} = "Event $event recovered";
|
||||
#$$Event{Path} = $event_path;
|
||||
$Event->MonitorId( $monitor_dir );
|
||||
$Event->Width( $Monitor->Width() );
|
||||
$Event->Height( $Monitor->Height() );
|
||||
$Event->Orientation( $Monitor->Orientation() );
|
||||
$Event->StorageId( $Storage->Id() );
|
||||
$Event->recover_timestamps();
|
||||
$Event->save({}, 1);
|
||||
Debug("Event resurrected as " . $Event->to_string() );
|
||||
} # end foreach event
|
||||
chdir( $Storage->Path() );
|
||||
} # end foreach monitor
|
||||
|
||||
} # end foreach Storage Area
|
||||
|
||||
Term();
|
||||
|
||||
sub confirm {
|
||||
my $prompt = shift || 'resurrect';
|
||||
my $action = shift || 'resurrecting';
|
||||
|
||||
my $yesno = 0;
|
||||
if ( $report ) {
|
||||
print( "\n" );
|
||||
} elsif ( $interactive ) {
|
||||
print(", $prompt Y/n/q: ");
|
||||
my $char = <>;
|
||||
chomp( $char );
|
||||
if ( $char eq 'q' ) {
|
||||
Term();
|
||||
}
|
||||
if ( !$char ) {
|
||||
$char = 'y';
|
||||
}
|
||||
$yesno = ( $char =~ /[yY]/ );
|
||||
} else {
|
||||
Info($action);
|
||||
$yesno = 1;
|
||||
}
|
||||
return $yesno;
|
||||
}
|
||||
|
||||
1;
|
||||
__END__
|
||||
|
||||
=head1 NAME
|
||||
|
||||
zmrecover.pl - ZoneMinder event file system and database recovery checker
|
||||
|
||||
=head1 SYNOPSIS
|
||||
|
||||
zmrecover.pl [-r,-report|-i,-interactive]
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
This script checks for consistency between the event filesystem and
|
||||
the database. If events are found in one and not the other they are
|
||||
deleted (optionally). Additionally any monitor event directories that
|
||||
do not correspond to a database monitor are similarly disposed of.
|
||||
However monitors in the database that don't have a directory are left
|
||||
alone as this is valid if they are newly created and have no events
|
||||
yet.
|
||||
|
||||
=head1 OPTIONS
|
||||
|
||||
-i, --interactive - Ask before applying any changes
|
||||
-m, --monitor_id - Only consider the given monitor
|
||||
-r, --report - Just report don't actually do anything
|
||||
-s, --storage_id - Specify a storage area to recover instead of all
|
||||
-v, --version - Print the installed version of ZoneMinder
|
||||
|
||||
=head1 COPYRIGHT
|
||||
|
||||
ZoneMinder Recover Script
|
||||
Copyright (C) 2018 ZoneMinder LLC
|
||||
|
||||
This program is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
=cut
|
Loading…
Reference in New Issue