updated on Thu Jan 19 20:01:47 UTC 2012
[aur-mirror.git] / zoneminder / zmfilter.pl
blob7875937bb02bc0563608cdaa3f0ee32f239b0bf5
1 #!/usr/bin/perl -wT
3 # ==========================================================================
5 # ZoneMinder Event Filter Script, $Date: 2008-10-09 10:17:07 +0100 (Thu, 09 Oct 2008) $, $Revision: 2659 $
6 # Copyright (C) 2001-2008 Philip Coombes
8 # This program is free software; you can redistribute it and/or
9 # modify it under the terms of the GNU General Public License
10 # as published by the Free Software Foundation; either version 2
11 # of the License, or (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program; if not, write to the Free Software
20 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22 # ==========================================================================
24 # This script continuously monitors the recorded events for the given
25 # monitor and applies any filters which would delete and/or upload
26 # matching events
28 use strict;
29 use bytes;
31 # ==========================================================================
33 # These are the elements you can edit to suit your installation
35 # ==========================================================================
37 use constant DBG_ID => "zmfilter"; # Tag that appears in debug to identify source
38 use constant DBG_LEVEL => 0; # 0 is errors, warnings and info only, > 0 for debug
40 use constant START_DELAY => 5; # How long to wait before starting
42 # ==========================================================================
44 # You shouldn't need to change anything from here downwards
46 # ==========================================================================
48 use ZoneMinder;
49 use DBI;
50 use POSIX;
51 use Time::HiRes qw/gettimeofday/;
52 use Date::Manip;
53 use Getopt::Long;
54 use PHP::Serialization qw(serialize unserialize);
55 use Data::Dumper;
57 use constant EVENT_PATH => ZM_PATH_WEB.'/'.ZM_DIR_EVENTS;
59 zmDbgInit( DBG_ID, level=>DBG_LEVEL );
60 zmDbgSetSignal();
62 if ( ZM_OPT_UPLOAD )
64 # Comment these out if you don't have them and don't want to upload
65 # or don't want to use that format
66 if ( ZM_UPLOAD_ARCH_FORMAT eq "zip" )
68 require Archive::Zip;
69 import Archive::Zip qw( :ERROR_CODES :CONSTANTS );
71 else
73 require Archive::Tar;
75 require Net::FTP;
78 if ( ZM_OPT_EMAIL )
80 if ( ZM_NEW_MAIL_MODULES )
82 require MIME::Lite;
83 require Net::SMTP;
85 else
87 require MIME::Entity;
91 if ( ZM_OPT_MESSAGE )
93 if ( ZM_NEW_MAIL_MODULES )
95 require MIME::Lite;
96 require Net::SMTP;
98 else
100 require MIME::Entity;
105 $| = 1;
107 $ENV{PATH} = '/bin:/usr/bin';
108 $ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL};
109 delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
111 my $delay = ZM_FILTER_EXECUTE_INTERVAL;
112 my $event_id = 0;
113 my $filter_parm = "";
115 sub Usage
117 print( "
118 Usage: zmfilter.pl [-f <filter name>,--filter=<filter name>]
119 Parameters are :-
120 -f<filter name>, --filter=<filter name> - The name of a specific filter to run
122 exit( -1 );
126 # More or less replicates the equivalent PHP function
128 sub strtotime
130 my $dt_str = shift;
131 return( UnixDate( $dt_str, '%s' ) );
135 # More or less replicates the equivalent PHP function
137 sub str_repeat
139 my $string = shift;
140 my $count = shift;
141 return( ${string}x${count} );
144 # Formats a date into MySQL format
145 sub DateTimeToSQL
147 my $dt_str = shift;
148 my $dt_val = strtotime( $dt_str );
149 if ( !$dt_val )
151 Error( "Unable to parse date string '$dt_str'\n" );
152 return( undef );
154 return( strftime( "%Y-%m-%d %H:%M:%S", localtime( $dt_val ) ) );
157 if ( !GetOptions( 'filter=s'=>\$filter_parm ) )
159 Usage();
162 chdir( EVENT_PATH );
164 my $dbh = zmDbConnect();
166 if ( $filter_parm )
168 Info( "Scanning for events using filter '$filter_parm'\n" );
170 else
172 Info( "Scanning for events\n" );
175 if ( !$filter_parm )
177 sleep( START_DELAY );
180 my $filters;
181 my $last_action = 0;
183 while( 1 )
185 if ( (time() - $last_action) > ZM_FILTER_RELOAD_DELAY )
187 Debug( "Reloading filters\n" );
188 $last_action = time();
189 $filters = getFilters( $filter_parm );
192 foreach my $filter ( @$filters )
194 checkFilter( $filter );
197 last if ( $filter_parm );
199 Debug( "Sleeping for $delay seconds\n" );
200 sleep( $delay );
203 sub getDiskPercent
205 my $command = "df .";
206 my $df = qx( $command );
207 my $space = -1;
208 if ( $df =~ /\s(\d+)%/ms )
210 $space = $1;
212 return( $space );
215 sub getDiskBlocks
217 my $command = "df .";
218 my $df = qx( $command );
219 my $space = -1;
220 if ( $df =~ /\s(\d+)\s+\d+\s+\d+%/ms )
222 $space = $1;
224 return( $space );
227 sub getLoad
229 my $command = "uptime .";
230 my $uptime = qx( $command );
231 my $load = -1;
232 if ( $uptime =~ /load average:\s+([\d.]+)/ms )
234 $load = $1;
235 Info( "Load: $load" );
237 return( $load );
240 sub getFilters
242 my $filter_name = shift;
244 my @filters;
245 my $sql = "select * from Filters where";
246 if ( $filter_name )
248 $sql .= " Name = ? and";
250 else
252 $sql .= " Background = 1 and";
254 $sql .= " (AutoArchive = 1 or AutoVideo = 1 or AutoUpload = 1 or AutoEmail = 1 or AutoMessage = 1 or AutoExecute = 1 or AutoDelete = 1) order by Name";
255 my $sth = $dbh->prepare_cached( $sql ) or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
256 my $res;
257 if ( $filter_name )
259 $res = $sth->execute( $filter_name ) or Fatal( "Can't execute '$sql': ".$sth->errstr() );
261 else
263 $res = $sth->execute() or Fatal( "Can't execute '$sql': ".$sth->errstr() );
265 FILTER: while( my $db_filter = $sth->fetchrow_hashref() )
267 Debug( "Found filter '$db_filter->{Name}'\n" );
268 my $filter_expr = unserialize( $db_filter->{Query} );
269 my $sql = "select E.Id,E.MonitorId,M.Name as MonitorName,M.DefaultRate,M.DefaultScale,E.Name,E.Cause,E.Notes,E.StartTime,unix_timestamp(E.StartTime) as Time,E.Length,E.Frames,E.AlarmFrames,E.TotScore,E.AvgScore,E.MaxScore,E.Archived,E.Videoed,E.Uploaded,E.Emailed,E.Messaged,E.Executed from Events as E inner join Monitors as M on M.Id = E.MonitorId where not isnull(E.EndTime)";
270 $db_filter->{Sql} = '';
272 if ( @{$filter_expr->{terms}} )
274 for ( my $i = 0; $i < @{$filter_expr->{terms}}; $i++ )
276 if ( exists($filter_expr->{terms}[$i]->{cnj}) )
278 $db_filter->{Sql} .= " ".$filter_expr->{terms}[$i]->{cnj}." ";
280 if ( exists($filter_expr->{terms}[$i]->{obr}) )
282 $db_filter->{Sql} .= " ".str_repeat( "(", $filter_expr->{terms}[$i]->{obr} )." ";
284 my $value = $filter_expr->{terms}[$i]->{val};
285 my @value_list;
286 if ( $filter_expr->{terms}[$i]->{attr} )
288 if ( $filter_expr->{terms}[$i]->{attr} =~ /^Monitor/ )
290 my ( $temp_attr_name ) = $filter_expr->{terms}[$i]->{attr} =~ /^Monitor(.+)$/;
291 $db_filter->{Sql} .= "M.".$temp_attr_name;
293 elsif ( $filter_expr->{terms}[$i]->{attr} eq 'DateTime' )
295 $db_filter->{Sql} .= "E.StartTime";
297 elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Date' )
299 $db_filter->{Sql} .= "to_days( E.StartTime )";
301 elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Time' )
303 $db_filter->{Sql} .= "extract( hour_second from E.StartTime )";
305 elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Weekday' )
307 $db_filter->{Sql} .= "weekday( E.StartTime )";
309 elsif ( $filter_expr->{terms}[$i]->{attr} eq 'DiskPercent' )
311 $db_filter->{Sql} .= "zmDiskPercent";
312 $db_filter->{HasDiskPercent} = !undef;
314 elsif ( $filter_expr->{terms}[$i]->{attr} eq 'DiskBlocks' )
316 $db_filter->{Sql} .= "zmDiskBlocks";
317 $db_filter->{HasDiskBlocks} = !undef;
319 elsif ( $filter_expr->{terms}[$i]->{attr} eq 'SystemLoad' )
321 $db_filter->{Sql} .= "zmSystemLoad";
322 $db_filter->{HasSystemLoad} = !undef;
324 else
326 $db_filter->{Sql} .= "E.".$filter_expr->{terms}[$i]->{attr};
329 ( my $stripped_value = $value ) =~ s/^["\']+?(.+)["\']+?$/$1/;
330 foreach my $temp_value ( split( '/["\'\s]*?,["\'\s]*?/', $stripped_value ) )
332 if ( $filter_expr->{terms}[$i]->{attr} =~ /^Monitor/ )
334 $value = "'$temp_value'";
336 elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Name' || $filter_expr->{terms}[$i]->{attr} eq 'Cause' || $filter_expr->{terms}[$i]->{attr} eq 'Notes' )
338 $value = "'$temp_value'";
340 elsif ( $filter_expr->{terms}[$i]->{attr} eq 'DateTime' )
342 $value = DateTimeToSQL( $temp_value );
343 if ( !$value )
345 Error( "Error parsing date/time '$temp_value', skipping filter '$db_filter->{Name}'\n" );
346 next FILTER;
348 $value = "'$value'";
350 elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Date' )
352 $value = DateTimeToSQL( $temp_value );
353 if ( !$value )
355 Error( "Error parsing date/time '$temp_value', skipping filter '$db_filter->{Name}'\n" );
356 next FILTER;
358 $value = "to_days( '$value' )";
360 elsif ( $filter_expr->{terms}[$i]->{attr} eq 'Time' )
362 $value = DateTimeToSQL( $temp_value );
363 if ( !$value )
365 Error( "Error parsing date/time '$temp_value', skipping filter '$db_filter->{Name}'\n" );
366 next FILTER;
368 $value = "extract( hour_second from '$value' )";
370 else
372 $value = $temp_value;
374 push( @value_list, $value );
377 if ( $filter_expr->{terms}[$i]->{op} )
379 if ( $filter_expr->{terms}[$i]->{op} eq '=~' )
381 $db_filter->{Sql} .= " regexp $value";
383 elsif ( $filter_expr->{terms}[$i]->{op} eq '!~' )
385 $db_filter->{Sql} .= " not regexp $value";
387 elsif ( $filter_expr->{terms}[$i]->{op} eq '=[]' )
389 $db_filter->{Sql} .= " in (".join( ",", @value_list ).")";
391 elsif ( $filter_expr->{terms}[$i]->{op} eq '!~' )
393 $db_filter->{Sql} .= " not in (".join( ",", @value_list ).")";
395 else
397 $db_filter->{Sql} .= " ".$filter_expr->{terms}[$i]->{op}." $value";
400 if ( exists($filter_expr->{terms}[$i]->{cbr}) )
402 $db_filter->{Sql} .= " ".str_repeat( ")", $filter_expr->{terms}[$i]->{cbr} )." ";
406 if ( $db_filter->{Sql} )
408 $sql .= " and ( ".$db_filter->{Sql}." )";
410 my @auto_terms;
411 if ( $db_filter->{AutoArchive} )
413 push( @auto_terms, "E.Archived = 0" )
415 if ( $db_filter->{AutoVideo} )
417 push( @auto_terms, "E.Videoed = 0" )
419 if ( $db_filter->{AutoUpload} )
421 push( @auto_terms, "E.Uploaded = 0" )
423 if ( $db_filter->{AutoEmail} )
425 push( @auto_terms, "E.Emailed = 0" )
427 if ( $db_filter->{AutoMessage} )
429 push( @auto_terms, "E.Messaged = 0" )
431 if ( $db_filter->{AutoExecute} )
433 push( @auto_terms, "E.Executed = 0" )
435 if ( @auto_terms )
437 $sql .= " and ( ".join( " or ", @auto_terms )." )";
439 if ( !$filter_expr->{sort_field} )
441 $filter_expr->{sort_field} = 'StartTime';
442 $filter_expr->{sort_asc} = 0;
444 my $sort_column = '';
445 if ( $filter_expr->{sort_field} eq 'Id' )
447 $sort_column = "E.Id";
449 elsif ( $filter_expr->{sort_field} eq 'MonitorName' )
451 $sort_column = "M.Name";
453 elsif ( $filter_expr->{sort_field} eq 'Name' )
455 $sort_column = "E.Name";
457 elsif ( $filter_expr->{sort_field} eq 'StartTime' )
459 $sort_column = "E.StartTime";
461 elsif ( $filter_expr->{sort_field} eq 'Secs' )
463 $sort_column = "E.Length";
465 elsif ( $filter_expr->{sort_field} eq 'Frames' )
467 $sort_column = "E.Frames";
469 elsif ( $filter_expr->{sort_field} eq 'AlarmFrames' )
471 $sort_column = "E.AlarmFrames";
473 elsif ( $filter_expr->{sort_field} eq 'TotScore' )
475 $sort_column = "E.TotScore";
477 elsif ( $filter_expr->{sort_field} eq 'AvgScore' )
479 $sort_column = "E.AvgScore";
481 elsif ( $filter_expr->{sort_field} eq 'MaxScore' )
483 $sort_column = "E.MaxScore";
485 else
487 $sort_column = "E.StartTime";
489 my $sort_order = $filter_expr->{sort_asc}?"asc":"desc";
490 $sql .= " order by ".$sort_column." ".$sort_order;
491 if ( $filter_expr->{limit} )
493 $sql .= " limit 0,".$filter_expr->{limit};
495 Debug( "SQL:$sql\n" );
496 $db_filter->{Sql} = $sql;
497 if ( $db_filter->{AutoExecute} )
499 my $script = $db_filter->{AutoExecuteCmd};
500 $script =~ s/\s.*$//;
501 if ( !-e $script )
503 Error( "Auto execute script '$script' not found, skipping filter '$db_filter->{Name}'\n" );
504 next FILTER;
507 elsif ( !-x $script )
509 Error( "Auto execute script '$script' not executable, skipping filter '$db_filter->{Name}'\n" );
510 next FILTER;
513 push( @filters, $db_filter );
515 $sth->finish();
517 return( \@filters );
520 sub checkFilter
522 my $filter = shift;
524 Debug( "Checking filter '$filter->{Name}'".
525 ($filter->{AutoDelete}?", delete":"").
526 ($filter->{AutoArchive}?", archive":"").
527 ($filter->{AutoVideo}?", video":"").
528 ($filter->{AutoUpload}?", upload":"").
529 ($filter->{AutoEmail}?", email":"").
530 ($filter->{AutoMessage}?", message":"").
531 ($filter->{AutoExecute}?", execute":"").
532 "\n"
534 my $sql = $filter->{Sql};
536 if ( $filter->{HasDiskPercent} )
538 my $disk_percent = getDiskPercent();
539 $sql =~ s/zmDiskPercent/$disk_percent/g;
541 if ( $filter->{HasDiskBlocks} )
543 my $disk_blocks = getDiskBlocks();
544 $sql =~ s/zmDiskBlocks/$disk_blocks/g;
546 if ( $filter->{HasSystemLoad} )
548 my $load = getLoad();
549 $sql =~ s/zmSystemLoad/$load/g;
552 my $sth = $dbh->prepare_cached( $sql ) or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
553 my $res = $sth->execute();
554 if ( !$res )
556 Error( "Can't execute filter '$sql', ignoring: ".$sth->errstr() );
557 return;
560 while( my $event = $sth->fetchrow_hashref() )
562 Debug( "Checking event $event->{Id}\n" );
563 my $delete_ok = !undef;
564 if ( $filter->{AutoArchive} )
566 Info( "Archiving event $event->{Id}\n" );
567 # Do it individually to avoid locking up the table for new events
568 my $sql = "update Events set Archived = 1 where Id = ?";
569 my $sth = $dbh->prepare_cached( $sql ) or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
570 my $res = $sth->execute( $event->{Id} ) or Fatal( "Can't execute '$sql': ".$sth->errstr() );
572 if ( ZM_OPT_FFMPEG && $filter->{AutoVideo} )
574 if ( !$event->{Videoed} )
576 $delete_ok = undef if ( !generateVideo( $filter, $event ) );
579 if ( ZM_OPT_EMAIL && $filter->{AutoEmail} )
581 if ( !$event->{Emailed} )
583 $delete_ok = undef if ( !sendEmail( $filter, $event ) );
586 if ( ZM_OPT_MESSAGE && $filter->{AutoMessage} )
588 if ( !$event->{Messaged} )
590 $delete_ok = undef if ( !sendMessage( $filter, $event ) );
593 if ( ZM_OPT_UPLOAD && $filter->{AutoUpload} )
595 if ( !$event->{Uploaded} )
597 $delete_ok = undef if ( !uploadArchFile( $filter, $event ) );
600 if ( $filter->{AutoExecute} )
602 if ( !$event->{Execute} )
604 $delete_ok = undef if ( !executeCommand( $filter, $event ) );
607 if ( $filter->{AutoDelete} )
609 if ( $delete_ok )
611 Info( "Deleting event $event->{Id}\n" );
612 # Do it individually to avoid locking up the table for new events
613 my $sql = "delete from Events where Id = ?";
614 my $sth = $dbh->prepare_cached( $sql ) or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
615 my $res = $sth->execute( $event->{Id} ) or Fatal( "Can't execute '$sql': ".$sth->errstr() );
617 if ( !ZM_OPT_FAST_DELETE )
619 my $sql = "delete from Frames where EventId = ?";
620 my $sth = $dbh->prepare_cached( $sql ) or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
621 my $res = $sth->execute( $event->{Id} ) or Fatal( "Can't execute '$sql': ".$sth->errstr() );
623 $sql = "delete from Stats where EventId = ?";
624 $sth = $dbh->prepare_cached( $sql ) or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
625 $res = $sth->execute( $event->{Id} ) or Fatal( "Can't execute '$sql': ".$sth->errstr() );
627 deleteEventFiles( $event->{Id}, $event->{MonitorId} );
630 else
632 Error( "Unable to delete event $event->{Id} as previous operations failed\n" );
636 $sth->finish();
639 sub generateVideo
641 my $filter = shift;
642 my $event = shift;
643 my $phone = shift;
645 my $rate = $event->{DefaultRate}/100;
646 my $scale = $event->{DefaultScale}/100;
647 my $format;
649 my @ffmpeg_formats = split( /\s+/, ZM_FFMPEG_FORMATS );
650 my $default_video_format;
651 my $default_phone_format;
652 foreach my $ffmpeg_format( @ffmpeg_formats )
654 if ( $ffmpeg_format =~ /^(.+)\*\*$/ )
656 $default_phone_format = $1;
658 elsif ( $ffmpeg_format =~ /^(.+)\*$/ )
660 $default_video_format = $1;
664 if ( $phone && $default_phone_format )
666 $format = $default_phone_format;
668 elsif ( $default_video_format )
670 $format = $default_video_format;
672 else
674 $format = $ffmpeg_formats[0];
677 my $command = ZM_PATH_BIN."/zmvideo.pl -e ".$event->{Id}." -r ".$rate." -s ".$scale." -f ".$format;
678 my $output = qx($command);
679 chomp( $output );
680 my $status = $? >> 8;
681 if ( $status || DBG_LEVEL > 0 )
683 Debug( "Output: $output\n" );
685 if ( $status )
687 Error( "Video generation '$command' failed with status: $status\n" );
688 if ( wantarray() )
690 return( undef, undef );
692 return( 0 );
694 else
696 my $sql = "update Events set Videoed = 1 where Id = ?";
697 my $sth = $dbh->prepare_cached( $sql ) or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
698 my $res = $sth->execute( $event->{Id} ) or Fatal( "Can't execute '$sql': ".$sth->errstr() );
699 if ( wantarray() )
701 return( $format, sprintf( "%s/%s", getEventPath( $event ), $output ) );
704 return( 1 );
707 sub uploadArchFile
709 my $filter = shift;
710 my $event = shift;
711 if ( !ZM_UPLOAD_FTP_HOST )
713 Error( "Cannot upload archive as no FTP host defined" );
714 return( 0 );
717 my $arch_file = ZM_UPLOAD_FTP_LOC_DIR.'/'.$event->{MonitorName}.'-'.$event->{Id};
718 my $arch_image_path = getEventPath( $event )."/".((ZM_UPLOAD_ARCH_ANALYSE)?'{*analyse,*capture}':'*capture').".jpg";
719 my @arch_image_files = glob($arch_image_path);
721 my $arch_error;
722 if ( ZM_UPLOAD_ARCH_FORMAT eq "zip" )
724 $arch_file .= '.zip';
725 my $zip = Archive::Zip->new();
726 Info( "Creating upload file '$arch_file', ".int(@arch_image_files)." files\n" );
728 my $status = &AZ_OK;
729 foreach my $image_file ( @arch_image_files )
731 Info( "Adding $image_file\n" );
732 my $member = $zip->addFile( $image_file );
733 last unless ( $member );
734 $member->desiredCompressionMethod( (ZM_UPLOAD_ARCH_COMPRESS)?&COMPRESSION_DEFLATED:&COMPRESSION_STORED );
736 $status = $zip->writeToFileNamed( $arch_file );
738 if ( $arch_error = ($status != &AZ_OK) )
740 Error( "Zip error: $status\n " );
743 elsif ( ZM_UPLOAD_ARCH_FORMAT eq "tar" )
745 if ( ZM_UPLOAD_ARCH_COMPRESS )
747 $arch_file .= '.tar.gz';
749 else
751 $arch_file .= '.tar';
753 Info( "Creating upload file '$arch_file', ".int(@arch_image_files)." files\n" );
755 if ( $arch_error = !Archive::Tar->create_archive( $arch_file, ZM_UPLOAD_ARCH_COMPRESS, @arch_image_files ) )
757 Error( "Tar error: ".Archive::Tar->error()."\n " );
761 if ( $arch_error )
763 return( 0 );
765 else
767 Info( "Uploading to ".ZM_UPLOAD_FTP_HOST."\n" );
768 my $ftp = Net::FTP->new( ZM_UPLOAD_FTP_HOST, Timeout=>ZM_UPLOAD_FTP_TIMEOUT, Passive=>ZM_UPLOAD_FTP_PASSIVE, Debug=>ZM_UPLOAD_FTP_DEBUG );
769 if ( !$ftp )
771 warn( "Can't create ftp connection: $@" );
772 return( 0 );
775 $ftp->login( ZM_UPLOAD_FTP_USER, ZM_UPLOAD_FTP_PASS ) or warn( "FTP - Can't login" );
776 $ftp->binary() or warn( "FTP - Can't go binary" );
777 $ftp->cwd( ZM_UPLOAD_FTP_REM_DIR ) or warn( "FTP - Can't cwd" );
778 $ftp->put( $arch_file ) or warn( "FTP - Can't upload '$arch_file'" );
779 $ftp->quit() or warn( "FTP - Can't quit" );
780 unlink( $arch_file );
781 my $sql = "update Events set Uploaded = 1 where Id = ?";
782 my $sth = $dbh->prepare_cached( $sql ) or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
783 my $res = $sth->execute( $event->{Id} ) or Fatal( "Can't execute '$sql': ".$sth->errstr() );
785 return( 1 );
788 sub substituteTags
790 my $text = shift;
791 my $filter = shift;
792 my $event = shift;
793 my $attachments_ref = shift;
795 # First we'd better check what we need to get
796 # We have a filter and an event, do we need any more
797 # monitor information?
798 my $need_monitor = $text =~ /%(?:MET|MEH|MED|MEW|MEN|MEA)%/;
800 my $monitor = {};
801 if ( $need_monitor )
803 my $db_now = strftime( "%Y-%m-%d %H:%M:%S", localtime() );
804 my $sql = "select M.Id, count(E.Id) as EventCount, count(if(E.Archived,1,NULL)) as ArchEventCount, count(if(E.StartTime>'$db_now' - INTERVAL 1 HOUR && E.Archived = 0,1,NULL)) as HourEventCount, count(if(E.StartTime>'$db_now' - INTERVAL 1 DAY && E.Archived = 0,1,NULL)) as DayEventCount, count(if(E.StartTime>'$db_now' - INTERVAL 7 DAY && E.Archived = 0,1,NULL)) as WeekEventCount, count(if(E.StartTime>'$db_now' - INTERVAL 1 MONTH && E.Archived = 0,1,NULL)) as MonthEventCount from Monitors as M left join Events as E on E.MonitorId = M.Id where MonitorId = ? group by E.MonitorId order by Id";
805 my $sth = $dbh->prepare_cached( $sql ) or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
806 my $res = $sth->execute( $event->{MonitorId} ) or Fatal( "Can't execute '$sql': ".$sth->errstr() );
807 $monitor = $sth->fetchrow_hashref();
808 $sth->finish();
809 return() if ( !$monitor );
812 # Do we need the image information too?
813 my $need_images = $text =~ /%(?:EPI1|EPIM|EI1|EIM)%/;
814 my $first_alarm_frame;
815 my $max_alarm_frame;
816 my $max_alarm_score = 0;
817 if ( $need_images )
819 my $sql = "select * from Frames where EventId = ? and Type = 'Alarm' order by FrameId";
820 my $sth = $dbh->prepare_cached( $sql ) or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
821 my $res = $sth->execute( $event->{Id} ) or Fatal( "Can't execute '$sql': ".$sth->errstr() );
822 while( my $frame = $sth->fetchrow_hashref() )
824 if ( !$first_alarm_frame )
826 $first_alarm_frame = $frame;
828 if ( $frame->{Score} > $max_alarm_score )
830 $max_alarm_frame = $frame;
831 $max_alarm_score = $frame->{Score};
834 $sth->finish();
837 my $url = ZM_URL;
838 $text =~ s/%ZP%/$url/g;
839 $text =~ s/%MN%/$event->{MonitorName}/g;
840 $text =~ s/%MET%/$monitor->{EventCount}/g;
841 $text =~ s/%MEH%/$monitor->{HourEventCount}/g;
842 $text =~ s/%MED%/$monitor->{DayEventCount}/g;
843 $text =~ s/%MEW%/$monitor->{WeekEventCount}/g;
844 $text =~ s/%MEM%/$monitor->{MonthEventCount}/g;
845 $text =~ s/%MEA%/$monitor->{ArchEventCount}/g;
846 $text =~ s/%MP%/$url?view=watch&mid=$event->{MonitorId}/g;
847 $text =~ s/%MPS%/$url?view=watchfeed&mid=$event->{MonitorId}&mode=stream/g;
848 $text =~ s/%MPI%/$url?view=watchfeed&mid=$event->{MonitorId}&mode=still/g;
849 $text =~ s/%EP%/$url?view=event&mid=$event->{MonitorId}&eid=$event->{Id}/g;
850 $text =~ s/%EPS%/$url?view=event&mode=stream&mid=$event->{MonitorId}&eid=$event->{Id}/g;
851 $text =~ s/%EPI%/$url?view=event&mode=still&mid=$event->{MonitorId}&eid=$event->{Id}/g;
852 $text =~ s/%EI%/$event->{Id}/g;
853 $text =~ s/%EN%/$event->{Name}/g;
854 $text =~ s/%EC%/$event->{Cause}/g;
855 $text =~ s/%ED%/$event->{Notes}/g;
856 $text =~ s/%ET%/$event->{StartTime}/g;
857 $text =~ s/%EL%/$event->{Length}/g;
858 $text =~ s/%EF%/$event->{Frames}/g;
859 $text =~ s/%EFA%/$event->{AlarmFrames}/g;
860 $text =~ s/%EST%/$event->{TotScore}/g;
861 $text =~ s/%ESA%/$event->{AvgScore}/g;
862 $text =~ s/%ESM%/$event->{MaxScore}/g;
863 if ( $first_alarm_frame )
865 $text =~ s/%EPI1%/$url?view=frame&mid=$event->{MonitorId}&eid=$event->{Id}&fid=$first_alarm_frame->{FrameId}/g;
866 $text =~ s/%EPIM%/$url?view=frame&mid=$event->{MonitorId}&eid=$event->{Id}&fid=$max_alarm_frame->{FrameId}/g;
867 if ( $attachments_ref && $text =~ s/%EI1%//g )
869 push( @$attachments_ref, { type=>"image/jpeg", path=>sprintf( "%s/%0".ZM_EVENT_IMAGE_DIGITS."d-capture.jpg", getEventPath( $event ), $first_alarm_frame->{FrameId} ) } );
871 if ( $attachments_ref && $text =~ s/%EIM%//g )
873 # Don't attach the same image twice
874 if ( !@$attachments_ref || ($first_alarm_frame->{FrameId} != $max_alarm_frame->{FrameId} ) )
876 push( @$attachments_ref, { type=>"image/jpeg", path=>sprintf( "%s/%0".ZM_EVENT_IMAGE_DIGITS."d-capture.jpg", getEventPath( $event ), $max_alarm_frame->{FrameId} ) } );
880 if ( $attachments_ref && ZM_OPT_FFMPEG )
882 if ( $text =~ s/%EV%//g )
884 my ( $format, $path ) = generateVideo( $filter, $event );
885 if ( !$format )
887 return( undef );
889 push( @$attachments_ref, { type=>"video/$format", path=>$path } );
891 if ( $text =~ s/%EVM%//g )
893 my ( $format, $path ) = generateVideo( $filter, $event, 1 );
894 if ( !$format )
896 return( undef );
898 push( @$attachments_ref, { type=>"video/$format", path=>$path } );
901 $text =~ s/%FN%/$filter->{Name}/g;
902 ( my $filter_name = $filter->{Name} ) =~ s/ /+/g;
903 $text =~ s/%FP%/$url?view=filter&mid=$event->{MonitorId}&filter_name=$filter_name/g;
905 return( $text );
908 sub sendEmail
910 my $filter = shift;
911 my $event = shift;
913 if ( !ZM_FROM_EMAIL )
915 warn( "No 'from' email address defined, not sending email" );
916 return( 0 );
918 if ( !ZM_EMAIL_ADDRESS )
920 warn( "No email address defined, not sending email" );
921 return( 0 );
924 Info( "Creating notification email\n" );
926 my $subject = substituteTags( ZM_EMAIL_SUBJECT, $filter, $event );
927 return( 0 ) if ( !$subject );
928 my @attachments;
929 my $body = substituteTags( ZM_EMAIL_BODY, $filter, $event, \@attachments );
930 return( 0 ) if ( !$body );
932 Info( "Sending notification email '$subject'\n" );
934 eval
936 if ( ZM_NEW_MAIL_MODULES )
938 ### Create the multipart container
939 my $mail = MIME::Lite->new (
940 From => ZM_FROM_EMAIL,
941 To => ZM_EMAIL_ADDRESS,
942 Subject => $subject,
943 Type => "multipart/mixed"
945 ### Add the text message part
946 $mail->attach (
947 Type => "TEXT",
948 Data => $body
950 ### Add the attachments
951 foreach my $attachment ( @attachments )
953 Info( "Attaching '$attachment->{path}\n" );
954 $mail->attach(
955 Path => $attachment->{path},
956 Type => $attachment->{type},
957 Disposition => "attachment"
960 ### Send the Message
961 MIME::Lite->send( "smtp", ZM_EMAIL_HOST, Timeout=>60 );
962 $mail->send();
964 else
966 my $from_email = substituteTags( ZM_FROM_EMAIL );
967 my $email_address = substituteTags( ZM_EMAIL_ADDRESS );
968 my @attachpaths ;
969 foreach my $attachment ( @attachments )
971 push(@attachpaths, $attachment->{path} );
973 my $attachstring ;
974 if ( @attachpaths )
976 $attachstring = join(" -a "," ", @attachpaths);
978 Info( "Executing /usr/bin/email -s \" $subject\" $attachstring -f $from_email $email_address \n" );
979 my $mail = `/usr/bin/email -s \"$subject\" $attachstring -f $from_email $email_address << EOF $body `;
982 if ( $@ )
984 warn( "Can't send email: $@" );
985 return( 0 );
987 else
989 Info( "Notification email sent\n" );
991 my $sql = "update Events set Emailed = 1 where Id = ?";
992 my $sth = $dbh->prepare_cached( $sql ) or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
993 my $res = $sth->execute( $event->{Id} ) or Fatal( "Can't execute '$sql': ".$sth->errstr() );
995 return( 1 );
998 sub sendMessage
1000 my $filter = shift;
1001 my $event = shift;
1003 if ( !ZM_FROM_EMAIL )
1005 warn( "No 'from' email address defined, not sending message" );
1006 return( 0 );
1008 if ( !ZM_MESSAGE_ADDRESS )
1010 warn( "No message address defined, not sending message" );
1011 return( 0 );
1014 Info( "Creating notification message\n" );
1016 my $subject = substituteTags( ZM_MESSAGE_SUBJECT, $filter, $event );
1017 return( 0 ) if ( !$subject );
1018 my @attachments;
1019 my $body = substituteTags( ZM_MESSAGE_BODY, $filter, $event, \@attachments );
1020 return( 0 ) if ( !$body );
1022 Info( "Sending notification message '$subject'\n" );
1024 eval
1026 if ( ZM_NEW_MAIL_MODULES )
1028 ### Create the multipart container
1029 my $mail = MIME::Lite->new (
1030 From => ZM_FROM_EMAIL,
1031 To => ZM_MESSAGE_ADDRESS,
1032 Subject => $subject,
1033 Type => "multipart/mixed"
1035 ### Add the text message part
1036 $mail->attach (
1037 Type => "TEXT",
1038 Data => $body
1040 ### Add the attachments
1041 foreach my $attachment ( @attachments )
1043 Info( "Attaching '$attachment->{path}\n" );
1044 $mail->attach(
1045 Path => $attachment->{path},
1046 Type => $attachment->{type},
1047 Disposition => "attachment"
1050 ### Send the Message
1051 MIME::Lite->send( "smtp", ZM_EMAIL_HOST, Timeout=>60 );
1052 $mail->send();
1054 else
1056 my $from_email = substituteTags( ZM_FROM_EMAIL );
1057 my $email_address = substituteTags( ZM_EMAIL_ADDRESS );
1058 my @attachpaths ;
1059 foreach my $attachment ( @attachments )
1061 push(@attachpaths, $attachment->{path} );
1063 my $attachstring ;
1064 if ( @attachpaths )
1066 $attachstring = join(" -a "," ", @attachpaths);
1068 Info( "Executing /usr/bin/email -s \" $subject\" $attachstring -f $from_email $email_address \n" );
1069 my $mail = `/usr/bin/email -s \"$subject\" $attachstring -f $from_email $email_address << EOF $body `;
1072 if ( $@ )
1074 warn( "Can't send email: $@" );
1075 return( 0 );
1077 else
1079 Info( "Notification message sent\n" );
1081 my $sql = "update Events set Messaged = 1 where Id = ?";
1082 my $sth = $dbh->prepare_cached( $sql ) or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
1083 my $res = $sth->execute( $event->{Id} ) or Fatal( "Can't execute '$sql': ".$sth->errstr() );
1085 return( 1 );
1088 sub executeCommand
1090 my $filter = shift;
1091 my $event = shift;
1093 my $event_path = getEventPath( $event );
1095 my $command = $filter->{AutoExecuteCmd};
1096 $command .= " $event_path";
1098 Info( "Executing '$command'\n" );
1099 my $output = qx($command);
1100 my $status = $? >> 8;
1101 if ( $status || DBG_LEVEL > 0 )
1103 chomp( $output );
1104 Debug( "Output: $output\n" );
1106 if ( $status )
1108 Error( "Command '$command' exited with status: $status\n" );
1109 return( 0 );
1111 else
1113 my $sql = "update Events set Executed = 1 where Id = ?";
1114 my $sth = $dbh->prepare_cached( $sql ) or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
1115 my $res = $sth->execute( $event->{Id} ) or Fatal( "Can't execute '$sql': ".$sth->errstr() );
1117 return( 1 );