Merge branch 'master' into zma_to_thread

This commit is contained in:
Isaac Connor 2020-12-07 16:26:26 -05:00
commit b261fbb397
92 changed files with 2657 additions and 11466 deletions

View File

@ -150,6 +150,9 @@ set(ZM_CACHEDIR "/var/cache/zoneminder" CACHE PATH
"Location of the web server cache busting files, default: /var/cache/zoneminder")
set(ZM_CONTENTDIR "/var/lib/zoneminder" CACHE PATH
"Location of dynamic content (events and images), default: /var/lib/zoneminder")
set(ZM_FONTDIR "${CMAKE_INSTALL_FULL_DATADIR}/zoneminder/fonts" CACHE PATH
"Location of the font files used for timestamping, default: <prefix>/${CMAKE_INSTALL_DATADIR}/zoneminder/fonts")
set(ZM_DB_HOST "localhost" CACHE STRING
"Hostname where ZoneMinder database located, default: localhost")
set(ZM_DB_NAME "zm" CACHE STRING
@ -913,6 +916,7 @@ set(BUILD_SHARED_LIBS "${BUILD_SHARED_LIBS_SAVED}")
add_subdirectory(src)
add_subdirectory(scripts)
add_subdirectory(db)
add_subdirectory(fonts)
add_subdirectory(web)
add_subdirectory(misc)
add_subdirectory(onvif)

View File

@ -337,6 +337,7 @@ CREATE TABLE `Groups` (
`Id` int(10) unsigned NOT NULL auto_increment,
`Name` varchar(64) NOT NULL default '',
`ParentId` int(10) unsigned,
FOREIGN KEY (`ParentId`) REFERENCES `Groups` (`Id`) ON DELETE CASCADE,
PRIMARY KEY (`Id`)
) ENGINE=@ZM_MYSQL_ENGINE@;
@ -348,7 +349,9 @@ DROP TABLE IF EXISTS `Groups_Monitors`;
CREATE TABLE `Groups_Monitors` (
`Id` INT(10) unsigned NOT NULL auto_increment,
`GroupId` int(10) unsigned NOT NULL,
FOREIGN KEY (`GroupId`) REFERENCES `Groups` (`Id`) ON DELETE CASCADE,
`MonitorId` int(10) unsigned NOT NULL,
FOREIGN KEY (`MonitorId`) REFERENCES `Monitors` (`Id`) ON DELETE CASCADE,
PRIMARY KEY (`Id`)
) ENGINE=@ZM_MYSQL_ENGINE@;

View File

@ -10,7 +10,7 @@ SET @s = (SELECT IF(
AND column_name = 'ParentId'
) > 0,
"SELECT 'Column GroupId exists in Groups'",
"ALTER TABLE Groups ADD `ParentId` int(10) unsigned AFTER `Name`"
"ALTER TABLE `Groups` ADD `ParentId` int(10) unsigned AFTER `Name`"
));
PREPARE stmt FROM @s;

View File

@ -3,7 +3,7 @@ SET @s = (SELECT IF(
AND table_name = 'Groups'
AND column_name = 'MonitorIds'
) > 0,
"ALTER TABLE Groups MODIFY `MonitorIds` text NOT NULL",
"ALTER TABLE `Groups` MODIFY `MonitorIds` text NOT NULL",
"SELECT 'Groups no longer has MonitorIds'"
));

70
db/zm_update-1.35.15.sql Normal file
View File

@ -0,0 +1,70 @@
/*
Add the EndTime IS NOT NULL term to the Update Disk Space Filter.
This will only work if they havn't modified the stock filter
*/
UPDATE Filters SET Query_json='{"terms":[{"attr":"DiskSpace","op":"IS","val":"NULL"},{"cnj":"and","obr":"0","attr":"EndDateTime","op":"IS NOT","val":"NULL","cbr":"0"}]}' WHERE Query_json='{"terms":[{"attr":"DiskSpace","op":"IS","val":"NULL"}]}';
/*
Add the EndTime IS NOT NULL term to the Purge When Full Filter.
This will only work if they havn't modified the stock filter .
This is important to prevent SQL Errors inserting into Frames table if PurgeWhenFull deletes in-progress events.
*/
UPDATE Filters SET Query_json='{"sort_field":"Id","terms":[{"val":0,"attr":"Archived","op":"="},{"cnj":"and","val":95,"attr":"DiskPercent","op":">="},{"cnj":"and","obr":"0","attr":"EndDateTime","op":"IS NOT","val":"NULL","cbr":"0"}],"limit":100,"sort_asc":1}' WHERE Query_json='{"sort_field":"Id","terms":[{"val":0,"attr":"Archived","op":"="},{"cnj":"and","val":95,"attr":"DiskPercent","op":">="}],"limit":100,"sort_asc":1}';
/* Add FOREIGN KEYS After deleting lost records */
set @exist := (select count(*) FROM information_schema.key_column_usage where table_name='Groups_Monitors' and column_name='GroupId' and referenced_table_name='Groups' and referenced_column_name='Id');
set @sqlstmt := if( @exist > 1, "SELECT 'You have more than 1 FOREIGN KEY. Please do manual cleanup'", "SELECT 'Ok'");
set @sqlstmt := if( @exist = 1, "SELECT 'FOREIGN KEY GroupId in Groups_Monitors already exists'", @sqlstmt);
set @sqlstmt := if( @exist = 0, "SELECT 'Adding foreign key for GroupId to Groups_Monitors'", @sqlstmt);
PREPARE stmt FROM @sqlstmt;
EXECUTE stmt;
set @sqlstmt := if( @exist = 0, "SELECT 'Deleting unlinked Groups_Monitors'", "SELECT '.'");
PREPARE stmt FROM @sqlstmt;
EXECUTE stmt;
set @sqlstmt := if( @exist = 0, "DELETE FROM `Groups_Monitors` WHERE `GroupId` NOT IN (SELECT `Id` FROM `Groups`)", "SELECT '.'");
PREPARE stmt FROM @sqlstmt;
EXECUTE stmt;
set @sqlstmt := if( @exist = 0, "ALTER TABLE `Groups_Monitors` ADD FOREIGN KEY (`GroupId`) REFERENCES `Groups` (`Id`) ON DELETE CASCADE", "SELECT '.'");
PREPARE stmt FROM @sqlstmt;
EXECUTE stmt;
/* Add FOREIGN KEYS After deleting lost records */
set @exist := (select count(*) FROM information_schema.key_column_usage where table_name='Groups_Monitors' and column_name='MonitorId' and referenced_table_name='Monitors' and referenced_column_name='Id');
set @sqlstmt := if( @exist > 1, "SELECT 'You have more than 1 FOREIGN KEY. Please do manual cleanup'", "SELECT 'Ok'");
set @sqlstmt := if( @exist = 1, "SELECT 'FOREIGN KEY MonitorId in Groups_Monitors already exists'", @sqlstmt);
set @sqlstmt := if( @exist = 0, "SELECT 'Adding foreign key for MonitorId to Groups_Monitors'", @sqlstmt);
PREPARE stmt FROM @sqlstmt;
EXECUTE stmt;
set @sqlstmt := if( @exist = 0, "SELECT 'Deleting unlinked Groups_Monitors'", "SELECT '.'");
PREPARE stmt FROM @sqlstmt;
EXECUTE stmt;
set @sqlstmt := if( @exist = 0, "DELETE FROM `Groups_Monitors` WHERE `MonitorId` NOT IN (SELECT `Id` FROM `Monitors`)", "SELECT '.'");
PREPARE stmt FROM @sqlstmt;
EXECUTE stmt;
set @sqlstmt := if( @exist = 0, "ALTER TABLE `Groups_Monitors` ADD FOREIGN KEY (`MonitorId`) REFERENCES `Monitors` (`Id`) ON DELETE CASCADE", "SELECT '.'");
PREPARE stmt FROM @sqlstmt;
EXECUTE stmt;
/* Add FOREIGN KEYS After deleting lost records */
set @exist := (select count(*) FROM information_schema.key_column_usage where table_name='Groups' and column_name='ParentId' and referenced_table_name='Groups' and referenced_column_name='Id');
set @sqlstmt := if( @exist > 1, "SELECT 'You have more than 1 FOREIGN KEY. Please do manual cleanup'", "SELECT 'Ok'");
set @sqlstmt := if( @exist = 1, "SELECT 'FOREIGN KEY ParentId in Groups already exists'", @sqlstmt);
set @sqlstmt := if( @exist = 0, "SELECT 'Adding foreign key for ParentId to Groups'", @sqlstmt);
PREPARE stmt FROM @sqlstmt;
EXECUTE stmt;
set @sqlstmt := if( @exist = 0, "SELECT 'Deleting unlinked Groups'", "SELECT '.'");
PREPARE stmt FROM @sqlstmt;
EXECUTE stmt;
set @sqlstmt := if( @exist = 0, "UPDATE `Groups` SET `ParentId` = NULL WHERE (ParentId IS NOT NULL) and ParentId NOT IN (SELECT * FROM(SELECT Id FROM `Groups`)tblTmp)", "SELECT '.'");
PREPARE stmt FROM @sqlstmt;
EXECUTE stmt;
set @sqlstmt := if( @exist = 0, "ALTER TABLE `Groups` ADD FOREIGN KEY (ParentId) REFERENCES `Groups` (Id) ON DELETE CASCADE", "SELECT '.'");
PREPARE stmt FROM @sqlstmt;
EXECUTE stmt;

View File

@ -28,7 +28,7 @@
%global _hardened_build 1
Name: zoneminder
Version: 1.35.14
Version: 1.35.15
Release: 1%{?dist}
Summary: A camera monitoring and analysis tool
Group: System Environment/Daemons
@ -44,7 +44,7 @@ License: GPLv2+ and LGPLv2+ and MIT
URL: http://www.zoneminder.com/
Source0: https://github.com/ZoneMinder/ZoneMinder/archive/%{version}.tar.gz#/zoneminder-%{version}.tar.gz
Source1: https://github.com/FriendOfCake/crud/archive/v%{crud_version}.tar.gz#/crud-%{crud_version}.tar.gz
Source1: https://github.com/FriendsOfCake/crud/archive/v%{crud_version}.tar.gz#/crud-%{crud_version}.tar.gz
Source2: https://github.com/ZoneMinder/CakePHP-Enum-Behavior/archive/%{ceb_version}.tar.gz#/cakephp-enum-behavior-%{ceb_version}.tar.gz
%{?rhel:BuildRequires: epel-rpm-macros}

View File

@ -5,5 +5,6 @@ var/cache/zoneminder/images
var/cache/zoneminder/temp
var/cache/zoneminder/cache
usr/share/zoneminder/db
usr/share/zoneminder/fonts
etc/zm/
etc/zm/conf.d

View File

@ -5,6 +5,7 @@ usr/lib/zoneminder
usr/share/polkit-1
usr/share/zoneminder/db
usr/share/zoneminder/www
usr/share/zoneminder/fonts
# libzoneminder-perl files:
usr/share/man/man3

View File

@ -5,5 +5,6 @@ var/cache/zoneminder/images
var/cache/zoneminder/temp
var/cache/zoneminder/cache
usr/share/zoneminder/db
usr/share/zoneminder/fonts
etc/zm/
etc/zm/conf.d

View File

@ -5,6 +5,7 @@ usr/lib/zoneminder
usr/share/polkit-1
usr/share/zoneminder/db
usr/share/zoneminder/www
usr/share/zoneminder/fonts
# libzoneminder-perl files:
usr/share/man/man3

5
fonts/CMakeLists.txt Normal file
View File

@ -0,0 +1,5 @@
# Glob all database upgrade scripts
file(GLOB fontfileslist RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "*.zmfnt")
# Install the fonts
install(FILES ${fontfileslist} DESTINATION "${ZM_FONTDIR}")

BIN
fonts/default.zmfnt Normal file

Binary file not shown.

View File

@ -123,14 +123,19 @@ sub get_service_urls {
);
if ( $result ) {
print "Have results from GetServices\n" if $verbose;
foreach my $svc ( @{ $result->get_Service() } ) {
my $services = $result->get_Service();
if ( $services ) {
foreach my $svc ( @{ $services } ) {
my $short_name = $namespace_map{$svc->get_Namespace()};
my $url_svc = $svc->get_XAddr()->get_value();
if ( defined $short_name && defined $url_svc ) {
print "Got $short_name service $url_svc\n" if $verbose;
$self->set_service($short_name, 'url', $url_svc);
}
}
} # end foreach service
} else {
print "No services from GetServices\n" if $verbose;
} # end if services
} else {
print "No results from GetServices\n" if $verbose;
}

View File

@ -2748,6 +2748,19 @@ our @options = (
requires => [ { name => 'ZM_WEB_LIST_THUMBS', value => 'yes' } ],
category => 'web',
},
{
name => 'ZM_WEB_ANIMATE_THUMBS',
default => 'yes',
description => 'Enlarge and show the live stream when a thumbnail is hovered over',
help => q`
Enabling this option causes the static thumbnail, shown on certain
views, to enlarge and show the live stream, when the thumbnail is
hovered over by the mouse.
`,
type => $types{boolean},
requires => [ { name => 'ZM_WEB_LIST_THUMBS', value => 'yes' } ],
category => 'web',
},
{
name => 'ZM_WEB_USE_OBJECT_TAGS',
default => 'yes',
@ -3767,6 +3780,14 @@ our @options = (
type => $types{boolean},
category => 'logging',
},
{
name => 'ZM_FONT_FILE_LOCATION',
default => '@ZM_FONTDIR@/default.zmfnt',
description => 'Font file location',
help => 'This font is used for timestamp labels.',
type => $types{string},
category => 'config',
},
);
our %options_hash = map { ( $_->{name}, $_ ) } @options;

View File

@ -41,7 +41,7 @@ require Number::Bytes::Human;
require Date::Parse;
require POSIX;
use Date::Format qw(time2str);
use Time::HiRes qw(gettimeofday tv_interval);
use Time::HiRes qw(gettimeofday tv_interval stat);
#our @ISA = qw(ZoneMinder::Object);
use parent qw(ZoneMinder::Object);
@ -791,44 +791,50 @@ sub recover_timestamps {
return;
}
my @contents = readdir(DIR);
Debug('Have ' . @contents . " files in $path");
Debug('Have ' . @contents . ' files in '.$path);
closedir(DIR);
my @mp4_files = grep( /^\d+\-video\.mp4$/, @contents);
my @mp4_files = grep(/^\d+\-video\.mp4$/, @contents);
if ( @mp4_files ) {
$$Event{DefaultVideo} = $mp4_files[0];
}
my @analyse_jpgs = grep( /^\d+\-analyse\.jpg$/, @contents);
my @analyse_jpgs = grep(/^\d+\-analyse\.jpg$/, @contents);
if ( @analyse_jpgs ) {
$$Event{Save_JPEGs} |= 2;
$$Event{SaveJPEGs} |= 2;
}
my @capture_jpgs = grep( /^\d+\-capture\.jpg$/, @contents);
my @capture_jpgs = grep(/^\d+\-capture\.jpg$/, @contents);
if ( @capture_jpgs ) {
$$Event{Frames} = scalar @capture_jpgs;
$$Event{Save_JPEGs} |= 1;
$$Event{SaveJPEGs} |= 1;
# 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]";
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->StartDateTime( Date::Format::time2str('%Y-%m-%d %H:%M:%S', $first_timestamp) );
if ( $Event->Scheme() eq 'Deep' and $Event->RelativePath(undef) and ($path ne $Event->Path(undef)) ) {
my ( $year, $month, $day, $hour, $minute, $second ) =
($path =~ /(\d{2})\/(\d{2})\/(\d{2})\/(\d{2})\/(\d{2})\/(\d{2})$/);
Error("Updating starttime to $path $year/$month/$day $hour:$minute:$second");
$Event->StartDateTime(sprintf('%.4d-%.2d-%.2d %.2d:%.2d:%.2d', 2000+$year, $month, $day, $hour, $minute, $second));
}
$Event->EndDateTime( Date::Format::time2str('%Y-%m-%d %H:%M:%S', $last_timestamp) );
Debug("From capture Jpegs have duration $duration = $last_timestamp - $first_timestamp : $$Event{StartDateTime} to $$Event{EndDateTime}");
$ZoneMinder::Database::dbh->begin_work();
foreach my $jpg ( @capture_jpgs ) {
my ( $id ) = $jpg =~ /^(\d+)\-capture\.jpg$/;
if ( ! ZoneMinder::Frame->find_one( EventId=>$$Event{Id}, FrameId=>$id ) ) {
my $file = "$path/$jpg";
if ( ! ZoneMinder::Frame->find_one(EventId=>$$Event{Id}, FrameId=>$id) ) {
my $file = $path.'/'.$jpg;
( $file ) = $file =~ /^(.*)$/;
my $timestamp = (stat($file))[9];
my $Frame = new ZoneMinder::Frame();
@ -839,11 +845,11 @@ sub recover_timestamps {
Type=>'Normal',
Score=>0,
});
}
}
} # end if Frame not found
} # end foreach capture jpg
$ZoneMinder::Database::dbh->commit();
} elsif ( @mp4_files ) {
my $file = "$path/$mp4_files[0]";
my $file = $path.'/'.$mp4_files[0];
( $file ) = $file =~ /^(.*)$/;
my $first_timestamp = (stat($file))[9];

View File

@ -92,11 +92,13 @@ sub Execute {
}
if ( $self->{HasDiskPercent} ) {
my $disk_percent = getDiskPercent($$self{Storage} ? $$self{Storage}->Path() : ());
$$self{Storage} = ZoneMinder::Storage->find_one() if ! $$self{Storage};
my $disk_percent = getDiskPercent($$self{Storage} ? $$self{Storage}->Path() : $Config{ZM_DIR_EVENTS});
$sql =~ s/zmDiskPercent/$disk_percent/g;
}
if ( $self->{HasDiskBlocks} ) {
my $disk_blocks = getDiskBlocks();
$$self{Storage} = ZoneMinder::Storage->find_one() if ! $$self{Storage};
my $disk_blocks = getDiskBlocks($$self{Storage} ? $$self{Storage}->Path() : $Config{ZM_DIR_EVENTS});
$sql =~ s/zmDiskBlocks/$disk_blocks/g;
}
if ( $self->{HasSystemLoad} ) {
@ -148,15 +150,8 @@ sub Sql {
}
my $filter_expr = ZoneMinder::General::jsonDecode($self->{Query_json});
my $sql = 'SELECT E.*,
unix_timestamp(E.StartDateTime) as Time,
M.Name as MonitorName,
M.DefaultRate,
M.DefaultScale
FROM Events as E
INNER JOIN Monitors as M on M.Id = E.MonitorId
LEFT JOIN Storage as S on S.Id = E.StorageId
';
my $sql = 'SELECT E.*, unix_timestamp(E.StartDateTime) as Time
FROM Events as E';
if ( $filter_expr->{terms} ) {
foreach my $term ( @{$filter_expr->{terms}} ) {
@ -174,12 +169,16 @@ sub Sql {
if ( $term->{attr} eq 'AlarmedZoneId' ) {
$term->{op} = 'EXISTS';
} elsif ( $term->{attr} =~ /^Monitor/ ) {
$sql = 'SELECT E.*, unix_timestamp(E.StartDateTime) as Time, M.Name as MonitorName
FROM Events as E INNER JOIN Monitors as M on M.Id = E.MonitorId';
my ( $temp_attr_name ) = $term->{attr} =~ /^Monitor(.+)$/;
$self->{Sql} .= 'M.'.$temp_attr_name;
} elsif ( $term->{attr} eq 'ServerId' or $term->{attr} eq 'MonitorServerId' ) {
$sql = 'SELECT E.*, unix_timestamp(E.StartDateTime) as Time, M.Name as MonitorName
FROM Events as E INNER JOIN Monitors as M on M.Id = E.MonitorId';
$self->{Sql} .= 'M.ServerId';
} elsif ( $term->{attr} eq 'StorageServerId' ) {
$self->{Sql} .= 'S.ServerId';
$self->{Sql} .= '(SELECT Storage.ServerId FROM Storage WHERE Storage.Id=E.StorageId)';
} elsif ( $term->{attr} eq 'FilterServerId' ) {
$self->{Sql} .= $Config{ZM_SERVER_ID};
# StartTime options
@ -308,7 +307,7 @@ sub Sql {
} elsif ( $value eq 'Even' ) {
$self->{Sql} .= ' % 2 = 0';
} else {
$self->{Sql} .= " IS $value";
$self->{Sql} .= ' IS '.$value;
}
} elsif ( $term->{op} eq 'EXISTS' ) {
$self->{Sql} .= ' EXISTS '.$value;
@ -373,6 +372,8 @@ sub Sql {
if ( $filter_expr->{sort_field} eq 'Id' ) {
$sort_column = 'E.Id';
} elsif ( $filter_expr->{sort_field} eq 'MonitorName' ) {
$sql = 'SELECT E.*, unix_timestamp(E.StartDateTime) as Time, M.Name as MonitorName
FROM Events as E INNER JOIN Monitors as M on M.Id = E.MonitorId';
$sort_column = 'M.Name';
} elsif ( $filter_expr->{sort_field} eq 'Name' ) {
$sort_column = 'E.Name';
@ -422,7 +423,7 @@ sub getDiskPercent {
}
sub getDiskBlocks {
my $command = 'df .';
my $command = 'df ' . ($_[0] ? $_[0] : '.');
my $df = qx( $command );
my $space = -1;
if ( $df =~ /\s(\d+)\s+\d+\s+\d+%/ms ) {

View File

@ -32,6 +32,7 @@ require ZoneMinder::Base;
require ZoneMinder::Object;
require ZoneMinder::Storage;
require ZoneMinder::Server;
require ZoneMinder::Monitor_Status;
#our @ISA = qw(Exporter ZoneMinder::Base);
use parent qw(ZoneMinder::Object);
@ -245,6 +246,14 @@ sub control {
}
} # end sub control
sub Status {
my $self = shift;
$$self{Status} = shift if @_;
if ( ! $$self{Status} ) {
$$self{Status} = ZoneMinder::Monitor_Status->find_one(MonitorId=>$$self{Id});
}
return $$self{Status};
}
1;
__END__

View File

@ -39,6 +39,10 @@ $table = 'Monitor_Status';
$serial = $primary_key = 'MonitorId';
%fields = map { $_ => $_ } qw(
MonitorId
Status
CaptureFPS
AnalysisFPS
CaptureBandwidth
TotalEvents
TotalEventDiskSpace
HourEvents
@ -54,6 +58,10 @@ $serial = $primary_key = 'MonitorId';
);
%defaults = (
Status => 'Unknown',
CaptureFPS => undef,
AnalysisFPS => undef,
CaptureBandwidth => undef,
TotalEvents => undef,
TotalEventDiskSpace => undef,
HourEvents => undef,

View File

@ -27,7 +27,7 @@ use strict;
use ZoneMinder;
use Getopt::Long;
use autouse 'Pod::Usage'=>qw(pod2usage);
use POSIX qw/strftime EPIPE/;
use POSIX qw/strftime EPIPE EINTR/;
use Socket;
use Data::Dumper;
use Module::Load::Conditional qw{can_load};
@ -162,24 +162,24 @@ if ( $options{command} ) {
listen(SERVER, SOMAXCONN) or Fatal("Can't listen: $!");
my $rin = '';
vec( $rin, fileno(SERVER), 1 ) = 1;
vec($rin, fileno(SERVER), 1) = 1;
my $win = $rin;
my $ein = $win;
my $timeout = MAX_COMMAND_WAIT;
while( 1 ) {
while ( 1 ) {
my $nfound = select(my $rout = $rin, undef, undef, $timeout);
if ( $nfound > 0 ) {
if ( vec( $rout, fileno(SERVER), 1 ) ) {
if ( vec($rout, fileno(SERVER), 1) ) {
my $paddr = accept(CLIENT, SERVER);
my $message = <CLIENT>;
close(CLIENT);
next if !$message;
my $params = jsonDecode($message);
Debug( Dumper( $params ) );
Debug(Dumper($params));
my $command = $params->{command};
close(CLIENT);
if ( $command eq 'quit' ) {
last;
} elsif ( $command ) {
@ -191,23 +191,23 @@ if ( $options{command} ) {
Fatal('Bogus descriptor');
}
} elsif ( $nfound < 0 ) {
if ( $! == EPIPE ) {
if ( $! == EINTR ) {
# Likely just SIGHUP
Debug("Can't select: $!");
} elsif ( $! == EPIPE ) {
Error("Can't select: $!");
} else {
Fatal("Can't select: $!");
}
} else {
#print( "Select timed out\n" );
last;
Debug('Select timed out');
}
} # end while forever
Info("Control server $id/$protocol exiting");
unlink($sock_file);
$control->close();
exit(0);
} # end if !server up
exit(0);
1;

View File

@ -381,8 +381,10 @@ sub checkFilter {
} # end if AutoCopy
if ( $filter->{UpdateDiskSpace} ) {
if ( $$filter{LockRows} ) {
$ZoneMinder::Database::dbh->begin_work();
$Event->lock_and_load();
}
my $old_diskspace = $$Event{DiskSpace};
my $new_diskspace = $Event->DiskSpace(undef);
@ -394,7 +396,7 @@ sub checkFilter {
) {
$Event->save();
}
$ZoneMinder::Database::dbh->commit();
$ZoneMinder::Database::dbh->commit() if !$$filter{LockRows};
} # end if UpdateDiskSpace
} # end foreach event
ZoneMinder::Database::end_transaction($dbh, $in_transaction) if $$filter{LockRows};
@ -405,8 +407,9 @@ sub generateVideo {
my $Event = shift;
my $phone = shift;
my $rate = $Event->{DefaultRate}/100;
my $scale = $Event->{DefaultScale}/100;
my $Monitor = $Event->Monitor();
my $rate = $$Monitor{DefaultRate}/100;
my $scale = $$Monitor{DefaultScale}/100;
my $format;
my @ffmpeg_formats = split(/\s+/, $Config{ZM_FFMPEG_FORMATS});
@ -655,9 +658,11 @@ sub substituteTags {
# First we'd better check what we need to get
# We have a filter and an event, do we need any more
# monitor information?
my $need_monitor = $text =~ /%(?:MET|MEH|MED|MEW|MEN|MEA)%/;
my $need_monitor = $text =~ /%(?:MN|MET|MEH|MED|MEW|MEN|MEA)%/;
my $need_status = $text =~ /%(?:MET|MEH|MED|MEW|MEN|MEA)%/;
my $Monitor = $Event->Monitor() if $need_monitor;
my $Status = $Monitor->Status() if $need_status;
# Do we need the image information too?
my $need_images = $text =~ /%(?:EPI1|EPIM|EI1|EIM|EI1A|EIMA|EIMOD)%/;
@ -687,13 +692,13 @@ sub substituteTags {
my $url = $Config{ZM_URL};
$text =~ s/%ZP%/$url/g;
$text =~ s/%MN%/$Event->{MonitorName}/g;
$text =~ s/%MET%/$Monitor->{TotalEvents}/g;
$text =~ s/%MEH%/$Monitor->{HourEvents}/g;
$text =~ s/%MED%/$Monitor->{DayEvents}/g;
$text =~ s/%MEW%/$Monitor->{WeekEvents}/g;
$text =~ s/%MEM%/$Monitor->{MonthEvents}/g;
$text =~ s/%MEA%/$Monitor->{ArchivedEvents}/g;
$text =~ s/%MN%/$Monitor->{Name}/g;
$text =~ s/%MET%/$Status->{TotalEvents}/g;
$text =~ s/%MEH%/$Status->{HourEvents}/g;
$text =~ s/%MED%/$Status->{DayEvents}/g;
$text =~ s/%MEW%/$Status->{WeekEvents}/g;
$text =~ s/%MEM%/$Status->{MonthEvents}/g;
$text =~ s/%MEA%/$Status->{ArchivedEvents}/g;
$text =~ s/%MP%/$url?view=watch&mid=$Event->{MonitorId}/g;
$text =~ s/%MPS%/$url?view=watch&mid=$Event->{MonitorId}&mode=stream/g;
$text =~ s/%MPI%/$url?view=watch&mid=$Event->{MonitorId}&mode=still/g;

View File

@ -350,7 +350,7 @@ sub loadMonitors {
$new_monitors{$monitor->{Id}} = $monitor;
} # end while fetchrow
%monitors = %new_monitors;
}
} # end sub loadMonitors
sub handleMessage {
my $connection = shift;
@ -368,10 +368,14 @@ sub handleMessage {
$text = '' if !defined($text);
my $monitor = $monitors{$id};
if ( !$monitor ) {
loadMonitors();
$monitor = $monitors{$id};
if ( !$monitor ) {
Warning("Can't find monitor '$id' for message '$message'");
return;
}
}
if ( !zmMemVerify($monitor) ) {
Warning("Can't verify monitor '$id' for message '$message'");
return;

View File

@ -21,7 +21,8 @@ set(ZM_BIN_SRC_FILES
zm_eventstream.cpp
zm_exception.cpp
zm_fifo.cpp
zm_file_camera.cpp zm_ffmpeg_camera.cpp
zm_file_camera.cpp
zm_font.cpp
zm_frame.cpp
zm_group.cpp
zm_image.cpp
@ -32,6 +33,7 @@ set(ZM_BIN_SRC_FILES
zm_monitor.cpp
zm_monitorstream.cpp
zm_ffmpeg.cpp
zm_ffmpeg_camera.cpp
zm_ffmpeg_input.cpp
zm_mpeg.cpp
zm_packet.cpp

File diff suppressed because it is too large Load Diff

View File

@ -17,62 +17,51 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <string.h>
#include <fcntl.h>
#include "zm.h"
#include "zm_buffer.h"
unsigned int Buffer::assign( const unsigned char *pStorage, unsigned int pSize )
{
if ( mAllocation < pSize )
{
unsigned int Buffer::assign(const unsigned char *pStorage, unsigned int pSize) {
if ( mAllocation < pSize ) {
delete[] mStorage;
mAllocation = pSize;
mHead = mStorage = new unsigned char[pSize];
}
mSize = pSize;
memcpy( mStorage, pStorage, mSize );
memcpy(mStorage, pStorage, mSize);
mHead = mStorage;
mTail = mHead + mSize;
return( mSize );
return mSize;
}
unsigned int Buffer::expand( unsigned int count )
{
unsigned int Buffer::expand(unsigned int count) {
int spare = mAllocation - mSize;
int headSpace = mHead - mStorage;
int tailSpace = spare - headSpace;
int width = mTail - mHead;
if ( spare > (int)count )
{
if ( tailSpace < (int)count )
{
memmove( mStorage, mHead, mSize );
if ( spare > static_cast<int>(count) ) {
if ( tailSpace < static_cast<int>(count) ) {
memmove(mStorage, mHead, mSize);
mHead = mStorage;
mTail = mHead + width;
}
}
else
{
} else {
mAllocation += count;
unsigned char *newStorage = new unsigned char[mAllocation];
if ( mStorage )
{
memcpy( newStorage, mHead, mSize );
if ( mStorage ) {
memcpy(newStorage, mHead, mSize);
delete[] mStorage;
}
mStorage = newStorage;
mHead = mStorage;
mTail = mHead + width;
}
return( mSize );
return mSize;
}
int Buffer::read_into( int sd, unsigned int bytes ) {
int Buffer::read_into(int sd, unsigned int bytes) {
// Make sure there is enough space
this->expand(bytes);
int bytes_read = read( sd, mTail, bytes );
int bytes_read = read(sd, mTail, bytes);
if ( bytes_read > 0 ) {
mTail += bytes_read;
mSize += bytes_read;

File diff suppressed because it is too large Load Diff

View File

@ -325,10 +325,7 @@ const char *ConfigItem::StringValue() const {
return cfg_value.string_value;
}
Config::Config() {
n_items = 0;
items = 0;
}
Config::Config() : n_items(0), items(nullptr) { }
Config::~Config() {
if ( items ) {
@ -342,10 +339,7 @@ Config::~Config() {
}
void Config::Load() {
static char sql[ZM_SQL_SML_BUFSIZ];
strncpy(sql, "SELECT `Name`, `Value`, `Type` FROM `Config` ORDER BY `Id`", sizeof(sql) );
if ( mysql_query(&dbconn, sql) ) {
if ( mysql_query(&dbconn, "SELECT `Name`, `Value`, `Type` FROM `Config` ORDER BY `Id`") ) {
Error("Can't run query: %s", mysql_error(&dbconn));
exit(mysql_errno(&dbconn));
}
@ -363,10 +357,11 @@ void Config::Load() {
}
items = new ConfigItem *[n_items];
for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++ ) {
for ( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++ ) {
items[i] = new ConfigItem(dbrow[0], dbrow[1], dbrow[2]);
}
mysql_free_result(result);
result = nullptr;
}
void Config::Assign() {

View File

@ -40,38 +40,59 @@ bool zmDbConnect() {
Error("Can't initialise database connection: %s", mysql_error(&dbconn));
return false;
}
bool reconnect = 1;
if ( mysql_options(&dbconn, MYSQL_OPT_RECONNECT, &reconnect) )
Error("Can't set database auto reconnect option: %s", mysql_error(&dbconn));
if ( !staticConfig.DB_SSL_CA_CERT.empty() )
if ( !staticConfig.DB_SSL_CA_CERT.empty() ) {
mysql_ssl_set(&dbconn,
staticConfig.DB_SSL_CLIENT_KEY.c_str(),
staticConfig.DB_SSL_CLIENT_CERT.c_str(),
staticConfig.DB_SSL_CA_CERT.c_str(),
nullptr, nullptr);
}
std::string::size_type colonIndex = staticConfig.DB_HOST.find(":");
if ( colonIndex == std::string::npos ) {
if ( !mysql_real_connect(&dbconn, staticConfig.DB_HOST.c_str(), staticConfig.DB_USER.c_str(), staticConfig.DB_PASS.c_str(), nullptr, 0, nullptr, 0) ) {
Error( "Can't connect to server: %s", mysql_error(&dbconn));
return false;
}
} else {
std::string dbHost = staticConfig.DB_HOST.substr( 0, colonIndex );
std::string dbPortOrSocket = staticConfig.DB_HOST.substr( colonIndex+1 );
if ( dbPortOrSocket[0] == '/' ) {
if ( !mysql_real_connect(&dbconn, nullptr, staticConfig.DB_USER.c_str(), staticConfig.DB_PASS.c_str(), nullptr, 0, dbPortOrSocket.c_str(), 0) ) {
if ( !mysql_real_connect(
&dbconn,
staticConfig.DB_HOST.c_str(),
staticConfig.DB_USER.c_str(),
staticConfig.DB_PASS.c_str(),
nullptr, 0, nullptr, 0) ) {
Error("Can't connect to server: %s", mysql_error(&dbconn));
return false;
}
} else {
if ( !mysql_real_connect( &dbconn, dbHost.c_str(), staticConfig.DB_USER.c_str(), staticConfig.DB_PASS.c_str(), nullptr, atoi(dbPortOrSocket.c_str()), nullptr, 0 ) ) {
Error( "Can't connect to server: %s", mysql_error( &dbconn ) );
std::string dbHost = staticConfig.DB_HOST.substr(0, colonIndex);
std::string dbPortOrSocket = staticConfig.DB_HOST.substr(colonIndex+1);
if ( dbPortOrSocket[0] == '/' ) {
if ( !mysql_real_connect(
&dbconn,
nullptr,
staticConfig.DB_USER.c_str(),
staticConfig.DB_PASS.c_str(),
nullptr, 0, dbPortOrSocket.c_str(), 0) ) {
Error("Can't connect to server: %s", mysql_error(&dbconn));
return false;
}
} else {
if ( !mysql_real_connect(
&dbconn,
dbHost.c_str(),
staticConfig.DB_USER.c_str(),
staticConfig.DB_PASS.c_str(),
nullptr,
atoi(dbPortOrSocket.c_str()),
nullptr, 0) ) {
Error("Can't connect to server: %s", mysql_error(&dbconn));
return false;
}
}
}
if ( mysql_select_db( &dbconn, staticConfig.DB_NAME.c_str() ) ) {
Error( "Can't select database: %s", mysql_error( &dbconn ) );
if ( mysql_select_db(&dbconn, staticConfig.DB_NAME.c_str()) ) {
Error("Can't select database: %s", mysql_error(&dbconn));
return false;
}
zmDbConnected = true;
@ -140,7 +161,7 @@ MYSQL_RES *zmDbRow::fetch(const char *query) {
}
row = mysql_fetch_row(result_set);
if ( ! row ) {
if ( !row ) {
mysql_free_result(result_set);
result_set = nullptr;
Error("Error getting row from query %s. Error is %s", query, mysql_error(&dbconn));
@ -155,4 +176,5 @@ zmDbRow::~zmDbRow() {
mysql_free_result(result_set);
result_set = nullptr;
}
row = nullptr;
}

View File

@ -29,8 +29,8 @@ class zmDbRow {
MYSQL_ROW row;
public:
zmDbRow() : result_set(nullptr), row(nullptr) { };
MYSQL_RES *fetch( const char *query );
zmDbRow( MYSQL_RES *, MYSQL_ROW *row );
MYSQL_RES *fetch(const char *query);
zmDbRow(MYSQL_RES *, MYSQL_ROW *row);
~zmDbRow();
MYSQL_ROW mysql_row() const { return row; };

View File

@ -1,5 +1,5 @@
//
// ZoneMinder Event Class Implementation, $Date$, $Revision$
// ZoneMinder Event Class Implementation
// Copyright (C) 2001-2008 Philip Coombes
//
// This program is free software; you can redistribute it and/or
@ -52,6 +52,7 @@ Event::Event(
const std::string &p_cause,
const StringSetMap &p_noteSetMap
) :
id(0),
monitor(p_monitor),
start_time(p_start_time),
cause(p_cause),
@ -76,9 +77,6 @@ Event::Event(
start_time = now;
}
Storage * storage = monitor->getStorage();
scheme = storage->Scheme();
unsigned int state_id = 0;
zmDbRow dbrow;
if ( dbrow.fetch("SELECT Id FROM States WHERE IsActive=1") ) {
@ -87,11 +85,12 @@ Event::Event(
// Copy it in case opening the mp4 doesn't work we can set it to another value
save_jpegs = monitor->GetOptSaveJPEGs();
Storage * storage = monitor->getStorage();
char sql[ZM_SQL_MED_BUFSIZ];
snprintf(sql, sizeof(sql),
"INSERT INTO `Events` "
"( `MonitorId`, `StorageId`, `Name`, `StartTime`, `Width`, `Height`, `Cause`, `Notes`, `StateId`, `Orientation`, `Videoed`, `DefaultVideo`, `SaveJPEGs`, `Scheme` )"
"( `MonitorId`, `StorageId`, `Name`, `StartDateTime`, `Width`, `Height`, `Cause`, `Notes`, `StateId`, `Orientation`, `Videoed`, `DefaultVideo`, `SaveJPEGs`, `Scheme` )"
" VALUES "
"( %d, %d, 'New Event', from_unixtime( %ld ), %d, %d, '%s', '%s', %d, %d, %d, '%s', %d, '%s' )",
monitor->Id(),
@ -117,9 +116,66 @@ Event::Event(
}
id = mysql_insert_id(&dbconn);
if ( !SetPath(storage) ) {
// Try another
Warning("Failed creating event dir at %s", storage->Path());
std::string sql = stringtf("SELECT `Id` FROM `Storage` WHERE `Id` != %u", storage->Id());
if ( monitor->ServerId() )
sql += stringtf(" AND ServerId=%u", monitor->ServerId());
Debug(1, "%s", sql.c_str());
storage = nullptr;
MYSQL_RES *result = zmDbFetch(sql.c_str());
if ( result ) {
for ( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++ ) {
storage = new Storage(atoi(dbrow[0]));
if ( SetPath(storage) )
break;
delete storage;
storage = nullptr;
} // end foreach row of Storage
mysql_free_result(result);
result = nullptr;
}
if ( !storage ) {
Info("No valid local storage area found. Trying all other areas.");
// Try remote
sql = "SELECT `Id` FROM `Storage` WHERE ServerId IS NULL";
if ( monitor->ServerId() )
sql += stringtf(" OR ServerId != %u", monitor->ServerId());
MYSQL_RES *result = zmDbFetch(sql.c_str());
if ( result ) {
for ( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row(result); i++ ) {
storage = new Storage(atoi(dbrow[0]));
if ( SetPath(storage) )
break;
delete storage;
storage = nullptr;
} // end foreach row of Storage
mysql_free_result(result);
result = nullptr;
}
}
if ( !storage ) {
storage = new Storage();
Warning("Failed to find a storage area to save events.");
}
sql = stringtf("UPDATE Events SET StorageId = '%d' WHERE Id=%" PRIu64, storage->Id(), id);
db_mutex.lock();
int rc = mysql_query(&dbconn, sql.c_str());
db_mutex.unlock();
if ( rc ) {
Error("Can't update event: %s. sql was (%s)", mysql_error(&dbconn), sql.c_str());
}
}
Debug(1, "Using storage area at %s", path.c_str());
db_mutex.unlock();
if ( untimedEvent ) {
Warning("Event %d has zero time, setting to current", id);
Warning("Event %" PRIu64 " has zero time, setting to current", id);
}
end_time.tv_sec = 0;
frames = 0;
@ -128,79 +184,7 @@ Event::Event(
max_score = 0;
have_video_keyframe = false;
alarm_frame_written = false;
struct tm *stime = localtime(&start_time.tv_sec);
std::string id_file;
path = stringtf("%s/%d", storage->Path(), monitor->Id());
// Try to make the Monitor Dir. Normally this would exist, but in odd cases might not.
if ( mkdir(path.c_str(), 0755) ) {
if ( errno != EEXIST )
Error("Can't mkdir %s: %s", path.c_str(), strerror(errno));
}
if ( storage->Scheme() == Storage::DEEP ) {
int dt_parts[6];
dt_parts[0] = stime->tm_year-100;
dt_parts[1] = stime->tm_mon+1;
dt_parts[2] = stime->tm_mday;
dt_parts[3] = stime->tm_hour;
dt_parts[4] = stime->tm_min;
dt_parts[5] = stime->tm_sec;
std::string date_path;
std::string time_path;
for ( unsigned int i = 0; i < sizeof(dt_parts)/sizeof(*dt_parts); i++ ) {
path += stringtf("/%02d", dt_parts[i]);
if ( mkdir(path.c_str(), 0755) ) {
// FIXME This should not be fatal. Should probably move to a different storage area.
if ( errno != EEXIST )
Error("Can't mkdir %s: %s", path.c_str(), strerror(errno));
}
if ( i == 2 )
date_path = path;
}
time_path = stringtf("%02d/%02d/%02d", stime->tm_hour, stime->tm_min, stime->tm_sec);
// Create event id symlink
id_file = stringtf("%s/.%" PRIu64, date_path.c_str(), id);
if ( symlink(time_path.c_str(), id_file.c_str()) < 0 )
Error("Can't symlink %s -> %s: %s", id_file.c_str(), time_path.c_str(), strerror(errno));
} else if ( storage->Scheme() == Storage::MEDIUM ) {
path += stringtf("/%04d-%02d-%02d",
stime->tm_year+1900, stime->tm_mon+1, stime->tm_mday
);
if ( mkdir(path.c_str(), 0755) ) {
if ( errno != EEXIST )
Error("Can't mkdir %s: %s", path.c_str(), strerror(errno));
}
path += stringtf("/%" PRIu64, id);
if ( mkdir(path.c_str(), 0755) ) {
if ( errno != EEXIST )
Error("Can't mkdir %s: %s", path.c_str(), strerror(errno));
}
} else {
path += stringtf("/%" PRIu64, id);
if ( mkdir(path.c_str(), 0755) ) {
if ( errno != EEXIST )
Error("Can't mkdir %s: %s", path.c_str(), strerror(errno));
}
// Create empty id tag file
id_file = stringtf("%s/.%" PRIu64, path.c_str(), id);
if ( FILE *id_fp = fopen(id_file.c_str(), "w") ) {
fclose(id_fp);
} else {
Error("Can't fopen %s: %s", id_file.c_str(), strerror(errno));
}
} // deep storage or not
Debug(2, "Created event %d at %s", id, path.c_str());
last_db_frame = 0;
video_name = "";
snapshot_file = path + "/snapshot.jpg";
@ -220,11 +204,13 @@ Event::Event(
video_name = stringtf("%" PRIu64 "-%s.%s", id, "video", container.c_str());
snprintf(sql, sizeof(sql), "UPDATE Events SET DefaultVideo = '%s' WHERE Id=%" PRIu64, video_name.c_str(), id);
db_mutex.lock();
if ( mysql_query(&dbconn, sql) ) {
db_mutex.unlock();
Error("Can't update event: %s. sql was (%s)", mysql_error(&dbconn), sql);
return;
}
db_mutex.unlock();
video_file = path + "/" + video_name;
Debug(1, "Writing video file to %s", video_file.c_str());
Camera * camera = monitor->getCamera();
@ -681,7 +667,6 @@ void Event::AddFrame(Image *image, struct timeval timestamp, int score, Image *a
bool db_frame = ( frame_type != BULK ) || (frames==1) || ((frames%config.bulk_frame_interval)==0) ;
if ( db_frame ) {
static char sql[ZM_SQL_MED_BUFSIZ];
struct DeltaTimeval delta_time;
DELTA_TIMEVAL(delta_time, timestamp, start_time, DT_PREC_2);
Debug(1, "Frame delta is %d.%d - %d.%d = %d.%d",
@ -692,14 +677,14 @@ void Event::AddFrame(Image *image, struct timeval timestamp, int score, Image *a
double fps = monitor->get_capture_fps();
if ( write_to_db
or
(frame_data.size() > MAX_DB_FRAMES)
(frame_data.size() >= MAX_DB_FRAMES)
or
(frame_type==BULK)
or
( fps and (frame_data.size() > fps) )
) {
Debug(1, "Adding %d frames to DB because write_to_db:%d or frames > analysis fps %f or BULK",
frame_data.size(), write_to_db, fps);
Debug(1, "Adding %d frames to DB because write_to_db:%d or frames > analysis fps %f or BULK(%d)",
frame_data.size(), write_to_db, fps, (frame_type==BULK));
WriteDbFrames();
last_db_frame = frames;
@ -731,3 +716,77 @@ void Event::AddFrame(Image *image, struct timeval timestamp, int score, Image *a
end_time = timestamp;
} // end void Event::AddFrame(Image *image, struct timeval timestamp, int score, Image *alarm_image)
bool Event::SetPath(Storage *storage) {
scheme = storage->Scheme();
path = stringtf("%s/%d", storage->Path(), monitor->Id());
// Try to make the Monitor Dir. Normally this would exist, but in odd cases might not.
if ( mkdir(path.c_str(), 0755) and ( errno != EEXIST ) ) {
Error("Can't mkdir %s: %s", path.c_str(), strerror(errno));
return false;
}
struct tm *stime = localtime(&start_time.tv_sec);
if ( scheme == Storage::DEEP ) {
int dt_parts[6];
dt_parts[0] = stime->tm_year-100;
dt_parts[1] = stime->tm_mon+1;
dt_parts[2] = stime->tm_mday;
dt_parts[3] = stime->tm_hour;
dt_parts[4] = stime->tm_min;
dt_parts[5] = stime->tm_sec;
std::string date_path;
std::string time_path;
for ( unsigned int i = 0; i < sizeof(dt_parts)/sizeof(*dt_parts); i++ ) {
path += stringtf("/%02d", dt_parts[i]);
if ( mkdir(path.c_str(), 0755) and ( errno != EEXIST ) ) {
Error("Can't mkdir %s: %s", path.c_str(), strerror(errno));
return false;
}
if ( i == 2 )
date_path = path;
}
time_path = stringtf("%02d/%02d/%02d", stime->tm_hour, stime->tm_min, stime->tm_sec);
// Create event id symlink
std::string id_file = stringtf("%s/.%" PRIu64, date_path.c_str(), id);
if ( symlink(time_path.c_str(), id_file.c_str()) < 0 ) {
Error("Can't symlink %s -> %s: %s", id_file.c_str(), time_path.c_str(), strerror(errno));
return false;
}
} else if ( scheme == Storage::MEDIUM ) {
path += stringtf("/%04d-%02d-%02d",
stime->tm_year+1900, stime->tm_mon+1, stime->tm_mday
);
if ( mkdir(path.c_str(), 0755) and ( errno != EEXIST ) ) {
Error("Can't mkdir %s: %s", path.c_str(), strerror(errno));
return false;
}
path += stringtf("/%" PRIu64, id);
if ( mkdir(path.c_str(), 0755) and ( errno != EEXIST ) ) {
Error("Can't mkdir %s: %s", path.c_str(), strerror(errno));
return false;
}
} else {
path += stringtf("/%" PRIu64, id);
if ( mkdir(path.c_str(), 0755) and ( errno != EEXIST ) ) {
Error("Can't mkdir %s: %s", path.c_str(), strerror(errno));
return false;
}
// Create empty id tag file
std::string id_file = stringtf("%s/.%" PRIu64, path.c_str(), id);
if ( FILE *id_fp = fopen(id_file.c_str(), "w") ) {
fclose(id_fp);
} else {
Error("Can't fopen %s: %s", id_file.c_str(), strerror(errno));
return false;
}
} // deep storage or not
return true;
} // end bool Event::SetPath

View File

@ -47,20 +47,19 @@ class Zone;
class Monitor;
class EventStream;
#define MAX_PRE_ALARM_FRAMES 16 // Maximum number of prealarm frames that can be stored
// Maximum number of prealarm frames that can be stored
#define MAX_PRE_ALARM_FRAMES 16
typedef uint64_t event_id_t;
typedef enum { NORMAL=0, BULK, ALARM } FrameType;
typedef enum { NORMAL=0, BULK, ALARM } FrameType;
#include "zm_frame.h"
//
// Class describing events, i.e. captured periods of activity.
//
class Event {
friend class EventStream;
protected:
static int sd;
public:
typedef std::set<std::string> StringSet;
typedef std::map<std::string,StringSet> StringSetMap;
@ -103,13 +102,18 @@ class Event {
Storage::Schemes scheme;
int save_jpegs;
void createNotes( std::string &notes );
void createNotes(std::string &notes);
public:
static bool OpenFrameSocket( int );
static bool ValidateFrameSocket( int );
static bool OpenFrameSocket(int);
static bool ValidateFrameSocket(int);
Event( Monitor *p_monitor, struct timeval p_start_time, const std::string &p_cause, const StringSetMap &p_noteSetMap );
Event(
Monitor *p_monitor,
struct timeval p_start_time,
const std::string &p_cause,
const StringSetMap &p_noteSetMap
);
~Event();
uint64_t Id() const { return id; }
@ -120,37 +124,56 @@ class Event {
const struct timeval &StartTime() const { return start_time; }
const struct timeval &EndTime() const { return end_time; }
bool SendFrameImage( const Image *image, bool alarm_frame=false );
bool WriteFrameImage( Image *image, struct timeval timestamp, const char *event_file, bool alarm_frame=false ) const;
bool WriteFrameVideo( const Image *image, const struct timeval timestamp, VideoWriter* videow );
void updateNotes( const StringSetMap &stringSetMap );
void AddFrames( int n_frames, Image **images, struct timeval **timestamps );
void AddFrame( Image *image, struct timeval timestamp, int score=0, Image *alarm_frame=nullptr );
void AddPacket( ZMPacket *p, int score=0, Image *alarm_frame=nullptr );
bool WritePacket( ZMPacket &p );
bool SendFrameImage(const Image *image, bool alarm_frame=false);
bool WriteFrameImage(
Image *image,
struct timeval timestamp,
const char *event_file,
bool alarm_frame=false
) const;
bool WriteFrameVideo(
const Image *image,
const struct timeval timestamp,
VideoWriter* videow
) const;
void updateNotes(const StringSetMap &stringSetMap);
void AddFrames(int n_frames, Image **images, struct timeval **timestamps);
void AddFrame(
Image *image,
struct timeval timestamp,
int score=0,
Image *alarm_image=nullptr);
private:
void AddFramesInternal( int n_frames, int start_frame, Image **images, struct timeval **timestamps );
void AddFramesInternal(
int n_frames,
int start_frame,
Image **images,
struct timeval **timestamps);
void WriteDbFrames();
void UpdateFramesDelta(double offset);
bool SetPath(Storage *storage);
public:
static const char *getSubPath( struct tm *time ) {
static const char *getSubPath(struct tm *time) {
static char subpath[PATH_MAX] = "";
snprintf(subpath, sizeof(subpath), "%02d/%02d/%02d/%02d/%02d/%02d", time->tm_year-100, time->tm_mon+1, time->tm_mday, time->tm_hour, time->tm_min, time->tm_sec);
snprintf(subpath, sizeof(subpath), "%02d/%02d/%02d/%02d/%02d/%02d",
time->tm_year-100, time->tm_mon+1, time->tm_mday,
time->tm_hour, time->tm_min, time->tm_sec);
return subpath;
}
static const char *getSubPath( time_t *time ) {
return Event::getSubPath( localtime( time ) );
static const char *getSubPath(time_t *time) {
return Event::getSubPath(localtime(time));
}
const char* getEventFile(void) const {
return video_file.c_str();
}
public:
static int PreAlarmCount() {
return pre_alarm_count;
}
@ -167,7 +190,12 @@ class Event {
}
pre_alarm_count = 0;
}
static void AddPreAlarmFrame(Image *image, struct timeval timestamp, int score=0, Image *alarm_frame=nullptr) {
static void AddPreAlarmFrame(
Image *image,
struct timeval timestamp,
int score=0,
Image *alarm_frame=nullptr
) {
pre_alarm_data[pre_alarm_count].image = new Image(*image);
pre_alarm_data[pre_alarm_count].timestamp = timestamp;
pre_alarm_data[pre_alarm_count].score = score;

View File

@ -1,5 +1,5 @@
//
// ZoneMinder Event Class Implementation, $Date$, $Revision$
// ZoneMinder Event Stream Class Implementation
// Copyright (C) 2001-2008 Philip Coombes
//
// This program is free software; you can redistribute it and/or
@ -16,24 +16,13 @@
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
#include <fcntl.h>
#include <sys/socket.h>
//
#include <arpa/inet.h>
#include <sys/un.h>
#include <sys/uio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <getopt.h>
#include <arpa/inet.h>
#include <glob.h>
#include <cinttypes>
#include "zm.h"
#include "zm_db.h"
#include "zm_time.h"
#include "zm_mpeg.h"
#include "zm_signal.h"
#include "zm_event.h"
#include "zm_eventstream.h"
#include "zm_storage.h"

View File

@ -57,8 +57,8 @@ static bool zmFifoDbgOpen() {
int zmFifoDbgInit(Monitor *monitor) {
zm_fifodbg_inited = true;
snprintf(zm_fifodbg_log, sizeof(zm_fifodbg_log), "%s/%d/dbgpipe.log",
monitor->getStorage()->Path(), monitor->Id());
snprintf(zm_fifodbg_log, sizeof(zm_fifodbg_log), "%s/dbgpipe-%d.log",
staticConfig.PATH_SOCKS.c_str(), monitor->Id());
zmFifoDbgOpen();
return 1;
}
@ -117,6 +117,7 @@ bool FifoStream::sendRAWFrames() {
return false;
}
if ( fwrite(buffer, bytes_read, 1, stdout) != 1 ) {
if ( !zm_terminate )
Error("Problem during writing: %s", strerror(errno));
close(fd);
return false;
@ -181,7 +182,7 @@ bool FifoStream::sendMJEGFrames() {
return true;
if ( fprintf(stdout,
"--ZoneMinderFrame\r\n"
"--" BOUNDARY "\r\n"
"Content-Type: image/jpeg\r\n"
"Content-Length: %d\r\n\r\n",
total_read) < 0 ) {
@ -206,35 +207,40 @@ void FifoStream::setStreamStart(const char * path) {
void FifoStream::setStreamStart(int monitor_id, const char * format) {
char diag_path[PATH_MAX];
const char * filename;
Monitor * monitor = Monitor::Load(monitor_id, false, Monitor::QUERY);
if ( !strcmp(format, "reference") ) {
snprintf(diag_path, sizeof(diag_path), "%s/diagpipe-r-%d.jpg",
staticConfig.PATH_SOCKS.c_str(), monitor->Id());
stream_type = MJPEG;
filename = "diagpipe-r.jpg";
} else if ( !strcmp(format, "delta") ) {
filename = "diagpipe-d.jpg";
snprintf(diag_path, sizeof(diag_path), "%s/diagpipe-d-%d.jpg",
staticConfig.PATH_SOCKS.c_str(), monitor->Id());
stream_type = MJPEG;
} else {
if ( strcmp(format, "raw") ) {
Warning("Unknown or unspecified format. Defaulting to raw");
}
snprintf(diag_path, sizeof(diag_path), "%s/dbgpipe-%d.log",
staticConfig.PATH_SOCKS.c_str(), monitor->Id());
stream_type = RAW;
filename = "dbgpipe.log";
}
snprintf(diag_path, sizeof(diag_path), "%s/%d/%s",
monitor->getStorage()->Path(), monitor->Id(), filename);
setStreamStart(diag_path);
}
void FifoStream::runStream() {
if ( stream_type == MJPEG ) {
fprintf(stdout, "Content-Type: multipart/x-mixed-replace;boundary=ZoneMinderFrame\r\n\r\n");
fprintf(stdout, "Content-Type: multipart/x-mixed-replace;boundary=" BOUNDARY "\r\n\r\n");
} else {
fprintf(stdout, "Content-Type: text/html\r\n\r\n");
}
/* only 1 person can read from a fifo at a time, so use a lock */
char lock_file[PATH_MAX];
snprintf(lock_file, sizeof(lock_file), "%s.rlock", stream_path);
file_create_if_missing(lock_file, false);
Debug(1, "Locking %s", lock_file);
int fd_lock = open(lock_file, O_RDONLY);
if ( fd_lock < 0 ) {
@ -242,8 +248,13 @@ void FifoStream::runStream() {
return;
}
int res = flock(fd_lock, LOCK_EX | LOCK_NB);
while ( (res < 0 and errno == EAGAIN) and (! zm_terminate) ) {
Warning("Flocking problem on %s: - %s", lock_file, strerror(errno));
sleep(1);
res = flock(fd_lock, LOCK_EX | LOCK_NB);
}
if ( res < 0 ) {
Error("Flocking problem on %s: - %s", lock_file, strerror(errno));
Error("Flocking problem on %d != %d %s: - %s", EAGAIN, res, lock_file, strerror(errno));
close(fd_lock);
return;
}

74
src/zm_font.cpp Normal file
View File

@ -0,0 +1,74 @@
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include "zm.h"
#include "zm_font.h"
#include "zm_utils.h"
int ZmFont::ReadFontFile(const std::string &loc) {
FILE *f = fopen(loc.c_str(), "rb");
if ( !f ) return -1; // FILE NOT FOUND
struct stat st;
stat(loc.c_str(), &st);
font = new ZMFONT;
size_t header_size = 8 + (sizeof(ZMFONT_BH) * NUM_FONT_SIZES);
// MAGIC + pad + BitmapHeaders
size_t readsize = fread(&font[0], 1, header_size, f);
if ( readsize < header_size ) {
delete font;
font = nullptr;
return -2; // EOF reached, invalid file
}
if ( memcmp(font->MAGIC, "ZMFNT", 5) != 0 ) // Check whether magic is correct
return -3;
for ( int i = 0; i < NUM_FONT_SIZES; i++ ) {
/* Character Width cannot be greater than 64 as a row is represented as a uint64_t,
height cannot be greater than 200(arbitary number which i have chosen, shouldn't need more than this) and
idx should not be more than filesize
*/
if ( (font->header[i].charWidth > 64 && font->header[i].charWidth == 0) ||
(font->header[i].charHeight > 200 && font->header[i].charHeight == 0) ||
(font->header[i].idx > st.st_size) ) {
delete font;
font = nullptr;
return -4;
}
} // end foreach font size
datasize = st.st_size - header_size;
font->data = new uint64_t[datasize/sizeof(uint64_t)];
readsize = fread(&font->data[0], 1, datasize, f);
if ( readsize < datasize ) { // Shouldn't happen
delete[] font->data;
font->data = nullptr;
delete font;
font = nullptr;
return -2;
}
fclose(f);
return 0;
}
ZmFont::~ZmFont() {
if ( font && font->data ) {
delete[] font->data;
font->data = nullptr;
}
if ( font ) {
delete font;
font = nullptr;
}
}
uint64_t *ZmFont::GetBitmapData() {
return &font->data[font->header[size].idx];
}

File diff suppressed because it is too large Load Diff

View File

@ -18,7 +18,6 @@
//
#include "zm.h"
#include "zm_font.h"
#include "zm_bigfont.h"
#include "zm_image.h"
#include "zm_utils.h"
#include "zm_rgb.h"
@ -78,6 +77,9 @@ static deinterlace_4field_fptr_t fptr_deinterlace_4field_gray8;
/* Pointer to image buffer memory copy function */
imgbufcpy_fptr_t fptr_imgbufcpy;
/* Font */
static ZmFont font;
void Image::update_function_pointers() {
/* Because many loops are unrolled and work on 16 colours/time or 4 pixels/time, we have to meet requirements */
if ( pixels % 16 || pixels % 12 ) {
@ -116,7 +118,7 @@ Image::Image() {
size = 0;
allocation = 0;
buffer = 0;
buffertype = 0;
buffertype = ZM_BUFTYPE_DONTFREE;
holdbuffer = 0;
text[0] = '\0';
blend = fptr_blend;
@ -134,24 +136,26 @@ Image::Image(const char *filename) {
size = 0;
allocation = 0;
buffer = 0;
buffertype = 0;
buffertype = ZM_BUFTYPE_DONTFREE;
holdbuffer = 0;
ReadJpeg(filename, ZM_COLOUR_RGB24, ZM_SUBPIX_ORDER_RGB);
text[0] = '\0';
update_function_pointers();
}
Image::Image(int p_width, int p_height, int p_colours, int p_subpixelorder, uint8_t *p_buffer, unsigned int p_padding) {
Image::Image(int p_width, int p_height, int p_colours, int p_subpixelorder, uint8_t *p_buffer, unsigned int p_padding) :
width(p_width),
height(p_height),
colours(p_colours),
padding(p_padding),
subpixelorder(p_subpixelorder),
buffer(p_buffer) {
if ( !initialised )
Initialise();
width = p_width;
height = p_height;
pixels = width*height;
colours = p_colours;
pixels = width * height;
linesize = p_width * p_colours;
padding = p_padding;
subpixelorder = p_subpixelorder;
size = linesize*height + padding;
size = linesize * height + padding;
buffer = nullptr;
holdbuffer = 0;
if ( p_buffer ) {
@ -166,16 +170,18 @@ Image::Image(int p_width, int p_height, int p_colours, int p_subpixelorder, uint
update_function_pointers();
}
Image::Image(int p_width, int p_linesize, int p_height, int p_colours, int p_subpixelorder, uint8_t *p_buffer, unsigned int p_padding) {
Image::Image(int p_width, int p_linesize, int p_height, int p_colours, int p_subpixelorder, uint8_t *p_buffer, unsigned int p_padding) :
width(p_width),
linesize(p_linesize),
height(p_height),
colours(p_colours),
padding(p_padding),
subpixelorder(p_subpixelorder),
buffer(p_buffer)
{
if ( !initialised )
Initialise();
width = p_width;
linesize = p_linesize;
height = p_height;
pixels = width*height;
colours = p_colours;
padding = p_padding;
subpixelorder = p_subpixelorder;
size = linesize*height + padding;
buffer = nullptr;
holdbuffer = 0;
@ -493,6 +499,12 @@ void Image::Initialise() {
g_u_table = g_u_table_global;
b_u_table = b_u_table_global;
int res = font.ReadFontFile(config.font_file_location);
if ( res == -1 ) {
Panic("Invalid font location: %s", config.font_file_location);
} else if ( res == -2 || res == -3 || res == -4 ) {
Panic("Invalid font file.");
}
initialised = true;
}
@ -659,7 +671,7 @@ void Image::Assign(
return;
}
} else {
if ( new_size > allocation || !buffer ) {
if ( (new_size > allocation) || !buffer ) {
DumpImgBuffer();
AllocImgBuffer(new_size);
}
@ -895,8 +907,8 @@ bool Image::ReadJpeg(const char *filename, unsigned int p_colours, unsigned int
jpeg_read_header(cinfo, TRUE);
if ( cinfo->num_components != 1 && cinfo->num_components != 3 ) {
Error( "Unexpected colours when reading jpeg image: %d", colours );
if ( (cinfo->num_components != 1) && (cinfo->num_components != 3) ) {
Error("Unexpected colours when reading jpeg image: %d", colours);
jpeg_abort_decompress(cinfo);
fclose(infile);
return false;
@ -911,7 +923,7 @@ bool Image::ReadJpeg(const char *filename, unsigned int p_colours, unsigned int
new_width = cinfo->image_width;
new_height = cinfo->image_height;
if ( width != new_width || height != new_height ) {
if ( (width != new_width) || (height != new_height) ) {
Debug(9, "Image dimensions differ. Old: %ux%u New: %ux%u", width, height, new_width, new_height);
}
@ -1115,7 +1127,7 @@ cinfo->out_color_space = JCS_EXT_RGB;
#else
cinfo->out_color_space = JCS_RGB;
#endif
*/
*/
cinfo->in_color_space = JCS_RGB;
}
break;
@ -1206,7 +1218,7 @@ bool Image::DecodeJpeg(
new_width = cinfo->image_width;
new_height = cinfo->image_height;
if ( width != new_width || height != new_height ) {
if ( (width != new_width) || (height != new_height) ) {
Debug(9, "Image dimensions differ. Old: %ux%u New: %ux%u",
width, height, new_width, new_height);
}
@ -1878,8 +1890,12 @@ const Coord Image::centreCoord( const char *text, int size=1 ) const {
line = text+index;
line_no++;
}
int x = (width - (max_line_len * ZM_CHAR_WIDTH * size) ) / 2;
int y = (height - (line_no * LINE_HEIGHT * size) ) / 2;
font.SetFontSize(size-1);
uint16_t char_width = font.GetCharWidth();
uint16_t char_height = font.GetCharHeight();
int x = (width - (max_line_len * char_width )) / 2;
int y = (height - (line_no * char_height) ) / 2;
return Coord(x, y);
}
@ -1925,13 +1941,15 @@ void Image::MaskPrivacy( const unsigned char *p_bitmask, const Rgb pixel_colour
}
/* RGB32 compatible: complete */
/* Bitmap decoding trick has been adopted from here:
https://lemire.me/blog/2018/02/21/iterating-over-set-bits-quickly/
*/
void Image::Annotate(
const char *p_text,
const Coord &coord,
const unsigned int size,
const Rgb fg_colour,
const Rgb bg_colour) {
Debug(1, "text %s", p_text);
strncpy(text, p_text, sizeof(text)-1);
Debug(1, "text %s", text);
@ -1945,31 +1963,32 @@ void Image::Annotate(
const uint8_t fg_g_col = GREEN_VAL_RGBA(fg_colour);
const uint8_t fg_b_col = BLUE_VAL_RGBA(fg_colour);
const uint8_t fg_bw_col = fg_colour & 0xff;
const Rgb fg_rgb_col = rgb_convert(fg_colour,subpixelorder);
const bool fg_trans = (fg_colour == RGB_TRANSPARENT);
const Rgb fg_rgb_col = rgb_convert(fg_colour, subpixelorder);
const uint8_t bg_r_col = RED_VAL_RGBA(bg_colour);
const uint8_t bg_g_col = GREEN_VAL_RGBA(bg_colour);
const uint8_t bg_b_col = BLUE_VAL_RGBA(bg_colour);
const uint8_t bg_bw_col = bg_colour & 0xff;
const Rgb bg_rgb_col = rgb_convert(bg_colour,subpixelorder);
const Rgb bg_rgb_col = rgb_convert(bg_colour, subpixelorder);
const bool bg_trans = (bg_colour == RGB_TRANSPARENT);
int zm_text_bitmask = 0x80;
if ( size == 2 )
zm_text_bitmask = 0x8000;
font.SetFontSize(size-1);
const uint16_t char_width = font.GetCharWidth();
const uint16_t char_height = font.GetCharHeight();
const uint64_t *font_bitmap = font.GetBitmapData();
Debug(1, "Font size %d, char_width %d char_height %d", size, char_width, char_height);
while ( (index < text_len) && (line_len = strcspn(line, "\n")) ) {
unsigned int line_width = line_len * ZM_CHAR_WIDTH * size;
unsigned int line_width = line_len * char_width;
unsigned int lo_line_x = coord.X();
unsigned int lo_line_y = coord.Y() + (line_no * LINE_HEIGHT * size);
unsigned int lo_line_y = coord.Y() + (line_no * char_height);
unsigned int min_line_x = 0;
// FIXME What if line_width > width?
unsigned int max_line_x = width - line_width;
unsigned int min_line_y = 0;
unsigned int max_line_y = height - (LINE_HEIGHT * size);
unsigned int max_line_y = height - char_height;
if ( lo_line_x > max_line_x )
lo_line_x = max_line_x;
@ -1981,7 +2000,7 @@ void Image::Annotate(
lo_line_y = min_line_y;
unsigned int hi_line_x = lo_line_x + line_width;
unsigned int hi_line_y = lo_line_y + (LINE_HEIGHT * size);
unsigned int hi_line_y = lo_line_y + char_height;
// Clip anything that runs off the right of the screen
if ( hi_line_x > width )
@ -1991,99 +2010,78 @@ void Image::Annotate(
if ( colours == ZM_COLOUR_GRAY8 ) {
unsigned char *ptr = &buffer[(lo_line_y*width)+lo_line_x];
for ( unsigned int y = lo_line_y, r = 0; y < hi_line_y && r < (ZM_CHAR_HEIGHT * size); y++, r++, ptr += width ) {
for ( unsigned int y = lo_line_y, r = 0; y < hi_line_y && r < char_height; y++, r++, ptr += width ) {
unsigned char *temp_ptr = ptr;
for ( unsigned int x = lo_line_x, c = 0; x < hi_line_x && c < line_len; c++ ) {
int f;
if ( size == 2 ) {
if ( (line[c] * ZM_CHAR_HEIGHT * size) + r > sizeof(bigfontdata) ) {
if ( line[c] > 0xFF ) {
Warning("Unsupported character %c in %s", line[c], line);
continue;
}
f = bigfontdata[(line[c] * ZM_CHAR_HEIGHT * size) + r];
} else {
if ( (line[c] * ZM_CHAR_HEIGHT) + r > sizeof(fontdata) ) {
Warning("Unsupported character %c in %s", line[c], line);
continue;
}
f = fontdata[(line[c] * ZM_CHAR_HEIGHT) + r];
}
for ( unsigned int i = 0; i < (ZM_CHAR_WIDTH * size) && x < hi_line_x; i++, x++, temp_ptr++ ) {
if ( f & (zm_text_bitmask >> i) ) {
if ( !fg_trans )
*temp_ptr = fg_bw_col;
} else if ( !bg_trans ) {
*temp_ptr = bg_bw_col;
}
uint64_t f = font_bitmap[(line[c] * char_height) + r];
if ( !bg_trans ) memset(temp_ptr, bg_bw_col, char_width);
while ( f != 0 ) {
uint64_t t = f & -f;
int idx = char_width - __builtin_ctzll(f);
*(temp_ptr + idx) = fg_bw_col;
f ^= t;
}
temp_ptr += char_width;
}
}
} else if ( colours == ZM_COLOUR_RGB24 ) {
unsigned int wc = width * colours;
unsigned char *ptr = &buffer[((lo_line_y*width)+lo_line_x)*colours];
for ( unsigned int y = lo_line_y, r = 0; y < hi_line_y && r < (ZM_CHAR_HEIGHT * size); y++, r++, ptr += wc ) {
for ( unsigned int y = lo_line_y, r = 0; y < hi_line_y && r < char_height; y++, r++, ptr += wc ) {
unsigned char *temp_ptr = ptr;
for ( unsigned int x = lo_line_x, c = 0; x < hi_line_x && c < line_len; c++ ) {
int f;
if ( size == 2 ) {
if ( (line[c] * ZM_CHAR_HEIGHT * size) + r > sizeof(bigfontdata) ) {
if ( line[c] > 0xFF ) {
Warning("Unsupported character %c in %s", line[c], line);
continue;
}
f = bigfontdata[(line[c] * ZM_CHAR_HEIGHT * size) + r];
} else {
if ( (line[c] * ZM_CHAR_HEIGHT) + r > sizeof(fontdata) ) {
Warning("Unsupported character %c in %s", line[c], line);
continue;
}
f = fontdata[(line[c] * ZM_CHAR_HEIGHT) + r];
}
for ( unsigned int i = 0; i < (ZM_CHAR_WIDTH * size) && x < hi_line_x; i++, x++, temp_ptr += colours ) {
if ( f & (zm_text_bitmask >> i) ) {
if ( !fg_trans ) {
RED_PTR_RGBA(temp_ptr) = fg_r_col;
GREEN_PTR_RGBA(temp_ptr) = fg_g_col;
BLUE_PTR_RGBA(temp_ptr) = fg_b_col;
}
} else if ( !bg_trans ) {
RED_PTR_RGBA(temp_ptr) = bg_r_col;
GREEN_PTR_RGBA(temp_ptr) = bg_g_col;
BLUE_PTR_RGBA(temp_ptr) = bg_b_col;
uint64_t f = font_bitmap[(line[c] * char_height) + r];
if ( !bg_trans ) {
for ( int i = 0; i < char_width; i++ ) { // We need to set individual r,g,b components
unsigned char *colour_ptr = temp_ptr + (i*3);
RED_PTR_RGBA(colour_ptr) = bg_r_col;
GREEN_PTR_RGBA(colour_ptr) = bg_g_col;
BLUE_PTR_RGBA(colour_ptr) = bg_b_col;
}
}
while ( f != 0 ) {
uint64_t t = f & -f;
int idx = char_width - __builtin_ctzll(f);
unsigned char *colour_ptr = temp_ptr + (idx*3);
RED_PTR_RGBA(colour_ptr) = fg_r_col;
GREEN_PTR_RGBA(colour_ptr) = fg_g_col;
BLUE_PTR_RGBA(colour_ptr) = fg_b_col;
f ^= t;
}
temp_ptr += char_width * colours;
}
}
} else if ( colours == ZM_COLOUR_RGB32 ) {
unsigned int wc = width * colours;
uint8_t *ptr = &buffer[((lo_line_y*width)+lo_line_x)<<2];
for ( unsigned int y = lo_line_y, r = 0; y < hi_line_y && r < (ZM_CHAR_HEIGHT * size); y++, r++, ptr += wc ) {
uint8_t *ptr = &buffer[((lo_line_y*width)+lo_line_x) << 2];
for ( unsigned int y = lo_line_y, r = 0; y < hi_line_y && r < char_height; y++, r++, ptr += wc ) {
Rgb* temp_ptr = (Rgb*)ptr;
for ( unsigned int x = lo_line_x, c = 0; x < hi_line_x && c < line_len; c++ ) {
int f;
if ( size == 2 ) {
if ( (line[c] * ZM_CHAR_HEIGHT * size) + r > sizeof(bigfontdata) ) {
if ( line[c] > 0xFF ) {
Warning("Unsupported character %c in %s", line[c], line);
continue;
}
f = bigfontdata[(line[c] * ZM_CHAR_HEIGHT * size) + r];
} else {
if ( (line[c] * ZM_CHAR_HEIGHT) + r > sizeof(fontdata) ) {
Warning("Unsupported character %c in %s", line[c], line);
continue;
}
f = fontdata[(line[c] * ZM_CHAR_HEIGHT) + r];
}
for ( unsigned int i = 0; i < (ZM_CHAR_WIDTH * size) && x < hi_line_x; i++, x++, temp_ptr++ ) {
if ( f & (zm_text_bitmask >> i) ) {
if ( !fg_trans ) {
*temp_ptr = fg_rgb_col;
}
} else if ( !bg_trans ) {
*temp_ptr = bg_rgb_col;
uint64_t f = font_bitmap[(line[c] * char_height) + r];
if ( !bg_trans ) {
for ( int i = 0; i < char_width; i++ )
*(temp_ptr + i) = bg_rgb_col;
}
while ( f != 0 ) {
uint64_t t = f & -f;
int idx = char_width - __builtin_ctzll(f);
*(temp_ptr + idx) = fg_rgb_col;
f ^= t;
}
temp_ptr += char_width;
}
}

View File

@ -64,7 +64,7 @@ inline static uint8_t* AllocBuffer(size_t p_bufsize) {
}
inline static void DumpBuffer(uint8_t* buffer, int buffertype) {
if ( buffer && buffertype != ZM_BUFTYPE_DONTFREE ) {
if ( buffer && (buffertype != ZM_BUFTYPE_DONTFREE) ) {
if ( buffertype == ZM_BUFTYPE_ZM ) {
zm_freealigned(buffer);
} else if ( buffertype == ZM_BUFTYPE_MALLOC ) {
@ -123,6 +123,7 @@ protected:
inline void DumpImgBuffer() {
DumpBuffer(buffer, buffertype);
buffertype = ZM_BUFTYPE_DONTFREE;
buffer = nullptr;
allocation = 0;
}

View File

@ -40,10 +40,14 @@ void bind_libvnc_symbols() {
static void GotFrameBufferUpdateCallback(rfbClient *rfb, int x, int y, int w, int h){
VncPrivateData *data = (VncPrivateData *)(*rfbClientGetClientData_f)(rfb, &TAG_0);
data->buffer = rfb->frameBuffer;
Debug(1, "GotFrameBufferUpdateallback x:%d y:%d w%d h:%d width: %d, height: %d",
x,y,w,h, rfb->width, rfb->height);
}
static char* GetPasswordCallback(rfbClient* cl){
return strdup((const char *)(*rfbClientGetClientData_f)(cl, &TAG_1));
Debug(1, "Getcredentials: %s", (*rfbClientGetClientData_f)(cl, &TAG_1));
return strdup(
(const char *)(*rfbClientGetClientData_f)(cl, &TAG_1));
}
static rfbCredential* GetCredentialsCallback(rfbClient* cl, int credentialType){
@ -53,6 +57,7 @@ static rfbCredential* GetCredentialsCallback(rfbClient* cl, int credentialType){
return nullptr;
}
Debug(1, "Getcredentials: %s:%s", (*rfbClientGetClientData_f)(cl, &TAG_1), (*rfbClientGetClientData_f)(cl, &TAG_2));
c->userCredential.password = strdup((const char *)(*rfbClientGetClientData_f)(cl, &TAG_1));
c->userCredential.username = strdup((const char *)(*rfbClientGetClientData_f)(cl, &TAG_2));
return c;
@ -87,6 +92,7 @@ VncCamera::VncCamera(
p_capture,
p_record_audio
),
mRfb(nullptr),
mHost(host),
mPort(port),
mUser(user),
@ -110,20 +116,33 @@ VncCamera::VncCamera(
Panic("Unexpected colours: %d", colours);
}
if ( capture )
Initialise();
}
if ( capture ) {
Debug(3, "Initializing Client");
bind_libvnc_symbols();
scale.init();
}
}
VncCamera::~VncCamera() {
if ( capture )
Terminate();
if ( capture ) {
if ( mRfb->frameBuffer )
free(mRfb->frameBuffer);
(*rfbClientCleanup_f)(mRfb);
}
}
void VncCamera::Initialise() {
Debug(2, "Initializing Client");
bind_libvnc_symbols();
mRfb = (*rfbGetClient_f)(8, 3, 4);
int VncCamera::PrimeCapture() {
Debug(1, "Priming capture from %s", mHost.c_str());
if ( ! mRfb ) {
mVncData.buffer = nullptr;
mVncData.width = 0;
mVncData.height = 0;
mRfb = (*rfbGetClient_f)(8 /* bits per sample */, 3 /* samples per pixel */, 4 /* bytes Per Pixel */);
mRfb->frameBuffer = (uint8_t *)av_malloc(8*4*width*height);
mRfb->canHandleNewFBSize = false;
(*rfbClientSetClientData_f)(mRfb, &TAG_0, &mVncData);
(*rfbClientSetClientData_f)(mRfb, &TAG_1, (void *)mPass.c_str());
@ -136,26 +155,22 @@ void VncCamera::Initialise() {
mRfb->programName = "Zoneminder VNC Monitor";
mRfb->serverHost = strdup(mHost.c_str());
mRfb->serverPort = atoi(mPort.c_str());
scale.init();
}
void VncCamera::Terminate() {
if ( mRfb->frameBuffer )
free(mRfb->frameBuffer);
(*rfbClientCleanup_f)(mRfb);
return;
}
int VncCamera::PrimeCapture() {
Debug(1, "Priming capture from %s", mHost.c_str());
}
if ( ! (*rfbInitClient_f)(mRfb, 0, nullptr) ) {
/* IF rfbInitClient fails, it calls rdbClientCleanup which will free mRfb */
Warning("Failed to Priming capture from %s", mHost.c_str());
mRfb = nullptr;
return -1;
}
if ( (mRfb->width != width) or (mRfb->height != height) ) {
Warning("Specified dimensions do not match screen size monitor: (%dx%d) != vnc: (%dx%d)",
width, height, mRfb->width, mRfb->height);
}
return 0;
}
int VncCamera::PreCapture() {
Debug(2, "PreCapture");
int rc = (*WaitForMessage_f)(mRfb, 500);
if ( rc < 0 ) {
return -1;
@ -163,13 +178,16 @@ int VncCamera::PreCapture() {
return rc;
}
rfbBool res = (*HandleRFBServerMessage_f)(mRfb);
Debug(3, "PreCapture rc from HandleMessage %d", res == TRUE ? 1 : -1);
return res == TRUE ? 1 : -1;
}
int VncCamera::Capture(Image &image) {
Debug(2, "Capturing");
uint8_t *directbuffer = image.WriteBuffer(width, height, colours, subpixelorder);
scale.Convert(
int VncCamera::Capture(ZMPacket &zm_packet) {
if ( ! mVncData.buffer ) {
return 0;
}
uint8_t *directbuffer = zm_packet.image->WriteBuffer(width, height, colours, subpixelorder);
int rc = scale.Convert(
mVncData.buffer,
mRfb->si.framebufferHeight * mRfb->si.framebufferWidth * 4,
directbuffer,
@ -180,17 +198,13 @@ int VncCamera::Capture(Image &image) {
mRfb->si.framebufferHeight,
width,
height);
return 1;
return rc == 0 ? 1 : rc;
}
int VncCamera::PostCapture() {
return 0;
}
int VncCamera::CaptureAndRecord(Image &image, timeval recording, char* event_directory) {
return 0;
}
int VncCamera::Close() {
return 0;
}

View File

@ -9,9 +9,9 @@
#if HAVE_LIBVNC
#include <rfb/rfbclient.h>
// Used by vnc callbacks
struct VncPrivateData
{
struct VncPrivateData {
uint8_t *buffer;
uint8_t width;
uint8_t height;
@ -47,14 +47,10 @@ public:
~VncCamera();
void Initialise();
void Terminate();
int PreCapture();
int PrimeCapture();
int Capture( Image &image );
int Capture(ZMPacket &packet);
int PostCapture();
int CaptureAndRecord( Image &image, timeval recording, char* event_directory );
int Close();
};

View File

@ -333,11 +333,12 @@ Monitor::Monitor()
auto_resume_time(0),
last_motion_score(0),
camera(0),
event(0),
n_zones(0),
zones(nullptr),
timestamps(0),
images(0),
privacy_bitmask( nullptr ),
images(nullptr),
privacy_bitmask(nullptr),
event_delete_thread(nullptr),
n_linked_monitors(0),
linked_monitors(nullptr)
@ -566,8 +567,6 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) {
else
event_close_mode = CLOSE_IDLE;
Debug(1, "monitor purpose=%d", purpose);
mem_size = sizeof(SharedData)
+ sizeof(TriggerData)
+ sizeof(VideoStoreData) //Information to pass back to the capture process
@ -591,16 +590,47 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) {
// maybe unneeded
// Should maybe store this for later use
std::string monitor_dir = stringtf("%s/%d", storage->Path(), id);
shared_data = nullptr;
if ( purpose == CAPTURE ) {
if ( mkdir(monitor_dir.c_str(), 0755) && ( errno != EEXIST ) ) {
Error("Can't mkdir %s: %s", monitor_dir.c_str(), strerror(errno));
}
} else if ( purpose == ANALYSIS ) {
while (
( !(this->connect() and shared_data->valid) )
or
( shared_data->last_write_index == (unsigned int)image_buffer_count )
or
( shared_data->last_write_time == 0 )
) {
Debug(1, "Waiting for capture daemon shared_data(%d) last_write_index(%d), last_write_time(%d)",
(shared_data ? 1:0),
(shared_data ? shared_data->last_write_index : 0),
(shared_data ? shared_data->last_write_time : 0));
this->disconnect();
sleep(1);
if ( zm_terminate ) break;
}
ref_image.Assign(width, height, camera->Colours(), camera->SubpixelOrder(),
image_buffer[shared_data->last_write_index].image->Buffer(), camera->ImageSize());
adaptive_skip = true;
if ( config.record_diag_images ) {
diag_path_r = monitor_dir + "/diag-r.jpg";
diag_path_d = monitor_dir + "/diag-d.jpg";
if ( config.record_diag_images_fifo ) {
diag_path_ref = stringtf("%s/diagpipe-r-%d.jpg", staticConfig.PATH_SOCKS.c_str(), id);
diag_path_delta = stringtf("%s/diagpipe-d-%d.jpg", staticConfig.PATH_SOCKS.c_str(), id);
FifoStream::fifo_create_if_missing(diag_path_ref.c_str());
FifoStream::fifo_create_if_missing(diag_path_delta.c_str());
} else {
diag_path_ref = stringtf("%s/%d/diag-r.jpg", storage->Path(), id);
diag_path_delta = stringtf("%s/%d/diag-d.jpg", storage->Path(), id);
}
}
}
start_time = last_fps_time = time( 0 );
//this->delta_image( width, height, ZM_COLOUR_GRAY8, ZM_SUBPIX_ORDER_NONE ),
//ref_image( width, height, p_camera->Colours(), p_camera->SubpixelOrder() ),
@ -769,6 +799,27 @@ Camera * Monitor::getCamera() {
#else // HAVE_LIBCURL
Error("You must have libcurl installed to use ffmpeg cameras for monitor %d", id);
#endif // HAVE_LIBCURL
} else if ( type == VNC ) {
#if HAVE_LIBVNC
camera = new VncCamera(
id,
host.c_str(),
port.c_str(),
user.c_str(),
pass.c_str(),
width,
height,
colours,
brightness,
contrast,
hue,
colour,
purpose==CAPTURE,
record_audio
);
#else // HAVE_LIBVNC
Fatal("You must have libvnc installed to use VNC cameras for monitor id %d", id);
#endif // HAVE_LIBVNC
} // end if type
camera->setMonitor(this);
@ -815,7 +866,7 @@ bool Monitor::connect() {
Debug(3, "Connecting to monitor. Purpose is %d", purpose);
#if ZM_MEM_MAPPED
snprintf(mem_file, sizeof(mem_file), "%s/zm.mmap.%d", staticConfig.PATH_MAP.c_str(), id);
map_fd = open(mem_file, O_RDWR|O_CREAT, (mode_t)0600);
map_fd = open(mem_file, O_RDWR|O_CREAT, (mode_t)0660);
if ( map_fd < 0 ) {
Error("Can't open memory map file %s, probably not enough space free: %s", mem_file, strerror(errno));
return false;
@ -864,12 +915,12 @@ bool Monitor::connect() {
}
}
#endif
if ( mem_ptr == MAP_FAILED )
Fatal("Can't map file %s (%d bytes) to memory: %s(%d)", mem_file, mem_size, strerror(errno), errno);
if ( mem_ptr == nullptr ) {
Error("mmap gave a NULL address:");
} else {
Debug(3, "mmapped to %p", mem_ptr);
if ( (mem_ptr == MAP_FAILED) or (mem_ptr == nullptr) ) {
Error("Can't map file %s (%d bytes) to memory: %s(%d)", mem_file, mem_size, strerror(errno), errno);
close(map_fd);
map_fd = -1;
mem_ptr = nullptr;
return false;
}
#else // ZM_MEM_MAPPED
shm_id = shmget((config.shm_key&0xffff0000)|id, mem_size, IPC_CREAT|0700);
@ -960,12 +1011,72 @@ bool Monitor::connect() {
timestamps = new struct timeval *[pre_event_count];
images = new Image *[pre_event_count];
last_signal = shared_data->signal;
}
} // end if purpose == ANALYSIS
Debug(3, "Success connecting");
return true;
} // Monitor::connect
bool Monitor::disconnect() {
if ( !mem_ptr )
return true;
#if ZM_MEM_MAPPED
if ( mem_ptr > (void *)0 ) {
msync(mem_ptr, mem_size, MS_ASYNC);
munmap(mem_ptr, mem_size);
}
if ( map_fd >= 0 )
close(map_fd);
map_fd = -1;
if ( purpose == CAPTURE ) {
if ( unlink(mem_file) < 0 ) {
Warning("Can't unlink '%s': %s", mem_file, strerror(errno));
}
}
#else // ZM_MEM_MAPPED
struct shmid_ds shm_data;
if ( shmctl(shm_id, IPC_STAT, &shm_data) < 0 ) {
Debug(3, "Can't shmctl: %s", strerror(errno));
return false;
}
shm_id = 0;
if ( shm_data.shm_nattch <= 1 ) {
if ( shmctl(shm_id, IPC_RMID, 0) < 0 ) {
Debug(3, "Can't shmctl: %s", strerror(errno));
return false;
}
}
if ( shmdt(mem_ptr) < 0 ) {
Debug(3, "Can't shmdt: %s", strerror(errno));
return false;
}
#endif // ZM_MEM_MAPPED
if ( image_buffer ) {
for ( int i = 0; i < image_buffer_count; i++ ) {
delete image_buffer[i].image;
image_buffer[i].image = nullptr;
}
delete[] image_buffer;
image_buffer = nullptr;
}
if ( purpose == ANALYSIS ) {
delete[] timestamps;
timestamps = nullptr;
delete[] images;
images = nullptr;
} // end if purpose == ANALYSIS
mem_ptr = nullptr;
shared_data = nullptr;
return true;
} // end bool Monitor::disconnect()
Monitor::~Monitor() {
if ( mem_ptr ) {
if ( event ) {
@ -985,92 +1096,36 @@ Monitor::~Monitor() {
event_delete_thread = nullptr;
}
if ( deinterlacing_value == 4 ) {
delete next_buffer.image;
}
#if 1
for ( int i=0; i < image_buffer_count; i++ ) {
delete image_buffer[i].image;
}
#endif
delete[] image_buffer;
if ( purpose == ANALYSIS ) {
shared_data->state = state = IDLE;
// I think we set it to the count so that it is technically 1 behind capture, which starts at 0
shared_data->last_read_index = image_buffer_count;
shared_data->last_read_time = 0;
if ( Event::PreAlarmCount() )
Event::EmptyPreAlarmFrames();
} else if ( purpose == CAPTURE ) {
shared_data->valid = false;
memset(mem_ptr, 0, mem_size);
}
#if ZM_MEM_MAPPED
if ( msync(mem_ptr, mem_size, MS_SYNC) < 0 )
Error("Can't msync: %s", strerror(errno));
if ( munmap(mem_ptr, mem_size) < 0 )
Fatal("Can't munmap: %s", strerror(errno));
close(map_fd);
if ( purpose == CAPTURE ) {
// How about we store this in the object on instantiation so that we don't have to do this again.
char mmap_path[PATH_MAX] = "";
snprintf(mmap_path, sizeof(mmap_path), "%s/zm.mmap.%d", staticConfig.PATH_MAP.c_str(), id);
if ( unlink(mmap_path) < 0 ) {
Warning("Can't unlink '%s': %s", mmap_path, strerror(errno));
if ( (deinterlacing & 0xff) == 4 ) {
delete next_buffer.image;
delete next_buffer.timestamp;
}
}
#else // ZM_MEM_MAPPED
struct shmid_ds shm_data;
if ( shmctl(shm_id, IPC_STAT, &shm_data) < 0 ) {
Fatal("Can't shmctl: %s", strerror(errno));
}
if ( shm_data.shm_nattch <= 1 ) {
if ( shmctl(shm_id, IPC_RMID, 0) < 0 ) {
Fatal("Can't shmctl: %s", strerror(errno));
}
}
#endif // ZM_MEM_MAPPED
disconnect();
} // end if mem_ptr
if ( videoStore ) {
delete videoStore;
videoStore = NULL;
}
if ( n_linked_monitors ) {
for( int i = 0; i < n_linked_monitors; i++ ) {
delete linked_monitors[i];
}
delete[] linked_monitors;
linked_monitors = 0;
}
if ( timestamps ) {
delete[] timestamps;
timestamps = 0;
}
if ( images ) {
delete[] images;
images = 0;
}
delete packetqueue;
packetqueue = NULL;
packetqueue = nullptr;
if ( privacy_bitmask ) {
delete[] privacy_bitmask;
privacy_bitmask = NULL;
}
for ( int i=0; i < n_zones; i++ ) {
for ( int i = 0; i < n_zones; i++ ) {
delete zones[i];
}
delete[] zones;
delete camera;
delete storage;
}
} // end Monitor::~Monitor()
void Monitor::AddZones(int p_n_zones, Zone *p_zones[]) {
for ( int i=0; i < n_zones; i++ )
@ -1703,7 +1758,9 @@ bool Monitor::Analyse() {
// signal is set by capture
bool signal = shared_data->signal;
bool signal_change = (signal != last_signal);
Debug(3, "Motion detection is enabled signal(%d) signal_change(%d)", signal, signal_change);
Debug(3, "Motion detection is enabled signal(%d) signal_change(%d) trigger state(%d)",
signal, signal_change, trigger_data->trigger_state);
// if we have been told to be OFF, then we are off and don't do any processing.
if ( trigger_data->trigger_state != TRIGGER_OFF ) {
@ -1795,7 +1852,11 @@ bool Monitor::Analyse() {
for ( int i = 0; i < n_linked_monitors; i++ ) {
// TODO: Shouldn't we try to connect?
if ( linked_monitors[i]->isConnected() ) {
Debug(4, "Linked monitor %d %s is connected",
linked_monitors[i]->Id(), linked_monitors[i]->Name());
if ( linked_monitors[i]->hasAlarmed() ) {
Debug(4, "Linked monitor %d %s is alarmed",
linked_monitors[i]->Id(), linked_monitors[i]->Name());
if ( !event ) {
if ( first_link ) {
if ( cause.length() )
@ -1806,6 +1867,9 @@ bool Monitor::Analyse() {
}
noteSet.insert(linked_monitors[i]->Name());
score += 50;
} else {
Debug(4, "Linked monitor %d %s is not alarmed",
linked_monitors[i]->Id(), linked_monitors[i]->Name());
}
} else {
Debug(1, "Linked monitor %d %d is not connected. Connecting.", i, linked_monitors[i]->Id());
@ -2182,7 +2246,7 @@ void Monitor::ReloadLinkedMonitors(const char *p_linked_monitors) {
int n_monitors = mysql_num_rows(result);
if ( n_monitors == 1 ) {
MYSQL_ROW dbrow = mysql_fetch_row(result);
Debug(1, "Linking to monitor %d", link_ids[i]);
Debug(1, "Linking to monitor %d %s", atoi(dbrow[0]), dbrow[1]);
linked_monitors[count++] = new MonitorLink(link_ids[i], dbrow[1]);
} else {
Warning("Can't link to monitor %d, invalid id, function or not enabled", link_ids[i]);
@ -2214,6 +2278,7 @@ int Monitor::LoadMonitors(std::string sql, Monitor **&monitors, Purpose purpose)
}
if ( mysql_errno(&dbconn) ) {
Error("Can't fetch row: %s", mysql_error(&dbconn));
mysql_free_result(result);
return 0;
}
mysql_free_result(result);
@ -2543,8 +2608,8 @@ unsigned int Monitor::DetectMotion(const Image &comp_image, Event::StringSet &zo
ref_image.Delta(comp_image, &delta_image);
if ( config.record_diag_images ) {
ref_image.WriteJpeg(diag_path_r.c_str(), config.record_diag_images_fifo);
delta_image.WriteJpeg(diag_path_d.c_str(), config.record_diag_images_fifo);
ref_image.WriteJpeg(diag_path_ref.c_str(), config.record_diag_images_fifo);
delta_image.WriteJpeg(diag_path_delta.c_str(), config.record_diag_images_fifo);
}
// Blank out all exclusion zones

View File

@ -331,8 +331,8 @@ protected:
Image ref_image;
Image alarm_image; // Used in creating analysis images, will be initialized in Analysis
Image write_image; // Used when creating snapshot images
std::string diag_path_r;
std::string diag_path_d;
std::string diag_path_ref;
std::string diag_path_delta;
Purpose purpose; // What this monitor has been created to do
int event_count;
@ -402,6 +402,7 @@ public:
void AddPrivacyBitmask( Zone *p_zones[] );
bool connect();
bool disconnect();
inline int ShmValid() const {
return shared_data && shared_data->valid;
@ -410,23 +411,20 @@ public:
inline unsigned int Id() const { return id; }
inline const char *Name() const { return name; }
inline unsigned int ServerId() { return server_id; }
inline Storage *getStorage() {
if ( ! storage ) {
storage = new Storage(storage_id);
}
return storage;
}
inline Function GetFunction() const {
return function;
}
inline Function GetFunction() const { return function; }
inline bool Enabled() const {
if ( function <= MONITOR )
return false;
return enabled;
}
inline const char *EventPrefix() const {
return event_prefix;
}
inline const char *EventPrefix() const { return event_prefix; }
inline bool Ready() const {
if ( function <= MONITOR ) {
Error("Should not be calling Ready if the function doesn't include motion detection");
@ -443,12 +441,8 @@ public:
return false;
return( enabled && shared_data->active );
}
inline bool Exif() const {
return embed_exif;
}
inline bool RecordAudio() {
return record_audio;
}
inline bool Exif() const { return embed_exif; }
inline bool RecordAudio() { return record_audio; }
/*
inline Purpose Purpose() { return purpose };

View File

@ -698,6 +698,7 @@ void MonitorStream::runStream() {
if ( !sendFrame(snap->image, snap->timestamp) ) {
Debug(2, "sendFrame failed, quiting.");
zm_terminate = true;
break;
}
// Perhaps we should use NOW instead.
last_frame_timestamp = *timestamp;
@ -709,6 +710,7 @@ void MonitorStream::runStream() {
if ( !sendFrame(snap->image, snap->timestamp) ) {
Debug(2, "sendFrame failed, quiting.");
zm_terminate = true;
break;
}
}

View File

@ -26,25 +26,25 @@
#include <stdlib.h>
#include <stdio.h>
Storage::Storage() {
Storage::Storage() : id(0) {
Warning("Instantiating default Storage Object. Should not happen.");
id = 0;
strcpy(name, "Default");
if ( staticConfig.DIR_EVENTS[0] != '/' ) {
// not using an absolute path. Make it one by appending ZM_PATH_WEB
snprintf( path, sizeof (path), "%s/%s", staticConfig.PATH_WEB.c_str( ), staticConfig.DIR_EVENTS.c_str() );
snprintf(path, sizeof(path), "%s/%s",
staticConfig.PATH_WEB.c_str(), staticConfig.DIR_EVENTS.c_str());
} else {
strncpy(path, staticConfig.DIR_EVENTS.c_str(), sizeof(path)-1 );
strncpy(path, staticConfig.DIR_EVENTS.c_str(), sizeof(path)-1);
}
scheme = MEDIUM;
scheme_str = "Medium";
}
Storage::Storage( MYSQL_ROW &dbrow ) {
Storage::Storage(MYSQL_ROW &dbrow) {
unsigned int index = 0;
id = atoi( dbrow[index++] );
strncpy( name, dbrow[index++], sizeof(name)-1 );
strncpy( path, dbrow[index++], sizeof(path)-1 );
id = atoi(dbrow[index++]);
strncpy(name, dbrow[index++], sizeof(name)-1);
strncpy(path, dbrow[index++], sizeof(path)-1);
type_str = std::string(dbrow[index++]);
scheme_str = std::string(dbrow[index++]);
if ( scheme_str == "Deep" ) {
@ -57,16 +57,15 @@ Storage::Storage( MYSQL_ROW &dbrow ) {
}
/* If a zero or invalid p_id is passed, then the old default path will be assumed. */
Storage::Storage( unsigned int p_id ) {
id = 0;
Storage::Storage(unsigned int p_id) : id(p_id) {
if ( p_id ) {
if ( id ) {
char sql[ZM_SQL_SML_BUFSIZ];
snprintf(sql, sizeof(sql), "SELECT `Id`, `Name`, `Path`, `Type`, `Scheme` FROM `Storage` WHERE `Id`=%d", p_id);
Debug(2,"Loading Storage for %d using %s", p_id, sql );
snprintf(sql, sizeof(sql), "SELECT `Id`, `Name`, `Path`, `Type`, `Scheme` FROM `Storage` WHERE `Id`=%u", id);
Debug(2, "Loading Storage for %u using %s", id, sql);
zmDbRow dbrow;
if ( !dbrow.fetch(sql) ) {
Error("Unable to load storage area for id %d: %s", p_id, mysql_error(&dbconn));
Error("Unable to load storage area for id %d: %s", id, mysql_error(&dbconn));
} else {
unsigned int index = 0;
id = atoi(dbrow[index++]);
@ -81,17 +80,18 @@ Storage::Storage( unsigned int p_id ) {
} else {
scheme = SHALLOW;
}
Debug(1, "Loaded Storage area %d '%s'", id, this->Name());
Debug(1, "Loaded Storage area %d '%s'", id, name);
}
}
if ( !id ) {
if ( staticConfig.DIR_EVENTS[0] != '/' ) {
// not using an absolute path. Make it one by appending ZM_PATH_WEB
snprintf(path, sizeof (path), "%s/%s", staticConfig.PATH_WEB.c_str(), staticConfig.DIR_EVENTS.c_str());
snprintf(path, sizeof(path), "%s/%s",
staticConfig.PATH_WEB.c_str(), staticConfig.DIR_EVENTS.c_str());
} else {
strncpy(path, staticConfig.DIR_EVENTS.c_str(), sizeof(path)-1);
}
Debug(1,"No id passed to Storage constructor. Using default path %s instead", path);
Debug(1, "No id passed to Storage constructor. Using default path %s instead", path);
strcpy(name, "Default");
scheme = MEDIUM;
scheme_str = "Medium";

View File

@ -52,8 +52,9 @@ bool StreamBase::loadMonitor(int p_monitor_id) {
Error("Unable to load monitor id %d for streaming", monitor_id);
return false;
}
if ( monitor->GetFunction() == Monitor::NONE ) {
Error("Monitor %d has function NONE. Will not be able to connect to it.", monitor_id);
Info("Monitor %d has function NONE. Will not be able to connect to it.", monitor_id);
return false;
}
@ -71,7 +72,7 @@ bool StreamBase::checkInitialised() {
return false;
}
if ( monitor->GetFunction() == Monitor::NONE ) {
Error("Monitor %d has function NONE. Will not be able to connect to it.", monitor_id);
Info("Monitor %d has function NONE. Will not be able to connect to it.", monitor_id);
return false;
}
if ( !monitor->ShmValid() ) {
@ -124,11 +125,14 @@ bool StreamBase::checkCommandQueue() {
processCommand(&msg);
return true;
}
} else {
} else if ( connkey ) {
Warning("No sd in checkCommandQueue, comms not open?");
} else {
// Perfectly valid if only getting a snapshot
Debug(1, "No sd in checkCommandQueue, comms not open?");
}
return false;
}
} // end bool StreamBase::checkCommandQueue()
Image *StreamBase::prepareImage(Image *image) {

View File

@ -139,8 +139,12 @@ int SWScale::Convert(
unsigned int new_height
) {
/* Parameter checking */
if(in_buffer == nullptr || out_buffer == nullptr) {
Error("NULL Input or output buffer");
if ( in_buffer == nullptr ) {
Error("NULL Input buffer");
return -1;
}
if ( out_buffer == nullptr ) {
Error("NULL output buffer");
return -1;
}
// if(in_pf == 0 || out_pf == 0) {
@ -172,11 +176,13 @@ int SWScale::Convert(
#if LIBSWSCALE_VERSION_CHECK(0, 8, 0, 8, 0)
/* Warn if the input or output pixelformat is not supported */
if(!sws_isSupportedInput(in_pf)) {
Warning("swscale does not support the input format: %c%c%c%c",(in_pf)&0xff,((in_pf)&0xff),((in_pf>>16)&0xff),((in_pf>>24)&0xff));
if ( !sws_isSupportedInput(in_pf) ) {
Warning("swscale does not support the input format: %c%c%c%c",
(in_pf)&0xff,((in_pf)&0xff),((in_pf>>16)&0xff),((in_pf>>24)&0xff));
}
if(!sws_isSupportedOutput(out_pf)) {
Warning("swscale does not support the output format: %c%c%c%c",(out_pf)&0xff,((out_pf>>8)&0xff),((out_pf>>16)&0xff),((out_pf>>24)&0xff));
if ( !sws_isSupportedOutput(out_pf) ) {
Warning("swscale does not support the output format: %c%c%c%c",
(out_pf)&0xff,((out_pf>>8)&0xff),((out_pf>>16)&0xff),((out_pf>>24)&0xff));
}
#endif
@ -186,7 +192,7 @@ int SWScale::Convert(
#else
size_t insize = avpicture_get_size(in_pf, width, height);
#endif
if(insize != in_buffer_size) {
if ( insize != in_buffer_size ) {
Error("The input buffer size does not match the expected size for the input format. Required: %d Available: %d", insize, in_buffer_size);
return -4;
}

View File

@ -113,11 +113,16 @@ void Zone::Setup(
}
if ( config.record_diag_images ) {
if ( config.record_diag_images_fifo ) {
snprintf(diag_path, sizeof(diag_path),
config.record_diag_images_fifo ? "%s/diagpipe-%d-poly.jpg" : "%s/diag-%d-poly.jpg",
monitor->getStorage()->Path(), id);
if ( config.record_diag_images_fifo )
"%s/diagpipe-%d-poly.jpg",
staticConfig.PATH_SOCKS.c_str(), id);
FifoStream::fifo_create_if_missing(diag_path);
} else {
snprintf(diag_path, sizeof(diag_path), "%s/diag-%d-poly.jpg",
monitor->getStorage()->Path(), id);
}
pg_image->WriteJpeg(diag_path, config.record_diag_images_fifo);
} else {
diag_path[0] = 0;
@ -139,10 +144,11 @@ void Zone::RecordStats(const Event *event) {
"INSERT INTO Stats SET MonitorId=%d, ZoneId=%d, EventId=%" PRIu64 ", FrameId=%d, PixelDiff=%d, AlarmPixels=%d, FilterPixels=%d, BlobPixels=%d, Blobs=%d, MinBlobSize=%d, MaxBlobSize=%d, MinX=%d, MinY=%d, MaxX=%d, MaxY=%d, Score=%d",
monitor->Id(), id, event->Id(), event->Frames(), pixel_diff, alarm_pixels, alarm_filter_pixels, alarm_blob_pixels, alarm_blobs, min_blob_size, max_blob_size, alarm_box.LoX(), alarm_box.LoY(), alarm_box.HiX(), alarm_box.HiY(), score
);
if ( mysql_query(&dbconn, sql) ) {
int rc = mysql_query(&dbconn, sql);
db_mutex.unlock();
if ( rc ) {
Error("Can't insert event stats: %s", mysql_error(&dbconn));
}
db_mutex.unlock();
} // end void Zone::RecordStats( const Event *event )
bool Zone::CheckOverloadCount() {
@ -904,7 +910,7 @@ int Zone::Load(Monitor *monitor, Zone **&zones) {
zones[i] = new Zone(monitor, Id, Name, polygon);
} else if ( atoi(dbrow[2]) == Zone::PRIVACY ) {
zones[i] = new Zone(monitor, Id, Name, (Zone::ZoneType)Type, polygon);
}
} else {
zones[i] = new Zone(
monitor, Id, Name, (Zone::ZoneType)Type, polygon, AlarmRGB,
(Zone::CheckMethod)CheckMethod, MinPixelThreshold, MaxPixelThreshold,
@ -912,6 +918,7 @@ int Zone::Load(Monitor *monitor, Zone **&zones) {
MinFilterPixels, MaxFilterPixels,
MinBlobPixels, MaxBlobPixels, MinBlobs, MaxBlobs,
OverloadFrames, ExtendAlarmFrames);
}
} // end foreach row
mysql_free_result(result);
return n_zones;

View File

@ -106,9 +106,9 @@ int main( int argc, char *argv[] ) {
}
}
if (optind < argc) {
if ( optind < argc ) {
fprintf(stderr, "Extraneous options, ");
while (optind < argc)
while ( optind < argc )
printf("%s ", argv[optind++]);
printf("\n");
Usage();
@ -130,7 +130,7 @@ int main( int argc, char *argv[] ) {
hwcaps_detect();
Monitor *monitor = Monitor::Load(id, true, Monitor::ANALYSIS);
zmFifoDbgInit( monitor );
zmFifoDbgInit(monitor);
if ( monitor ) {
Info("In mode %d/%d, warming up", monitor->GetFunction(), monitor->Enabled());
@ -148,7 +148,14 @@ int main( int argc, char *argv[] ) {
monitor->UpdateAdaptiveSkip();
last_analysis_update_time = time(nullptr);
while( (!zm_terminate) && monitor->ShmValid() ) {
while ( !zm_terminate ) {
if ( !monitor->ShmValid() ) {
monitor->disconnect();
Info("Waiting for shm to become valid");
usleep(100000);
monitor->connect();
continue;
}
// Process the next image
sigprocmask(SIG_BLOCK, &block_set, nullptr);

View File

@ -102,9 +102,7 @@ int main(int argc, const char *argv[], char **envp) {
Debug(1, "Query: %s", query);
char temp_query[1024];
strncpy(temp_query, query, sizeof(temp_query)-1);
char *q_ptr = temp_query;
char *q_ptr = (char *)query;
char *parms[16]; // Shouldn't be more than this
int parm_no = 0;
while ( (parm_no < 16) && (parms[parm_no] = strtok(q_ptr, "&")) ) {
@ -118,9 +116,13 @@ int main(int argc, const char *argv[], char **envp) {
if ( !value )
value = "";
if ( !strcmp(name, "source") ) {
source = !strcmp(value, "event")?ZMS_EVENT:ZMS_MONITOR;
if ( !strcmp(value, "fifo") )
if ( !strcmp(value, "event") ) {
source = ZMS_EVENT;
} else if ( !strcmp(value, "fifo") ) {
source = ZMS_FIFO;
} else {
source = ZMS_MONITOR;
}
} else if ( !strcmp(name, "mode") ) {
mode = !strcmp(value, "jpeg")?ZMS_JPEG:ZMS_MPEG;
mode = !strcmp(value, "raw")?ZMS_RAW:mode;
@ -329,6 +331,7 @@ int main(int argc, const char *argv[], char **envp) {
Error("Neither a monitor or event was specified.");
} // end if monitor or event
Debug(1, "Terminating");
logTerm();
zmDbClose();

View File

@ -1 +1 @@
1.35.14
1.35.15

View File

@ -1,5 +1,4 @@
<?php
$message = '';
$data = array();
@ -16,7 +15,7 @@ if ( empty($_REQUEST['task']) ) {
}
if ( empty($_REQUEST['eids']) ) {
if ( isset($_REQUEST['task']) && $_REQUEST['task'] != "query" ) $message = 'No event id(s) supplied';
if ( isset($_REQUEST['task']) && $_REQUEST['task'] != 'query' ) $message = 'No event id(s) supplied';
} else {
$eids = $_REQUEST['eids'];
}
@ -26,6 +25,7 @@ if ( $message ) {
return;
}
require_once('includes/Filter.php');
$filter = isset($_REQUEST['filter']) ? ZM\Filter::parse($_REQUEST['filter']) : new ZM\Filter();
if ( $user['MonitorIds'] ) {
$filter = $filter->addTerm(array('cnj'=>'and', 'attr'=>'MonitorId', 'op'=>'IN', 'val'=>$user['MonitorIds']));
@ -58,7 +58,8 @@ if ( isset($_REQUEST['offset']) ) {
$order = (isset($_REQUEST['order']) and (strtolower($_REQUEST['order']) == 'asc')) ? 'ASC' : 'DESC';
// Limit specifies the number of rows to return
$limit = 100;
// Set the default to 0 for events view, to prevent an issue with ALL pagination
$limit = 0;
if ( isset($_REQUEST['limit']) ) {
if ( ( !is_int($_REQUEST['limit']) and !ctype_digit($_REQUEST['limit']) ) ) {
ZM\Error('Invalid value for limit: ' . $_REQUEST['limit']);
@ -115,6 +116,7 @@ function deleteRequest($eid) {
}
function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $limit) {
$data = array(
'total' => 0,
'totalNotFiltered' => 0,
@ -143,50 +145,15 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim
$sort = 'Id';
}
$data = array();
$query = array();
$query['values'] = array();
$values = array();
$likes = array();
$where = ($filter->sql()?'('.$filter->sql().')' : '');
// There are two search bars in the log view, normal and advanced
// Making an exuctive decision to ignore the normal search, when advanced search is in use
// Alternatively we could try to do both
if ( count($advsearch) ) {
foreach ( $advsearch as $col=>$text ) {
if ( in_array($col, $columns) ) {
array_push($likes, 'E.'.$col.' LIKE ?');
array_push($query['values'], $text);
} else if ( in_array($col, $col_alt) ) {
array_push($likes, 'M.'.$col.' LIKE ?');
array_push($query['values'], $text);
} else {
ZM\Error("'$col' is not a sortable column name");
continue;
}
} # end foreach col in advsearch
$wherevalues = $query['values'];
$where .= ($where != '') ? ' AND (' .implode(' OR ', $likes). ')' : implode(' OR ', $likes);
} else if ( $search != '' ) {
$search = '%' .$search. '%';
foreach ( $columns as $col ) {
array_push($likes, 'E.'.$col.' LIKE ?');
array_push($query['values'], $search);
}
$wherevalues = $query['values'];
$where .= ( $where != '') ? ' AND (' .implode(' OR ', $likes). ')' : implode(' OR ', $likes);
}
if ( $where )
$where = ' WHERE '.$where;
$where = $filter->sql()?' WHERE ('.$filter->sql().')' : '';
$sort = $sort == 'Monitor' ? 'M.Name' : 'E.'.$sort;
$col_str = 'E.*, M.Name AS Monitor';
$query['sql'] = 'SELECT ' .$col_str. ' FROM `' .$table. '` AS E INNER JOIN Monitors AS M ON E.MonitorId = M.Id'.$where.' ORDER BY ' .$sort. ' ' .$order. ' LIMIT ?, ?';
array_push($query['values'], $offset, $limit);
$sql = 'SELECT ' .$col_str. ' FROM `Events` AS E INNER JOIN Monitors AS M ON E.MonitorId = M.Id'.$where.' ORDER BY '.$sort.' '.$order;
//ZM\Debug('Calling the following sql query: ' .$query['sql']);
//ZM\Debug('Calling the following sql query: ' .$sql);
$storage_areas = ZM\Storage::find();
$StorageById = array();
@ -194,13 +161,61 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim
$StorageById[$S->Id()] = $S;
}
$rows = array();
foreach ( dbFetchAll($query['sql'], NULL, $query['values']) as $row ) {
$unfiltered_rows = array();
$event_ids = array();
foreach ( dbFetchAll($sql, NULL, $values) as $row ) {
$event = new ZM\Event($row);
if ( !$filter->test_post_sql_conditions($event) ) {
$event->remove_from_cache();
continue;
}
$event_ids[] = $event->Id();
$unfiltered_rows[] = $row;
}
ZM\Debug('Have ' . count($unfiltered_rows) . ' events matching base filter.');
$filtered_rows = null;
if ( count($advsearch) or $search != '' ) {
$search_filter = new ZM\Filter();
$search_filter = $search_filter->addTerm(array('cnj'=>'and', 'attr'=>'Id', 'op'=>'IN', 'val'=>$event_ids));
// There are two search bars in the log view, normal and advanced
// Making an exuctive decision to ignore the normal search, when advanced search is in use
// Alternatively we could try to do both
if ( count($advsearch) ) {
$terms = array();
foreach ( $advsearch as $col=>$text ) {
$terms[] = array('cnj'=>'and', 'attr'=>$col, 'op'=>'LIKE', 'val'=>$text);
} # end foreach col in advsearch
$terms[0]['obr'] = 1;
$terms[count($terms)-1]['cbr'] = 1;
$search_filter->addTerms($terms);
} else if ( $search != '' ) {
$search = '%' .$search. '%';
$terms = array();
foreach ( $columns as $col ) {
$terms[] = array('cnj'=>'or', 'attr'=>$col, 'op'=>'LIKE', 'val'=>$search);
}
$terms[0]['obr'] = 1;
$terms[0]['cnj'] = 'and';
$terms[count($terms)-1]['cbr'] = 1;
$search_filter = $search_filter->addTerms($terms, array('obr'=>1, 'cbr'=>1, 'op'=>'OR'));
} # end if search
$sql = 'SELECT ' .$col_str. ' FROM `Events` AS E INNER JOIN Monitors AS M ON E.MonitorId = M.Id WHERE '.$search_filter->sql().' ORDER BY ' .$sort. ' ' .$order;
$filtered_rows = dbFetchAll($sql);
ZM\Debug('Have ' . count($filtered_rows) . ' events matching search filter.');
} else {
$filtered_rows = $unfiltered_rows;
} # end if search_filter->terms() > 1
$returned_rows = array();
foreach ( array_slice($filtered_rows, $offset, $limit) as $row ) {
$event = new ZM\Event($row);
$scale = intval(5*100*ZM_WEB_LIST_THUMB_WIDTH / $event->Width());
$imgSrc = $event->getThumbnailSrc(array(),'&amp;');
$streamSrc = $event->getStreamSrc(array(
@ -218,14 +233,15 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim
$row['Storage'] = ( $row['StorageId'] and isset($StorageById[$row['StorageId']]) ) ? $StorageById[$row['StorageId']]->Name() : 'Default';
$row['Notes'] = nl2br(htmlspecialchars($row['Notes']));
$row['DiskSpace'] = human_filesize($event->DiskSpace());
$rows[] = $row;
}
$data['rows'] = $rows;
$returned_rows[] = $row;
} # end foreach row matching search
$data['rows'] = $returned_rows;
# totalNotFiltered must equal total, except when either search bar has been used
$data['totalNotFiltered'] = dbFetchOne('SELECT count(*) AS Total FROM Events AS E INNER JOIN Monitors AS M ON E.MonitorId = M.Id'. ($filter->sql() ? ' WHERE '.$filter->sql():''), 'Total');
$data['totalNotFiltered'] = count($unfiltered_rows);
if ( $search != '' || count($advsearch) ) {
$data['total'] = dbFetchOne('SELECT count(*) AS Total FROM Events AS E INNER JOIN Monitors AS M ON E.MonitorId = M.Id'.$where , 'Total', $wherevalues);
$data['total'] = count($filtered_rows);
} else {
$data['total'] = $data['totalNotFiltered'];
}

189
web/ajax/frames.php Normal file
View File

@ -0,0 +1,189 @@
<?php
$data = array();
$message = '';
//
// INITIALIZE AND CHECK SANITY
//
if ( !canView('Events') ) $message = 'Insufficient permissions to view frames for user '.$user['Username'];
// task must be set
if ( empty($_REQUEST['task']) ) {
$message = 'This request requires a task to be set';
// query is the only supported task at the moment
} else if ( $_REQUEST['task'] != 'query' ) {
$message = 'Unrecognised task '.$_REQUEST['task'];
} else {
$task = $_REQUEST['task'];
}
if ( empty($_REQUEST['eid']) ) {
$message = 'No event id supplied';
} else {
$eid = validInt($_REQUEST['eid']);
}
if ( $message ) {
ajaxError($message);
return;
}
// Search contains a user entered string to search on
$search = isset($_REQUEST['search']) ? $_REQUEST['search'] : '';
// Advanced search contains an array of "column name" => "search text" pairs
// Bootstrap table sends json_ecoded array, which we must decode
$advsearch = isset($_REQUEST['advsearch']) ? json_decode($_REQUEST['advsearch'], JSON_OBJECT_AS_ARRAY) : array();
// Sort specifies the name of the column to sort on
$sort = 'FrameId';
if ( isset($_REQUEST['sort']) ) {
$sort = $_REQUEST['sort'];
}
// Offset specifies the starting row to return, used for pagination
$offset = 0;
if ( isset($_REQUEST['offset']) ) {
if ( ( !is_int($_REQUEST['offset']) and !ctype_digit($_REQUEST['offset']) ) ) {
ZM\Error('Invalid value for offset: ' . $_REQUEST['offset']);
} else {
$offset = $_REQUEST['offset'];
}
}
// Order specifies the sort direction, either asc or desc
$order = (isset($_REQUEST['order']) and (strtolower($_REQUEST['order']) == 'asc')) ? 'ASC' : 'DESC';
// Limit specifies the number of rows to return
$limit = 0;
if ( isset($_REQUEST['limit']) ) {
if ( ( !is_int($_REQUEST['limit']) and !ctype_digit($_REQUEST['limit']) ) ) {
ZM\Error('Invalid value for limit: ' . $_REQUEST['limit']);
} else {
$limit = $_REQUEST['limit'];
}
}
//
// MAIN LOOP
//
// Only one supported task at the moment
switch ( $task ) {
case 'query' :
$data = queryRequest($eid, $search, $advsearch, $sort, $offset, $order, $limit);
break;
default :
ZM\Fatal("Unrecognised task '$task'");
} // end switch task
ajaxResponse($data);
//
// FUNCTION DEFINITIONS
//
function queryRequest($eid, $search, $advsearch, $sort, $offset, $order, $limit) {
$data = array(
'total' => 0,
'totalNotFiltered' => 0,
'rows' => array(),
'updated' => preg_match('/%/', DATE_FMT_CONSOLE_LONG) ? strftime(DATE_FMT_CONSOLE_LONG) : date(DATE_FMT_CONSOLE_LONG)
);
// The names of the dB columns in the events table we are interested in
$columns = array('FrameId', 'Type', 'TimeStamp', 'Delta', 'Score');
if ( !in_array($sort, $columns) ) {
ZM\Error('Invalid sort field: ' . $sort);
$sort = 'FrameId';
}
$Event = new ZM\Event($eid);
$Monitor = $Event->Monitor();
$values = array();
$likes = array();
$where = 'WHERE EventId = '.$eid;
$sql = 'SELECT * FROM `Frames` '.$where.' ORDER BY '.$sort.' '.$order;
//ZM\Debug('Calling the following sql query: ' .$sql);
$unfiltered_rows = array();
$frame_ids = array();
require_once('includes/Frame.php');
foreach ( dbFetchAll($sql, NULL, $values) as $row ) {
$frame = new ZM\Frame($row);
$frame_ids[] = $frame->Id();
$unfiltered_rows[] = $row;
}
ZM\Debug('Have ' . count($unfiltered_rows) . ' frames matching base filter.');
$filtered_rows = null;
require_once('includes/Filter.php');
if ( count($advsearch) or $search != '' ) {
$search_filter = new ZM\Filter();
$search_filter = $search_filter->addTerm(array('cnj'=>'and', 'attr'=>'FrameId', 'op'=>'IN', 'val'=>$frame_ids));
// There are two search bars in the log view, normal and advanced
// Making an exuctive decision to ignore the normal search, when advanced search is in use
// Alternatively we could try to do both
if ( count($advsearch) ) {
$terms = array();
foreach ( $advsearch as $col=>$text ) {
$terms[] = array('cnj'=>'and', 'attr'=>$col, 'op'=>'LIKE', 'val'=>$text);
} # end foreach col in advsearch
$terms[0]['obr'] = 1;
$terms[count($terms)-1]['cbr'] = 1;
$search_filter->addTerms($terms);
} else if ( $search != '' ) {
$search = '%' .$search. '%';
$terms = array();
foreach ( $columns as $col ) {
$terms[] = array('cnj'=>'or', 'attr'=>$col, 'op'=>'LIKE', 'val'=>$search);
}
$terms[0]['obr'] = 1;
$terms[0]['cnj'] = 'and';
$terms[count($terms)-1]['cbr'] = 1;
$search_filter = $search_filter->addTerms($terms, array('obr'=>1, 'cbr'=>1, 'op'=>'OR'));
} # end if search
$sql = 'SELECT * FROM `Frames` WHERE '.$search_filter->sql().' ORDER BY ' .$sort. ' ' .$order;
$filtered_rows = dbFetchAll($sql);
ZM\Debug('Have ' . count($filtered_rows) . ' frames matching search filter.');
} else {
$filtered_rows = $unfiltered_rows;
} # end if search_filter->terms() > 1
$returned_rows = array();
foreach ( array_slice($filtered_rows, $offset, $limit) as $row ) {
if ( ZM_WEB_LIST_THUMBS ) {
$base_img_src = '?view=image&amp;fid=' .$row['Id'];
$ratio_factor = $Monitor->ViewHeight() / $Monitor->ViewWidth();
$thmb_width = ZM_WEB_LIST_THUMB_WIDTH ? 'width='.ZM_WEB_LIST_THUMB_WIDTH : '';
$thmb_height = 'height="'.( ZM_WEB_LIST_THUMB_HEIGHT ? ZM_WEB_LIST_THUMB_HEIGHT : ZM_WEB_LIST_THUMB_WIDTH*$ratio_factor ) .'"';
$thmb_fn = 'filename=' .$Event->MonitorId(). '_' .$row['EventId']. '_' .$row['FrameId']. '.jpg';
$img_src = join('&amp;', array_filter(array($base_img_src, $thmb_width, $thmb_height, $thmb_fn)));
$full_img_src = join('&amp;', array_filter(array($base_img_src, $thmb_fn)));
$frame_src = '?view=frame&amp;eid=' .$row['EventId']. '&amp;fid=' .$row['FrameId'];
$row['Thumbnail'] = '<img src="' .$img_src. '" '.$thmb_width. ' ' .$thmb_height. 'img_src="' .$img_src. '" full_img_src="' .$full_img_src. '">';
}
$returned_rows[] = $row;
} # end foreach row matching search
$data['rows'] = $returned_rows;
# totalNotFiltered must equal total, except when either search bar has been used
$data['totalNotFiltered'] = count($unfiltered_rows);
if ( $search != '' || count($advsearch) ) {
$data['total'] = count($filtered_rows);
} else {
$data['total'] = $data['totalNotFiltered'];
}
return $data;
}

View File

@ -0,0 +1,27 @@
<?php
if ( !canEdit('Events') ) return;
$eid = isset($_REQUEST['eid']) ? $_REQUEST['eid'] : '';
$eid = validInt($eid);
$Event = new ZM\Event($eid);
?>
<div class="modal" id="eventRenameModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><?php echo translate('Rename') .' '. translate('Event') ?></h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<input type="text" value="<?php echo validHtmlStr($Event->Name()) ?>"/>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" id="eventRenameBtn"><?php echo translate('Save') ?></button>
<button type="button" class="btn btn-secondary" data-dismiss="modal"><?php echo translate('Cancel') ?></button>
</div>
</div>
</div>
</div>

View File

@ -31,12 +31,11 @@
'Shallow' => translate('Shallow'),
);
$servers = ZM\Server::find( null, array('order'=>'lower(Name)') );
global $Servers;
$ServersById = array();
foreach ( $servers as $S ) {
foreach ( $Servers as $S ) {
$ServersById[$S->Id()] = $S;
}
?>
<div class="modal fade" id="storageModal" data-backdrop="static" data-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog">
@ -56,48 +55,54 @@
<input type="hidden" name="view" value="storage"/>
<input type="hidden" name="object" value="storage"/>
<input type="hidden" name="id" value="<?php echo validHtmlStr($sid) ?>"/>
<table class="major table-sm">
<div class="table-responsive">
<table class="major table table-sm">
<tbody>
<tr>
<th class="text-right pr-3" scope="row"><?php echo translate('Name') ?></th>
<th class="text-right " scope="row"><?php echo translate('Name') ?></th>
<td><input type="text" name="newStorage[Name]" value="<?php echo $newStorage->Name() ?>"/></td>
</tr>
<tr>
<th class="text-right pr-3" scope="row"><?php echo translate('Path') ?></th>
<th class="text-right " scope="row"><?php echo translate('Path') ?></th>
<td><input type="text" name="newStorage[Path]" value="<?php echo $newStorage->Path() ?>"/></td>
</tr>
<tr>
<th class="text-right pr-3" scope="row"><?php echo translate('Url') ?></th>
<th class="text-right " scope="row"><?php echo translate('Url') ?></th>
<td><input type="text" name="newStorage[Url]" value="<?php echo $newStorage->Url() ?>"/></td>
</tr>
<tr>
<th class="text-right pr-3" scope="row"><?php echo translate('Server') ?></th>
<th class="text-right " scope="row"><?php echo translate('Server') ?></th>
<td><?php echo htmlSelect('newStorage[ServerId]', array(''=>'Remote / No Specific Server') + $ServersById, $newStorage->ServerId()) ?></td>
</tr>
<tr>
<th class="text-right pr-3" scope="row"><?php echo translate('Type') ?></th>
<th class="text-right " scope="row"><?php echo translate('Type') ?></th>
<td><?php echo htmlSelect('newStorage[Type]', $type_options, $newStorage->Type()) ?></td>
</tr>
<tr>
<th class="text-right pr-3" scope="row"><?php echo translate('StorageScheme') ?></th>
<th class="text-right " scope="row"><?php echo translate('StorageScheme') ?></th>
<td><?php echo htmlSelect('newStorage[Scheme]', $scheme_options, $newStorage->Scheme()) ?></td>
</tr>
<tr>
<th class="text-right pr-3" scope="row"><?php echo translate('StorageDoDelete') ?></th>
<th class="text-right " scope="row"><?php echo translate('StorageDoDelete') ?></th>
<td>
<input type="radio" name="newStorage[DoDelete]" value="1" <?php echo $newStorage->DoDelete() ? $checked : $null ?>>Yes
<input type="radio" name="newStorage[DoDelete]" value="0" <?php echo $newStorage->DoDelete() ? $null : $checked ?>>No
<input type="radio" name="newStorage[DoDelete]" id="newStorage[DoDelete]1" value="1" <?php echo $newStorage->DoDelete() ? $checked : $null ?>/>
<label class="form-check-label" for="newStorage[DoDelete]1">Yes</label>
<input type="radio" name="newStorage[DoDelete]" id="newStorage[DoDelete]0" value="0" <?php echo $newStorage->DoDelete() ? $null : $checked ?>/>
<label class="form-check-label" for="newStorage[DoDelete]0">No</label>
</td>
</tr>
<tr>
<th class="text-right pr-3" scope="row"><?php echo translate('Enabled') ?></th>
<th class="text-right " scope="row"><?php echo translate('Enabled') ?></th>
<td>
<input type="radio" name="newStorage[Enabled]" value="1" <?php echo $newStorage->Enabled() ? $checked : $null ?>>Yes
<input type="radio" name="newStorage[Enabled]" value="0" <?php echo $newStorage->Enabled() ? $null : $checked ?>>No
<input type="radio" name="newStorage[Enabled]" id="newStorage[Enabled]1" value="1" <?php echo $newStorage->Enabled() ? $checked : $null ?>/>
<label class="form-check-label" for="newStorage[Enabled]1">Yes</label>
<input type="radio" name="newStorage[Enabled]" id="newStorage[Enabled]0" value="0" <?php echo $newStorage->Enabled() ? $null : $checked ?>/>
<label class="form-check-label" for="newStorage[Enabled]0">No</label>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="modal-footer">
<button name="action" id="storageSubmitBtn" type="submit" class="btn btn-primary" value="Save"><?php echo translate('Save') ?></button>

View File

@ -267,7 +267,6 @@ function collectData() {
if ( count($fieldSql) ) {
$sql = 'SELECT '.join(', ', $fieldSql).' FROM '.$entitySpec['table'];
#$sql = 'SELECT '.join(', ', array_map($fieldSql, function($f){return '`'.$f.'`';})).' FROM '.$entitySpec['table'];
if ( $joinSql )
$sql .= ' '.join(' ', array_unique($joinSql));
if ( $id && !empty($entitySpec['selector']) ) {
@ -314,27 +313,27 @@ function collectData() {
$limit = $entitySpec['limit'];
elseif ( !empty($_REQUEST['count']) )
$limit = validInt($_REQUEST['count']);
$limit_offset='';
$limit_offset = '';
if ( !empty($_REQUEST['offset']) )
$limit_offset = validInt($_REQUEST['offset']) . ', ';
if ( !empty( $limit ) )
if ( !empty($limit) )
$sql .= ' limit '.$limit_offset.$limit;
if ( isset($limit) && $limit == 1 ) {
if ( $sqlData = dbFetchOne($sql, NULL, $values) ) {
foreach ( $postFuncs as $element=>$func )
$sqlData[$element] = eval( 'return( '.$func.'( $sqlData ) );' );
$data = array_merge( $data, $sqlData );
$data = array_merge($data, $sqlData);
}
} else {
$count = 0;
foreach( dbFetchAll( $sql, NULL, $values ) as $sqlData ) {
foreach ( dbFetchAll($sql, NULL, $values) as $sqlData ) {
foreach ( $postFuncs as $element=>$func )
$sqlData[$element] = eval( 'return( '.$func.'( $sqlData ) );' );
$sqlData[$element] = eval('return( '.$func.'( $sqlData ) );');
$data[] = $sqlData;
if ( isset($limi) && ++$count >= $limit )
if ( isset($limit) && ++$count >= $limit )
break;
}
}
} # end foreach
} # end if have limit == 1
}
}
#ZM\Debug(print_r($data, true));
@ -347,11 +346,11 @@ if ( !isset($_REQUEST['layout']) ) {
$_REQUEST['layout'] = 'json';
}
switch( $_REQUEST['layout'] ) {
switch ( $_REQUEST['layout'] ) {
case 'xml NOT CURRENTLY SUPPORTED' :
header('Content-type: application/xml');
echo('<?xml version="1.0" encoding="iso-8859-1"?>
');
');
echo '<'.strtolower($_REQUEST['entity']).'>
';
foreach ( $data as $key=>$value ) {
@ -365,15 +364,16 @@ switch( $_REQUEST['layout'] ) {
$response = array( strtolower(validJsStr($_REQUEST['entity'])) => $data );
if ( isset($_REQUEST['loopback']) )
$response['loopback'] = validJsStr($_REQUEST['loopback']);
#ZM\Warning(print_r($response, true));
ajaxResponse($response);
break;
}
case 'text' :
header('Content-type: text/plain' );
echo join( ' ', array_values( $data ) );
header('Content-type: text/plain');
echo join(' ', array_values($data));
break;
default:
ZM\Error('Unsupported layout: '. $_REQUEST['layout']);
ZM\Error('Unsupported layout: '.$_REQUEST['layout']);
}
function getFrameImage() {
@ -381,38 +381,38 @@ function getFrameImage() {
$frameId = $_REQUEST['id'][1];
$sql = 'SELECT * FROM Frames WHERE EventId = ? AND FrameId = ?';
if ( !($frame = dbFetchOne( $sql, NULL, array($eventId, $frameId ) )) ) {
if ( !($frame = dbFetchOne($sql, NULL, array($eventId, $frameId))) ) {
$frame = array();
$frame['EventId'] = $eventId;
$frame['FrameId'] = $frameId;
$frame['Type'] = 'Virtual';
}
$event = dbFetchOne( 'select * from Events where Id = ?', NULL, array( $frame['EventId'] ) );
$frame['Image'] = getImageSrc( $event, $frame, SCALE_BASE );
return( $frame );
$event = dbFetchOne('SELECT * FROM Events WHERE Id = ?', NULL, array($frame['EventId']));
$frame['Image'] = getImageSrc($event, $frame, SCALE_BASE);
return $frame;
}
function getNearFrame() {
$eventId = $_REQUEST['id'][0];
$frameId = $_REQUEST['id'][1];
$sql = 'select FrameId from Frames where EventId = ? and FrameId <= ? order by FrameId desc limit 1';
if ( !$nearFrameId = dbFetchOne( $sql, 'FrameId', array( $eventId, $frameId ) ) ) {
$sql = 'select * from Frames where EventId = ? and FrameId > ? order by FrameId asc limit 1';
if ( !$nearFrameId = dbFetchOne( $sql, 'FrameId', array( $eventId, $frameId ) ) ) {
$sql = 'SELECT FrameId FROM Frames WHERE EventId = ? AND FrameId <= ? ORDER BY FrameId DESC LIMIT 1';
if ( !$nearFrameId = dbFetchOne($sql, 'FrameId', array($eventId, $frameId)) ) {
$sql = 'SELECT * FROM Frames WHERE EventId = ? AND FrameId > ? ORDER BY FrameId ASC LIMIT 1';
if ( !$nearFrameId = dbFetchOne($sql, 'FrameId', array($eventId, $frameId)) ) {
return( array() );
}
}
$_REQUEST['entity'] = 'frame';
$_REQUEST['id'][1] = $nearFrameId;
return( collectData() );
return collectData();
}
function getNearEvents() {
global $user, $sortColumn, $sortOrder;
$eventId = $_REQUEST['id'];
$NearEvents = array( 'EventId'=>$eventId );
$NearEvents = array('EventId'=>$eventId);
$event = dbFetchOne('SELECT * FROM Events WHERE Id=?', NULL, array($eventId));
if ( !$event ) return $NearEvents;
@ -423,7 +423,8 @@ function getNearEvents() {
$filter = $filter->addTerm(array('cnj'=>'and', 'attr'=>'MonitorId', 'op'=>'IN', 'val'=>$user['MonitorIds']));
}
# When listing, it may make sense to list them in descending order. But when viewing Prev should timewise earlier and Next should be after.
# When listing, it may make sense to list them in descending order.
# But when viewing Prev should timewise earlier and Next should be after.
if ( $sortColumn == 'E.Id' or $sortColumn == 'E.StartDateTime' ) {
$sortOrder = 'ASC';
}
@ -470,6 +471,6 @@ function getNearEvents() {
$NearEvents['NextEventId'] = $NearEvents['NextEventStartTime'] = $NearEvents['NextEventDefVideoPath'] = 0;
}
return $NearEvents;
}
} # end function getNearEvents()
?>

View File

@ -116,23 +116,23 @@ class GroupsController extends AppController {
throw new UnauthorizedException(__('Insufficient Privileges'));
return;
}
$this->Group->id = $id;
if ( $this->Group->save($this->request->data) ) {
return $this->flash(
__('The group has been saved.'),
array('action' => 'index')
);
$message = 'Saved';
} else {
$message = 'Error';
// if there is a validation message, use it
if ( !$this->group->validates() ) {
$message .= ': '.$this->Group->validationErrors;
}
} else {
$options = array('conditions' => array('Group.' . $this->Group->primaryKey => $id));
$this->request->data = $this->Group->find('first', $options);
}
$monitors = $this->Group->Monitor->find('list');
} # end if post/put
$group = $this->Group->findById($id);
$this->set(array(
'message' => $message,
'monitors'=> $monitors,
'_serialize' => array('message')
'group' => $group,
'_serialize' => array('group')
));
}

View File

@ -237,6 +237,15 @@ class FilterTerm {
case 'StartDateTime':
$sql .= 'E.StartDateTime';
break;
case 'FrameId':
$sql .= 'Id';
break;
case 'Type':
case 'TimeStamp':
case 'Delta':
case 'Score':
$sql .= $this->attr;
break;
case 'FramesEventId':
$sql .= 'F.EventId';
break;
@ -419,6 +428,12 @@ class FilterTerm {
public static function is_valid_attr($attr) {
$attrs = array(
'Score',
'Delta',
'TimeStamp',
'Type',
'FrameId',
'EventId',
'ExistsInFileSystem',
'Emailed',
'DiskSpace',
@ -434,6 +449,7 @@ class FilterTerm {
'Time',
'Weekday',
'StartDateTime',
'FramesId',
'FramesEventId',
'StartDate',
'StartTime',

View File

@ -11,16 +11,8 @@ class ZM_Object {
$row = NULL;
if ( $IdOrRow ) {
global $object_cache;
if ( ! isset($object_cache[$class]) ) {
$object_cache[$class] = array();
}
$cache = &$object_cache[$class];
if ( is_integer($IdOrRow) or ctype_digit($IdOrRow) ) {
if ( isset($cache[$IdOrRow]) ) {
return $cache[$IdOrRow];
}
$table = $class::$table;
$row = dbFetchOne("SELECT * FROM `$table` WHERE `Id`=?", NULL, array($IdOrRow));
if ( !$row ) {
@ -34,6 +26,11 @@ class ZM_Object {
foreach ($row as $k => $v) {
$this->{$k} = $v;
}
global $object_cache;
if ( ! isset($object_cache[$class]) ) {
$object_cache[$class] = array();
}
$cache = &$object_cache[$class];
$cache[$row['Id']] = $this;
}
} # end if isset($IdOrRow)

View File

@ -71,7 +71,9 @@ if ( !empty($_REQUEST['mid']) && canEdit('Monitors', $_REQUEST['mid']) ) {
$monitor->sendControlCommand('quit');
}
} // end if changes
$redirect = $_SERVER['HTTP_REFERER'];
# HTTP_REFERER will typically be ?view=zone so no good.
# if a referer is passed in $_REQUEST then use it otherwise go to ?view=zones
$redirect = isset($_REQUEST['REFERER']) ? $_REQUEST['REFERER'] : '?view=zones';
} // end if action
} // end if $mid and canEdit($mid)
?>

View File

@ -128,7 +128,7 @@ function dbEscape( $string ) {
return $dbConn->quote($string);
}
function dbQuery($sql, $params=NULL) {
function dbQuery($sql, $params=NULL, $debug = false) {
global $dbConn;
if ( dbLog($sql, true) )
return;
@ -145,7 +145,7 @@ function dbQuery($sql, $params=NULL) {
return NULL;
}
} else {
if ( defined('ZM_DB_DEBUG') ) {
if ( defined('ZM_DB_DEBUG') or $debug ) {
ZM\Debug("SQL: $sql values:" . ($params?implode(',',$params):''));
}
$result = $dbConn->query($sql);
@ -154,7 +154,7 @@ function dbQuery($sql, $params=NULL) {
return NULL;
}
}
if ( defined('ZM_DB_DEBUG') ) {
if ( defined('ZM_DB_DEBUG') or $debug ) {
ZM\Debug('SQL: '.$sql.' '.($params?implode(',',$params):'').' rows: '.$result->rowCount());
}
} catch(PDOException $e) {
@ -189,13 +189,13 @@ function dbFetchOne($sql, $col=false, $params=NULL) {
}
function dbFetchAll($sql, $col=false, $params=NULL) {
$dbRows = array();
$result = dbQuery($sql, $params);
if ( ! $result ) {
ZM\Error("SQL-ERR dbFetchAll no result, statement was '".$sql."'".($params ? 'params: '.join(',', $params) : ''));
return false;
return $dbRows;
}
$dbRows = array();
while ( $dbRow = $result->fetch(PDO::FETCH_ASSOC) )
$dbRows[] = $col ? $dbRow[$col] : $dbRow;
return $dbRows;

View File

@ -1003,10 +1003,18 @@ function parseSort($saveToSession=false, $querySep='&amp;') {
$sortColumn = 'E.DiskSpace';
break;
case 'StartTime' :
# legacy
$_REQUEST['sort_field'] = 'StartDateTime';
$sortColumn = 'E.StartDateTime';
break;
case 'StartDateTime' :
$sortColumn = 'E.StartDateTime';
break;
case 'EndTime' :
#legacy
$_REQUEST['sort_field'] = 'EndDateTime';
$sortColumn = 'E.EndDateTime';
break;
case 'EndDateTime' :
$sortColumn = 'E.EndDateTime';
break;

View File

@ -728,23 +728,11 @@ li.search-choice {
}
.zoom {
padding: 50px;
transition: transform .2s; /* Animation */
margin: 0 auto;
}
.zoom:hover {
transform-origin: 70% 50%;
transform: scale(5); /* (arbitray zoom value - Note if the zoom is too large, it will go outside of the viewport) */
}
.zoom-right {
padding: 0px;
transition: transform .2s; /* Animation */
margin: 0 auto;
}
.zoom-right:hover {
.zoom-console {
transform-origin: 0% 50%;
transform: scale(5); /* (arbitray zoom value - Note if the zoom is too large, it will go outside of the viewport) */
}

View File

@ -26,8 +26,28 @@ input.large {
}
input[name="newConfig[ZM_OPT_GOOG_RECAPTCHA_SITEKEY]"],
input[name="newConfig[ZM_OPT_GOOG_RECAPTCHA_SECRETKEY]"] {
width: 100%;
input[name="newConfig[ZM_OPT_GOOG_RECAPTCHA_SECRETKEY]"],
input[name="newConfig[ZM_OPT_GEOLOCATION_TILE_PROVIDER]"],
input[name="newConfig[ZM_OPT_GEOLOCATION_ACCESS_TOKEN]"],
input[name="newConfig[ZM_AUTH_HASH_SECRET]"],
input[name="newConfig[ZM_UPDATE_CHECK_PROXY]"],
input[name="newConfig[ZM_WEB_TITLE]"],
input[name="newConfig[ZM_WEB_TITLE_PREFIX]"],
input[name="newConfig[ZM_HOME_URL]"],
input[name="newConfig[ZM_HOME_CONTENT]"],
input[name="newConfig[ZM_WEB_CONSOLE_BANNER]"],
input[name="newConfig[ZM_LOG_DEBUG_TARGET]"],
input[name="newConfig[ZM_LOG_DEBUG_FILE]"],
input[name="newConfig[ZM_MESSAGE_ADDRESS]"],
input[name="newConfig[ZM_MESSAGE_SUBJECT]"],
input[name="newConfig[ZM_FROM_EMAIL]"],
input[name="newConfig[ZM_URL]"],
input[name="newConfig[ZM_SSMTP_PATH]"],
input[name="newConfig[ZM_CSP_REPORT_URI]"],
input[name="newStorage[Name]"],
input[name="newStorage[Path]"],
input[name="newStorage[Url]"] {
width: 90%;
}
#options label {

View File

@ -571,3 +571,6 @@ li.search-choice {
}
/* end chosen override */
modal-content {
background-color: #222222;
}

View File

@ -83,7 +83,7 @@ function exportEventDetail($event, $exportFrames, $exportImages) {
<tr><th scope="row"><?php echo translate('Monitor') ?></th><td><?php echo validHtmlStr($event->Monitor()->Name()) ?> (<?php echo $event->MonitorId() ?>)</td></tr>
<tr><th scope="row"><?php echo translate('Cause') ?></th><td><?php echo validHtmlStr($event->Cause()) ?></td></tr>
<tr><th scope="row"><?php echo translate('Notes') ?></th><td><?php echo validHtmlStr($event->Notes()) ?></td></tr>
<tr><th scope="row"><?php echo translate('Time') ?></th><td><?php echo strftime(STRF_FMT_DATETIME_SHORTER, strtotime($event->StartTime())) ?></td></tr>
<tr><th scope="row"><?php echo translate('Time') ?></th><td><?php echo strftime(STRF_FMT_DATETIME_SHORTER, strtotime($event->StartDateTime())) ?></td></tr>
<tr><th scope="row"><?php echo translate('Duration') ?></th><td><?php echo $event->Length() ?></td></tr>
<tr><th scope="row"><?php echo translate('Frames') ?></th><td><?php echo $event->Frames() ?></td></tr>
<tr><th scope="row"><?php echo translate('AttrAlarmFrames') ?></th><td><?php echo $event->AlarmFrames() ?></td></tr>
@ -999,5 +999,5 @@ function exportEvents(
unlink($monitorPath.'/'.$html_eventMaster);
}
return '?view=archive%26type='.$exportFormat.'%26connkey='.$connkey;
return '?view=archive&type='.$exportFormat.'&connkey='.$connkey;
} // end function exportEvents

View File

@ -502,11 +502,11 @@ function getBandwidthHTML($bandwidth_options, $user) {
$result .= '<div class="dropdown-menu" aria-labelledby="dropdown_bandwidth">'.PHP_EOL;
if ( count($bandwidth_options) > 1 ) {
if ( isset($bandwidth_options['high']) )
$result .= '<a data-pdsa-dropdown-val="high" class="dropdown-item" href="#">' .translate('High'). '</a>'.PHP_EOL;
$result .= '<a data-pdsa-dropdown-val="high" class="dropdown-item bwselect" href="#">' .translate('High'). '</a>'.PHP_EOL;
if ( isset($bandwidth_options['medium']) )
$result .= '<a data-pdsa-dropdown-val="medium" class="dropdown-item" href="#">' .translate('Medium'). '</a>'.PHP_EOL;
$result .= '<a data-pdsa-dropdown-val="medium" class="dropdown-item bwselect" href="#">' .translate('Medium'). '</a>'.PHP_EOL;
# low is theoretically always available
$result .= '<a data-pdsa-dropdown-val="low" class="dropdown-item" href="#">' .translate('Low'). '</a>'.PHP_EOL;
$result .= '<a data-pdsa-dropdown-val="low" class="dropdown-item bwselect" href="#">' .translate('Low'). '</a>'.PHP_EOL;
}
$result .= '</div>'.PHP_EOL;

View File

@ -639,7 +639,7 @@ function delCookie(name) {
}
function bwClickFunction() {
$j("#dropdown_bandwidth a").click(function() {
$j('.bwselect').click(function() {
var bwval = $j(this).data('pdsa-dropdown-val');
setCookie("zmBandwidth", bwval, 3600);
getNavBar();
@ -830,10 +830,11 @@ function startDownload( exportFile ) {
}
function exportResponse(data, responseText) {
console.log(data);
console.log('exportResponse data: ' + JSON.stringify(data));
var generated = (data.result=='Ok') ? 1 : 0;
var exportFile = '?view=archive&type='+data.exportFormat+'&connkey='+data.connkey;
//var exportFile = '?view=archive&type='+data.exportFormat+'&connkey='+data.connkey;
var exportFile = data.exportFile;
$j('#exportProgress').removeClass( 'text-warning' );
if ( generated ) {
@ -888,3 +889,31 @@ function manageShutdownBtns(element) {
})
.fail(logAjaxFail);
}
function thumbnail_onmouseover(event) {
timeout = setTimeout(function() {
var img = event.target;
var imgClass = ( currentView == 'console' ) ? 'zoom-console' : 'zoom';
img.src = '';
img.src = img.getAttribute('stream_src');
img.addClass(imgClass);
}, 350);
}
function thumbnail_onmouseout(event) {
clearTimeout(timeout);
var img = event.target;
var imgClass = ( currentView == 'console' ) ? 'zoom-console' : 'zoom';
img.src = '';
img.src = img.getAttribute('still_src');
img.removeClass(imgClass);
}
function initThumbAnimation() {
if ( ANIMATE_THUMBS ) {
$j('.colThumbnail img').each(function() {
this.addEventListener('mouseover', thumbnail_onmouseover, false);
this.addEventListener('mouseout', thumbnail_onmouseout, false);
});
}
}

View File

@ -49,6 +49,8 @@ var canViewMonitors = <?php echo canView('Monitors')?'true':'false' ?>;
var canEditGroups = <?php echo canEdit('Groups')?'true':'false' ?>;
var ANIMATE_THUMBS = <?php echo ZM_WEB_ANIMATE_THUMBS?'true':'false' ?>;
var refreshParent = <?php
if ( ! empty($refreshParent) ) {
if ( $refreshParent == true ) {

View File

@ -82,10 +82,10 @@ if ( $groupSql )
foreach ( array('ServerId','StorageId','Status','Function') as $filter ) {
if ( isset($_SESSION[$filter]) ) {
if ( is_array($_SESSION[$filter]) ) {
$conditions[] = $filter . ' IN ('.implode(',', array_map(function(){return '?';}, $_SESSION[$filter])). ')';
$conditions[] = '`'.$filter . '` IN ('.implode(',', array_map(function(){return '?';}, $_SESSION[$filter])). ')';
$values = array_merge($values, $_SESSION[$filter]);
} else {
$conditions[] = $filter . '=?';
$conditions[] = '`'.$filter . '`=?';
$values[] = $_SESSION[$filter];
}
}
@ -248,9 +248,9 @@ $html .= '</span>
'multiple'=>'multiple',
'data-placeholder'=>'All',
) );
# Repurpose this variable to be the list of MonitorIds as a result of all the filtering
$selected_monitor_ids = array_map(function($monitor_row){return $monitor_row['Id'];}, $displayMonitors);
$html .= '</span>
# Repurpose this variable to be the list of MonitorIds as a result of all the filtering
$selected_monitor_ids = array_map(function($monitor_row){return $monitor_row['Id'];}, $displayMonitors);
$html .= '</span>
';
echo $html;
?>

View File

@ -294,6 +294,7 @@ for( $monitor_i = 0; $monitor_i < count($displayMonitors); $monitor_i += 1 ) {
$imgHTML='';
if ( ZM_WEB_LIST_THUMBS && ($monitor['Status'] == 'Connected') && $running ) {
$options = array();
$ratio_factor = $Monitor->ViewHeight() / $Monitor->ViewWidth();
$options['width'] = ZM_WEB_LIST_THUMB_WIDTH;
$options['height'] = ZM_WEB_LIST_THUMB_HEIGHT ? ZM_WEB_LIST_THUMB_HEIGHT : ZM_WEB_LIST_THUMB_WIDTH*$ratio_factor;
@ -306,7 +307,7 @@ for( $monitor_i = 0; $monitor_i < count($displayMonitors); $monitor_i += 1 ) {
$thmbWidth = ( $options['width'] ) ? 'width:'.$options['width'].'px;' : '';
$thmbHeight = ( $options['height'] ) ? 'height:'.$options['height'].'px;' : '';
$imgHTML = '<div class="colThumbnail zoom-right"><a';
$imgHTML = '<div class="colThumbnail"><a';
$imgHTML .= $stream_available ? ' href="?view=watch&amp;mid='.$monitor['Id'].'">' : '>';
$imgHTML .= '<img id="thumbnail' .$Monitor->Id(). '" src="' .$stillSrc. '" style="'
.$thmbWidth.$thmbHeight. '" stream_src="' .$streamSrc. '" still_src="' .$stillSrc. '"'.

View File

@ -42,7 +42,7 @@ xhtmlHeaders(__FILE__, translate('ControlCaps'));
<button id="deleteBtn" class="btn btn-danger" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Delete') ?>" disabled><i class="fa fa-trash"></i></button>
</div>
<div id="content">
<div id="content" class="table-responsive-sm">
<table
id="controlTable"
data-locale="<?php echo i18n() ?>"
@ -54,7 +54,6 @@ xhtmlHeaders(__FILE__, translate('ControlCaps'));
data-remember-order="true"
data-click-to-select="true"
data-maintain-meta-data="true"
data-mobile-responsive="true"
data-buttons-class="btn btn-normal"
data-toolbar="#toolbar"
data-show-columns="true"

View File

@ -46,7 +46,7 @@ xhtmlHeaders(__FILE__, translate('Devices') );
<button id="deleteBtn" class="btn btn-danger" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Delete') ?>" disabled><i class="fa fa-trash"></i></button>
</div>
<div id="content" class="row justify-content-center">
<div id="content" class="row justify-content-center table-responsive-sm">
<table
id="devicesTable"
data-locale="<?php echo i18n() ?>"
@ -59,7 +59,6 @@ xhtmlHeaders(__FILE__, translate('Devices') );
data-remember-order="true"
data-click-to-select="true"
data-maintain-meta-data="true"
data-mobile-responsive="true"
data-buttons-class="btn btn-normal"
data-toolbar="#toolbar"
data-show-columns="true"

View File

@ -124,39 +124,36 @@ $filterQuery = $filter->querystring();
$connkey = generateConnKey();
$focusWindow = true;
$popup = (isset($_REQUEST['popup']) && ($_REQUEST['popup'] == 1));
xhtmlHeaders(__FILE__, translate('Event'));
xhtmlHeaders(__FILE__, translate('Event').' '.$Event->Id());
?>
<body>
<div id="page">
<?php if ( !$popup ) echo getNavBarHTML() ?>
<div id="header">
<?php echo getNavBarHTML() ?>
<?php
if ( !$Event->Id() ) {
echo 'Event was not found.';
} else {
if ( !file_exists($Event->Path()) ) {
if ( !file_exists($Event->Path()) )
echo '<div class="error">Event was not found at '.$Event->Path().'. It is unlikely that playback will be possible.</div>';
}
$storage = validHtmlStr($Event->Storage()->Name()).( $Event->SecondaryStorageId() ? ', '.validHtmlStr($Event->SecondaryStorage()->Name()) : '' );
?>
<div id="dataBar">
<span id="dataId" title="<?php echo translate('Id') ?>"><?php echo $Event->Id() ?></span>
<span id="dataMonitor" title="<?php echo translate('Monitor') ?>"><?php echo $Monitor->Id().' '.validHtmlStr($Monitor->Name()) ?></span>
<span id="dataCause" title="<?php echo $Event->Notes()?validHtmlStr($Event->Notes()):translate('AttrCause') ?>"><?php echo validHtmlStr($Event->Cause()) ?></span>
<span id="dataTime" title="<?php echo translate('Time') ?>"><?php echo strftime(STRF_FMT_DATETIME_SHORT, strtotime($Event->StartDateTime())) ?></span>
<span id="dataDuration" title="<?php echo translate('Duration') ?>"><?php echo $Event->Length().'s' ?></span>
<span id="dataFrames" title="<?php echo translate('AttrFrames').'/'.translate('AttrAlarmFrames') ?>"><?php echo $Event->Frames() ?>/<?php echo $Event->AlarmFrames() ?></span>
<span id="dataScore" title="<?php echo translate('AttrTotalScore').'/'.translate('AttrAvgScore').'/'.translate('AttrMaxScore') ?>"><?php echo $Event->TotScore() ?>/<?php echo $Event->AvgScore() ?>/<?php echo $Event->MaxScore() ?></span>
<span id="Storage">
<?php echo
human_filesize($Event->DiskSpace(null)) . ' on ' . validHtmlStr($Event->Storage()->Name()).
( $Event->SecondaryStorageId() ? ', '.validHtmlStr($Event->SecondaryStorage()->Name()) : '' )
?></span>
<div id="closeWindow"><a href="#" data-on-click="<?php echo $popup ? 'closeWindow' : 'backWindow' ?>"><?php echo $popup ? translate('Close') : translate('Back') ?></a></div>
<!-- BEGIN HEADER -->
<div class="d-flex flex-row justify-content-between px-3 py-1">
<div id="toolbar" >
<button id="backBtn" class="btn btn-normal" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Back') ?>" disabled><i class="fa fa-arrow-left"></i></button>
<button id="refreshBtn" class="btn btn-normal" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Refresh') ?>" ><i class="fa fa-refresh"></i></button>
<button id="renameBtn" class="btn btn-normal" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Rename') ?>" disabled><i class="fa fa-font"></i></button>
<button id="archiveBtn" class="btn btn-normal" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Archive') ?>" disabled><i class="fa fa-archive"></i></button>
<button id="unarchiveBtn" class="btn btn-normal" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Unarchive') ?>" disabled><i class="fa fa-file-archive-o"></i></button>
<button id="editBtn" class="btn btn-normal" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Edit') ?>" disabled><i class="fa fa-pencil"></i></button>
<button id="exportBtn" class="btn btn-normal" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Export') ?>"><i class="fa fa-external-link"></i></button>
<button id="downloadBtn" class="btn btn-normal" data-toggle="tooltip" data-placement="top" title="<?php echo translate('DownloadVideo') ?>"><i class="fa fa-download"></i></button>
<button id="statsBtn" class="btn btn-normal" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Stats') ?>" ><i class="fa fa-info"></i></button>
<button id="deleteBtn" class="btn btn-danger" data-toggle="tooltip" data-placement="top" title="<?php echo translate('Delete') ?>"><i class="fa fa-trash"></i></button>
</div>
<<<<<<< HEAD
<div id="menuBar1">
<div id="nameControl">
<input type="text" id="eventName" name="eventName" value="<?php echo validHtmlStr($Event->Name()) ?>" />
@ -200,10 +197,86 @@ if ( canEdit('Events') ) {
<label for="codec"><?php echo translate('Codec') ?></label>
<?php echo htmlSelect('codec', $codecs, $codec, array('data-on-change'=>'changeCodec','id'=>'codec')); ?>
</div>
=======
<h2><?php echo translate('Event').' '.$Event->Id() ?></h2>
<div class="d-flex flex-row">
<div id="replayControl"><label for="replayMode"><?php echo translate('Replay') ?></label><?php echo htmlSelect('replayMode', $replayModes, $replayMode, array('data-on-change'=>'changeReplayMode','id'=>'replayMode')); ?></div>
<div id="scaleControl"><label for="scale"><?php echo translate('Scale') ?></label><?php echo htmlSelect('scale', $scales, $scale, array('data-on-change'=>'changeScale','id'=>'scale')); ?></div>
<div id="codecControl"><label for="codec"><?php echo translate('Codec') ?></label><?php echo htmlSelect('codec', $codecs, $codec, array('data-on-change'=>'changeCodec','id'=>'codec')); ?></div>
>>>>>>> master
</div>
</div>
<div id="content">
<!-- BEGIN VIDEO CONTENT ROW -->
<div id="content" class="d-flex flex-row justify-content-center">
<div class="">
<!-- VIDEO STATISTICS TABLE -->
<table id="eventStatsTable" class="table-sm table-borderless">
<tbody>
<tr>
<th class="text-right"><?php echo translate('EventId') ?></th>
<td id="dataEventId"><?php echo $Event->Id() ?></td>
</tr>
<tr>
<th class="text-right"><?php echo translate('EventName') ?></th>
<td id="dataEventName"><?php echo $Event->Name() ?></td>
</tr>
<tr>
<th class="text-right"><?php echo translate('AttrMonitorId') ?></th>
<td id="dataMonitorId"><?php echo $Monitor->Id() ?></td>
</tr>
<tr>
<th class="text-right"><?php echo translate('AttrMonitorName') ?></th>
<td id="dataMonitorName"><?php echo validHtmlStr($Monitor->Name()) ?></td>
</tr>
<tr>
<th class="text-right"><?php echo translate('Cause') ?></th>
<td id="dataCause"><?php echo validHtmlStr($Event->Cause()) ?></td>
</tr>
<tr>
<th class="text-right"><?php echo translate('AttrStartTime') ?></th>
<td id="dataStartTime"><?php echo strftime(STRF_FMT_DATETIME_SHORT, strtotime($Event->StartDateTime())) ?></td>
</tr>
<tr>
<th class="text-right"><?php echo translate('Duration') ?></th>
<td id="dataDuration"><?php echo $Event->Length().'s' ?></td>
</tr>
<tr>
<th class="text-right"><?php echo translate('AttrFrames') ?></th>
<td id="dataFrames"><?php echo $Event->Frames() ?></td>
</tr>
<tr>
<th class="text-right"><?php echo translate('AttrAlarmFrames') ?></th>
<td id="dataAlarmFrames"><?php echo $Event->AlarmFrames() ?></td>
</tr>
<tr>
<th class="text-right"><?php echo translate('AttrTotalScore') ?></th>
<td id="dataTotalScore"><?php echo $Event->TotScore() ?></td>
</tr>
<tr>
<th class="text-right"><?php echo translate('AttrAvgScore') ?></th>
<td id="dataAvgScore"><?php echo $Event->AvgScore() ?></td>
</tr>
<tr>
<th class="text-right"><?php echo translate('AttrMaxScore') ?></th>
<td id="dataMaxScore"><?php echo $Event->MaxScore() ?></td>
</tr>
<tr>
<th class="text-right"><?php echo translate('DiskSpace') ?></th>
<td id="dataDiskSpace"><?php echo human_filesize($Event->DiskSpace(null)) ?></td>
</tr>
<tr>
<th class="text-right"><?php echo translate('Storage') ?></th>
<td id="dataStorage"><?php echo $storage?></td>
</tr>
</tbody>
</table>
</div>
<div class="">
<div id="eventVideo">
<!-- VIDEO CONTENT -->
<?php
if ( $video_tag ) {
?>
@ -328,6 +401,8 @@ echo htmlSelect('rate', $rates, intval($rate), array('id'=>'rateValue'));
<?php
} // end if Event exists
?>
</div>
</div><!--content-->
</div><!--page-->
<?php xhtmlFooter() ?>

View File

@ -65,7 +65,7 @@ getBodyTopHTML();
</div>
<!-- Table styling handled by bootstrap-tables -->
<div class="row justify-content-center">
<div class="row justify-content-center table-responsive-sm">
<table
id="eventTable"
data-locale="<?php echo i18n() ?>"
@ -87,7 +87,6 @@ getBodyTopHTML();
data-show-fullscreen="true"
data-click-to-select="true"
data-maintain-meta-data="true"
data-mobile-responsive="true"
data-buttons-class="btn btn-normal"
data-show-jump-to="true"
data-show-refresh="true"

View File

@ -28,87 +28,6 @@ require_once('includes/Filter.php');
$eid = validInt($_REQUEST['eid']);
$Event = new ZM\Event($eid);
$Monitor = $Event->Monitor();
$countSql = 'SELECT COUNT(*) AS FrameCount FROM Frames AS F WHERE 1 ';
$frameSql = 'SELECT *, unix_timestamp(TimeStamp) AS UnixTimeStamp FROM Frames AS F WHERE 1 ';
// override the sort_field handling in parseSort for frames
if ( empty($_REQUEST['sort_field']) )
$_REQUEST['sort_field'] = 'FramesTimeStamp';
if ( !isset($_REQUEST['sort_asc']) )
$_REQUEST['sort_asc'] = true;
if ( !isset($_REQUEST['filter']) ) {
// generate a dummy filter from the eid for pagination
$_REQUEST['filter'] = array('Query' => array('terms' => array()));
$_REQUEST['filter'] = addFilterTerm(
$_REQUEST['filter'],
0,
array( 'cnj' => 'and', 'attr' => 'FramesEventId', 'op' => '=', 'val' => $eid )
);
}
parseSort();
$filter = ZM\Filter::parse($_REQUEST['filter']);
$filterQuery = $filter->querystring();
if ( $filter->sql() ) {
$countSql .= ' AND ('.$filter->sql().')';
$frameSql .= ' AND ('.$filter->sql().')';
}
$frameSql .= " ORDER BY $sortColumn $sortOrder";
if ( $sortColumn != 'Id' )
$frameSql .= ',Id '.$sortOrder;
if ( isset($_REQUEST['scale']) ) {
$scale = validNum($_REQUEST['scale']);
} else if ( isset($_COOKIE['zmWatchScale'.$Monitor->Id()]) ) {
$scale = validNum($_COOKIE['zmWatchScale'.$Monitor->Id()]);
} else if ( isset($_COOKIE['zmWatchScale']) ) {
$scale = validNum($_COOKIE['zmWatchScale']);
} else {
$scale = max(reScale(SCALE_BASE, $Monitor->DefaultScale(), ZM_WEB_DEFAULT_SCALE), SCALE_BASE);
}
$page = isset($_REQUEST['page']) ? validInt($_REQUEST['page']) : 1;
$limit = isset($_REQUEST['limit']) ? validInt($_REQUEST['limit']) : 0;
$nFrames = dbFetchOne($countSql, 'FrameCount');
if ( !empty($limit) && ($nFrames > $limit) ) {
$nFrames = $limit;
}
$pages = (int)ceil($nFrames/ZM_WEB_EVENTS_PER_PAGE);
if ( !empty($page) ) {
if ( $page <= 0 )
$page = 1;
else if ( $pages and ( $page > $pages ) )
$page = $pages;
$limitStart = (($page-1)*ZM_WEB_EVENTS_PER_PAGE);
if ( empty($limit) ) {
$limitAmount = ZM_WEB_EVENTS_PER_PAGE;
} else {
$limitLeft = $limit - $limitStart;
$limitAmount = ($limitLeft>ZM_WEB_EVENTS_PER_PAGE)?ZM_WEB_EVENTS_PER_PAGE:$limitLeft;
}
$frameSql .= " LIMIT $limitStart, $limitAmount";
} else if ( !empty($limit) ) {
$frameSql .= ' LIMIT 0, '.$limit;
}
$maxShortcuts = 5;
$totalQuery = $sortQuery.'&amp;eid='.$eid.$limitQuery.$filterQuery;
$pagination = getPagination($pages, $page, $maxShortcuts, $totalQuery);
$frames = dbFetchAll($frameSql);
$focusWindow = true;
xhtmlHeaders(__FILE__, translate('Frames').' - '.$Event->Id());
?>
@ -122,10 +41,12 @@ xhtmlHeaders(__FILE__, translate('Frames').' - '.$Event->Id());
</div>
<!-- Table styling handled by bootstrap-tables -->
<div class="row justify-content-center">
<div class="row justify-content-center table-responsive-sm">
<table
id="framesTable"
data-locale="<?php echo i18n() ?>"
data-side-pagination="server"
data-ajax="ajaxRequest"
data-pagination="true"
data-show-pagination-switch="true"
data-page-list="[10, 25, 50, 100, 200, All]"
@ -139,12 +60,12 @@ xhtmlHeaders(__FILE__, translate('Frames').' - '.$Event->Id());
data-toolbar="#toolbar"
data-show-fullscreen="true"
data-maintain-meta-data="true"
data-mobile-responsive="true"
data-buttons-class="btn btn-normal"
data-detail-view="true"
data-detail-formatter="detailFormatter"
data-show-toggle="true"
data-show-jump-to="true"
data-show-refresh="true"
class="table-sm table-borderless">
<thead>
@ -152,57 +73,15 @@ xhtmlHeaders(__FILE__, translate('Frames').' - '.$Event->Id());
<tr>
<th class="px-3" data-align="center" data-sortable="false" data-field="EventId"><?php echo translate('EventId') ?></th>
<th class="px-3" data-align="center" data-sortable="true" data-field="FrameId"><?php echo translate('FrameId') ?></th>
<th class="px-3" data-align="center" data-sortable="true" data-field="FrameType"><?php echo translate('Type') ?></th>
<th class="px-3" data-align="center" data-sortable="true" data-field="FrameTimeStamp"><?php echo translate('TimeStamp') ?></th>
<th class="px-3" data-align="center" data-sortable="true" data-field="FrameDelta"><?php echo translate('TimeDelta') ?></th>
<th class="px-3" data-align="center" data-sortable="true" data-field="FrameScore"><?php echo translate('Score') ?></th>
<?php
if ( ZM_WEB_LIST_THUMBS ) {
?>
<th class="px-3" data-align="center" data-sortable="true" data-field="Type"><?php echo translate('Type') ?></th>
<th class="px-3" data-align="center" data-sortable="true" data-field="TimeStamp"><?php echo translate('TimeStamp') ?></th>
<th class="px-3" data-align="center" data-sortable="true" data-field="Delta"><?php echo translate('TimeDelta') ?></th>
<th class="px-3" data-align="center" data-sortable="true" data-field="Score"><?php echo translate('Score') ?></th>
<th class="px-3" data-align="center" data-sortable="false" data-field="Thumbnail"><?php echo translate('Thumbnail') ?></th>
<?php
}
?>
</tr>
</thead>
<tbody>
<?php
if ( count($frames) ) {
foreach ( $frames as $frame ) {
$Frame = new ZM\Frame($frame);
?>
<tr class="<?php echo strtolower($frame['Type']) ?>">
<td><?php echo $frame['EventId'] ?></td>
<td><?php echo $frame['FrameId'] ?></td>
<td><?php echo $frame['Type'] ?></td>
<td><?php echo strftime(STRF_FMT_TIME, $frame['UnixTimeStamp']) ?></td>
<td><?php echo number_format( $frame['Delta'], 2 ) ?></td>
<td><?php echo $frame['Score'] ?></td>
<?php
if ( ZM_WEB_LIST_THUMBS ) {
$base_img_src = '?view=image&amp;fid=' .$Frame->Id();
$ratio_factor = $Monitor->ViewHeight() / $Monitor->ViewWidth();
$thmb_width = ZM_WEB_LIST_THUMB_WIDTH ? 'width='.ZM_WEB_LIST_THUMB_WIDTH : '';
$thmb_height = 'height="'.( ZM_WEB_LIST_THUMB_HEIGHT ? ZM_WEB_LIST_THUMB_HEIGHT : ZM_WEB_LIST_THUMB_WIDTH*$ratio_factor ) .'"';
$thmb_fn = 'filename=' .$Event->MonitorId(). '_' .$frame['EventId']. '_' .$frame['FrameId']. '.jpg';
$img_src = join('&amp;', array_filter(array($base_img_src, $thmb_width, $thmb_height, $thmb_fn)));
$full_img_src = join('&amp;', array_filter(array($base_img_src, $thmb_fn)));
$frame_src = '?view=frame&amp;eid=' .$Event->Id(). '&amp;fid=' .$frame['FrameId'];
echo '<td class="colThumbnail zoom"><img src="' .$img_src. '" '.$thmb_width. ' ' .$thmb_height. 'img_src="' .$img_src. '" full_img_src="' .$full_img_src. '"></td>'.PHP_EOL;
}
?>
</tr>
<?php
} // end foreach frame
} else {
?>
<tr>
<td colspan="5"><?php echo translate('NoFramesRecorded') ?></td>
</tr>
<?php
}
?>
<!-- Row data populated via Ajax -->
</tbody>
</table>
</div>

View File

@ -1,22 +1,3 @@
function thumbnail_onmouseover(event) {
var img = event.target;
img.src = '';
img.src = img.getAttribute('stream_src');
}
function thumbnail_onmouseout(event) {
var img = event.target;
img.src = '';
img.src = img.getAttribute('still_src');
}
function initThumbAnimation() {
$j('.colThumbnail img').each(function() {
this.addEventListener('mouseover', thumbnail_onmouseover, false);
this.addEventListener('mouseout', thumbnail_onmouseout, false);
});
}
function setButtonStates( element ) {
var form = element.form;
var checked = 0;

View File

@ -1,7 +1,40 @@
$j.ajaxSetup({timeout: AJAX_TIMEOUT}); //sets timeout for all getJSON.
var table = $j('#eventStatsTable');
var backBtn = $j('#backBtn');
var renameBtn = $j('#renameBtn');
var archiveBtn = $j('#archiveBtn');
var unarchiveBtn = $j('#unarchiveBtn');
var editBtn = $j('#editBtn');
var exportBtn = $j('#exportBtn');
var downloadBtn = $j('#downloadBtn');
var deleteBtn = $j('#deleteBtn');
var prevEventId = 0;
var nextEventId = 0;
var prevEventStartTime = 0;
var nextEventStartTime = 0;
var PrevEventDefVideoPath = "";
var NextEventDefVideoPath = "";
var slider = null;
var scroll = null;
var currEventId = null;
var CurEventDefVideoPath = null;
var vid = null;
var spf = Math.round((eventData.Length / eventData.Frames)*1000000 )/1000000;//Seconds per frame for videojs frame by frame.
var intervalRewind;
var revSpeed = .5;
var cueFrames = null; //make cueFrames available even if we don't send another ajax query
var streamCmdTimer = null;
var streamStatus = null;
var lastEventId = 0;
var zmsBroke = false; //Use alternate navigation if zms has crashed
var streamParms = "view=request&request=stream&connkey="+connKey;
if ( auth_hash ) streamParms += '&auth='+auth_hash;
var frameBatch = 40;
var currFrameId = null;
var eventReq = new Request.JSON( {url: thisUrl, method: 'get', timeout: AJAX_TIMEOUT, link: 'cancel', onSuccess: getEventResponse} );
var actReq = new Request.JSON( {url: thisUrl, method: 'get', timeout: AJAX_TIMEOUT, link: 'cancel', onSuccess: getActResponse} );
var frameReq = new Request.JSON( {url: thisUrl, method: 'get', timeout: AJAX_TIMEOUT, link: 'chain', onSuccess: getFrameResponse} );
var streamReq = new Request.JSON( {url: monitorUrl, method: 'get', timeout: AJAX_TIMEOUT, link: 'chain', onSuccess: getCmdResponse} );
// Function called when video.js hits the end of the video
function vjsReplay() {
@ -44,10 +77,6 @@ function vjsReplay() {
}
} // end function vjsReplay
$j.ajaxSetup({timeout: AJAX_TIMEOUT}); //sets timeout for all getJSON.
var cueFrames = null; //make cueFrames available even if we don't send another ajax query
function initialAlarmCues(eventId) {
$j.getJSON(thisUrl + '?view=request&request=status&entity=frames&id=' + eventId, setAlarmCues) //get frames data for alarmCues and inserts into html
.fail(logAjaxFail);
@ -134,7 +163,6 @@ function renderAlarmCues(containerEl) {
return alarmHtml;
}
function changeCodec() {
location.replace(thisUrl + '?view=event&eid=' + eventData.Id + filterQuery + sortQuery+'&codec='+$j('#codec').val());
}
@ -217,16 +245,6 @@ function changeRate() {
Cookie.write('zmEventRate', rate, {duration: 10*365, samesite: 'strict'});
} // end function changeRate
var streamParms = "view=request&request=stream&connkey="+connKey;
if ( auth_hash ) {
streamParms += '&auth='+auth_hash;
}
var streamCmdTimer = null;
var streamStatus = null;
var lastEventId = 0;
var zmsBroke = false; //Use alternate navigation if zms has crashed
function getCmdResponse( respObj, respText ) {
if ( checkStreamForErrors('getCmdResponse', respObj) ) {
console.log('Got an error from getCmdResponse');
@ -291,14 +309,6 @@ function getCmdResponse( respObj, respText ) {
streamCmdTimer = streamQuery.delay(streamTimeout); //Timeout is refresh rate for progressBox and time display
} // end function getCmdResponse( respObj, respText )
var streamReq = new Request.JSON( {
url: monitorUrl,
method: 'get',
timeout: AJAX_TIMEOUT,
link: 'chain',
onSuccess: getCmdResponse
} );
function pauseClicked() {
if ( vid ) {
if ( intervalRewind ) {
@ -553,11 +563,6 @@ function streamQuery() {
streamReq.send( streamParms+"&command="+CMD_QUERY );
}
var slider = null;
var scroll = null;
var currEventId = null;
var CurEventDefVideoPath = null;
function getEventResponse(respObj, respText) {
if ( checkStreamForErrors('getEventResponse', respObj) ) {
console.log('getEventResponse: errors');
@ -572,28 +577,31 @@ function getEventResponse(respObj, respText) {
}
currEventId = eventData.Id;
$('dataId').set( 'text', eventData.Id );
$j('#dataEventId').text( eventData.Id );
$j('#dataEventName').text( eventData.Name );
$j('#dataMonitorId').text( eventData.MonitorId );
$j('#dataMonitorName').text( eventData.MonitorName );
$j('#dataCause').text( eventData.Cause );
if ( eventData.Notes ) {
$('dataCause').setProperty( 'title', eventData.Notes );
$j('#dataCause').prop( 'title', eventData.Notes );
} else {
$('dataCause').setProperty( 'title', causeString );
}
$('dataCause').set( 'text', eventData.Cause );
$('dataTime').set( 'text', eventData.StartDateTime );
$('dataDuration').set( 'text', eventData.Length );
$('dataFrames').set( 'text', eventData.Frames+"/"+eventData.AlarmFrames );
$('dataScore').set( 'text', eventData.TotScore+"/"+eventData.AvgScore+"/"+eventData.MaxScore );
$('eventName').setProperty( 'value', eventData.Name );
history.replaceState(null, null, '?view=event&eid=' + eventData.Id + filterQuery + sortQuery);//if popup removed, check if this allows forward
if ( canEditEvents ) {
if ( parseInt(eventData.Archived) ) {
$('archiveEvent').addClass( 'hidden' );
$('unarchiveEvent').removeClass( 'hidden' );
} else {
$('archiveEvent').removeClass( 'hidden' );
$('unarchiveEvent').addClass( 'hidden' );
}
$j('#dataCause').prop( 'title', causeString );
}
$j('#dataStartTime').text( eventData.StartDateTime );
$j('#dataDuration').text( eventData.Length );
$j('#dataFrames').text( eventData.Frames );
$j('#dataAlarmFrames').text( eventData.AlarmFrames );
$j('dataTotalScore').text( eventData.TotScore );
$j('dataAvgScore').text( eventData.AvgScore );
$j('dataMaxScore').text( eventData.MaxScore );
$j('dataDiskSpace').text( eventData.DiskSpace );
$j('dataStorage').text( eventData.Storage );
// Refresh the status of the archive buttons
archiveBtn.prop('disabled', !(!eventData.Archived && canEditEvents));
unarchiveBtn.prop('disabled', !(eventData.Archived && canEditEvents));
history.replaceState(null, null, '?view=event&eid=' + eventData.Id + filterQuery + sortQuery); //if popup removed, check if this allows forward
// Technically, events can be different sizes, so may need to update the size of the image, but it might be better to have it stay scaled...
//var eventImg = $('eventImage');
//eventImg.setStyles( { 'width': eventData.width, 'height': eventData.height } );
@ -613,8 +621,6 @@ function getEventResponse(respObj, respText) {
nearEventsQuery( eventData.Id );
} // end function getEventResponse
var eventReq = new Request.JSON( {url: thisUrl, method: 'get', timeout: AJAX_TIMEOUT, link: 'cancel', onSuccess: getEventResponse} );
function eventQuery( eventId ) {
var eventParms = 'view=request&request=status&entity=event&id='+eventId;
if ( auth_hash ) {
@ -623,13 +629,6 @@ function eventQuery( eventId ) {
eventReq.send( eventParms );
}
var prevEventId = 0;
var nextEventId = 0;
var prevEventStartTime = 0;
var nextEventStartTime = 0;
var PrevEventDefVideoPath = "";
var NextEventDefVideoPath = "";
function getNearEventsResponse( respObj, respText ) {
if ( checkStreamForErrors('getNearEventsResponse', respObj) ) {
return;
@ -656,8 +655,6 @@ function nearEventsQuery( eventId ) {
nearEventsReq.send( parms );
}
var frameBatch = 40;
function loadEventThumb( event, frame, loadImage ) {
var thumbImg = $('eventThumb'+frame.FrameId);
if ( !thumbImg ) {
@ -789,15 +786,11 @@ function getFrameResponse(respObj, respText) {
loadEventThumb(eventData, frame, respObj.loopback=="true");
}
var frameReq = new Request.JSON( {url: thisUrl, method: 'get', timeout: AJAX_TIMEOUT, link: 'chain', onSuccess: getFrameResponse} );
function frameQuery( eventId, frameId, loadImage ) {
var parms = "view=request&request=status&entity=frameimage&id[0]="+eventId+"&id[1]="+frameId+"&loopback="+loadImage;
frameReq.send(parms);
}
var currFrameId = null;
function checkFrames( eventId, frameId, loadImage ) {
if ( !eventData ) {
console.error("No event "+eventId+" found");
@ -917,8 +910,6 @@ function getActResponse( respObj, respText ) {
}
}
var actReq = new Request.JSON( {url: thisUrl, method: 'get', timeout: AJAX_TIMEOUT, link: 'cancel', onSuccess: getActResponse} );
function actQuery(action, parms) {
var actParms = "view=request&request=event&id="+eventData.Id+"&action="+action;
if ( auth_hash ) {
@ -930,55 +921,17 @@ function actQuery(action, parms) {
actReq.send(actParms);
}
function deleteEvent() {
pauseClicked(); //Provides visual feedback that your click happened.
var deleteReq = new Request.JSON({
url: thisUrl,
method: 'post',
timeout: AJAX_TIMEOUT,
onSuccess: function onDeleteSuccess(respObj, respText) {
getActResponse(respObj, respText);
// We must wait for the deletion to happen before navigating to the next
// event or this request will be cancelled.
streamNext(true);
},
});
deleteReq.send("view=request&request=event&id="+eventData.Id+"&action=delete");
}
function renameEvent() {
var newName = $('eventName').get('value');
var newName = $j('input').val();
actQuery('rename', {eventName: newName});
}
// Manage the EDIT button
function editEvent() {
$j.getJSON(thisUrl + '?request=modal&modal=eventdetail&eid='+eventData.Id)
.done(function(data) {
insertModalHtml('eventDetailModal', data.html);
$j('#eventDetailModal').modal('show');
// Manage the Save button
$j('#eventDetailSaveBtn').click(function(evt) {
evt.preventDefault();
$j('#eventDetailForm').submit();
});
})
.fail(logAjaxFail);
//FIXME: update the value of the event name rather than reload the whole page
window.location.reload(true);
}
function exportEvent() {
window.location.assign('?view=export&eid='+eventData.Id);
}
function archiveEvent() {
actQuery('archive');
}
function unarchiveEvent() {
actQuery('unarchive');
}
function showEventFrames() {
window.location.assign('?view=frames&eid='+eventData.Id);
}
@ -1079,7 +1032,56 @@ function handleClick( event ) {
}
}
// Load the Delete Confirmation Modal HTML via Ajax call
function getDelConfirmModal() {
$j.getJSON(thisUrl + '?request=modal&modal=delconfirm')
.done(function(data) {
insertModalHtml('deleteConfirm', data.html);
manageDelConfirmModalBtns();
})
.fail(logAjaxFail);
}
// Manage the DELETE CONFIRMATION modal button
function manageDelConfirmModalBtns() {
document.getElementById("delConfirmBtn").addEventListener("click", function onDelConfirmClick(evt) {
if ( ! canEditEvents ) {
enoperm();
return;
}
evt.preventDefault();
$j.getJSON(thisUrl + '?request=events&task=delete&eids[]='+eventData.Id)
.done( function(data) {
streamNext( true );
})
.fail(logAjaxFail);
});
// Manage the CANCEL modal button
document.getElementById("delCancelBtn").addEventListener("click", function onDelCancelClick(evt) {
$j('#deleteConfirm').modal('hide');
});
}
function getEvtStatsCookie() {
var cookie = 'zmEventStats';
var stats = getCookie(cookie);
if ( !stats ) {
stats = 'on';
setCookie(cookie, stats, 10*365);
}
return stats;
}
function initPage() {
// Load the delete confirmation modal into the DOM
getDelConfirmModal();
var stats = getEvtStatsCookie();
if ( stats != 'on' ) table.toggle(false);
//FIXME prevent blocking...not sure what is happening or best way to unblock
if ( $j('#videoobj').length ) {
vid = videojs('videoobj');
@ -1130,7 +1132,136 @@ function initPage() {
document.querySelectorAll('select[name="rate"]').forEach(function(el) {
el.onchange = window['changeRate'];
});
// enable or disable buttons based on current selection and user rights
renameBtn.prop('disabled', !canEditEvents);
archiveBtn.prop('disabled', !(!eventData.Archived && canEditEvents));
unarchiveBtn.prop('disabled', !(eventData.Archived && canEditEvents));
editBtn.prop('disabled', !canEditEvents);
exportBtn.prop('disabled', !canViewEvents);
downloadBtn.prop('disabled', !canViewEvents);
deleteBtn.prop('disabled', !canEditEvents);
// Don't enable the back button if there is no previous zm page to go back to
backBtn.prop('disabled', !document.referrer.length);
// Manage the BACK button
document.getElementById("backBtn").addEventListener("click", function onBackClick(evt) {
evt.preventDefault();
window.history.back();
});
// Manage the REFRESH Button
document.getElementById("refreshBtn").addEventListener("click", function onRefreshClick(evt) {
evt.preventDefault();
window.location.reload(true);
});
// Manage the Event RENAME button
document.getElementById("renameBtn").addEventListener("click", function onDownloadClick(evt) {
evt.preventDefault();
$j.getJSON(thisUrl + '?request=modal&modal=eventrename&eid='+eventData.Id)
.done(function(data) {
insertModalHtml('eventRenameModal', data.html);
$j('#eventRenameModal').modal('show');
// Manage the SAVE button
$j('#eventRenameBtn').click(renameEvent);
})
.fail(logAjaxFail);
});
// Manage the ARCHIVE button
document.getElementById("archiveBtn").addEventListener("click", function onArchiveClick(evt) {
evt.preventDefault();
$j.getJSON(thisUrl + '?request=events&task=archive&eids[]='+eventData.Id)
.done( function(data) {
//FIXME: update the status of the archive button reather than reload the whole page
window.location.reload(true);
})
.fail(logAjaxFail);
});
// Manage the UNARCHIVE button
document.getElementById("unarchiveBtn").addEventListener("click", function onUnarchiveClick(evt) {
if ( ! canEditEvents ) {
enoperm();
return;
}
evt.preventDefault();
$j.getJSON(thisUrl + '?request=events&task=unarchive&eids[]='+eventData.Id)
.done( function(data) {
//FIXME: update the status of the unarchive button reather than reload the whole page
window.location.reload(true);
})
.fail(logAjaxFail);
});
// Manage the EDIT button
document.getElementById("editBtn").addEventListener("click", function onEditClick(evt) {
if ( ! canEditEvents ) {
enoperm();
return;
}
evt.preventDefault();
$j.getJSON(thisUrl + '?request=modal&modal=eventdetail&eids[]='+eventData.Id)
.done(function(data) {
insertModalHtml('eventDetailModal', data.html);
$j('#eventDetailModal').modal('show');
// Manage the Save button
$j('#eventDetailSaveBtn').click(function(evt) {
evt.preventDefault();
$j('#eventDetailForm').submit();
});
})
.fail(logAjaxFail);
});
// Manage the EXPORT button
document.getElementById("exportBtn").addEventListener("click", function onExportClick(evt) {
evt.preventDefault();
window.location.assign('?view=export&eids[]='+eventData.Id);
});
// Manage the DOWNLOAD VIDEO button
document.getElementById("downloadBtn").addEventListener("click", function onDownloadClick(evt) {
evt.preventDefault();
$j.getJSON(thisUrl + '?request=modal&modal=download&eids[]='+eventData.Id)
.done(function(data) {
insertModalHtml('downloadModal', data.html);
$j('#downloadModal').modal('show');
// Manage the GENERATE DOWNLOAD button
$j('#exportButton').click(exportEvent);
})
.fail(logAjaxFail);
});
// Manage the Event STATISTICS Button
document.getElementById("statsBtn").addEventListener("click", function onStatsClick(evt) {
evt.preventDefault();
var cookie = 'zmEventStats';
// Toggle the visiblity of the stats table and write an appropriate cookie
if ( table.is(':visible') ) {
setCookie(cookie, 'off', 10*365);
table.toggle(false);
} else {
setCookie(cookie, 'on', 10*365);
table.toggle(true);
}
});
// Manage the DELETE button
document.getElementById("deleteBtn").addEventListener("click", function onDeleteClick(evt) {
if ( ! canEditEvents ) {
enoperm();
return;
}
evt.preventDefault();
$j('#deleteConfirm').modal('show');
});
}
// Kick everything off
window.addEventListener('DOMContentLoaded', initPage);
$j(document).ready(initPage);

View File

@ -40,6 +40,7 @@ var connKey = '<?php echo $connkey ?>';
var eventData = {
Id: '<?php echo $Event->Id() ?>',
Name: '<?php echo $Event->Name() ?>',
MonitorId: '<?php echo $Event->MonitorId() ?>',
Width: '<?php echo $Event->Width() ?>',
Height: '<?php echo $Event->Height() ?>',
@ -47,7 +48,10 @@ var eventData = {
StartDateTime: '<?php echo $Event->StartDateTime() ?>',
EndDateTime: '<?php echo $Event->EndDateTime() ?>',
Frames: '<?php echo $Event->Frames() ?>',
MonitorName: '<?php echo validJsStr($Monitor->Name()) ?>'
MonitorName: '<?php echo validJsStr($Monitor->Name()) ?>',
DiskSpace: '<?php echo human_filesize($Event->DiskSpace(null)) ?>',
Storage: '<?php validHtmlStr($Event->Storage()->Name()).( $Event->SecondaryStorageId() ? ', '.validHtmlStr($Event->SecondaryStorage()->Name()) : '' ) ?>',
Archived: <?php echo $Event->Archived?'true':'false' ?>
};
var monitorUrl = '<?php echo $Event->Storage()->Server()->UrlToIndex(); ?>';

View File

@ -74,25 +74,6 @@ function processRows(rows) {
return rows;
}
function thumbnail_onmouseover(event) {
var img = event.target;
img.src = '';
img.src = img.getAttribute('stream_src');
}
function thumbnail_onmouseout(event) {
var img = event.target;
img.src = '';
img.src = img.getAttribute('still_src');
}
function initThumbAnimation() {
$j('.colThumbnail img').each(function() {
this.addEventListener('mouseover', thumbnail_onmouseover, false);
this.addEventListener('mouseout', thumbnail_onmouseout, false);
});
}
// Returns the event id's of the selected rows
function getIdSelections() {
var table = $j('#eventTable');
@ -332,7 +313,7 @@ function initPage() {
var thumb_ndx = $j('#eventTable tr th').filter(function() {
return $j(this).text().trim() == 'Thumbnail';
}).index();
table.find("tr td:nth-child(" + (thumb_ndx+1) + ")").addClass('colThumbnail zoom');
table.find("tr td:nth-child(" + (thumb_ndx+1) + ")").addClass('colThumbnail');
});
table.bootstrapTable('resetSearch');

View File

@ -31,11 +31,11 @@ function startDownload(file) {
function exportProgress() {
if ( exportTimer ) {
var tickerText = $('exportProgressTicker').get('text');
var tickerText = $j('#exportProgressTicker').text();
if ( tickerText.length < 1 || tickerText.length > 4 ) {
$('exportProgressTicker').set('text', '.');
$j('#exportProgressTicker').text('.');
} else {
$('exportProgressTicker').appendText('.');
$j('#exportProgressTicker').append('.');
}
}
}
@ -43,9 +43,9 @@ function exportProgress() {
function exportResponse(respObj, respText) {
clearInterval(exportTimer);
if ( respObj.result != 'Ok' ) {
$('exportProgressTicker').set('text', respObj.message);
$j('#exportProgressTicker').text(respObj.message);
} else {
$('exportProgressTicker').set('text', exportSucceededString);
$j('#exportProgressTicker').text(exportSucceededString);
startDownload.pass(decodeURIComponent(respObj.exportFile)).delay(1500);
}
return;

View File

@ -1,3 +1,31 @@
var backBtn = $j('#backBtn');
var table = $j('#framesTable');
// Called by bootstrap-table to retrieve zm frame data
function ajaxRequest(params) {
if ( params.data && params.data.filter ) {
params.data.advsearch = params.data.filter;
delete params.data.filter;
}
$j.getJSON(thisUrl + '?view=request&request=frames&task=query&eid='+eid, params.data)
.done(function(data) {
var rows = processRows(data.rows);
// rearrange the result into what bootstrap-table expects
console.log('Total: '+data.total);
console.log('TotalnotFiltered: '+data.totalNotFiltered);
params.success({total: data.total, totalNotFiltered: data.totalNotFiltered, rows: rows});
})
.fail(logAjaxFail);
}
function processRows(rows) {
$j.each(rows, function(ndx, row) {
// WIP: process each row here
// VERIFY: Might not need to do anything here for the frames table
});
return rows;
}
function thumbnail_onmouseover(event) {
var img = event.target;
img.src = '';
@ -11,14 +39,16 @@ function thumbnail_onmouseout(event) {
}
function initThumbAnimation() {
if ( WEB_ANIMATE_THUMBS ) {
$j('.colThumbnail img').each(function() {
this.addEventListener('mouseover', thumbnail_onmouseover, false);
this.addEventListener('mouseout', thumbnail_onmouseout, false);
});
}
}
function processClicks(event, field, value, row, $element) {
if ( field == 'FrameScore' ) {
if ( field == 'Score' ) {
window.location.assign('?view=stats&eid='+row.EventId+'&fid='+row.FrameId);
} else {
window.location.assign('?view=frame&eid='+row.EventId+'&fid='+row.FrameId);
@ -34,9 +64,10 @@ function detailFormatter(index, row, $detail) {
})
.fail(logAjaxFail);
}
function initPage() {
var backBtn = $j('#backBtn');
var table = $j('#framesTable');
// Remove the thumbnail column from the DOM if thumbnails are off globally
if ( !WEB_LIST_THUMBS ) $j('th[data-field="Thumbnail"]').remove();
// Init the bootstrap-table
table.bootstrapTable({icons: icons});
@ -71,6 +102,25 @@ function initPage() {
evt.preventDefault();
window.location.reload(true);
});
// Update table links each time after new data is loaded
table.on('post-body.bs.table', function(data) {
var type_ndx = $j('#framesTable tr th').filter(function() {
return $j(this).text().trim() == 'Type';
}).index();
$j('#framesTable tr').each(function(ndx, row) {
var row = $j(row);
var type = row.find('td').eq(type_ndx).text().trim();
row.addClass(type.toLowerCase());
});
var thumb_ndx = $j('#framesTable tr th').filter(function() {
return $j(this).text().trim() == 'Thumbnail';
}).index();
var thmbClass = WEB_ANIMATE_THUMBS ? 'colThumbnail zoom' : 'colThumbnail';
table.find("tr td:nth-child(" + (thumb_ndx+1) + ")").addClass(thmbClass);
});
}
$j(document).ready(function() {

View File

@ -0,0 +1,3 @@
var eid = <?php echo validInt($_REQUEST['eid']) ?>;
var WEB_LIST_THUMBS = <?php echo ZM_WEB_LIST_THUMBS?'true':'false' ?>;
var WEB_ANIMATE_THUMBS = <?php echo ZM_WEB_ANIMATE_THUMBS?'true':'false' ?>;

View File

@ -30,29 +30,28 @@ function changeDateTime(e) {
window.location = uri;
}
function datetime_change(newDate, oldData) {
if (newDate !== oldData.lastVal) {
changeDateTime();
}
}
function initPage() {
$j('#minTime').datetimepicker({
timeFormat: "HH:mm:ss",
dateFormat: "yy-mm-dd",
maxDate: +0,
constrainInput: false,
onClose: function(newDate, oldData) {
if (newDate !== oldData.lastVal) {
changeDateTime();
}
}
onClose: datetime_change
});
$j('#maxTime').datetimepicker({
timeFormat: "HH:mm:ss",
dateFormat: "yy-mm-dd",
minDate: $j('#minTime').val(),
maxDate: +0,
constrainInput: false,
onClose: function(newDate, oldData) {
if (newDate !== oldData.lastVal) {
changeDateTime();
}
}
onClose: datetime_change
});
}
// Kick everything off

View File

@ -1,113 +1,13 @@
var streamStatus;
var auth_hash;
var alarmState = STATE_IDLE;
var lastAlarmState = STATE_IDLE;
var backBtn = $j('#backBtn');
var settingsBtn = $j('#settingsBtn');
var enableAlmBtn = $j('#enableAlmBtn');
var forceAlmBtn = $j('#forceAlmBtn');
function showEvents() {
$('ptzControls').addClass('hidden');
$('events').removeClass('hidden');
if ( $('eventsControl') ) {
$('eventsControl').addClass('hidden');
}
if ( $('controlControl') ) {
$('controlControl').removeClass('hidden');
}
showMode = 'events';
}
function showPtzControls() {
$('events').addClass('hidden');
$('ptzControls').removeClass('hidden');
if ( $('eventsControl') ) {
$('eventsControl').removeClass('hidden');
}
if ( $('controlControl') ) {
$('controlControl').addClass('hidden');
}
showMode = 'control';
}
function changeScale() {
var scale = $('scale').get('value');
var newWidth;
var newHeight;
if ( scale == '0' || scale == 'auto' ) {
var newSize = scaleToFit(monitorWidth, monitorHeight, $j('#liveStream'+monitorId), $j('#replayStatus'));
newWidth = newSize.width;
newHeight = newSize.height;
autoScale = newSize.autoScale;
} else {
$j(window).off('resize', endOfResize); //remove resize handler when Scale to Fit is not active
newWidth = monitorWidth * scale / SCALE_BASE;
newHeight = monitorHeight * scale / SCALE_BASE;
}
Cookie.write('zmWatchScale'+monitorId, scale, {duration: 10*365, samesite: 'strict'});
/*Stream could be an applet so can't use moo tools*/
var streamImg = $('liveStream'+monitorId);
if ( streamImg ) {
streamImg.style.width = newWidth + 'px';
streamImg.style.height = newHeight + 'px';
streamImg.src = streamImg.src.replace(/scale=\d+/i, 'scale='+(scale== 'auto' ? autoScale : scale));
} else {
console.error('No element found for liveStream'+monitorId);
}
}
var alarmState = STATE_IDLE;
var lastAlarmState = STATE_IDLE;
function setAlarmState( currentAlarmState ) {
alarmState = currentAlarmState;
var stateClass = '';
if ( alarmState == STATE_ALARM ) {
stateClass = 'alarm';
} else if ( alarmState == STATE_ALERT ) {
stateClass = 'alert';
}
$('stateValue').set('text', stateStrings[alarmState]);
if ( stateClass ) {
$('stateValue').setProperty('class', stateClass);
} else {
$('stateValue').removeProperty('class');
}
var isAlarmed = ( alarmState == STATE_ALARM || alarmState == STATE_ALERT );
var wasAlarmed = ( lastAlarmState == STATE_ALARM || lastAlarmState == STATE_ALERT );
var newAlarm = ( isAlarmed && !wasAlarmed );
var oldAlarm = ( !isAlarmed && wasAlarmed );
if ( newAlarm ) {
if ( SOUND_ON_ALARM ) {
// Enable the alarm sound
if ( !canPlayPauseAudio ) {
$('alarmSound').removeClass('hidden');
} else {
$('MediaPlayer').Play();
}
}
if ( POPUP_ON_ALARM ) {
window.focus();
}
}
if ( oldAlarm ) { // done with an event do a refresh
if ( SOUND_ON_ALARM ) {
// Disable alarm sound
if ( !canPlayPauseAudio ) {
$('alarmSound').addClass('hidden');
} else {
$('MediaPlayer').Stop();
}
}
eventCmdQuery();
}
lastAlarmState = alarmState;
} // end function setAlarmState( currentAlarmState )
var table = $j('#eventList');
var filterQuery = '&filter[Query][terms][0][attr]=MonitorId&filter[Query][terms][0][op]=%3d&filter[Query][terms][0][val]='+monitorId;
if ( monitorType != 'WebSite' ) {
var streamCmdParms = 'view=request&request=stream&connkey='+connKey;
@ -126,16 +26,178 @@ if ( monitorType != 'WebSite' ) {
var streamCmdTimer = null;
}
var streamStatus;
/*
This is the format of the json object sent by bootstrap-table
var params =
{
"type":"get",
"data":
{
"search":"some search text",
"sort":"StartDateTime",
"order":"asc",
"offset":0,
"limit":25
"filter":
{
"Name":"some advanced search text"
"StartDateTime":"some more advanced search text"
}
},
"cache":true,
"contentType":"application/json",
"dataType":"json"
};
*/
// Called by bootstrap-table to retrieve zm event data
function ajaxRequest(params) {
// Maintain legacy behavior by statically setting these parameters
params.data.order = 'desc';
params.data.limit = maxDisplayEvents;
params.data.sort = 'Id';
$j.getJSON(thisUrl + '?view=request&request=events&task=query'+filterQuery, params.data)
.done(function(data) {
var rows = processRows(data.rows);
// rearrange the result into what bootstrap-table expects
params.success({total: data.total, totalNotFiltered: data.totalNotFiltered, rows: rows});
})
.fail(logAjaxFail);
}
function processRows(rows) {
$j.each(rows, function(ndx, row) {
var eid = row.Id;
row.Delete = '<i class="fa fa-trash text-danger"></i>';
row.Id = '<a href="?view=event&amp;eid=' + eid + filterQuery + '">' + eid + '</a>';
row.Name = '<a href="?view=event&amp;eid=' + eid + filterQuery + '">' + row.Name + '</a>';
row.Frames = '<a href="?view=frames&amp;eid=' + eid + '">' + row.Frames + '</a>';
row.AlarmFrames = '<a href="?view=frames&amp;eid=' + eid + '">' + row.AlarmFrames + '</a>';
row.MaxScore = '<a href="?view=frame&amp;eid=' + eid + '&amp;fid=0">' + row.MaxScore + '</a>';
if ( LIST_THUMBS ) row.Thumbnail = '<a href="?view=event&amp;eid=' + eid + filterQuery + '&amp;page=1">' + row.imgHtml + '</a>';
});
return rows;
}
function showEvents() {
$j('#ptzControls').addClass('hidden');
$j('#events').removeClass('hidden');
if ( $j('#eventsControl') ) {
$j('#eventsControl').addClass('hidden');
}
if ( $j('#controlControl') ) {
$j('#controlControl').removeClass('hidden');
}
showMode = 'events';
}
function showPtzControls() {
$j('#events').addClass('hidden');
$j('#ptzControls').removeClass('hidden');
if ( $j('#eventsControl') ) {
$j('#eventsControl').removeClass('hidden');
}
if ( $j('#controlControl') ) {
$j('#controlControl').addClass('hidden');
}
showMode = 'control';
}
function changeScale() {
var scale = $j('#scale').val();
var newWidth;
var newHeight;
if ( scale == '0' || scale == 'auto' ) {
var newSize = scaleToFit(monitorWidth, monitorHeight, $j('#liveStream'+monitorId), $j('#replayStatus'));
newWidth = newSize.width;
newHeight = newSize.height;
autoScale = newSize.autoScale;
} else {
$j(window).off('resize', endOfResize); //remove resize handler when Scale to Fit is not active
newWidth = monitorWidth * scale / SCALE_BASE;
newHeight = monitorHeight * scale / SCALE_BASE;
}
Cookie.write('zmWatchScale'+monitorId, scale, {duration: 10*365, samesite: 'strict'});
var streamImg = $j('#liveStream'+monitorId);
if ( streamImg ) {
var oldSrc = streamImg.attr('src');
var newSrc = oldSrc.replace(/scale=\d+/i, 'scale='+(scale== 'auto' ? autoScale : scale));
streamImg.width( newWidth );
streamImg.height( newHeight );
streamImg.src = newSrc;
} else {
console.error('No element found for liveStream'+monitorId);
}
}
function setAlarmState( currentAlarmState ) {
alarmState = currentAlarmState;
var stateClass = '';
if ( alarmState == STATE_ALARM ) {
stateClass = 'alarm';
} else if ( alarmState == STATE_ALERT ) {
stateClass = 'alert';
}
$j('#stateValue').text(stateStrings[alarmState]);
if ( stateClass ) {
$j('#stateValue').addClass(stateClass);
} else {
$j('#stateValue').removeClass();
}
var isAlarmed = ( alarmState == STATE_ALARM || alarmState == STATE_ALERT );
var wasAlarmed = ( lastAlarmState == STATE_ALARM || lastAlarmState == STATE_ALERT );
var newAlarm = ( isAlarmed && !wasAlarmed );
var oldAlarm = ( !isAlarmed && wasAlarmed );
if ( newAlarm ) {
table.bootstrapTable('refresh');
if ( SOUND_ON_ALARM ) {
// Enable the alarm sound
if ( !canPlayPauseAudio ) {
$j('#alarmSound').removeClass('hidden');
} else {
$j('#MediaPlayer').trigger('play');
}
}
if ( POPUP_ON_ALARM ) {
window.focus();
}
}
if ( oldAlarm ) { // done with an event do a refresh
table.bootstrapTable('refresh');
if ( SOUND_ON_ALARM ) {
// Disable alarm sound
if ( !canPlayPauseAudio ) {
$j('#alarmSound').addClass('hidden');
} else {
$j('#MediaPlayer').trigger('pause');
}
}
}
lastAlarmState = alarmState;
} // end function setAlarmState( currentAlarmState )
function getStreamCmdError(text, error) {
console.log(error);
// Error are normally due to failed auth. reload the page.
window.location.reload();
}
function getStreamCmdFailure(xhr) {
console.log(xhr);
}
function getStreamCmdResponse(respObj, respText) {
watchdogOk('stream');
if ( streamCmdTimer ) {
@ -145,35 +207,36 @@ function getStreamCmdResponse(respObj, respText) {
// The get status command can get backed up, in which case we won't be able to get the semaphore and will exit.
if ( respObj.status ) {
streamStatus = respObj.status;
$('fpsValue').set('text', streamStatus.fps);
$j('#fpsValue').text(streamStatus.fps);
setAlarmState(streamStatus.state);
$('levelValue').set('text', streamStatus.level);
$j('#levelValue').text(streamStatus.level);
var newClass = 'ok';
if ( streamStatus.level > 95 ) {
$('levelValue').className = 'alarm';
newClass = 'alarm';
} else if ( streamStatus.level > 80 ) {
$('levelValue').className = 'alert';
} else {
$('levelValue').className = 'ok';
newClass = 'alert';
}
$j('#levelValue').removeClass();
$j('#levelValue').addClass(newClass);
var delayString = secsToTime(streamStatus.delay);
if ( streamStatus.paused == true ) {
$('modeValue').set('text', 'Paused');
$('rate').addClass('hidden');
$('delayValue').set('text', delayString);
$('delay').removeClass('hidden');
$('level').removeClass('hidden');
$j('#modeValue').text('Paused');
$j('#rate').addClass('hidden');
$j('#delayValue').text(delayString);
$j('#delay').removeClass('hidden');
$j('#level').removeClass('hidden');
streamCmdPause(false);
} else if ( streamStatus.delayed == true ) {
$('modeValue').set('text', 'Replay');
$('rateValue').set('text', streamStatus.rate);
$('rate').removeClass('hidden');
$('delayValue').set('text', delayString);
$('delay').removeClass('hidden');
$('level').removeClass('hidden');
$j('#modeValue').text('Replay');
$j('#rateValue').text(streamStatus.rate);
$j('#rate').removeClass('hidden');
$j('#delayValue').text(delayString);
$j('#delay').removeClass('hidden');
$j('#level').removeClass('hidden');
if ( streamStatus.rate == 1 ) {
streamCmdPlay(false);
} else if ( streamStatus.rate > 0 ) {
@ -190,14 +253,14 @@ function getStreamCmdResponse(respObj, respText) {
}
} // rate
} else {
$('modeValue').set( 'text', 'Live' );
$('rate').addClass( 'hidden' );
$('delay').addClass( 'hidden' );
$('level').addClass( 'hidden' );
$j('#modeValue').text( 'Live' );
$j('#rate').addClass( 'hidden' );
$j('#delay').addClass( 'hidden' );
$j('#level').addClass( 'hidden' );
streamCmdPlay(false);
} // end if paused or delayed
$('zoomValue').set('text', streamStatus.zoom);
$j('zoomValue').text(streamStatus.zoom);
if ( streamStatus.zoom == '1.0' ) {
setButtonState('zoomOutBtn', 'unavail');
} else {
@ -227,13 +290,15 @@ function getStreamCmdResponse(respObj, respText) {
if ( streamStatus.auth ) {
auth_hash = streamStatus.auth;
// Try to reload the image stream.
var streamImg = $('liveStream');
var streamImg = $j('#liveStream'+monitorId);
if ( streamImg ) {
streamImg.src = streamImg.src.replace(/auth=\w+/i, 'auth='+streamStatus.auth);
var oldSrc = streamImg.attr('src');
var newSrc = oldSrc.replace(/auth=\w+/i, 'auth='+streamStatus.auth);
streamImg.src = newSrc;
}
streamCmdParms = streamCmdParms.replace(/auth=\w+/i, 'auth='+streamStatus.auth);
statusCmdParms = statusCmdParms.replace(/auth=\w+/i, 'auth='+streamStatus.auth);
eventCmdParms = eventCmdParms.replace(/auth=\w+/i, 'auth='+streamStatus.auth);
table.bootstrapTable('refresh');
controlParms = controlParms.replace(/auth=\w+/i, 'auth='+streamStatus.auth);
} // end if have a new auth hash
} // end if respObj.status
@ -243,9 +308,12 @@ function getStreamCmdResponse(respObj, respText) {
// If it's an auth error, we should reload the whole page.
window.location.reload();
if ( 0 ) {
var streamImg = $('liveStream'+monitorId);
var streamImg = $j('#liveStream'+monitorId);
if ( streamImg ) {
streamImg.src = streamImg.src.replace(/rand=\d+/i, 'rand='+Math.floor((Math.random() * 1000000) ));
var oldSrc = streamImg.attr('src');
var newSrc = oldSrc.replace(/rand=\d+/i, 'rand='+Math.floor((Math.random() * 1000000) ));
streamImg.src = newSrc;
console.log('Changing livestream src to ' + streamImg.src);
} else {
console.log('Unable to find streamImg liveStream');
@ -427,7 +495,7 @@ function getStatusCmdResponse(respObj, respText) {
}
if ( respObj.result == 'Ok' ) {
$('fpsValue').set('text', respObj.monitor.FrameRate);
$j('#fpsValue').text(respObj.monitor.FrameRate);
setAlarmState(respObj.monitor.Status);
} else {
checkStreamForErrors('getStatusCmdResponse', respObj);
@ -503,187 +571,6 @@ function cmdForce() {
}
}
function getActResponse( respObj, respText ) {
if ( respObj.result == 'Ok' ) {
if ( respObj.refreshParent && window.opener ) {
console.log('refreshing parent');
window.opener.location.reload();
}
}
eventCmdQuery();
}
function deleteEvent(event, eventId) {
var actParms = 'view=request&request=event&action=delete&id='+eventId;
if ( auth_hash ) {
actParms += '&auth='+auth_hash;
}
var actReq = new Request.JSON( {
url: thisUrl,
method: 'post',
timeout: 3000,
onSuccess: getActResponse
} );
actReq.send(actParms);
event.stop();
}
if ( monitorType != 'WebSite' ) {
var eventCmdParms = "view=request&request=status&entity=events&id="+monitorId+"&count="+maxDisplayEvents+"&sort=Id%20desc";
if ( auth_hash ) {
eventCmdParms += '&auth='+auth_hash;
}
var eventCmdReq = new Request.JSON( {
url: monitorUrl,
method: 'get',
timeout: AJAX_TIMEOUT,
link: 'cancel',
onSuccess: getEventCmdResponse,
onTimeout: eventCmdQuery
} );
var eventCmdTimer = null;
var eventCmdFirst = true;
}
function highlightRow( row ) {
$(row).toggleClass('highlight');
}
function getEventCmdResponse( respObj, respText ) {
watchdogOk('event');
if ( eventCmdTimer ) {
eventCmdTimer = clearTimeout(eventCmdTimer);
}
if ( respObj.result == 'Ok' ) {
var dbEvents = respObj.events.reverse();
var eventList = $('eventList');
var eventListBody = $(eventList).getElement('tbody');
var eventListRows = $(eventListBody).getElements('tr');
eventListRows.each( function(row) {
row.removeClass('updated');
} );
for ( var i = 0; i < dbEvents.length; i++ ) {
var zm_event = dbEvents[i];
var row = $('event'+zm_event.Id);
var newEvent = (row == null ? true : false);
if ( newEvent ) {
row = new Element('tr', {'id': 'event'+zm_event.Id});
new Element('td', {'class': 'colId'}).inject(row);
new Element('td', {'class': 'colName'}).inject(row);
new Element('td', {'class': 'colTime'}).inject(row);
new Element('td', {'class': 'colSecs'}).inject(row);
new Element('td', {'class': 'colFrames'}).inject(row);
new Element('td', {'class': 'colScore'}).inject(row);
new Element('td', {'class': 'colDelete'}).inject(row);
var link = new Element('a', {
'href': '#',
'events': {
'click': openEvent.pass( [
zm_event.Id,
'&filter[Query][terms][0][attr]=MonitorId&filter[Query][terms][0][op]=%3d&filter[Query][terms][0][val]='+monitorId+'&page=1'
] )
}
});
link.set('text', zm_event.Id);
link.inject(row.getElement('td.colId'));
link = new Element('a', {
'href': '#',
'events': {
'click': openEvent.pass( [
zm_event.Id,
'&filter[Query][terms][0][attr]=MonitorId&filter[Query][terms][0][op]=%3d&filter[Query][terms][0][val]='+monitorId+'&page=1'
] )
}
});
link.set('text', zm_event.Name);
link.inject(row.getElement('td.colName'));
row.getElement('td.colTime').set('text', zm_event.StartDateTime);
row.getElement('td.colSecs').set('text', zm_event.Length);
link = new Element('a', {'href': '#', 'events': {'click': openFrames.pass( [zm_event.Id] )}});
link.set('text', zm_event.Frames+'/'+zm_event.AlarmFrames);
link.inject(row.getElement('td.colFrames'));
link = new Element('a', {'href': '#', 'events': {'click': openFrame.pass( [zm_event.Id, '0'] )}});
link.set('text', zm_event.AvgScore+'/'+zm_event.MaxScore);
link.inject(row.getElement('td.colScore'));
link = new Element('button', {
'type': 'button',
'title': deleteString,
'data-event-id': zm_event.Id,
'events': {
'click': function(e) {
var event_id = e.target.getAttribute('data-event-id');
if ( !event_id ) {
console.log('No event id in deleteEvent');
console.log(e);
} else {
deleteEvent(e, event_id);
}
},
'mouseover': highlightRow.pass(row),
'mouseout': highlightRow.pass(row)
}
});
link.set('text', 'X');
link.inject(row.getElement('td.colDelete'));
if ( i == 0 ) {
row.inject($(eventListBody));
} else {
row.inject($(eventListBody), 'top');
if ( !eventCmdFirst ) {
row.addClass('recent');
}
}
} else {
row.getElement('td.colName a').set('text', zm_event.Name);
row.getElement('td.colSecs').set('text', zm_event.Length);
row.getElement('td.colFrames a').set('text', zm_event.Frames+'/'+zm_event.AlarmFrames);
row.getElement('td.colScore a').set('text', zm_event.AvgScore+'/'+zm_event.MaxScore);
row.removeClass('recent');
}
row.addClass('updated');
} // end foreach event
var rows = $(eventListBody).getElements('tr');
for ( var i = 0; i < rows.length; i++ ) {
if ( !rows[i].hasClass('updated') ) {
rows[i].destroy();
rows.splice( i, 1 );
i--;
}
}
while ( rows.length > maxDisplayEvents ) {
rows[rows.length-1].destroy();
rows.length--;
}
} else {
checkStreamForErrors('getEventCmdResponse', respObj);
} // end if objresult == ok
var eventCmdTimeout = eventsRefreshTimeout;
if ( alarmState == STATE_ALARM || alarmState == STATE_ALERT ) {
eventCmdTimeout = eventCmdTimeout/5;
}
eventCmdTimer = eventCmdQuery.delay(eventCmdTimeout);
eventCmdFirst = false;
}
function eventCmdQuery() {
if ( eventCmdTimer ) { // avoid firing another if we are firing one
eventCmdTimer = clearTimeout(eventCmdTimer);
}
eventCmdReq.send(eventCmdParms);
}
if ( monitorType != 'WebSite' ) {
var controlParms = 'view=request&request=control&id='+monitorId;
if ( auth_hash ) {
@ -717,13 +604,15 @@ function controlCmd(event) {
var locParms = '';
if ( event && (xtell || ytell) ) {
var target = event.target;
var coords = $(target).getCoordinates();
var offset = $j(target).offset();
var width = $j(target).width();
var height = $j(target).height();
var x = event.pageX - coords.left;
var y = event.pageY - coords.top;
var x = event.pageX - offset.left;
var y = event.pageY - offset.top;
if ( xtell ) {
var xge = parseInt((x*100)/coords.width);
var xge = parseInt((x*100)/width);
if ( xtell == -1 ) {
xge = 100 - xge;
} else if ( xtell == 2 ) {
@ -732,7 +621,7 @@ function controlCmd(event) {
locParms += '&xge='+xge;
}
if ( ytell ) {
var yge = parseInt((y*100)/coords.height);
var yge = parseInt((y*100)/height);
if ( ytell == -1 ) {
yge = 100 - yge;
} else if ( ytell == 2 ) {
@ -763,11 +652,14 @@ function fetchImage( streamImage ) {
}
function handleClick( event ) {
var $target = $(event.target);
var scaleX = parseInt(monitorWidth / $target.getWidth());
var scaleY = parseInt(monitorHeight / $target.getHeight());
var x = (event.page.x - $target.getLeft()) * scaleX;
var y = (event.page.y - $target.getTop()) * scaleY;
var target = event.target;
var width = $j(target).width();
var height = $j(target).height();
var scaleX = parseInt(monitorWidth / width);
var scaleY = parseInt(monitorHeight / height);
var x = (event.page.x - target.getLeft()) * scaleX;
var y = (event.page.y - target.getTop()) * scaleY;
if ( showMode == 'events' || !imageControlMode ) {
if ( event.shift ) {
@ -784,11 +676,11 @@ function handleClick( event ) {
function appletRefresh() {
if ( streamStatus && (!streamStatus.paused && !streamStatus.delayed) ) {
var streamImg = $('liveStream'+monitorId);
var streamImg = $j('#liveStream'+monitorId);
if ( streamImg ) {
var parent = streamImg.getParent();
streamImg.dispose();
streamImg.inject( parent );
var parent = streamImg.parent();
streamImg.remove();
streamImg.append( parent );
} else {
console.error("Nothing found for liveStream"+monitorId);
}
@ -802,14 +694,12 @@ function appletRefresh() {
var watchdogInactive = {
'stream': false,
'status': false,
'event': false
'status': false
};
var watchdogFunctions = {
'stream': streamCmdQuery,
'status': statusCmdQuery,
'event': eventCmdQuery
};
//Make sure the various refreshes are still taking effect
@ -832,15 +722,9 @@ function reloadWebSite() {
}
function updatePresetLabels() {
var form = $('ctrlPresetForm');
var preset_ddm = form.elements['preset'];
var lblNdx = $j( '#ctrlPresetForm option:selected' ).val();
var presetIndex = preset_ddm[preset_ddm.selectedIndex].value;
if ( labels[presetIndex] ) {
form.newLabel.value = labels[presetIndex];
} else {
form.newLabel.value = '';
}
$j('#newLabel').val(labels[lblNdx]);
}
function getCtrlPresetModal() {
@ -872,6 +756,44 @@ function getSettingsModal() {
.fail(logAjaxFail);
}
function processClicks(event, field, value, row, $element) {
if ( field == 'Delete' ) {
$j.getJSON(thisUrl + '?request=modal&modal=delconfirm')
.done(function(data) {
insertModalHtml('deleteConfirm', data.html);
manageDelConfirmModalBtns();
$j('#deleteConfirm').data('eid', row.Id.replace(/(<([^>]+)>)/gi, ''));
$j('#deleteConfirm').modal('show');
})
.fail(logAjaxFail);
}
}
// Manage the DELETE CONFIRMATION modal button
function manageDelConfirmModalBtns() {
document.getElementById("delConfirmBtn").addEventListener("click", function onDelConfirmClick(evt) {
if ( ! canEditEvents ) {
enoperm();
return;
}
var eid = $j('#deleteConfirm').data('eid');
evt.preventDefault();
$j.getJSON(thisUrl + '?request=events&task=delete&eids[]='+eid)
.done( function(data) {
table.bootstrapTable('refresh');
$j('#deleteConfirm').modal('hide');
})
.fail(logAjaxFail);
});
// Manage the CANCEL modal button
document.getElementById("delCancelBtn").addEventListener("click", function onDelCancelClick(evt) {
$j('#deleteConfirm').modal('hide');
});
}
function initPage() {
if ( canViewControl ) {
// Load the PTZ Preset modal into the DOM
@ -889,9 +811,6 @@ function initPage() {
watchdogCheck.pass('stream').periodical(statusRefreshTimeout*2);
}
eventCmdTimer = eventCmdQuery.delay( (Math.random()+0.1)*statusRefreshTimeout );
watchdogCheck.pass('event').periodical(eventsRefreshTimeout*2);
if ( canStreamNative || (streamMode == 'single') ) {
var streamImg = $('imageFeed').getElement('img');
if ( !streamImg ) {
@ -924,6 +843,7 @@ function initPage() {
} else if ( monitorRefresh > 0 ) {
setInterval(reloadWebSite, monitorRefresh*1000);
}
// Manage the BACK button
document.getElementById("backBtn").addEventListener("click", function onBackClick(evt) {
evt.preventDefault();
@ -948,6 +868,28 @@ function initPage() {
// Only enable the settings button for local cameras
settingsBtn.prop('disabled', !canViewControl);
if ( monitorType != 'Local' ) settingsBtn.hide();
// Init the bootstrap-table
if ( monitorType != 'WebSite' ) table.bootstrapTable({icons: icons});
// Update table rows each time after new data is loaded
table.on('post-body.bs.table', function(data) {
$j('#eventList tr:contains("New Event")').addClass('recent');
});
// Take appropriate action when the user clicks on a cell
table.on('click-cell.bs.table', processClicks);
// Some toolbar events break the thumbnail animation, so re-init eventlistener
table.on('all.bs.table', initThumbAnimation);
// Update table links each time after new data is loaded
table.on('post-body.bs.table', function(data) {
var thumb_ndx = $j('#eventList tr th').filter(function() {
return $j(this).text().trim() == 'Thumbnail';
}).index();
table.find("tr td:nth-child(" + (thumb_ndx+1) + ")").addClass('colThumbnail');
});
} // initPage
// Kick everything off

View File

@ -50,6 +50,7 @@ var SCALE_BASE = <?php echo SCALE_BASE ?>;
var SOUND_ON_ALARM = <?php echo ZM_WEB_SOUND_ON_ALARM ?>;
var POPUP_ON_ALARM = <?php echo ZM_WEB_POPUP_ON_ALARM ?>;
var LIST_THUMBS = <?php echo ZM_WEB_LIST_THUMBS?'true':'false' ?>;
var streamMode = "<?php echo $streamMode ?>";
var showMode = "<?php echo ($showPtzControls && !empty($control))?"control":"events" ?>";

View File

@ -27,7 +27,7 @@ xhtmlHeaders(__FILE__, translate('SystemLog'));
?>
<body>
<?php echo getNavBarHTML() ?>
<div id="page" class="px-3">
<div id="page" class="px-3 table-responsive-sm">
<div id="logSummary" class="text-center">
<?php echo translate('State') ?>:&nbsp;<span id="logState"></span>&nbsp;-&nbsp;
@ -62,7 +62,6 @@ xhtmlHeaders(__FILE__, translate('SystemLog'));
data-toolbar="#toolbar"
data-show-fullscreen="true"
data-maintain-meta-data="true"
data-mobile-responsive="true"
data-buttons-class="btn btn-normal"
data-show-jump-to="true"
data-auto-refresh="true"

View File

@ -369,8 +369,10 @@ $fastblendopts_alarm = array(
);
$label_size = array(
1 => translate('Default'),
2 => translate('Large'),
1 => translate('Small'),
2 => translate('Default'),
3 => translate('Large'),
4 => translate('Extra Large'),
);
$codecs = array(
@ -1030,7 +1032,7 @@ echo htmlSelect('newMonitor[OutputContainer]', $videowriter_containers, $monitor
<td><input type="number" name="newMonitor[AlarmFrameCount]" value="<?php echo validHtmlStr($monitor->AlarmFrameCount()) ?>" min="1"/></td>
</tr>
<tr>
<td class="text-right pr-3"><?php echo translate('EstimatedRamUse') ?></td>
<td class="text-right pr-3"><?php echo translate('Estimated Ram Use') ?></td>
<td id="estimated_ram_use"><?php echo human_filesize($monitor->ImageBufferCount() * $monitor->Width() * $monitor->Height() * $monitor->Colours(), 0) ?></td>
</tr>
<?php

View File

@ -38,30 +38,24 @@ if ( isset($_REQUEST['maxTime']) ) {
$maxTime = strftime('%FT%T',time() - 3600);
}
$filter = array(
'Query' => array(
'terms' => array(
array('attr'=>'StartDateTime', 'op'=>'>=', 'val'=>$minTime, 'obr'=>'1'),
array('attr'=>'StartDateTime', 'op'=>'<=', 'val'=>$maxTime, 'cnj'=>'and', 'cbr'=>'1'),
)
),
);
$filter = new ZM\Filter();
$filter->addTerm(array('attr'=>'StartDateTime', 'op'=>'>=', 'val'=>$minTime, 'obr'=>'1'));
$filter->addTerm(array('attr'=>'StartDateTime', 'op'=>'<=', 'val'=>$maxTime, 'cnj'=>'and', 'cbr'=>'1'));
if ( count($selected_monitor_ids) ) {
$filter['Query']['terms'][] = (array('attr'=>'MonitorId', 'op'=>'IN', 'val'=>implode(',', $selected_monitor_ids), 'cnj'=>'and'));
$filter->addTerm(array('attr'=>'MonitorId', 'op'=>'IN', 'val'=>implode(',', $selected_monitor_ids), 'cnj'=>'and'));
} else if ( ( $group_id != 0 || isset($_SESSION['ServerId']) || isset($_SESSION['StorageId']) || isset($_SESSION['Status']) ) ) {
# this should be redundant
for ( $i=0; $i < count($displayMonitors); $i++ ) {
if ( $i == 0 ) {
$filter['Query']['terms'][] = array('attr'=>'MonitorId', 'op'=>'=', 'val'=>$displayMonitors[$i]['Id'], 'cnj'=>'and', 'obr'=>'1');
$filter->addTerm(array('attr'=>'MonitorId', 'op'=>'=', 'val'=>$displayMonitors[$i]['Id'], 'cnj'=>'and', 'obr'=>'1'));
} else if ( $i == count($displayMonitors)-1 ) {
$filter['Query']['terms'][] = array('attr'=>'MonitorId', 'op'=>'=', 'val'=>$displayMonitors[$i]['Id'], 'cnj'=>'or', 'cbr'=>'1');
$filter->addTerm(array('attr'=>'MonitorId', 'op'=>'=', 'val'=>$displayMonitors[$i]['Id'], 'cnj'=>'or', 'cbr'=>'1'));
} else {
$filter['Query']['terms'][] = array('attr'=>'MonitorId', 'op'=>'=', 'val'=>$displayMonitors[$i]['Id'], 'cnj'=>'or');
$filter->addTerm(array('attr'=>'MonitorId', 'op'=>'=', 'val'=>$displayMonitors[$i]['Id'], 'cnj'=>'or'));
}
}
}
parseFilter($filter);
$filterQuery = $filter['query'];
$filterQuery = $filter->querystring();
ZM\Debug($filterQuery);
$eventsSql = 'SELECT *,
@ -113,17 +107,16 @@ while ( $event = $result->fetch(PDO::FETCH_ASSOC) ) {
?>
<body>
<?php echo $navbar ?>
<form name="monitorForm" method="get" action="?">
<input type="hidden" name="view" value="<?php echo $view ?>"/>
<input type="hidden" name="action" value=""/>
<?php echo $navbar ?>
<div class="filterBar">
<?php echo $filterbar ?>
<div id="DateTimeDiv">
<label>Event Start Time</label>
<input type="text" name="minTime" id="minTime" value="<?php echo preg_replace('/T/', ' ', $minTime) ?>" oninput="this.form.submit();"/> to
<input type="text" name="maxTime" id="maxTime" value="<?php echo preg_replace('/T/', ' ', $maxTime) ?>" oninput="this.form.submit();"/>
<input type="text" name="minTime" id="minTime" value="<?php echo preg_replace('/T/', ' ', $minTime) ?>"/> to
<input type="text" name="maxTime" id="maxTime" value="<?php echo preg_replace('/T/', ' ', $maxTime) ?>"/>
</div>
</div><!--FilterBar-->
@ -150,12 +143,7 @@ for ( $monitor_i = 0; $monitor_i < count($displayMonitors); $monitor_i += 1 ) {
$Monitor = new ZM\Monitor($monitor);
$montagereview_link = '?view=montagereview&live=0&MonitorId='.$monitor['Id'].'&minTime='.$minTime.'&maxTime='.$maxTime;
$monitor_filter = addFilterTerm(
$filter,
count($filter['Query']['terms']),
array('cnj'=>'and', 'attr'=>'MonitorId', 'op'=>'=', 'val'=>$monitor['Id'])
);
parseFilter($monitor_filter);
$monitor_filter = $filter->addTerm(array('cnj'=>'and', 'attr'=>'MonitorId', 'op'=>'=', 'val'=>$monitor['Id']));
if ( isset($EventsByMonitor[$Monitor->Id()]) ) {
$EventCounts = $EventsByMonitor[$Monitor->Id()];
@ -175,24 +163,12 @@ for ( $monitor_i = 0; $monitor_i < count($displayMonitors); $monitor_i += 1 ) {
}
if ( count($FileMissing) ) {
$FileMissing_filter = array(
'Query' => array(
'terms' => array(
array('attr'=>'Id', 'op'=>'IN', 'val'=>implode(',', array_map(function($Event){return $Event->Id();}, $FileMissing)))
)
)
);
parseFilter($FileMissing_filter);
$FileMissing_filter = new ZM\Filter();
$FileMissing_filter->addTerm(array('attr'=>'Id', 'op'=>'IN', 'val'=>implode(',', array_map(function($Event){return $Event->Id();}, $FileMissing))));
}
if ( count($ZeroSize) ) {
$ZeroSize_filter = array(
'Query' => array(
'terms' => array(
array('attr'=>'Id', 'op'=>'IN', 'val'=>implode(',', array_map(function($Event){return $Event->Id();}, $ZeroSize)))
)
)
);
parseFilter($ZeroSize_filter);
$ZeroSize_filter = new ZM\Filter();
$ZeroSize_filter->addTerm(array('attr'=>'Id', 'op'=>'IN', 'val'=>implode(',', array_map(function($Event){return $Event->Id();}, $ZeroSize))));
}
?>
<tr id="<?php echo 'monitor_id-'.$monitor['Id'] ?>" title="<?php echo $monitor['Id'] ?>">
@ -202,24 +178,27 @@ for ( $monitor_i = 0; $monitor_i < count($displayMonitors); $monitor_i += 1 ) {
<div class="small text-nowrap text-muted">
<?php echo implode('<br/>',
array_map(function($group_id){
$Group = new ZM\Group($group_id);
$Group = ZM\Group::find_one(array('Id'=>$group_id));
if ( $Group ) {
$Groups = $Group->Parents();
array_push($Groups, $Group);
return implode(' &gt; ', array_map(function($Group){ return '<a href="?view=montagereview&GroupId='.$Group->Id().'">'.$Group->Name().'</a>'; }, $Groups ));
array_push( $Groups, $Group );
}
return implode(' &gt; ', array_map(function($Group){ return '<a href="?view=montagereview&amp;GroupId='.$Group->Id().'">'.validHtmlStr($Group->Name()).'</a>'; }, $Groups ));
}, $Monitor->GroupIds()));
?>
</div></td>
<td class="colServer"><?php echo validHtmlStr($Monitor->Server()->Name())?></td>
<td class="colEvents"><a href="?view=<?php echo ZM_WEB_EVENTS_VIEW ?>&amp;page=1<?php echo $monitor_filter['query'] ?>"><?php echo isset($EventsByMonitor[$Monitor->Id()])?count($EventsByMonitor[$Monitor->Id()]['Events']):0 ?></a></td>
<td class="colEvents"><a href="?view=<?php echo ZM_WEB_EVENTS_VIEW ?>&amp;page=1<?php echo $monitor_filter->querystring() ?>"><?php echo isset($EventsByMonitor[$Monitor->Id()])?count($EventsByMonitor[$Monitor->Id()]['Events']):0 ?></a></td>
<td class="colFirstEvent"><?php echo $FirstEvent ? $FirstEvent->link_to($FirstEvent->Id().' at '.$FirstEvent->StartDateTime()) : 'none'?></td>
<td class="colLastEvent"><?php echo $LastEvent ? $LastEvent->link_to($LastEvent->Id().' at '.$LastEvent->StartDateTime()) : 'none'?></td>
<td class="colMinGap"><?php echo $MinGap ?></td>
<td class="colMaxGap"><?php echo $MaxGap ?></td>
<td class="colFileMissing<?php echo count($FileMissing) ? ' errorText' : ''?>">
<?php echo count($FileMissing) ? '<a href="?view='.ZM_WEB_EVENTS_VIEW.'&amp;page=1'.$FileMissing_filter['query'].'">'.count($FileMissing).'</a>' : '0' ?>
<?php echo count($FileMissing) ? '<a href="?view='.ZM_WEB_EVENTS_VIEW.'&amp;page=1'.$FileMissing_filter->querystring().'">'.count($FileMissing).'</a>' : '0' ?>
</td>
<td class="colZeroSize<?php echo count($ZeroSize) ? ' errorText' : ''?>">
<?php echo count($ZeroSize) ? '<a href="?view='.ZM_WEB_EVENTS_VIEW.'&amp;page=1'.$ZeroSize_filter['query'].'">'.count($ZeroSize).'</a>' : '0' ?>
<?php echo count($ZeroSize) ? '<a href="?view='.ZM_WEB_EVENTS_VIEW.'&amp;page=1'.$ZeroSize_filter->querystring().'">'.count($ZeroSize).'</a>' : '0' ?>
</td>
</tr>
<?php

View File

@ -44,7 +44,9 @@ xhtmlHeaders(__FILE__, translate('Stats')." - ".$eid." - ".$fid );
<div id="content" class="row justify-content-center">
<form name="contentForm" id="contentForm" method="get" action="?">
<input type="hidden" name="view" value="none"/>
<div class="table-responsive-sm">
<?php echo getStatsTableHTML($eid, $fid) ?>
</div>
</form>
</div>
</div>

View File

@ -145,7 +145,7 @@ $tree = false;
if ( isset($_REQUEST['filter']) ) {
$filter = ZM\Filter::parse($_REQUEST['filter']);
$tree = $filter->tree();
ZM\Warning("Parse tree: " . print_r($tree,true));
ZM\Debug('Parse tree: ' . print_r($tree,true));
}
if ( isset($_REQUEST['range']) )

View File

@ -159,21 +159,43 @@ if ( $showPtzControls ) {
}
if ( canView('Events') && ($monitor->Type() != 'WebSite') ) {
?>
<div id="events">
<table id="eventList">
<!-- Table styling handled by bootstrap-tables -->
<div id="events" class="row justify-content-center table-responsive-sm">
<table
id="eventList"
data-locale="<?php echo i18n() ?>"
data-side-pagination="server"
data-ajax="ajaxRequest"
data-cookie="true"
data-cookie-id-table="zmEventListTable"
data-cookie-expire="2y"
data-show-columns="true"
data-show-export="true"
data-uncheckAll="true"
data-buttons-class="btn btn-normal"
data-show-refresh="true"
class="table-sm table-borderless"
>
<thead>
<!-- Row styling is handled by bootstrap-tables -->
<tr>
<th class="colId"><?php echo translate('Id') ?></th>
<th class="colName"><?php echo translate('Name') ?></th>
<th class="colTime"><?php echo translate('Time') ?></th>
<th class="colSecs"><?php echo translate('Secs') ?></th>
<th class="colFrames"><?php echo translate('Frames') ?></th>
<th class="colScore"><?php echo translate('Score') ?></th>
<th class="colDelete">&nbsp;</th>
<th data-sortable="false" data-field="Delete"><?php echo translate('Delete') ?></th>
<th data-sortable="false" data-field="Id"><?php echo translate('Id') ?></th>
<th data-sortable="false" data-field="Name"><?php echo translate('Name') ?></th>
<th data-sortable="false" data-field="StartDateTime"><?php echo translate('AttrStartTime') ?></th>
<th data-sortable="false" data-field="Length"><?php echo translate('Duration') ?></th>
<th data-sortable="false" data-field="Frames"><?php echo translate('Frames') ?></th>
<th data-sortable="false" data-field="AlarmFrames"><?php echo translate('AlarmBrFrames') ?></th>
<th data-sortable="false" data-field="AvgScore"><?php echo translate('AvgBrScore') ?></th>
<th data-sortable="false" data-field="MaxScore"><?php echo translate('MaxBrScore') ?></th>
<th data-sortable="false" data-field="Thumbnail"><?php echo translate('Thumbnail') ?></th>
</tr>
</thead>
<tbody>
<!-- Row data populated via Ajax -->
</tbody>
</table>
</div>
<?php

View File

@ -131,6 +131,7 @@ xhtmlHeaders(__FILE__, translate('Zone'));
</div>
<div id="content">
<form name="zoneForm" id="zoneForm" method="post" action="?">
<input type="hidden" name="REFERER" value="<?php echo $_SERVER['HTTP_REFERER'] ?>"/>
<input type="hidden" name="view" value="<?php echo $view ?>"/>
<input type="hidden" name="action" value="zone"/>
<input type="hidden" name="mid" value="<?php echo $mid ?>"/>

View File

@ -55,7 +55,8 @@ if ( $errorText ) {
die();
}
if ( ! ($fh = @fopen($path,'rb') ) ) {
if ( ! ($fh = @fopen($path, 'rb') ) ) {
ZM\Error('Can\'t open video at '.$path);
header('HTTP/1.0 404 Not Found');
die();
}

View File

@ -88,16 +88,19 @@ if [[ -n "$ZM_CONFIG" && ! -f "$ZM_CONFIG" ]]; then
fi
# Load zm.conf
if [ -n "$ZM_CONFIG" ]; then
echo "Using custom zm.conf $ZM_CONFIG"
source "$ZM_CONFIG"
elif [ -f "zm.conf" ]; then
echo "Using local zm.conf"
source "zm.conf"
elif [ -f "/etc/zm.conf" ]; then
echo "Using system zm.conf"
source "/etc/zm.conf"
else
for zmconf in "$ZM_CONFIG" ./zm.conf /etc/zm.conf /etc/zoneminder/zm.conf; do
if [[ -f "$zmconf" ]]; then
echo "Using $zmconf"
source "$zmconf"
# remove filename from path
zmconf2="${zmconf%/*}"
# source conf.d
for i in $(find "${zmconf2}/conf.d" -name \*.conf |sort); do . "$i"; done;
break
fi
done
if [[ -z "$zmconf2" ]]; then
echo -e "Failed locating zoneminder configuration file (zm.conf)\nUse the -z option to specify the full path to the zoneminder configuration file"
exit 45
fi