Merge branch 'master' into server_path_prefix

This commit is contained in:
Isaac Connor 2018-11-22 10:04:33 -05:00
commit c5f7fb7b18
199 changed files with 8128 additions and 4276 deletions

View File

@ -1,23 +1,43 @@
You should only file an issue if you found a bug. Feature and enhancement requests, general discussions and support questions should occur in one of the following areas: **THIS FORUM IS FOR BUG REPORTS ONLY**
Do not post feature or enhancement requests, general discussions or support questions here.
Feature and enhancement requests, general discussions, and support questions should occur in one of the following areas:
- The [ZoneMinder-Chat Slack channel](https://zoneminder-chat.herokuapp.com/) - The [ZoneMinder-Chat Slack channel](https://zoneminder-chat.herokuapp.com/)
- The [ZoneMinder Forum](https://forums.zoneminder.com/) - The [ZoneMinder Forum](https://forums.zoneminder.com/)
**Do not post feature or enhancement requests, general discussions or support questions here.**
Docker related issues should be posted here: https://github.com/ZoneMinder/zmdockerfiles Docker related issues should be posted here: https://github.com/ZoneMinder/zmdockerfiles
Make sure you are running the latest version of ZoneMinder before reporting an issue. In order to submit a bug report, please populate the fields below. This is required.
**ZoneMinder Version (`zmaudit.pl -v`):** **Describe Your Environment**
- Version of ZoneMinder [release version, development version, or commit]
- How you installed ZoneMinder [e.g. PPA, RPMFusion, from-source, etc]
- Full name and version of OS
**Are you using a development snapshot / git checkout? If so, what is the latest commit? (`git rev-parse HEAD`):** **If the issue concerns a camera**
- Make and Model
- frame rate
- resolution
- ZoneMinder Source Type:
**Linux Distribution and Version (`cat /etc/os-release` or `cat /etc/redhat-release`):** **Describe the bug**
A clear and concise description of what the bug is.
**If the issue concerns a camera, provide the make, model, frame rate, resolution and ZoneMinder Source Type:** **To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Relevant log lines:** **Expected behavior**
A clear and concise description of what you expected to happen.
**Debug Logs**
``` ```
log lines here
<insert debug logs here, please make sure they are within the ``` quotes so they are formatted properly>
``` ```

21
.github/config.yml vendored Normal file
View File

@ -0,0 +1,21 @@
# Configuration for welcome - https://github.com/behaviorbot/welcome
# Configuration for new-issue-welcome - https://github.com/behaviorbot/new-issue-welcome
# Comment to be posted to on first time issues
newIssueWelcomeComment: >
Thanks for opening your first issue here! Just a reminder, this forum is for Bug Reports only. Be sure to follow the issue template!
# Configuration for new-pr-welcome - https://github.com/behaviorbot/new-pr-welcome
# Comment to be posted to on PRs from first time contributors in your repository
#newPRWelcomeComment: >
# Thanks for opening this pull request! Please check out our contributing guidelines.
# Configuration for first-pr-merge - https://github.com/behaviorbot/first-pr-merge
# Comment to be posted to on pull requests merged by a first time user
#firstPRMergeComment: >
# Congrats on merging your first pull request! We here at behaviorbot are proud of you!
# It is recommend to include as many gifs and emojis as possible

13
.github/no-response.yml vendored Normal file
View File

@ -0,0 +1,13 @@
# Configuration for probot-no-response - https://github.com/probot/no-response
# Number of days of inactivity before an Issue is closed for lack of response
daysUntilClose: 7
# Label requiring a response
responseRequiredLabel: more-information-needed
# Comment to post when closing an Issue for lack of response. Set to `false` to disable
closeComment: >
This issue has been automatically closed because there has been no response
to our request for more information from the original author. With only the
information that is currently in the issue, we don't have enough information
to take action. Please reach out if you have or find the answers we need so
that we can investigate further.

View File

@ -38,6 +38,7 @@ env:
- OS=el DIST=7 - OS=el DIST=7
- OS=fedora DIST=27 DOCKER_REPO=knnniggett/packpack - OS=fedora DIST=27 DOCKER_REPO=knnniggett/packpack
- OS=fedora DIST=28 DOCKER_REPO=knnniggett/packpack - OS=fedora DIST=28 DOCKER_REPO=knnniggett/packpack
- OS=fedora DIST=29 DOCKER_REPO=knnniggett/packpack
- OS=ubuntu DIST=trusty - OS=ubuntu DIST=trusty
- OS=ubuntu DIST=xenial - OS=ubuntu DIST=xenial
- OS=ubuntu DIST=trusty ARCH=i386 - OS=ubuntu DIST=trusty ARCH=i386

View File

@ -42,7 +42,7 @@ This is the recommended method to install ZoneMinder onto your system. ZoneMinde
If a repository that hosts ZoneMinder packages is not available for your distro, then you are encouraged to build your own package, rather than build from source. While each distro is different in ways that set it apart from all the others, they are often similar enough to allow you to adapt another distro's package building instructions to your own. If a repository that hosts ZoneMinder packages is not available for your distro, then you are encouraged to build your own package, rather than build from source. While each distro is different in ways that set it apart from all the others, they are often similar enough to allow you to adapt another distro's package building instructions to your own.
### Building a ZoneMinder Package ### Building a ZoneMinder Package ###
Building ZoneMinder into a package is not any harder than building from source. As a matter of fact, if you have successfully built ZoneMinder from source in the past, then you may find these steps to be easier. Building ZoneMinder into a package is not any harder than building from source. As a matter of fact, if you have successfully built ZoneMinder from source in the past, then you may find these steps to be easier.
@ -53,9 +53,9 @@ Lastly, if you desire to build a development snapshot from the master branch, it
Please visit our [ReadtheDocs site](https://zoneminder.readthedocs.org/en/stable/installationguide/index.html) for distro specific instructions. Please visit our [ReadtheDocs site](https://zoneminder.readthedocs.org/en/stable/installationguide/index.html) for distro specific instructions.
### Package Maintainers ### Package Maintainers
Many of the ZoneMinder configration variable default values are not configurable at build time through autotools or cmake. A new tool called *zmeditconfigdata.sh* has been added to allow package maintainers to manipulate any variable stored in ConfigData.pm without patching the source. Many of the ZoneMinder configuration variable default values are not configurable at build time through autotools or cmake. A new tool called *zmeditconfigdata.sh* has been added to allow package maintainers to manipulate any variable stored in ConfigData.pm without patching the source.
For example, let's say I have created a new ZoneMinder package that contains the cambolzola javascript file. However, by default cambozola support is turned off. To fix that, add this to the pacakging script: For example, let's say I have created a new ZoneMinder package that contains the cambozola javascript file. However, by default cambozola support is turned off. To fix that, add this to the packaging script:
```bash ```bash
./utils/zmeditconfigdata.sh ZM_OPT_CAMBOZOLA yes ./utils/zmeditconfigdata.sh ZM_OPT_CAMBOZOLA yes
``` ```

View File

@ -556,7 +556,9 @@ INSERT INTO States (Name,Definition,IsActive) VALUES ('default','','1');
DROP TABLE IF EXISTS `Servers`; DROP TABLE IF EXISTS `Servers`;
CREATE TABLE `Servers` ( CREATE TABLE `Servers` (
`Id` int(10) unsigned NOT NULL auto_increment, `Id` int(10) unsigned NOT NULL auto_increment,
`Protocol` TEXT,
`Hostname` TEXT, `Hostname` TEXT,
`Port` INTEGER UNSIGNED,
`PathPrefix` TEXT, `PathPrefix` TEXT,
`Name` varchar(64) NOT NULL default '', `Name` varchar(64) NOT NULL default '',
`State_Id` int(10) unsigned, `State_Id` int(10) unsigned,
@ -782,6 +784,9 @@ INSERT INTO `Controls` VALUES (NULL,'D-LINK DCS-3415','Remote','DCS3415',0,0,0,1
INSERT INTO `Controls` VALUES (NULL,'IOS Camera','Ffmpeg','IPCAMIOS',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,1,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); INSERT INTO `Controls` VALUES (NULL,'IOS Camera','Ffmpeg','IPCAMIOS',0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,1,0,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0);
INSERT INTO `Controls` VALUES (NULL,'Dericam P2','Ffmpeg','DericamP2',0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,10,0,1,1,1,0,0,0,1,1,0,0,0,0,1,1,45,0,0,1,0,0,0,0,1,1,45,0,0,0,0); INSERT INTO `Controls` VALUES (NULL,'Dericam P2','Ffmpeg','DericamP2',0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,10,0,1,1,1,0,0,0,1,1,0,0,0,0,1,1,45,0,0,1,0,0,0,0,1,1,45,0,0,0,0);
INSERT INTO `Controls` VALUES (NULL,'Trendnet','Remote','Trendnet',1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0); INSERT INTO `Controls` VALUES (NULL,'Trendnet','Remote','Trendnet',1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,1,0,1,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0);
INSERT INTO `Controls` VALUES (NULL,'PSIA','Remote','PSIA',0,0,0,1,0,0,1,0,0,0,0,0,0,0,100,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,20,0,1,1,1,0,0,1,0,1,0,0,0,0,1,-100,100,0,0,1,0,0,0,0,1,-100,100,0,0,0,0);
INSERT INTO `Controls` VALUES (NULL,'Dahua','Remote','Dahua',0,0,0,1,0,0,1,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,20,0,1,1,1,0,0,1,0,1,0,0,0,0,1,1,8,0,0,1,0,0,0,0,1,1,8,0,0,0,0);
-- --
-- Add some monitor preset values -- Add some monitor preset values
-- --
@ -807,6 +812,7 @@ INSERT into MonitorPresets VALUES (NULL,'Axis IP, mpeg4, unicast','Remote','rtsp
INSERT into MonitorPresets VALUES (NULL,'Axis IP, mpeg4, multicast','Remote','rtsp',0,255,'rtsp','rtpMulti','<ip-address>',554,'/mpeg4/media.amp','/trackID=',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); INSERT into MonitorPresets VALUES (NULL,'Axis IP, mpeg4, multicast','Remote','rtsp',0,255,'rtsp','rtpMulti','<ip-address>',554,'/mpeg4/media.amp','/trackID=',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT into MonitorPresets VALUES (NULL,'Axis IP, mpeg4, RTP/RTSP','Remote','rtsp',0,255,'rtsp','rtpRtsp','<ip-address>',554,'/mpeg4/media.amp','/trackID=',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); INSERT into MonitorPresets VALUES (NULL,'Axis IP, mpeg4, RTP/RTSP','Remote','rtsp',0,255,'rtsp','rtpRtsp','<ip-address>',554,'/mpeg4/media.amp','/trackID=',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT into MonitorPresets VALUES (NULL,'Axis IP, mpeg4, RTP/RTSP/HTTP','Remote',NULL,NULL,NULL,'rtsp','rtpRtspHttp','<ip-address>',554,'/mpeg4/media.amp','/trackID=',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); INSERT into MonitorPresets VALUES (NULL,'Axis IP, mpeg4, RTP/RTSP/HTTP','Remote',NULL,NULL,NULL,'rtsp','rtpRtspHttp','<ip-address>',554,'/mpeg4/media.amp','/trackID=',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'D-link DCS-930L, 640x480, mjpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/mjpeg.cgi',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP, 320x240, mpjpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/nphMotionJpeg?Resolution=320x240&Quality=Standard',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100); INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP, 320x240, mpjpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/nphMotionJpeg?Resolution=320x240&Quality=Standard',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP, 320x240, jpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/SnapshotJPEG?Resolution=320x240&Quality=Standard',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100); INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP, 320x240, jpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/SnapshotJPEG?Resolution=320x240&Quality=Standard',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP, 320x240, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','<ip-address>',80,'/SnapshotJPEG?Resolution=320x240&Quality=Standard',NULL,320,240,3,5.0,0,NULL,NULL,NULL,100,100); INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP, 320x240, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','<ip-address>',80,'/SnapshotJPEG?Resolution=320x240&Quality=Standard',NULL,320,240,3,5.0,0,NULL,NULL,NULL,100,100);

View File

@ -5,7 +5,7 @@
-- Add Refresh column to Monitors table -- Add Refresh column to Monitors table
-- --
ALTER TABLE `zm`.`Monitors` ALTER TABLE `Monitors`
CHANGE COLUMN `Type` `Type` ENUM('Local', 'Remote', 'File', 'Ffmpeg', 'Libvlc', 'cURL', 'WebSite') NOT NULL DEFAULT 'Local' ; CHANGE COLUMN `Type` `Type` ENUM('Local', 'Remote', 'File', 'Ffmpeg', 'Libvlc', 'cURL', 'WebSite') NOT NULL DEFAULT 'Local' ;
SET @s = (SELECT IF( SET @s = (SELECT IF(

View File

@ -1,15 +1 @@
-- ALTER TABLE Frames MODIFY COLUMN EventId bigint unsigned NOT NULL;
-- Add Prefix column to Storage
--
SET @s = (SELECT IF(
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE()
AND table_name = 'Servers'
AND column_name = 'PathPrefix'
) > 0,
"SELECT 'Column PathPrefix already exists in Servers'",
"ALTER TABLE Servers ADD `PathPrefix` TEXT AFTER `Hostname`"
));
PREPARE stmt FROM @s;
EXECUTE stmt;

5
db/zm_update-1.32.0.sql Normal file
View File

@ -0,0 +1,5 @@
--
-- This updates a 1.31.47 database to 1.32.0
--
-- No changes required
--

5
db/zm_update-1.32.1.sql Normal file
View File

@ -0,0 +1,5 @@
--
-- This updates a 1.32.0 database to 1.32.1
--
-- No changes required
--

5
db/zm_update-1.32.2.sql Normal file
View File

@ -0,0 +1,5 @@
--
-- This updates a 1.32.1 database to 1.32.2
--
-- No changes required
--

57
db/zm_update-1.32.3.sql Normal file
View File

@ -0,0 +1,57 @@
--
-- This updates a 1.32.2 database to 1.32.3
--
--
-- Add some additional monitor preset values
--
INSERT INTO MonitorPresets VALUES (NULL,'D-link DCS-930L, 640x480, mjpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/mjpeg.cgi',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100);
--
-- Add Protocol column to Storage
--
SET @s = (SELECT IF(
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE()
AND table_name = 'Servers'
AND column_name = 'Protocol'
) > 0,
"SELECT 'Column Protocol already exists in Servers'",
"ALTER TABLE Servers ADD `Protocol` TEXT AFTER `Id`"
));
PREPARE stmt FROM @s;
EXECUTE stmt;
--
-- Add Prefix column to Storage
--
SET @s = (SELECT IF(
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE()
AND table_name = 'Servers'
AND column_name = 'PathPrefix'
) > 0,
"SELECT 'Column PathPrefix already exists in Servers'",
"ALTER TABLE Servers ADD `PathPrefix` TEXT AFTER `Hostname`"
));
PREPARE stmt FROM @s;
EXECUTE stmt;
--
-- Add Port column to Storage
--
SET @s = (SELECT IF(
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE()
AND table_name = 'Servers'
AND column_name = 'Port'
) > 0,
"SELECT 'Column Port already exists in Servers'",
"ALTER TABLE Servers ADD `Port` INTEGER UNSIGNED AFTER `Hostname`"
));
PREPARE stmt FROM @s;
EXECUTE stmt;

View File

@ -37,11 +37,13 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}
, libphp-serialization-perl , libphp-serialization-perl
, libdate-manip-perl, libmime-lite-perl, libmime-tools-perl, libdbd-mysql-perl , libdate-manip-perl, libmime-lite-perl, libmime-tools-perl, libdbd-mysql-perl
, libwww-perl, libarchive-tar-perl, libarchive-zip-perl, libdevice-serialport-perl , libwww-perl, libarchive-tar-perl, libarchive-zip-perl, libdevice-serialport-perl
, libmodule-load-perl, libsys-mmap-perl, libjson-any-perl , libmodule-load-perl, libsys-mmap-perl, libjson-any-perl, libjson-maybexs-perl
, libnet-sftp-foreign-perl, libio-pty-perl, libexpect-perl , libnet-sftp-foreign-perl, libio-pty-perl, libexpect-perl
, libdata-dump-perl, libclass-std-fast-perl, libsoap-wsdl-perl, libio-socket-multicast-perl, libdigest-sha-perl , libdata-dump-perl, libclass-std-fast-perl, libsoap-wsdl-perl, libio-socket-multicast-perl, libdigest-sha-perl
, libsys-cpu-perl, libsys-meminfo-perl , libsys-cpu-perl, libsys-meminfo-perl
, libdata-uuid-perl , libdata-uuid-perl
,libnumber-bytes-human-perl
,libfile-slurp-perl
, libpcre3 , libpcre3
, ffmpeg | libav-tools, libavdevice53 | libavdevice55 | libavdevice57 , ffmpeg | libav-tools, libavdevice53 | libavdevice55 | libavdevice57
, rsyslog | system-log-daemon , rsyslog | system-log-daemon

View File

@ -1,4 +1,9 @@
# CMakeLists.txt for the Redhat/CentOS Target Distro. # CMakeLists.txt for the Redhat Target Distros.
#
# General strategy is to configure and install all files specific to Apache and Nginx
# Then let the rpm specfile sort them into the appropriate sub-package
#
# Display a message to show the RHEL build options are being processed. # Display a message to show the RHEL build options are being processed.
if(ZM_TARGET_DISTRO MATCHES "^el") if(ZM_TARGET_DISTRO MATCHES "^el")
@ -9,28 +14,38 @@ else(ZM_TARGET_DISTRO MATCHES "^el")
message([WARNING] "Unknown Build Option Detected" ...) message([WARNING] "Unknown Build Option Detected" ...)
endif(ZM_TARGET_DISTRO MATCHES "^el") endif(ZM_TARGET_DISTRO MATCHES "^el")
if((NOT ZM_TARGET_DISTRO MATCHES "^fc") AND (ZM_WEB_USER STREQUAL "nginx")) #
message([FATAL_ERROR] "Experimental Nginx support is currently only supported on Fedora" ...) # CONFIGURE STAGE
endif((NOT ZM_TARGET_DISTRO MATCHES "^fc") AND (ZM_WEB_USER STREQUAL "nginx")) #
# Configure the zoneminder service files # Configure the common zoneminder files
configure_file(systemd/zoneminder.logrotate.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.logrotate @ONLY) configure_file(common/zoneminder.logrotate.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.logrotate @ONLY)
if(ZM_WEB_USER STREQUAL "nginx") configure_file(common/zoneminder.service.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.service @ONLY)
configure_file(nginx/zoneminder.service.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.service @ONLY)
configure_file(nginx/zoneminder.conf.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.conf @ONLY)
configure_file(nginx/zoneminder.tmpfiles.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.tmpfiles @ONLY)
configure_file(nginx/zoneminder.php-fpm.conf.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.php-fpm.conf @ONLY)
configure_file(nginx/README.Fedora ${CMAKE_CURRENT_SOURCE_DIR}/readme/README.Fedora COPYONLY)
else(ZM_WEB_USER STREQUAL "nginx")
configure_file(systemd/zoneminder.service.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.service @ONLY)
configure_file(apache/zoneminder.conf.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.conf @ONLY)
configure_file(systemd/zoneminder.tmpfiles.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.tmpfiles @ONLY)
endif(ZM_WEB_USER STREQUAL "nginx")
# Create several empty folders
file(MAKE_DIRECTORY sock swap zoneminder zoneminder-upload events images temp) file(MAKE_DIRECTORY sock swap zoneminder zoneminder-upload events images temp)
# Install the empty folders # Configure the Apache zoneminder files
configure_file(httpd/zm-httpd.conf.in ${CMAKE_CURRENT_SOURCE_DIR}/zm-httpd.conf @ONLY)
configure_file(httpd/zoneminder.httpd.conf.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.httpd.conf @ONLY)
configure_file(httpd/zoneminder.httpd.tmpfiles.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.httpd.tmpfiles.conf @ONLY)
configure_file(httpd/com.zoneminder.systemctl.rules.httpd.in ${CMAKE_CURRENT_SOURCE_DIR}/com.zoneminder.systemctl.rules.httpd @ONLY)
# Configure the Nginx zoneminder files
configure_file(nginx/zm-nginx.conf ${CMAKE_CURRENT_SOURCE_DIR}/zm-nginx.conf COPYONLY)
configure_file(nginx/zoneminder.nginx.conf.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.nginx.conf @ONLY)
configure_file(nginx/zoneminder.nginx.tmpfiles.in ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.nginx.tmpfiles.conf @ONLY)
configure_file(nginx/zm-web-user.conf ${CMAKE_CURRENT_SOURCE_DIR}/zm-web-user.conf COPYONLY)
configure_file(nginx/zoneminder.php-fpm.conf ${CMAKE_CURRENT_SOURCE_DIR}/zoneminder.php-fpm.conf COPYONLY)
configure_file(nginx/com.zoneminder.systemctl.rules.nginx ${CMAKE_CURRENT_SOURCE_DIR}/com.zoneminder.systemctl.rules.nginx COPYONLY)
#
# INSTALLATION STAGE
#
# Install the common zoneminder files
install(FILES zoneminder.logrotate DESTINATION /etc/logrotate.d RENAME zoneminder PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ)
install(FILES zoneminder.service DESTINATION /usr/lib/systemd/system PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ)
install(DIRECTORY sock swap DESTINATION /var/lib/zoneminder DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) install(DIRECTORY sock swap DESTINATION /var/lib/zoneminder DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
install(DIRECTORY zoneminder DESTINATION /var/log DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) install(DIRECTORY zoneminder DESTINATION /var/log DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
install(DIRECTORY zoneminder DESTINATION /var/run DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) install(DIRECTORY zoneminder DESTINATION /var/run DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
@ -38,6 +53,22 @@ install(DIRECTORY zoneminder DESTINATION /var/cache DIRECTORY_PERMISSIONS OWNER_
install(DIRECTORY zoneminder-upload DESTINATION /var/spool DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) install(DIRECTORY zoneminder-upload DESTINATION /var/spool DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
install(DIRECTORY events images temp DESTINATION /var/lib/zoneminder DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) install(DIRECTORY events images temp DESTINATION /var/lib/zoneminder DIRECTORY_PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
# Install the Apache zoneminder files
install(FILES zm-httpd.conf DESTINATION /usr/lib/systemd/system/zoneminder.service.d PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ)
install(FILES zoneminder.httpd.conf DESTINATION /etc/zm/www PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ)
install(FILES zoneminder.httpd.tmpfiles.conf DESTINATION /usr/lib/tmpfiles.d PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ)
install(FILES com.zoneminder.systemctl.rules.httpd DESTINATION /etc/zm/www PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ)
# Install the Nginx zoneminder files
install(FILES zm-nginx.conf DESTINATION /usr/lib/systemd/system/zoneminder.service.d PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ)
install(FILES zoneminder.nginx.conf DESTINATION /etc/zm/www PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ)
install(FILES zoneminder.nginx.tmpfiles.conf DESTINATION /usr/lib/tmpfiles.d PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ)
install(FILES com.zoneminder.systemctl.rules.nginx DESTINATION /etc/zm/www PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ)
install(FILES zm-web-user.conf DESTINATION /etc/zm/conf.d PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ)
install(FILES zoneminder.php-fpm.conf DESTINATION /etc/php-fpm.d PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ)
# Miscellaneous
# Symlink the cake php temp folder to the ZoneMinder temp folder # Symlink the cake php temp folder to the ZoneMinder temp folder
install(CODE "execute_process(COMMAND ln -sf ../../../../../../var/lib/zoneminder/temp \"\$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/zoneminder/www/api/app/tmp\")") install(CODE "execute_process(COMMAND ln -sf ../../../../../../var/lib/zoneminder/temp \"\$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/zoneminder/www/api/app/tmp\")")
@ -45,16 +76,5 @@ install(CODE "execute_process(COMMAND ln -sf ../../../../../../var/lib/zoneminde
install(CODE "execute_process(COMMAND ln -sf ../../java/cambozola.jar \"\$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/zoneminder/www/cambozola.jar\")") install(CODE "execute_process(COMMAND ln -sf ../../java/cambozola.jar \"\$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/zoneminder/www/cambozola.jar\")")
# Install auxiliary files # Install auxiliary files
install(FILES misc/redalert.wav DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/zoneminder/www/sounds PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) install(FILES common/redalert.wav DESTINATION ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATAROOTDIR}/zoneminder/www/sounds PERMISSIONS OWNER_WRITE OWNER_READ OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
# Install zoneminder service files
install(FILES zoneminder.logrotate DESTINATION /etc/logrotate.d RENAME zoneminder PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ)
install(FILES zoneminder.conf DESTINATION /etc/zm/www PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ)
if(ZM_WEB_USER STREQUAL "nginx")
install(FILES zoneminder.php-fpm.conf DESTINATION /etc/php-fpm.d PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ RENAME zoneminder.conf)
endif(ZM_WEB_USER STREQUAL "nginx")
install(FILES zoneminder.service DESTINATION /usr/lib/systemd/system PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ)
install(FILES zoneminder.tmpfiles DESTINATION /usr/lib/tmpfiles.d RENAME zoneminder.conf PERMISSIONS OWNER_WRITE OWNER_READ GROUP_READ WORLD_READ)

View File

@ -0,0 +1,12 @@
@ZM_LOGDIR@/*.log {
missingok
notifempty
sharedscripts
delaycompress
compress
postrotate
@BINDIR@/zmpkg.pl logrot > /dev/null 2>/dev/null || true
endscript
daily
rotate 7
}

View File

@ -1,13 +1,12 @@
# ZoneMinder systemd unit file for RedHat distros and clones # ZoneMinder systemd unit file for RedHat distros and clones
# See drop-in folder for additional config directives
[Unit] [Unit]
Description=ZoneMinder CCTV recording and security system Description=ZoneMinder CCTV recording and security system
After=network.target mariadb.service httpd.service After=network.target mariadb.service
Requires=mariadb.service httpd.service Requires=mariadb.service
[Service] [Service]
User=@WEB_USER@
Group=@WEB_GROUP@
Type=forking Type=forking
ExecStart=@BINDIR@/zmpkg.pl start ExecStart=@BINDIR@/zmpkg.pl start
ExecReload=@BINDIR@/zmpkg.pl restart ExecReload=@BINDIR@/zmpkg.pl restart

View File

@ -0,0 +1,7 @@
polkit.addRule(function(action, subject) {
if (action.id == "com.zoneminder.policykit.pkexec.run-zmsystemctl" &&
subject.user != "@WEB_USER@") {
return polkit.Result.NO;
}
});

View File

@ -0,0 +1,8 @@
# Additional config directives for ZoneMinder with Apache web server
[Unit]
After=httpd.service
[Service]
User=@WEB_USER@
Group=@WEB_GROUP@

View File

@ -3,3 +3,4 @@ D @ZM_SOCKDIR@ 0755 @WEB_USER@ @WEB_GROUP@
D @ZM_CACHEDIR@ 0755 @WEB_USER@ @WEB_GROUP@ D @ZM_CACHEDIR@ 0755 @WEB_USER@ @WEB_GROUP@
d @ZM_DIR_EVENTS@ 0755 @WEB_USER@ @WEB_GROUP@ d @ZM_DIR_EVENTS@ 0755 @WEB_USER@ @WEB_GROUP@
D @ZM_DIR_IMAGES@ 0755 @WEB_USER@ @WEB_GROUP@ D @ZM_DIR_IMAGES@ 0755 @WEB_USER@ @WEB_GROUP@

View File

@ -1,125 +0,0 @@
module local_zoneminder 1.2;
require {
type afs_ka_port_t;
type netsupport_port_t;
type port_t;
type presence_port_t;
type postfix_master_t;
type postfix_qmgr_t;
type postfix_pickup_t;
type httpd_t;
type var_lib_t;
type ionixnetmon_port_t;
type glance_port_t;
type mmcc_port_t;
type postfix_master_t;
type commplex_port_t;
type syslogd_port_t;
type dcc_port_t;
type sip_port_t;
type amqp_port_t;
type condor_port_t;
type afs_fs_port_t;
type nodejs_debug_port_t;
type httpd_var_lib_t;
type websm_port_t;
type afs_pt_port_t;
type postfix_qmgr_t;
type git_port_t;
type ipp_port_t;
type aol_port_t;
type unconfined_t;
type kernel_t;
type init_t;
type auditd_t;
type mysqld_t;
type httpd_log_t;
type syslogd_t;
type httpd_t;
type initrc_state_t;
type initrc_t;
type var_lib_t;
type udev_t;
type mysqld_safe_t;
type sshd_t;
type crond_t;
type getty_t;
type httpd_var_lib_t;
type initrc_var_run_t;
type tmpfs_t;
type dhcpc_t;
type v4l_device_t;
type file_t;
class sock_file { write create unlink };
class unix_stream_socket { read connectto };
class lnk_file { write create getattr read lock unlink };
class dir {search getattr };
class udp_socket name_bind;
class file { write getattr read lock unlink open };
class shm { unix_read unix_write associate read write getattr };
class chr_file getattr;
}
#============= httpd_t ==============
allow httpd_t auditd_t:dir { search getattr };
allow httpd_t auditd_t:file { read getattr open };
allow httpd_t crond_t:dir { search getattr };
allow httpd_t crond_t:file { read getattr open };
allow httpd_t dhcpc_t:dir { search getattr };
allow httpd_t dhcpc_t:file { read getattr open };
allow httpd_t getty_t:dir { search getattr };
allow httpd_t getty_t:file { read getattr open };
allow httpd_t httpd_log_t:file write;
allow httpd_t httpd_var_lib_t:lnk_file { write getattr read lock unlink };
allow httpd_t init_t:dir { search getattr };
allow httpd_t init_t:file { read getattr open };
#!!!! The source type 'httpd_t' can write to a 'file' of the following types:
#squirrelmail_spool_t, mirrormanager_var_run_t, dirsrvadmin_config_t, httpd_lock_t, httpd_tmp_t, dirsrv_config_t, dirsrvadmin_tmp_t, httpd_cache_t, httpd_tmpfs_t, httpd_squirrelmail_t, dirsrv_var_run_t, dirsrv_var_log_t, httpd_var_lib_t, httpd_var_run_t, zarafa_var_lib_t, httpd_prewikka_rw_content_t, httpd_mediawiki_rw_content_t, httpd_squid_rw_content_t, passenger_var_run_t, httpd_smokeping_cgi_rw_content_t, httpd_openshift_rw_content_t, httpd_dirsrvadmin_rw_content_t, httpd_w3c_validator_rw_content_t, httpd_collectd_rw_content_t, cluster_var_lib_t, cluster_var_run_t, httpd_user_rw_content_t, httpd_awstats_rw_content_t, httpdcontent, root_t, httpd_cobbler_rw_content_t, httpd_munin_rw_content_t, cluster_conf_t, httpd_bugzilla_rw_content_t, passenger_tmp_t, httpd_cvs_rw_content_t, httpd_git_rw_content_t, httpd_sys_rw_content_t, httpd_sys_rw_content_t, httpd_nagios_rw_content_t, httpd_apcupsd_cgi_rw_content_t, httpd_nutups_cgi_rw_content_t, httpd_dspam_rw_content_t
allow httpd_t initrc_state_t:file { read write getattr unlink open };
allow httpd_t initrc_t:unix_stream_socket connectto;
allow httpd_t initrc_t:shm { unix_read unix_write associate read write getattr };
allow httpd_t initrc_var_run_t:file { write read lock open };
allow httpd_t kernel_t:dir { search getattr };
allow httpd_t kernel_t:file { read getattr open };
allow httpd_t mysqld_safe_t:dir { search getattr };
allow httpd_t mysqld_safe_t:file { read getattr open };
allow httpd_t mysqld_t:dir { search getattr };
allow httpd_t mysqld_t:file { read getattr open };
allow httpd_t sshd_t:dir { search getattr };
allow httpd_t sshd_t:file { read getattr open };
allow httpd_t syslogd_t:dir { search getattr };
allow httpd_t syslogd_t:file { read getattr open };
allow httpd_t tmpfs_t:sock_file write;
allow httpd_t udev_t:dir { search getattr };
allow httpd_t udev_t:file { read getattr open };
allow httpd_t unconfined_t:dir { search getattr };
allow httpd_t unconfined_t:file { read getattr open };
allow httpd_t var_lib_t:lnk_file { write getattr read lock unlink };
allow httpd_t var_lib_t:sock_file { write unlink };
allow httpd_t v4l_device_t:chr_file getattr;
allow httpd_t afs_fs_port_t:udp_socket name_bind;
allow httpd_t afs_ka_port_t:udp_socket name_bind;
allow httpd_t afs_pt_port_t:udp_socket name_bind;
allow httpd_t amqp_port_t:udp_socket name_bind;
allow httpd_t aol_port_t:udp_socket name_bind;
allow httpd_t commplex_port_t:udp_socket name_bind;
allow httpd_t condor_port_t:udp_socket name_bind;
allow httpd_t dcc_port_t:udp_socket name_bind;
allow httpd_t git_port_t:udp_socket name_bind;
allow httpd_t glance_port_t:udp_socket name_bind;
allow httpd_t httpd_var_lib_t:lnk_file create;
allow httpd_t ionixnetmon_port_t:udp_socket name_bind;
allow httpd_t ipp_port_t:udp_socket name_bind;
allow httpd_t mmcc_port_t:udp_socket name_bind;
allow httpd_t netsupport_port_t:udp_socket name_bind;
allow httpd_t nodejs_debug_port_t:udp_socket name_bind;
allow httpd_t port_t:udp_socket name_bind;
allow httpd_t postfix_master_t:dir { search getattr };
allow httpd_t postfix_master_t:file { read getattr open };
allow httpd_t postfix_pickup_t:dir { search getattr };
allow httpd_t postfix_pickup_t:file { read getattr open };
allow httpd_t postfix_qmgr_t:dir { search getattr };
allow httpd_t postfix_qmgr_t:file { read getattr open };
allow httpd_t presence_port_t:udp_socket name_bind;

View File

@ -1,165 +0,0 @@
What's New
==========
1. This is an *experimental* build of zoneminder which uses the
nginx web server.
2. The Apache ScriptAlias has been changed from "/cgi-bin/zm/zms" to
"/cgi-bin-zm/zms". This has been to done to avoid this bug:
https://bugzilla.redhat.com/show_bug.cgi?id=973067
IMPORTANT: You must manually inspect the value for PATH_ZMS under Options
and verify it is set to "/cgi-bin-zm/nph-zms". Failure to do so will result
in a broken system. You have been warned.
3. Due to the active state of the ZoneMinder project, we now recommend granting
ALL permission to the ZoneMinder mysql account. This change must be done
manually before ZoneMinder will run. See the installation steps below.
4. This package uses the HTTPS protocol by default to access the web portal.
Requests using HTTP will auto-redirect to HTTPS. See README.https for
more information.
5. This package ships with the new ZoneMinder API enabled.
New installs
============
1. This package supports either community-mysql-server or mariadb-server with
mariadb being the preferred choice. Unless you are already using MariaDB or
Mysql server, you need to ensure that the server is configured to start
during boot and properly secured by running:
sudo dnf install mariadb-server
sudo systemctl enable mariadb
sudo systemctl start mariadb.service
mysql_secure_installation
2. Assuming the database is local and using the password for the root account
set during the previous step, you will need to create the ZoneMinder
database and configure a database account for ZoneMinder to use:
mysql -uroot -p < /usr/share/zoneminder/db/zm_create.sql
mysql -uroot -p -e "grant all on zm.* to \
'zmuser'@localhost identified by 'zmpass';"
mysqladmin -uroot -p reload
The database account credentials, zmuser/zmpass, are arbitrary. Set them to
anything that suits your environment.
3. If you have chosen to change the zoneminder database account credentials to
something other than zmuser/zmpass, you must now edit /etc/zm/zm.conf.
Change ZM_DB_USER and ZM_DB_PASS to the values you created in the previous
step.
This version of zoneminder no longer requires you to make a similar change
to the credentials in /usr/share/zoneminder/www/api/app/Config/database.php
This now happens dynamically. Do *not* make any changes to this file.
4. Edit /etc/php.ini, uncomment the date.timezone line, and add your local
timezone. PHP will complain loudly if this is not set, or if it is set
incorrectly, and these complaints will show up in the zoneminder logging
system as errors.
If you are not sure of the proper timezone specification to use, look at
http://php.net/date.timezone
5. Disable SELinux
We currently do not have the resources to create and maintain an accurate
SELinux policy for ZoneMinder on Fedora. We will gladly accept pull
reqeusts from anyone who wishes to do the work. In the meantime, SELinux
will need to be disabled or put into permissive mode.
To immediately disbale SELinux for the current seesion, issue the following
from the command line:
sudo setenforce 0
To permanently disable SELinux, edit /etc/selinux/config and change the
SELINUX line from "enforcing" to "disabled". This change will take
effect after a reboot.
6. This package comes preconfigured for HTTPS using the default self signed
certificate on your system. We recommend you keep this configuration.
If this does not meet your needs, then read README.https to
learn about alternatives.
7. Edit /etc/sysconfig/fcgiwrap and set DAEMON_PROCS to the maximum number of
simulatneous streams the server should support. Generally, a good minimum
value for this equals the total number of cameras you expect to view at the
same time.
8. Now start the web server:
sudo systemctl enable nginx
sudo systemctl start nginx
9. Now start zoneminder:
sudo systemctl enable zoneminder
sudo systemctl start zoneminder
10.The Fedora repos have a ZoneMinder package available, but it does not
support ffmpeg or libvlc, which many modern IP cameras require. Most users
will want to prevent the ZoneMinder package in the Fedora repos from
overwriting the ZoneMinder package in zmrepo, during a future dnf update. To
prevent that from happening you must edit /etc/yum.repos.d/fedora.repo
and /etc/yum.repos.d/fedora-updates.repo. Add the line "exclude=zoneminder*"
without the quotes under the [fedora] and [fedora-updates] blocks,
respectively.
Upgrades
========
1. Verify /etc/zm/zm.conf.
If zm.conf was manually edited before running the upgrade, the installation
may not overwrite it. In this case, it will create the file
/etc/zm/zm.conf.rpmnew.
For example, this will happen if you are using database account credentials
other than zmuser/zmpass.
Compare /etc/zm/zm.conf to /etc/zm/zm.conf.rpmnew. Verify that zm.conf
contains any new config settings that may be in zm.conf.rpmnew.
This version of zoneminder no longer requires you to make a similar change
to the credentials in /usr/share/zoneminder/www/api/app/Config/database.php
This now happens dynamically. Do *not* make any changes to this file.
2. Verify permissions of the zmuser account.
Over time, the database account permissions required for normal operation
have increased. Verify the zmuser database account has been granted all
permission to the ZoneMinder database:
mysql -uroot -p -e "show grants for zmuser@localhost;"
See step 2 of the Installation section to add missing permissions.
3. Verify the ZoneMinder Apache configuration file in the folder
/etc/httpd/conf.d. You will have a file called "zoneminder.conf" and there
may also be a file called "zoneminder.conf.rpmnew". If the rpmnew file
exists, inspect it and merge anything new in that file with zoneminder.conf.
Verify the SSL REquirements meet your needs. Read README.https if necessary.
4. Upgrade the database before starting ZoneMinder.
Most upgrades can be performed by executing the following command:
sudo zmupdate.pl
Recent versions of ZoneMinder don't require any parameters added to the
zmupdate command. However, if ZoneMinder complains, you may need to call
zmupdate in the following manner:
sudo zmupdate.pl --user=root --pass=<mysql_root_pwd> --version=<from version>
5. Now restart nginx and php-fpm then start and zoneminder:
sudo systemctl restart nginx
sudo systemctl restart php-fpm
sudo systemctl start zoneminder

View File

@ -0,0 +1,7 @@
polkit.addRule(function(action, subject) {
if (action.id == "com.zoneminder.policykit.pkexec.run-zmsystemctl" &&
subject.user != "nginx") {
return polkit.Result.NO;
}
});

View File

@ -0,0 +1,9 @@
# Additional config directives for ZoneMinder with Nginx web server
[Unit]
After=nginx.service php-fpm.service fcgiwrap.service
Requires=php-fpm.service fcgiwrap@nginx.service
[Service]
User=nginx
Group=nginx

View File

@ -0,0 +1,3 @@
ZM_WEB_USER=nginx
ZM_WEB_GROUP=nginx

View File

@ -22,6 +22,10 @@ location /cgi-bin-zm {
fastcgi_pass unix:/run/fcgiwrap.sock; fastcgi_pass unix:/run/fcgiwrap.sock;
} }
location /zm/cache {
alias "@ZM_CACHEDIR@";
}
location /zm { location /zm {
gzip off; gzip off;
alias "@ZM_WEBDIR@"; alias "@ZM_WEBDIR@";
@ -43,7 +47,7 @@ location /zm {
location /zm/api/ { location /zm/api/ {
alias "@ZM_WEBDIR@"; alias "@ZM_WEBDIR@";
rewrite ^/zm/api(.+)$ /zm/api/index.php?p=$1 last; rewrite ^/zm/api(.+)$ /zm/api/app/webroot/index.php?p=$1 last;
} }
} }

View File

@ -0,0 +1,6 @@
D @ZM_TMPDIR@ 0755 nginx nginx
D @ZM_SOCKDIR@ 0755 nginx nginx
D @ZM_CACHEDIR@ 0755 nginx nginx
d @ZM_DIR_EVENTS@ 0755 nginx nginx
D @ZM_DIR_IMAGES@ 0755 nginx nginx

View File

@ -0,0 +1,14 @@
; This config file is needed when using ZoneMinder with web servers other
; than Apache. You can ignore this file if you are using Apache web server.
; Change the user and group of the default pool to the web server account
[www]
user = nginx
group = nginx
; These parameters are typically a tradoff between performance and memory
; consumption. See the contents of www.conf for details.
pm = ondemand
pm.max_children = 50
pm.process_idle_timeout = 10s

View File

@ -1,10 +0,0 @@
# Change the user and group of the default pool to the web server account
[www]
user = @WEB_USER@
group = @WEB_GROUP@
# Uncomment these on machines with little memory
#pm = ondemand
#pm.max_children = 10
#pm.process_idle_timeout = 10s

View File

@ -1,22 +0,0 @@
# ZoneMinder systemd unit file for Fedora
# Replace mariadb with community-mysql if using mysql service instead of mariadb
[Unit]
Description=ZoneMinder CCTV recording and security system
After=network.target mariadb.service nginx.service php-fpm.service fcgiwrap.service
Requires=mariadb.service nginx.service php-fpm.service fcgiwrap.service
[Service]
User=@WEB_USER@
Group=@WEB_GROUP@
Type=forking
ExecStart=@BINDIR@/zmpkg.pl start
ExecReload=@BINDIR@/zmpkg.pl restart
ExecStop=@BINDIR@/zmpkg.pl stop
PIDFile=@ZM_RUNDIR@/zm.pid
Environment=TZ=/etc/localtime
RuntimeDirectory=zoneminder
RuntimeDirectoryMode=0755
[Install]
WantedBy=multi-user.target

View File

@ -1,5 +0,0 @@
D @ZM_TMPDIR@ 0755 @WEB_USER@ @WEB_GROUP@
D @ZM_SOCKDIR@ 0755 @WEB_USER@ @WEB_GROUP@
D /var/lib/php/session 770 root @WEB_GROUP@
D /var/lib/php/wsdlcache 770 root @WEB_GROUP@

View File

@ -0,0 +1,37 @@
What's New
==========
1. See the ZoneMinder release notes for a list of new features:
https://github.com/ZoneMinder/zoneminder/releases
2. The contents of the ZoneMinder Apache config file have changed. In
addition, this ZoneMinder package now requires you to manually symlink the
ZoneMinder Apache config file. See new install step 6 and upgrade step 3
below for details.
3. This package has been split into sub-packages to allow compatibility with
other web servers. Here is a breakdown of the available packages:
zoneminder - Meta-package installs zoneminder-common and zoneminder-httpd
This exists soley for backwards compatibility.
zoneminder-common - Common files that do not differ based on the web server
zoneminder-httpd - Files needed for compatibility with the Apache web server
zoneminder-nginx - Files needed for compatibility with the Nginx web server
You can switch between different subpackages with dnf/yum. Be advised that,
if you modified any of the default config files supplied by the package,
rpm may not update the config file to the proper version. This is by design.
To avoid this issue, use drop-in files instead.
4. If you have installed ZoneMinder from the FedBerry repositories, this build
of ZoneMinder has support for Raspberry Pi hardware acceleration when using
ffmpeg. Unforunately, there is a problem with the same hardware acceleration
when using libvlc. Consequently, libvlc support in this build of ZoneMinder
has been disabled until the problem is resolved. See the following bug
report for details: https://trac.videolan.org/vlc/ticket/18594
5. Continue on to the next README that corresponds to the chosen webserver:
README.httpd - Follow these steps when using Apache
README.nginx - Follow these steps when using Nginx

View File

@ -1,17 +1,8 @@
What's New
==========
1. See the ZoneMinder release notes for a list of new features:
https://github.com/ZoneMinder/zoneminder/releases
2. The contents of the ZoneMinder Apache config file have changed. In
addition, this ZoneMinder package now requires you to manually symlink the
ZoneMinder Apache config file. See new install step 6 and upgrade step 3
below for details.
New installs New installs
============ ============
NOTE: EL7 users should replace "dnf" with "yum" in the instructions below.
1. Unless you are already using MariaDB server, you need to ensure that the 1. Unless you are already using MariaDB server, you need to ensure that the
server is configured to start during boot and properly secured by running: server is configured to start during boot and properly secured by running:
@ -72,19 +63,19 @@ New installs
6. Configure the web server 6. Configure the web server
This package uses the HTTPS protocol by default to access the web portal, This package uses the HTTPS protocol by default to access the web portal,
using rhe default self signed certificate on your system. Requests using using the default self signed certificate on your system. Requests using
HTTP will auto-redirect to HTTPS. HTTP will auto-redirect to HTTPS.
Inspect the web server configuration file and verify it meets your needs: Inspect the web server configuration file and verify it meets your needs:
/etc/zm/www/zoneminder.conf /etc/zm/www/zoneminder.httpd.conf
If you are running other web enabled services then you may need to edit If you are running other web enabled services then you may need to edit
this file to suite. See README.https to learn about other alternatives. this file to suite. See README.https to learn about other alternatives.
When in doubt, proceed with the default: When in doubt, proceed with the default:
sudo ln -s /etc/zm/www/zoneminder.conf /etc/httpd/conf.d/ sudo ln -sf /etc/zm/www/zoneminder.httpd.conf /etc/httpd/conf.d/
sudo dnf install mod_ssl sudo dnf install mod_ssl
7. Now start the web server: 7. Now start the web server:
@ -129,7 +120,7 @@ New installs
Upgrades Upgrades
======== ========
1. Conf.d folder support has been added to ZoneMinder 1.31.0. Any custom 1. Conf.d folder support has been added to ZoneMinder. Any custom
changes previously made to zm.conf must now be made in one or more custom changes previously made to zm.conf must now be made in one or more custom
config files, created under the conf.d folder. Do this now. See config files, created under the conf.d folder. Do this now. See
/etc/zm/conf.d/README for details. Once you recreate any custom config changes /etc/zm/conf.d/README for details. Once you recreate any custom config changes
@ -146,10 +137,17 @@ Upgrades
See step 2 of the Installation section to add missing permissions. See step 2 of the Installation section to add missing permissions.
3. Verify the ZoneMinder Apache configuration file in the folder 3. Verify the ZoneMinder Apache configuration file in the folder
/etc/zm/www. You will have a file called "zoneminder.conf" and there /etc/zm/www. You will have a file called "zoneminder.httpd.conf" and there
may also be a file called "zoneminder.conf.rpmnew". If the rpmnew file may also be one or more files with "rpmnew" extenstion. If the rpmnew file
exists, inspect it and merge anything new in that file with zoneminder.conf. exists, inspect it and merge anything new in that file with zoneminder.conf.
Verify the SSL REquirements meet your needs. Read README.https if necessary. Verify the SSL Requirements meet your needs. Read README.https if necessary.
The contents of this file must be merged into your Apache configuration.
See step 6 of the installation section if you have not already done this
during a previous upgrade.
IMPORTANT: Failure to complete this step properly will result in a mostly
empty or significantly corrupted web console post-upgrade.
4. Upgrade the database before starting ZoneMinder. 4. Upgrade the database before starting ZoneMinder.

View File

@ -20,7 +20,8 @@ experience.
to do this: https://wiki.centos.org/HowTos/Https . Additionally, Googling to do this: https://wiki.centos.org/HowTos/Https . Additionally, Googling
"centos certificate" reveals many articles on the subject. "centos certificate" reveals many articles on the subject.
3. You can turn off HTTPS entirely by simply commenting out the SSLRequireSSL 3. When using Apache, you can turn off HTTPS entirely by simply commenting
directives found in /etc/httpd/conf.d/zoneminder.conf. You should also out the SSLRequireSSL directives found in
comment out the HTTP -> HTTPS Rewrite rule. /etc/zm/www/zoneminder.apache.conf. You should also comment out the
HTTP -> HTTPS Rewrite rule.

View File

@ -1,28 +1,17 @@
What's New
==========
1. See the ZoneMinder release notes for a list of new features:
https://github.com/ZoneMinder/zoneminder/releases
2. The contents of the ZoneMinder Apache config file have changed. In
addition, this ZoneMinder package now requires you to manually symlink the
ZoneMinder Apache config file. See new install step 6 and upgrade step 3
below for details.
New installs New installs
============ ============
1. Unless you are already using MariaDB server, you need to ensure that the 1. Unless you are already using MariaDB server, you need to ensure that the
server is configured to start during boot and properly secured by running: server is configured to start during boot and properly secured by running:
sudo yum install mariadb-server sudo dnf install mariadb-server
sudo systemctl enable mariadb sudo systemctl enable mariadb
sudo systemctl start mariadb.service sudo systemctl start mariadb.service
mysql_secure_installation mysql_secure_installation
2. Using the password for the root account set during the previous step, you 2. Assuming the database is local and using the password for the root account
will need to create the ZoneMinder database and configure a database set during the previous step, you will need to create the ZoneMinder
account for ZoneMinder to use: database and configure a database account for ZoneMinder to use:
mysql -uroot -p < /usr/share/zoneminder/db/zm_create.sql mysql -uroot -p < /usr/share/zoneminder/db/zm_create.sql
mysql -uroot -p -e "grant all on zm.* to \ mysql -uroot -p -e "grant all on zm.* to \
@ -42,7 +31,7 @@ New installs
Once the file has been saved, set proper file & ownership permissions on it: Once the file has been saved, set proper file & ownership permissions on it:
sudo chown root:apache *.conf sudo chown root:nginx *.conf
sudo chmod 640 *.conf sudo chmod 640 *.conf
4. Edit /etc/php.ini, uncomment the date.timezone line, and add your local 4. Edit /etc/php.ini, uncomment the date.timezone line, and add your local
@ -56,7 +45,7 @@ New installs
5. Disable SELinux 5. Disable SELinux
We currently do not have the resources to create and maintain an accurate We currently do not have the resources to create and maintain an accurate
SELinux policy for ZoneMinder on CentOS 7. We will gladly accept pull SELinux policy for ZoneMinder on Fedora. We will gladly accept pull
reqeusts from anyone who wishes to do the work. In the meantime, SELinux reqeusts from anyone who wishes to do the work. In the meantime, SELinux
will need to be disabled or put into permissive mode. will need to be disabled or put into permissive mode.
@ -72,7 +61,7 @@ New installs
6. Configure the web server 6. Configure the web server
This package uses the HTTPS protocol by default to access the web portal, This package uses the HTTPS protocol by default to access the web portal,
using rhe default self signed certificate on your system. Requests using using the default self signed certificate on your system. Requests using
HTTP will auto-redirect to HTTPS. HTTP will auto-redirect to HTTPS.
Inspect the web server configuration file and verify it meets your needs: Inspect the web server configuration file and verify it meets your needs:
@ -84,20 +73,24 @@ New installs
When in doubt, proceed with the default: When in doubt, proceed with the default:
sudo ln -s /etc/zm/www/zoneminder.conf /etc/httpd/conf.d/ sudo ln -sf /etc/zm/www/zoneminder.nginx.conf /etc/nginx/default.d/
sudo yum install mod_ssl
7. Now start the web server: 7. Edit /etc/sysconfig/fcgiwrap and set DAEMON_PROCS to the maximum number of
simulatneous streams the server should support. Generally, a good minimum
value for this equals the total number of cameras you expect to view at the
same time.
sudo systemctl enable httpd 8. Now start the web server:
sudo systemctl start httpd
8. Now start zoneminder: sudo systemctl enable nginx
sudo systemctl start nginx
9. Now start zoneminder:
sudo systemctl enable zoneminder sudo systemctl enable zoneminder
sudo systemctl start zoneminder sudo systemctl start zoneminder
9. Optionally configure the firewall 10. Optionally configure the firewall
All Redhat distros ship with the firewall enabled. That means you will not All Redhat distros ship with the firewall enabled. That means you will not
be able to access the ZoneMinder web console from a remote machine until be able to access the ZoneMinder web console from a remote machine until
@ -117,7 +110,7 @@ New installs
security requirements and how you use the system. It is up to you to verify security requirements and how you use the system. It is up to you to verify
these commands are sufficient. these commands are sufficient.
10. Access the ZoneMinder web console 11. Access the ZoneMinder web console
You may now access the ZoneMinder web console from your web browser using You may now access the ZoneMinder web console from your web browser using
an appropriate url. Here are some examples: an appropriate url. Here are some examples:
@ -129,7 +122,7 @@ New installs
Upgrades Upgrades
======== ========
1. Conf.d folder support has been added to ZoneMinder 1.31.0. Any custom 1. Conf.d folder support has been added to ZoneMinder. Any custom
changes previously made to zm.conf must now be made in one or more custom changes previously made to zm.conf must now be made in one or more custom
config files, created under the conf.d folder. Do this now. See config files, created under the conf.d folder. Do this now. See
/etc/zm/conf.d/README for details. Once you recreate any custom config changes /etc/zm/conf.d/README for details. Once you recreate any custom config changes
@ -145,12 +138,16 @@ Upgrades
See step 2 of the Installation section to add missing permissions. See step 2 of the Installation section to add missing permissions.
3. Verify the ZoneMinder Apache configuration file in the folder 3. Verify the ZoneMinder Nginx configuration file in the folder
/etc/zm/www. You will have a file called "zoneminder.conf" and there /etc/zm/www. You will have a file called "zoneminder.conf" and there
may also be a file called "zoneminder.conf.rpmnew". If the rpmnew file may also be a file called "zoneminder.conf.rpmnew". If the rpmnew file
exists, inspect it and merge anything new in that file with zoneminder.conf. exists, inspect it and merge anything new in that file with zoneminder.conf.
Verify the SSL REquirements meet your needs. Read README.https if necessary. Verify the SSL REquirements meet your needs. Read README.https if necessary.
The contents of this file must be merged into your Nginx configuration.
See step 6 of the installation section if you have not already done this
during a previous upgrade.
4. Upgrade the database before starting ZoneMinder. 4. Upgrade the database before starting ZoneMinder.
Most upgrades can be performed by executing the following command: Most upgrades can be performed by executing the following command:
@ -163,9 +160,9 @@ Upgrades
sudo zmupdate.pl --user=root --pass=<mysql_root_pwd> --version=<from version> sudo zmupdate.pl --user=root --pass=<mysql_root_pwd> --version=<from version>
5. Now restart the web server then start zoneminder: 5. Now restart nginx and php-fpm then start zoneminder:
sudo systemctl restart httpd sudo systemctl restart nginx
sudo systemctl restart php-fpm
sudo systemctl start zoneminder sudo systemctl start zoneminder

View File

@ -1,8 +0,0 @@
@ZM_LOGDIR@/*.log {
missingok
notifempty
sharedscripts
postrotate
@BINDIR@/zmpkg.pl logrot 2> /dev/null > /dev/null || :
endscript
}

View File

@ -1,3 +1,4 @@
# Leaving this to allow one to build zoneminder-http subpackage using arbitrary user account
%global zmuid_final apache %global zmuid_final apache
%global zmgid_final apache %global zmgid_final apache
@ -7,10 +8,6 @@
# CakePHP-Enum-Behavior is configured as a git submodule # CakePHP-Enum-Behavior is configured as a git submodule
%global ceb_version 1.0-zm %global ceb_version 1.0-zm
%if "%{zmuid_final}" == "nginx"
%global with_nginx 1
%endif
%global sslcert %{_sysconfdir}/pki/tls/certs/localhost.crt %global sslcert %{_sysconfdir}/pki/tls/certs/localhost.crt
%global sslkey %{_sysconfdir}/pki/tls/private/localhost.key %global sslkey %{_sysconfdir}/pki/tls/private/localhost.key
@ -22,12 +19,12 @@
%global with_apcu_bc 1 %global with_apcu_bc 1
%endif %endif
%global readme_suffix %{?rhel:Redhat%{?rhel}}%{!?rhel:Fedora} # The default for everything but el7 these days
%global _hardened_build 1 %global _hardened_build 1
Name: zoneminder Name: zoneminder
Version: 1.31.45 Version: 1.32.2
Release: 1%{?dist} Release: 2%{?dist}
Summary: A camera monitoring and analysis tool Summary: A camera monitoring and analysis tool
Group: System Environment/Daemons Group: System Environment/Daemons
# Mootools is inder the MIT license: http://mootools.net/ # Mootools is inder the MIT license: http://mootools.net/
@ -75,6 +72,7 @@ BuildRequires: vlc-devel
BuildRequires: libcurl-devel BuildRequires: libcurl-devel
BuildRequires: libv4l-devel BuildRequires: libv4l-devel
BuildRequires: desktop-file-utils BuildRequires: desktop-file-utils
BuildRequires: gzip
# ZoneMinder looks for and records the location of the ffmpeg binary during build # ZoneMinder looks for and records the location of the ffmpeg binary during build
BuildRequires: ffmpeg BuildRequires: ffmpeg
@ -84,11 +82,25 @@ BuildRequires: ffmpeg-devel
BuildRequires: libmp4v2-devel BuildRequires: libmp4v2-devel
BuildRequires: x264-devel BuildRequires: x264-devel
%{?with_nginx:Requires: nginx} # Allow existing user base to seamlessly transition to sub-packages
%{?with_nginx:Requires: fcgiwrap} Requires: %{name}-common%{?_isa} = %{version}-%{release}
%{?with_nginx:Requires: php-fpm} Requires: %{name}-httpd%{?_isa} = %{version}-%{release}
%{!?with_nginx:Requires: httpd}
%{!?with_nginx:Requires: php} %description
ZoneMinder is a set of applications which is intended to provide a complete
solution allowing you to capture, analyze, record and monitor any cameras you
have attached to a Linux based machine. It is designed to run on kernels which
support the Video For Linux (V4L) interface and has been tested with cameras
attached to BTTV cards, various USB cameras and IP network cameras. It is
designed to support as many cameras as you can attach to your computer without
too much degradation of performance.
This is a meta package for backwards compatibility with the existing
ZoneMinder user base.
%package common
Summary: Common files for ZoneMinder, not tied to a specific web server
Requires: php-mysqli Requires: php-mysqli
Requires: php-common Requires: php-common
Requires: php-gd Requires: php-gd
@ -113,16 +125,12 @@ Requires: perl(Net::FTP)
Requires: perl(LWP::Protocol::https) Requires: perl(LWP::Protocol::https)
Requires: ca-certificates Requires: ca-certificates
Requires: zip Requires: zip
%{systemd_requires}
Requires(post): systemd
Requires(post): systemd-sysv
Requires(preun): systemd
Requires(postun): systemd
Requires(post): %{_bindir}/gpasswd Requires(post): %{_bindir}/gpasswd
Requires(post): %{_bindir}/less Requires(post): %{_bindir}/chown
%description %description common
ZoneMinder is a set of applications which is intended to provide a complete ZoneMinder is a set of applications which is intended to provide a complete
solution allowing you to capture, analyze, record and monitor any cameras you solution allowing you to capture, analyze, record and monitor any cameras you
have attached to a Linux based machine. It is designed to run on kernels which have attached to a Linux based machine. It is designed to run on kernels which
@ -131,15 +139,57 @@ attached to BTTV cards, various USB cameras and IP network cameras. It is
designed to support as many cameras as you can attach to your computer without designed to support as many cameras as you can attach to your computer without
too much degradation of performance. too much degradation of performance.
This is a meta-package that exists solely to allow the existing user base to
seamlessly transition to sub-packages.
%package httpd
Summary: ZoneMinder configuration for Apache web server
Requires: %{name}-common%{?_isa} = %{version}-%{release}
Requires: httpd
Requires: php
Conflicts: %{name}-nginx
%description httpd
ZoneMinder is a set of applications which is intended to provide a complete
solution allowing you to capture, analyze, record and monitor any cameras you
have attached to a Linux based machine. It is designed to run on kernels which
support the Video For Linux (V4L) interface and has been tested with cameras
attached to BTTV cards, various USB cameras and IP network cameras. It is
designed to support as many cameras as you can attach to your computer without
too much degradation of performance.
This sub-package contains configuration specific to Apache web server
%package nginx
Summary: ZoneMinder configuration for Nginx web server
Requires: %{name}-common%{?_isa} = %{version}-%{release}
Requires: nginx
Requires: php-fpm
Requires: fcgiwrap
Conflicts: %{name}-httpd
%description nginx
ZoneMinder is a set of applications which is intended to provide a complete
solution allowing you to capture, analyze, record and monitor any cameras you
have attached to a Linux based machine. It is designed to run on kernels which
support the Video For Linux (V4L) interface and has been tested with cameras
attached to BTTV cards, various USB cameras and IP network cameras. It is
designed to support as many cameras as you can attach to your computer without
too much degradation of performance.
This sub-package contains support for ZoneMinder with the Nginx web server
%prep %prep
%autosetup -p 1 -a 1 -n ZoneMinder-%{version} %autosetup -p 1 -a 1
%{__rm} -rf ./web/api/app/Plugin/Crud rm -rf ./web/api/app/Plugin/Crud
%{__mv} -f crud-%{crud_version} ./web/api/app/Plugin/Crud mv -f crud-%{crud_version} ./web/api/app/Plugin/Crud
# The all powerful autosetup macro does not work after the second source tarball # The all powerful autosetup macro does not work after the second source tarball
%{__gzip} -dc %{_sourcedir}/cakephp-enum-behavior-%{ceb_version}.tar.gz | tar -xvvf - gzip -dc %{_sourcedir}/cakephp-enum-behavior-%{ceb_version}.tar.gz | tar -xvvf -
%{__rm} -rf ./web/api/app/Plugin/CakePHP-Enum-Behavior rm -rf ./web/api/app/Plugin/CakePHP-Enum-Behavior
%{__mv} -f CakePHP-Enum-Behavior-%{ceb_version} ./web/api/app/Plugin/CakePHP-Enum-Behavior mv -f CakePHP-Enum-Behavior-%{ceb_version} ./web/api/app/Plugin/CakePHP-Enum-Behavior
# Change the following default values # Change the following default values
./utils/zmeditconfigdata.sh ZM_OPT_CAMBOZOLA yes ./utils/zmeditconfigdata.sh ZM_OPT_CAMBOZOLA yes
@ -152,7 +202,7 @@ too much degradation of performance.
%build %build
%cmake \ %cmake \
-DZM_WEB_USER="%{zmuid_final}" \ -DZM_WEB_USER="%{zmuid_final}" \
-DZM_WEB_GROUP="%{zmuid_final}" \ -DZM_WEB_GROUP="%{zmgid_final}" \
-DZM_TARGET_DISTRO="%{zmtargetdistro}" \ -DZM_TARGET_DISTRO="%{zmtargetdistro}" \
. .
@ -174,10 +224,13 @@ find %{buildroot} \( -name .htaccess -or -name .editorconfig -or -name .packlist
find %{buildroot}%{_datadir}/zoneminder/www/api \( -name cake -or -name cake.php \) -type f -exec sed -i 's\^#!/usr/bin/env bash$\#!%{_buildshell}\' {} \; -exec %{__chmod} 755 {} \; find %{buildroot}%{_datadir}/zoneminder/www/api \( -name cake -or -name cake.php \) -type f -exec sed -i 's\^#!/usr/bin/env bash$\#!%{_buildshell}\' {} \; -exec %{__chmod} 755 {} \;
# Use the system cacert file rather then the one bundled with CakePHP # Use the system cacert file rather then the one bundled with CakePHP
%{__rm} -f %{buildroot}%{_datadir}/zoneminder/www/api/lib/Cake/Config/cacert.pem rm -f %{buildroot}%{_datadir}/zoneminder/www/api/lib/Cake/Config/cacert.pem
%{__ln_s} ../../../../../../../..%{_sysconfdir}/pki/tls/certs/ca-bundle.crt %{buildroot}%{_datadir}/zoneminder/www/api/lib/Cake/Config/cacert.pem ln -s ../../../../../../../..%{_sysconfdir}/pki/tls/certs/ca-bundle.crt %{buildroot}%{_datadir}/zoneminder/www/api/lib/Cake/Config/cacert.pem
%post # Handle the polkit file differently for web server agnostic support (see post)
rm -f %{buildroot}%{_datadir}/polkit-1/rules.d/com.zoneminder.systemctl.rules
%post common
# Initial installation # Initial installation
if [ $1 -eq 1 ] ; then if [ $1 -eq 1 ] ; then
%systemd_post %{name}.service %systemd_post %{name}.service
@ -185,28 +238,50 @@ fi
# Upgrade from a previous version of zoneminder # Upgrade from a previous version of zoneminder
if [ $1 -eq 2 ] ; then if [ $1 -eq 2 ] ; then
# Add any new PTZ control configurations to the database (will not overwrite) # Add any new PTZ control configurations to the database (will not overwrite)
%{_bindir}/zmcamtool.pl --import >/dev/null 2>&1 || : %{_bindir}/zmcamtool.pl --import >/dev/null 2>&1 || :
# Freshen the database # Freshen the database
%{_bindir}/zmupdate.pl -f >/dev/null 2>&1 || : %{_bindir}/zmupdate.pl -f >/dev/null 2>&1 || :
# We can't run this automatically when new sql account permissions need to
# be manually added first
# Run zmupdate non-interactively
# zmupdate.pl --nointeractive
fi fi
# Warn the end user to read the README file
echo -e "\nVERY IMPORTANT: Before starting ZoneMinder, you must read the README file\nto finish the installation or upgrade!"
echo -e "\nThe README file is located here: %{_pkgdocdir}-common/README\n"
%post httpd
# For the case of changing from nginx <-> httpd, files in these folders must change ownership if they exist
%{_bindir}/chown -R %{zmuid_final}:%{zmgid_final} %{_sharedstatedir}/php/session/* >/dev/null 2>&1 || :
%{_bindir}/chown -R %{zmuid_final}:%{zmgid_final} %{_localstatedir}/log/zoneminder/* >/dev/null 2>&1 || :
%{_bindir}/chown -R %{zmuid_final}:%{zmgid_final} %{_sharedstatedir}/zoneminder/events/* >/dev/null 2>&1 || :
ln -sf %{_sysconfdir}/zm/www/com.zoneminder.systemctl.rules.httpd %{_datadir}/polkit-1/rules.d/com.zoneminder.systemctl.rules
# backwards compatibility
ln -sf %{_sysconfdir}/zm/www/zoneminder.httpd.conf %{_sysconfdir}/zm/www/zoneminder.conf
# Allow zoneminder access to local video sources, serial ports, and x10 # Allow zoneminder access to local video sources, serial ports, and x10
%{_bindir}/gpasswd -a %{zmuid_final} video >/dev/null 2>&1 || : %{_bindir}/gpasswd -a %{zmuid_final} video >/dev/null 2>&1 || :
%{_bindir}/gpasswd -a %{zmuid_final} dialout >/dev/null 2>&1 || : %{_bindir}/gpasswd -a %{zmuid_final} dialout >/dev/null 2>&1 || :
# Warn the end user to read the README file %post nginx
echo -e "\nVERY IMPORTANT: Before starting ZoneMinder, read README.%{readme_suffix} to finish the\ninstallation or upgrade!\n"
echo -e "\nThe README file is located here: %{_docdir}/%{name}\n" # Php package owns the session folder and sets group ownership to apache account
# We could override the folder permission, but adding nginx to the apache group works better
%{_bindir}/gpasswd -a nginx apache >/dev/null 2>&1 || :
# For the case of changing from httpd <-> nginx, files in these folders must change ownership if they exist
%{_bindir}/chown -R nginx:nginx %{_sharedstatedir}/php/session/* >/dev/null 2>&1 || :
%{_bindir}/chown -R nginx:nginx %{_localstatedir}/log/zoneminder/* >/dev/null 2>&1 || :
%{_bindir}/chown -R nginx:nginx %{_sharedstatedir}/zoneminder/events/* >/dev/null 2>&1 || :
ln -sf %{_sysconfdir}/zm/www/com.zoneminder.systemctl.rules.nginx %{_datadir}/polkit-1/rules.d/com.zoneminder.systemctl.rules
# backwards compatibility
ln -sf %{_sysconfdir}/zm/www/zoneminder.nginx.conf %{_sysconfdir}/zm/www/zoneminder.conf
# Allow zoneminder access to local video sources, serial ports, and x10
%{_bindir}/gpasswd -a nginx video >/dev/null 2>&1 || :
%{_bindir}/gpasswd -a nginx dialout >/dev/null 2>&1 || :
%if 0%{?with_nginx}
# Nginx does not create an SSL certificate like the apache package does so lets do that here # Nginx does not create an SSL certificate like the apache package does so lets do that here
if [ -f %{sslkey} -o -f %{sslcert} ]; then if [ -f %{sslkey} -o -f %{sslcert} ]; then
exit 0 exit 0
@ -232,7 +307,6 @@ SomeOrganizationalUnit
${FQDN} ${FQDN}
root@${FQDN} root@${FQDN}
EOF EOF
%endif
%preun %preun
%systemd_preun %{name}.service %systemd_preun %{name}.service
@ -240,19 +314,12 @@ EOF
%postun %postun
%systemd_postun_with_restart %{name}.service %systemd_postun_with_restart %{name}.service
%triggerun -- zoneminder < 1.25.0-4
# Save the current service runlevel info
# User must manually run systemd-sysv-convert --apply zoneminder
# to migrate them to systemd targets
%{_bindir}/systemd-sysv-convert --save zoneminder >/dev/null 2>&1 ||:
# Run these because the SysV package being removed won't do them
/sbin/chkconfig --del zoneminder >/dev/null 2>&1 || :
/bin/systemctl try-restart zoneminder.service >/dev/null 2>&1 || :
%files %files
# nothing
%files common
%license COPYING %license COPYING
%doc AUTHORS README.md distros/redhat/readme/README.%{readme_suffix} distros/redhat/readme/README.https %doc AUTHORS README.md distros/redhat/readme/README distros/redhat/readme/README.httpd distros/redhat/readme/README.nginx distros/redhat/readme/README.https
# We want these two folders to have "normal" read permission # We want these two folders to have "normal" read permission
# compared to the folder contents # compared to the folder contents
@ -262,21 +329,11 @@ EOF
# Config folder contents contain sensitive info # Config folder contents contain sensitive info
# and should not be readable by normal users # and should not be readable by normal users
%{_sysconfdir}/zm/conf.d/README %{_sysconfdir}/zm/conf.d/README
%config(noreplace) %attr(640,root,%{zmgid_final}) %{_sysconfdir}/zm/zm.conf
%config(noreplace) %attr(640,root,%{zmgid_final}) %{_sysconfdir}/zm/conf.d/*.conf
%ghost %attr(640,root,%{zmgid_final}) %{_sysconfdir}/zm/conf.d/zmcustom.conf
%config(noreplace) %attr(644,root,root) /etc/zm/www/zoneminder.conf
%config(noreplace) %{_sysconfdir}/logrotate.d/zoneminder %config(noreplace) %{_sysconfdir}/logrotate.d/zoneminder
%if 0%{?with_nginx}
%config(noreplace) %{_sysconfdir}/php-fpm.d/zoneminder.conf
%endif
%{_tmpfilesdir}/zoneminder.conf
%{_unitdir}/zoneminder.service %{_unitdir}/zoneminder.service
%{_datadir}/polkit-1/actions/com.zoneminder.systemctl.policy %{_datadir}/polkit-1/actions/com.zoneminder.systemctl.policy
%{_datadir}/polkit-1/rules.d/com.zoneminder.systemctl.rules
%{_bindir}/zmsystemctl.pl %{_bindir}/zmsystemctl.pl
%{_bindir}/zma %{_bindir}/zma
@ -307,8 +364,19 @@ EOF
%{_libexecdir}/zoneminder/ %{_libexecdir}/zoneminder/
%{_datadir}/zoneminder/ %{_datadir}/zoneminder/
%{_datadir}/applications/*%{name}.desktop %{_datadir}/applications/*zoneminder.desktop
%files httpd
%config(noreplace) %attr(640,root,%{zmgid_final}) %{_sysconfdir}/zm/zm.conf
%config(noreplace) %attr(640,root,%{zmgid_final}) %{_sysconfdir}/zm/conf.d/0*.conf
%ghost %attr(640,root,%{zmgid_final}) %{_sysconfdir}/zm/conf.d/zmcustom.conf
%config(noreplace) %{_sysconfdir}/zm/www/zoneminder.httpd.conf
%ghost %{_sysconfdir}/zm/www/zoneminder.conf
%config(noreplace) %{_sysconfdir}/zm/www/com.zoneminder.systemctl.rules.httpd
%ghost %{_datadir}/polkit-1/rules.d/com.zoneminder.systemctl.rules
%{_unitdir}/zoneminder.service.d/zm-httpd.conf
%{_tmpfilesdir}/zoneminder.httpd.tmpfiles.conf
%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_sharedstatedir}/zoneminder %dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_sharedstatedir}/zoneminder
%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_sharedstatedir}/zoneminder/events %dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_sharedstatedir}/zoneminder/events
%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_sharedstatedir}/zoneminder/images %dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_sharedstatedir}/zoneminder/images
@ -318,14 +386,72 @@ EOF
%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/cache/zoneminder %dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/cache/zoneminder
%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/log/zoneminder %dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/log/zoneminder
%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/spool/zoneminder-upload %dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/spool/zoneminder-upload
%dir %attr(755,%{zmuid_final},%{zmgid_final}) %{_localstatedir}/run/zoneminder
%files nginx
%config(noreplace) %attr(640,root,nginx) %{_sysconfdir}/zm/zm.conf
%config(noreplace) %attr(640,root,nginx) %{_sysconfdir}/zm/conf.d/*.conf
%ghost %attr(640,root,nginx) %{_sysconfdir}/zm/conf.d/zmcustom.conf
%config(noreplace) %{_sysconfdir}/zm/www/zoneminder.nginx.conf
%ghost %{_sysconfdir}/zm/www/zoneminder.conf
%config(noreplace) %{_sysconfdir}/zm/www/com.zoneminder.systemctl.rules.nginx
%ghost %{_datadir}/polkit-1/rules.d/com.zoneminder.systemctl.rules
%config(noreplace) %{_sysconfdir}/php-fpm.d/zoneminder.php-fpm.conf
%{_unitdir}/zoneminder.service.d/zm-nginx.conf
%{_tmpfilesdir}/zoneminder.nginx.tmpfiles.conf
%dir %attr(755,nginx,nginx) %{_sharedstatedir}/zoneminder
%dir %attr(755,nginx,nginx) %{_sharedstatedir}/zoneminder/events
%dir %attr(755,nginx,nginx) %{_sharedstatedir}/zoneminder/images
%dir %attr(755,nginx,nginx) %{_sharedstatedir}/zoneminder/sock
%dir %attr(755,nginx,nginx) %{_sharedstatedir}/zoneminder/swap
%dir %attr(755,nginx,nginx) %{_sharedstatedir}/zoneminder/temp
%dir %attr(755,nginx,nginx) %{_localstatedir}/cache/zoneminder
%dir %attr(755,nginx,nginx) %{_localstatedir}/log/zoneminder
%dir %attr(755,nginx,nginx) %{_localstatedir}/spool/zoneminder-upload
%changelog %changelog
* Sun Apr 22 2018 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.31.42-1 * Wed Nov 14 2018 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.32.2-2
- Remove support for sysvinit a.k.a. el6 - Break into sub-packages
- use desktop-file-install for new zoneminder.desktop file
- add new web cache folder * Sat Oct 13 2018 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.32.2-1
- 1.31.42 development snapshot - 1.32.2 release
- Bug fix release
* Thu Oct 04 2018 Sérgio Basto <sergio@serjux.com> - 1.32.1-2
- Mass rebuild for x264 and/or x265
* Tue Oct 2 2018 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.32.1-1
- 1.32.1 release
- Bug fix release
* Wed Sep 12 2018 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.32.0-1
- 1.32.0 release
- remove el6 (sys v init) support
- Make README name consistent across all supported distros
- remove jscalendar
- add requires php-json, zip
- support zm/conf.d folder
- support zm cache (busting) folder
* Sun Aug 19 2018 Leigh Scott <leigh123linux@googlemail.com> - 1.30.4-9
- Rebuilt for Fedora 29 Mass Rebuild binutils issue
* Fri Jul 27 2018 RPM Fusion Release Engineering <leigh123linux@gmail.com> - 1.30.4-8
- Rebuilt for https://fedoraproject.org/wiki/Fedora_29_Mass_Rebuild
* Thu Mar 08 2018 RPM Fusion Release Engineering <leigh123linux@googlemail.com> - 1.30.4-7
- Rebuilt for new ffmpeg snapshot
* Thu Mar 01 2018 RPM Fusion Release Engineering <leigh123linux@googlemail.com> - 1.30.4-6
- Rebuilt for https://fedoraproject.org/wiki/Fedora_28_Mass_Rebuild
* Thu Jan 18 2018 Leigh Scott <leigh123linux@googlemail.com> - 1.30.4-5
- Rebuilt for ffmpeg-3.5 git
* Thu Aug 31 2017 RPM Fusion Release Engineering <kwizart@rpmfusion.org> - 1.30.4-4
- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Mass_Rebuild
* Tue May 09 2017 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.30.4-1 * Tue May 09 2017 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.30.4-1
- modify autosetup macro parameters - modify autosetup macro parameters

View File

@ -50,6 +50,7 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}
,libdevice-serialport-perl ,libdevice-serialport-perl
,libimage-info-perl ,libimage-info-perl
,libjson-any-perl ,libjson-any-perl
,libjson-maybexs-perl
,libsys-mmap-perl [!hurd-any] ,libsys-mmap-perl [!hurd-any]
,liburi-encode-perl ,liburi-encode-perl
,libwww-perl ,libwww-perl

View File

@ -28,7 +28,7 @@ override_dh_auto_configure:
-DZM_CACHEDIR="/var/cache/zoneminder/cache" \ -DZM_CACHEDIR="/var/cache/zoneminder/cache" \
-DZM_DIR_EVENTS="/var/cache/zoneminder/events" \ -DZM_DIR_EVENTS="/var/cache/zoneminder/events" \
-DZM_DIR_IMAGES="/var/cache/zoneminder/images" \ -DZM_DIR_IMAGES="/var/cache/zoneminder/images" \
-DZM_PATH_ZMS="/zm/cgi-bin/nph-zms" \ -DZM_PATH_ZMS="/zm/cgi-bin/nph-zms"
override_dh_clean: override_dh_clean:
dh_clean $(MANPAGES1) dh_clean $(MANPAGES1)

View File

@ -32,7 +32,7 @@ Package: libzoneminder-perl
Section: perl Section: perl
Architecture: all Architecture: all
Depends: ${misc:Depends}, ${perl:Depends}, libdbi-perl, Depends: ${misc:Depends}, ${perl:Depends}, libdbi-perl,
libdevice-serialport-perl, libimage-info-perl, libjson-any-perl, libdevice-serialport-perl, libimage-info-perl, libjson-any-perl, libjson-maybexs-perl,
libsys-mmap-perl, liburi-encode-perl, libwww-perl libsys-mmap-perl, liburi-encode-perl, libwww-perl
Description: Perl libraries for ZoneMinder Description: Perl libraries for ZoneMinder
ZoneMinder is a video camera security and surveillance solution. ZoneMinder is a video camera security and surveillance solution.

View File

@ -45,7 +45,7 @@ Package: libzoneminder-perl
Section: perl Section: perl
Architecture: all Architecture: all
Depends: ${misc:Depends}, ${perl:Depends}, libdbi-perl, Depends: ${misc:Depends}, ${perl:Depends}, libdbi-perl,
libdevice-serialport-perl, libimage-info-perl, libjson-any-perl, libdevice-serialport-perl, libimage-info-perl, libjson-any-perl, libjson-maybexs-perl,
libsys-mmap-perl, liburi-encode-perl, libwww-perl libsys-mmap-perl, liburi-encode-perl, libwww-perl
Description: Perl libraries for ZoneMinder Description: Perl libraries for ZoneMinder
ZoneMinder is a video camera security and surveillance solution. ZoneMinder is a video camera security and surveillance solution.

View File

@ -53,6 +53,7 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}
,libdevice-serialport-perl ,libdevice-serialport-perl
,libimage-info-perl ,libimage-info-perl
,libjson-any-perl ,libjson-any-perl
,libjson-maybexs-perl
,libsys-mmap-perl [!hurd-any] ,libsys-mmap-perl [!hurd-any]
,liburi-encode-perl ,liburi-encode-perl
,libwww-perl ,libwww-perl

View File

@ -22,7 +22,7 @@ if [ "$1" = "configure" ]; then
if [ "$ZM_DB_HOST" = "localhost" ]; then if [ "$ZM_DB_HOST" = "localhost" ]; then
if [ -e "/lib/systemd/system/mysql.service" ] || [ -e "/lib/systemd/system/mariadb.service" ]; then if [ -e "/lib/systemd/system/mysql.service" ] || [ -e "/lib/systemd/system/mariadb.service" ] || [ -e "/etc/init.d/mysql" ]; then
# Ensure zoneminder is stopped # Ensure zoneminder is stopped
deb-systemd-invoke stop zoneminder.service || exit $? deb-systemd-invoke stop zoneminder.service || exit $?
@ -68,6 +68,7 @@ if [ "$1" = "configure" ]; then
# Add any new PTZ control configurations to the database (will not overwrite) # Add any new PTZ control configurations to the database (will not overwrite)
zmcamtool.pl --import >/dev/null 2>&1 zmcamtool.pl --import >/dev/null 2>&1
echo "Done Updating; starting ZoneMinder."
else else
echo 'NOTE: MySQL/MariaDB not running; please start mysql and run dpkg-reconfigure zoneminder when it is running.' echo 'NOTE: MySQL/MariaDB not running; please start mysql and run dpkg-reconfigure zoneminder when it is running.'
fi fi
@ -78,7 +79,6 @@ if [ "$1" = "configure" ]; then
else else
echo "Not doing database upgrade due to remote db server ($ZM_DB_HOST)." echo "Not doing database upgrade due to remote db server ($ZM_DB_HOST)."
fi fi
echo "Done Updating; starting ZoneMinder."
deb-systemd-invoke restart zoneminder.service deb-systemd-invoke restart zoneminder.service
fi fi

View File

@ -5,7 +5,6 @@ This document will provide an overview of ZoneMinder's API. This is work in prog
Overview Overview
^^^^^^^^ ^^^^^^^^
In an effort to further 'open up' ZoneMinder, an API was needed. This will In an effort to further 'open up' ZoneMinder, an API was needed. This will
allow quick integration with and development of ZoneMinder. allow quick integration with and development of ZoneMinder.
@ -13,6 +12,85 @@ The API is built in CakePHP and lives under the ``/api`` directory. It
provides a RESTful service and supports CRUD (create, retrieve, update, delete) provides a RESTful service and supports CRUD (create, retrieve, update, delete)
functions for Monitors, Events, Frames, Zones and Config. functions for Monitors, Events, Frames, Zones and Config.
Streaming Interface
^^^^^^^^^^^^^^^^^^^
Developers working on their application often ask if there is an "API" to receive live streams, or recorded event streams.
It is possible to stream both live and recorded streams. This isn't strictly an "API" per-se (that is, it is not integrated
into the Cake PHP based API layer discussed here) and also why we've used the term "Interface" instead of an "API".
Live Streams
~~~~~~~~~~~~~~
What you need to know is that if you want to display "live streams", ZoneMinder sends you streaming JPEG images (MJPEG)
which can easily be rendered in a browser using an ``img src`` tag.
For example:
::
<img src="https://yourserver/zm/cgi-bin/nph-zms?scale=50&width=640p&height=480px&mode=jpeg&maxfps=5&buffer=1000&&monitor=1&auth=b54a589e09f330498f4ae2203&connkey=36139" />
will display a live feed from monitor id 1, scaled down by 50% in quality and resized to 640x480px.
* This assumes ``/zm/cgi-bin`` is your CGI_BIN path. Change it to what is correct in your system
* The "auth" token you see above is required if you use ZoneMinder authentication. To understand how to get the auth token, please read the "Login, Logout & API security" section below.
* The "connkey" parameter is essentially a random number which uniquely identifies a stream. If you don't specify a connkey, ZM will generate its own. It is recommended to generate a connkey because you can then use it to "control" the stream (pause/resume etc.)
* Instead of dealing with the "auth" token, you can also use ``&user=username&pass=password`` where "username" and "password" are your ZoneMinder username and password respectively. Note that this is not recommended because you are transmitting them in a URL and even if you use HTTPS, they may show up in web server logs.
PTZ on live streams
-------------------
PTZ commands are pretty cryptic in ZoneMinder. This is not meant to be an exhaustive guide, but just something to whet your appetite:
Lets assume you have a monitor, with ID=6. Let's further assume you want to pan it left.
You'd need to send a:
``POST`` command to ``https://yourserver/zm/index.php`` with the following data payload in the command (NOT in the URL)
``view=request&request=control&id=6&control=moveConLeft&xge=30&yge=30``
Obviously, if you are using authentication, you need to be logged in for this to work.
Like I said, at this stage, this is only meant to get you started. Explore the ZoneMinder code and use "Inspect source" as you use PTZ commands in the ZoneMinder source code.
`control_functions.php <https://github.com/ZoneMinder/zoneminder/blob/10531df54312f52f0f32adec3d4720c063897b62/web/skins/classic/includes/control_functions.php>`__ is a great place to start.
Pre-recorded (past event) streams
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Similar to live playback, if you have chosen to store events in JPEG mode, you can play it back using:
::
<img src="https://yourserver/zm/cgi-bin/nph-zms?mode=jpeg&frame=1&replay=none&source=event&event=293820&connkey=77493&auth=b54a58f5f4ae2203" />
* This assumes ``/zm/cgi-bin`` is your CGI_BIN path. Change it to what is correct in your system
* This will playback event 293820, starting from frame 1 as an MJPEG stream
* Like before, you can add more parameters like ``scale`` etc.
* auth and connkey have the same meaning as before, and yes, you can replace auth by ``&user=usename&pass=password`` as before and the same security concerns cited above apply.
If instead, you have chosen to use the MP4 (Video) storage mode for events, you can directly play back the saved video file:
::
<video src="https://yourserver/zm/index.php?view=view_video&eid=294690&auth=33f3d558af84cf08" type="video/mp4"></video>
* This will play back the video recording for event 294690
What other parameters are supported?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The best way to answer this question is to play with ZoneMinder console. Open a browser, play back live or recorded feed, and do an "Inspect Source" to see what parameters
are generated. Change and observe.
Enabling API
^^^^^^^^^^^^
A default ZoneMinder installs with APIs enabled. You can explictly enable/disable the APIs
via the Options->System menu by enabling/disabling ``OPT_USE_API``. Note that if you intend
to use APIs with 3rd party apps, such as zmNinja or others that use APIs, you should also
enable ``AUTH_HASH_LOGINS``.
Login, Logout & API Security Login, Logout & API Security
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The APIs tie into ZoneMinder's existing security model. This means if you have The APIs tie into ZoneMinder's existing security model. This means if you have
@ -29,13 +107,13 @@ This means if you plan to use cuRL to experiment with these APIs, you first need
:: ::
curl -XPOST -d "user=XXXX&pass=YYYY" -c cookies.txt http://yourzmip/zm/api/login.json curl -XPOST -d "user=XXXX&pass=YYYY" -c cookies.txt http://yourzmip/zm/api/host/login.json
Staring ZM 1.32.0, you also have a `logout` API that basically clears your session. It looks like this: Staring ZM 1.32.0, you also have a `logout` API that basically clears your session. It looks like this:
:: ::
curl -b cookies.txt http://yourzmip/zm/api/logout.json curl -b cookies.txt http://yourzmip/zm/api/host/logout.json
**Login process for older versions of ZoneMinder** **Login process for older versions of ZoneMinder**
@ -125,6 +203,22 @@ Return a list of all monitors
curl http://server/zm/api/monitors.json curl http://server/zm/api/monitors.json
It is worthwhile to note that starting ZM 1.32.3 and beyond, this API also returns a ``Monitor_Status`` object per monitor. It looks like this:
::
"Monitor_Status": {
"MonitorId": "2",
"Status": "Connected",
"CaptureFPS": "1.67",
"AnalysisFPS": "1.67",
"CaptureBandwidth": "52095"
}
If you don't see this in your API, you are running an older version of ZM. This gives you a very convenient way to check monitor status without calling the ``daemonCheck`` API described later.
Retrieve monitor 1 Retrieve monitor 1
^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^
@ -142,6 +236,13 @@ This API changes monitor 1 to Modect and Enabled
curl -XPOST http://server/zm/api/monitors/1.json -d "Monitor[Function]=Modect&Monitor[Enabled]=1" curl -XPOST http://server/zm/api/monitors/1.json -d "Monitor[Function]=Modect&Monitor[Enabled]=1"
Get Daemon Status of Monitor 1
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
::
curl http://server/zm/api/monitors/daemonStatus/id:1/daemon:zmc.json
Add a monitor Add a monitor
^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^
@ -377,11 +478,13 @@ Create a Zone
&Zone[MaxBlobs]=\ &Zone[MaxBlobs]=\
&Zone[OverloadFrames]=0" &Zone[OverloadFrames]=0"
PTZ Control APIs PTZ Control Meta-Data APIs
^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^
PTZ controls associated with a monitor are stored in the Controls table and not the Monitors table inside ZM. What that means is when you get the details of a Monitor, you will only know if it is controllable (isControllable:true) and the control ID. PTZ controls associated with a monitor are stored in the Controls table and not the Monitors table inside ZM. What that means is when you get the details of a Monitor, you will only know if it is controllable (isControllable:true) and the control ID.
To be able to retrieve PTZ information related to that Control ID, you need to use the controls API To be able to retrieve PTZ information related to that Control ID, you need to use the controls API
Note that these APIs only retrieve control data related to PTZ. They don't actually move the camera. See the "PTZ on live streams" section to move the camera.
This returns all the control definitions: This returns all the control definitions:
:: ::
@ -399,7 +502,97 @@ ZM APIs have various APIs that help you in determining host (aka ZM) daemon stat
:: ::
curl -XGET http://server/zm/api/host/daemonCheck.json # 1 = ZM running 0=not running
curl -XGET http://server/zm/api/host/getLoad.json # returns current load of ZM curl -XGET http://server/zm/api/host/getLoad.json # returns current load of ZM
curl -XGET http://server/zm/api/host/getDiskPercent.json # returns in GB (not percentage), disk usage per monitor (that is, space taken to store various event related information,images etc. per monitor)
# Note that ZM 1.32.3 onwards has the same information in Monitors.json which is more reliable and works for multi-server too.
curl -XGET http://server/zm/api/host/daemonCheck.json # 1 = ZM running 0=not running
# The API below uses "du" to calculate disk space. We no longer recommend you use it if you have many events. Use the Storage APIs instead, described later
curl -XGET http://server/zm/api/host/getDiskPercent.json # returns in GB (not percentage), disk usage per monitor (that is,space taken to store various event related information,images etc. per monitor)
Storage and Server APIs
^^^^^^^^^^^^^^^^^^^^^^^
ZoneMinder introduced many new options that allowed you to configure multiserver/multistorage configurations. While a part of this was available in previous versions, a lot of rework was done as part of ZM 1.31 and 1.32. As part of that work, a lot of new and useful APIs were added. Some of these are part of ZM 1.32 and others will be part of ZM 1.32.3 (of course, if you build from master, you can access them right away, or wait till a stable release is out.
This returns storage data for my single server install. If you are using multi-storage, you'll see many such "Storage" entries, one for each storage defined:
::
curl http://server/zm/api/storage.json
Returns:
::
{
"storage": [
{
"Storage": {
"Id": "0",
"Path": "\/var\/cache\/zoneminder\/events",
"Name": "Default",
"Type": "local",
"Url": null,
"DiskSpace": "364705447651",
"Scheme": "Medium",
"ServerId": null,
"DoDelete": true
}
}
]
}
"DiskSpace" is the disk used in bytes. While this doesn't return disk space data as rich as ``/host/getDiskPercent``, it is much more efficient.
Similarly,
::
curl http://server/zm/api/servers.json
Returns:
::
{
"servers": [
{
"Server": {
"Id": "1",
"Name": "server1",
"Hostname": "server1.mydomain.com",
"State_Id": null,
"Status": "Running",
"CpuLoad": "0.9",
"TotalMem": "6186237952",
"FreeMem": "156102656",
"TotalSwap": "536866816",
"FreeSwap": "525697024",
"zmstats": false,
"zmaudit": false,
"zmtrigger": false
}
}
]
}
This only works if you have a multiserver setup in place. If you don't it will return an empty array.
Further Reading
^^^^^^^^^^^^^^^^
As described earlier, treat this document as an "introduction" to the important parts of the API and streaming interfaces.
There are several details that haven't yet been documented. Till they are, here are some resources:
* zmNinja, the open source mobile app for ZoneMinder is 100% based on ZM APIs. Explore its `source code <https://github.com/pliablepixels/zmNinja>`__ to see how things work.
* Launch up ZM console in a browser, and do an "Inspect source". See how images are being rendered. Go to the networks tab of the inspect source console and look at network requests that are made when you pause/play/forward streams.
* If you still can't find an answer, post your question in the `forums <https://forums.zoneminder.com/index.php>`__ (not the github repo).

View File

@ -35,7 +35,7 @@ guide you with a quick search.
`releases page <https://github.com/ZoneMinder/zoneminder/releases>`_ for `releases page <https://github.com/ZoneMinder/zoneminder/releases>`_ for
the latest release. the latest release.
Alternatively, the ZoneMinder project team maintains a ppa, which is updated immediately Alternatively, the ZoneMinder project team maintains a `PPA <https://askubuntu.com/questions/4983/what-are-ppas-and-how-do-i-use-them>`_, which is updated immediately
following a new release of ZoneMinder. To use this repository instead of the following a new release of ZoneMinder. To use this repository instead of the
official Ubuntu repository, enter the following from the command line: official Ubuntu repository, enter the following from the command line:
@ -43,6 +43,15 @@ guide you with a quick search.
add-apt-repository ppa:iconnor/zoneminder add-apt-repository ppa:iconnor/zoneminder
Please note that as of 1.32.0 We are creating a new PPA for each major version, as a means to prevent automatic upgrades from one major version to another. So instead of the above ppa line use the following:
::
add-apt-repository ppa:iconnor/zoneminder-1.32
If you are on Trusty or Xenial, you may want to add both, as there are some packages for dependencies included in the old ppa.
Update repo and upgrade. Update repo and upgrade.
:: ::
@ -51,6 +60,7 @@ Update repo and upgrade.
apt-get upgrade apt-get upgrade
apt-get dist-upgrade apt-get dist-upgrade
**Step 3:** Configure MySQL **Step 3:** Configure MySQL
.. sidebar :: Note .. sidebar :: Note
@ -62,8 +72,10 @@ Update repo and upgrade.
| /etc/alternatives/my.cnf -> /etc/mysql/mysql.cnf | /etc/alternatives/my.cnf -> /etc/mysql/mysql.cnf
| /etc/mysql/mysql.cnf is a basic file | /etc/mysql/mysql.cnf is a basic file
Certain new defaults in MySQL 5.7 are currently causing some issues with ZoneMinder, Certain new defaults in MySQL 5.7 cause some issues with ZoneMinder < 1.32.0,
the workaround is to modify the sql_mode setting of MySQL. the workaround is to modify the sql_mode setting of MySQL. Please note that these
changes are NOT required for ZoneMinder 1.32.0 and some people have reported them
causing problems in 1.32.0.
To better manage the MySQL server it is recommended to copy the sample config file and To better manage the MySQL server it is recommended to copy the sample config file and
replace the default my.cnf symbolic link. replace the default my.cnf symbolic link.
@ -104,10 +116,12 @@ Restart MySQL
**Step 5:** Configure the ZoneMinder Database **Step 5:** Configure the ZoneMinder Database
This step should not be required on ZoneMinder 1.32.0.
:: ::
mysql -uroot -p < /usr/share/zoneminder/db/zm_create.sql mysql -uroot -p < /usr/share/zoneminder/db/zm_create.sql
mysql -uroot -p -e "grant select,insert,update,delete,create,alter,index,lock tables on zm.* to 'zmuser'@localhost identified by 'zmpass';" mysql -uroot -p -e "grant lock tables,alter,drop,select,insert,update,delete,create,index,alter routine,create routine, trigger,execute on zm.* to 'zmuser'@localhost identified by 'zmpass';"
**Step 6:** Set permissions **Step 6:** Set permissions
@ -124,9 +138,16 @@ Set /etc/zm/zm.conf to root:www-data 740 and www-data access to content
:: ::
a2enconf zoneminder
a2enmod cgi a2enmod cgi
a2enmod rewrite a2enmod rewrite
a2enconf zoneminder
You may also want to enable to following modules to improve caching performance
::
a2enmod expires
a2enmod headers
**Step 8:** Enable and start Zoneminder **Step 8:** Enable and start Zoneminder

View File

@ -150,7 +150,7 @@ Orientation
WebSite WebSite
^^^^^^^ ^^^^^^^
This Source Type allows one to configure an arbitrary website as a non-reocrdable, fully interactive, monitor in ZoneMinder. Note that sites with self-signed certificates will not display until the end user first manually navigates to the site and accpets the unsigned certificate. Also note that some sites will set an X-Frame option in the header, which discourages their site from being displayed within a frame. ZoneMinder will detect this condition and present a warning in the log. When this occurs, the end user can choose to install a browser plugin or extension to workaround this issue. This Source Type allows one to configure an arbitrary website as a non-recordable, fully interactive, monitor in ZoneMinder. Note that sites with self-signed certificates will not display until the end user first manually navigates to the site and accpets the unsigned certificate. Also note that some sites will set an X-Frame option in the header, which discourages their site from being displayed within a frame. ZoneMinder will detect this condition and present a warning in the log. When this occurs, the end user can choose to install a browser plugin or extension to workaround this issue.
Website URL Website URL
Enter the full http or https url to the desired website. Enter the full http or https url to the desired website.
@ -164,6 +164,29 @@ Height (pixels)
Web Site Refresh Web Site Refresh
If the website in question has static content, optionally enter a time period in seconds for ZoneMinder to refresh the content. If the website in question has static content, optionally enter a time period in seconds for ZoneMinder to refresh the content.
Storage Tab
-----------
The storage section allows for each monitor to configure if and how video and audio are recorded.
Save JPEGs
Records video in individual JPEG frames. Storing JPEG frames requires more storage space than h264 but it allows to view an event anytime while it is being recorded.
* Disabled video is not recorded as JPEG frames. If this setting is selected, then "Video Writer" should be enabled otherwise there is no video recording at all.
* Frames only video is recorded in individual JPEG frames.
* Analysis images only (if available) video is recorded in invidual JPEG frames with an overlay of the motion detection analysis information. Note that this overlay remains permanently visible in the frames.
* Frames + Analysis images (if available) video is recorded twice, once as normal individual JPEG frames and once in invidual JPEG frames with analysis information overlaid.
Video Writer
Records video in real video format. It provides much better compression results than saving JPEGs, thus longer video history can be stored.
* Disabled video is not recorded in video format. If this setting is selected, then "Save JPEGs" should be enabled otherwise there is no video recording at all.
* X264 Encode the video or picture frames received from the camera are transcoded into h264 and stored as a video. This option is useful if the camera cannot natively stream h264.
* H264 Camera Passthrough this option assumes that the camera is already sending an h264 stream. Video will be recorded as is, without any post-processing in zoneminder. Video characteristics such as bitrate, encoding mode, etc. should be set directly in the camera.
Recording Audio
Check the box labeled "Whether to store the audio stream when saving an event." in order to save audio (if available) when events are recorded.
Timestamp Tab Timestamp Tab
------------- -------------

View File

@ -1,15 +1,14 @@
Introduction Introduction
============ ============
Welcome to ZoneMinder, the all-in-one Linux GPL'd security camera solution. Welcome to ZoneMinder, the all-in-one security camera solution for Linux with GPL License.
Most commercial "security systems" are designed as a monitoring system that also records. Recording quality can vary from bad to unusable, locating the relevant video can range from challenging to impractical, and exporting can often only be done with the manual present. ZoneMinder was designed primarily to record, and allow easy searches and exporting. Recordings are of the best possible quality, easy to filter and find, and simple to export using any system with a web browser. It also monitors. Commercial "security systems" are often designed as a monitoring system with little attention to recording quality. In such a system, locating and exporting relevant video can be challenging and often requires extensive human intervention. ZoneMinder was designed to provide the best possible record quality while allowing easy searching, filtering and exporting of security footage.
ZoneMinder is designed around a series of independent components that only function when necessary limiting any wasted resource and maximising the efficiency of your machine. A fairly ancient Pentium II PC should be able to track one camera per device at up to 25 frames per second with this dropping by half approximately for each additional camera on the same device. Additional cameras on other devices do not interact so can maintain this frame rate. Even monitoring several cameras still will not overload the CPU as frame processing is designed to synchronise with capture and not stall it. ZoneMinder is designed around a series of independent components that only function when necessary, limiting any wasted resource and maximising the efficiency of your machine. An outdated Pentium II PC can have multiple recording devices connected to it, and it is able to track one camera per device at up to 25 frames per second, which drops by approximately half for each additional camera on the same device. Additional cameras on devices that do not interact with other devices can maintain the 25 frame rate per second. Monitoring several cameras will not overload the CPU as frame processing is designed to synchronise with capture.
As well as being fast ZoneMinder is designed to be friendly and even more than that, actually useful. As well as the fast video interface core it also comes with a user friendly and comprehensive PHP based web interface allowing you to control and monitor your cameras from home, at work, on the road, or even a web enabled cell phone. It supports variable web capabilities based on available bandwidth. The web interface also allows you to view events that your cameras have captured and archive them or review them time and again, or delete the ones you no longer wish to keep. The web pages directly interact with the core daemons ensuring full co-operation at all times. ZoneMinder can even be installed as a system service ensuring it is right there if your computer has to reboot for any reason. A fast video interface core, a user-friendly and comprehensive PHP based web interface allows ZoneMinder to be efficient, friendly and most importantly useful. You can control and monitor your cameras from home, at work, on the road, or a web-enabled cell phone. It supports variable web capabilities based on available bandwidth. The web interface also allows you to view events that your cameras have captured, which can be archived, reviewed or deleted. The web application directly interacts with the core daemons ensuring full co-operation at all times. ZoneMinder can also be installed as a system service to reboot a system remotely.
The core of ZoneMinder is the capture and analysis of images and there is a highly configurable set of parameters that allow you to ensure that you can eliminate false positives whilst ensuring that anything you don't want to miss will be captured and saved. ZoneMinder allows you to define a set of 'zones' for each camera of varying sensitivity and functionality. This allows you to eliminate regions that you don't wish to track or define areas that will alarm if various thresholds are exceeded in conjunction with other zones. The core of ZoneMinder is the capture and analysis of images and a highly configurable set of parameters that eliminate false positives whilst ensuring minimum loss of footage. For example, you can define a set of 'zones' for each camera of varying sensitivity and functionality. This eliminates zones that you don't wish to track or define areas that will alarm if various thresholds are exceeded in conjunction with other zones.
ZoneMinder is free, but if you do find it useful then please feel free to visit http://www.zoneminder.com/donate.html and help to fund future improvements to ZoneMinder.
ZoneMinder is free under GPL License, but if you do find it useful, then please feel free to visit http://www.zoneminder.com/donate.html and help us fund our future improvements.

View File

@ -15,7 +15,7 @@ AUTH_HASH_SECRET - When ZoneMinder is running in hashed authenticated mode it is
AUTH_HASH_IPS - When ZoneMinder is running in hashed authenticated mode it can optionally include the requesting IP address in the resultant hash. This adds an extra level of security as only requests from that address may use that authentication key. However in some circumstances, such as access over mobile networks, the requesting address can change for each request which will cause most requests to fail. This option allows you to control whether IP addresses are included in the authentication hash on your system. If you experience intermitent problems with authentication, switching this option off may help. AUTH_HASH_IPS - When ZoneMinder is running in hashed authenticated mode it can optionally include the requesting IP address in the resultant hash. This adds an extra level of security as only requests from that address may use that authentication key. However in some circumstances, such as access over mobile networks, the requesting address can change for each request which will cause most requests to fail. This option allows you to control whether IP addresses are included in the authentication hash on your system. If you experience intermitent problems with authentication, switching this option off may help.
AUTH_HASH_LOGINS - The normal process for logging into ZoneMinder is via the login screen with username and password. In some circumstances it may be desirable to allow access directly to one or more pages, for instance from a third party application. If this option is enabled then adding an 'auth' parameter to any request will include a shortcut login bypassing the login screen, if not already logged in. As authentication hashes are time and, optionally, IP limited this can allow short-term access to ZoneMinder screens from other web pages etc. In order to use this the calling application will hae to generate the authentication hash itself and ensure it is valid. If you use this option you should ensure that you have modified the ZM_AUTH_HASH_SECRET to somethign unique to your system. AUTH_HASH_LOGINS - The normal process for logging into ZoneMinder is via the login screen with username and password. In some circumstances it may be desirable to allow access directly to one or more pages, for instance from a third party application. If this option is enabled then adding an 'auth' parameter to any request will include a shortcut login bypassing the login screen, if not already logged in. As authentication hashes are time and, optionally, IP limited, this can allow short-term access to ZoneMinder screens from other web pages etc. In order to use this, the calling application will have to generate the authentication hash itself and ensure it is valid. If you use this option you should ensure that you have modified the ZM_AUTH_HASH_SECRET to something unique to your system.
OPT_FAST_DELETE - Normally an event created as the result of an alarm consists of entries in one or more database tables plus the various files associated with it. When deleting events in the browser it can take a long time to remove all of this if your are trying to do a lot of events at once. It is recommended that you set this option which means that the browser client only deletes the key entries in the events table, which means the events will no longer appear in the listing, and leaves the zmaudit daemon to clear up the rest later. OPT_FAST_DELETE - Normally an event created as the result of an alarm consists of entries in one or more database tables plus the various files associated with it. When deleting events in the browser it can take a long time to remove all of this if your are trying to do a lot of events at once. It is recommended that you set this option which means that the browser client only deletes the key entries in the events table, which means the events will no longer appear in the listing, and leaves the zmaudit daemon to clear up the rest later.
@ -38,6 +38,7 @@ OPT_CONTROL - ZoneMinder includes limited support for controllable cameras. A nu
OPT_TRIGGERS - ZoneMinder can interact with external systems which prompt or cancel alarms. This is done via the zmtrigger.pl script. This option indicates whether you want to use these external triggers. Most people will say no here. OPT_TRIGGERS - ZoneMinder can interact with external systems which prompt or cancel alarms. This is done via the zmtrigger.pl script. This option indicates whether you want to use these external triggers. Most people will say no here.
CHECK_FOR_UPDATES - From ZoneMinder version 1.17.0 onwards new versions are expected to be more frequent. To save checking manually for each new version ZoneMinder can check with the zoneminder.com website to determine the most recent release. These checks are infrequent, about once per week, and no personal or system information is transmitted other than your current version number. If you do not wish these checks to take place or your ZoneMinder system has no internet access you can switch these check off with this configuration variable CHECK_FOR_UPDATES - From ZoneMinder version 1.17.0 onwards new versions are expected to be more frequent. To save checking manually for each new version ZoneMinder can check with the zoneminder.com website to determine the most recent release. These checks are infrequent, about once per week, and no personal or system information is transmitted other than your current version number. If you do not wish these checks to take place or your ZoneMinder system has no internet access you can switch these check off with this configuration variable
UPDATE_CHECK_PROXY - If you use a proxy to access the internet then ZoneMinder needs to know so it can access zoneminder.com to check for updates. If you do use a proxy enter the full proxy url here in the form of http://<proxy host>:<proxy port>/ UPDATE_CHECK_PROXY - If you use a proxy to access the internet then ZoneMinder needs to know so it can access zoneminder.com to check for updates. If you do use a proxy enter the full proxy url here in the form of http://<proxy host>:<proxy port>/
SHM_KEY - ZoneMinder uses shared memory to speed up communication between modules. To identify the right area to use shared memory keys are used. This option controls what the base key is, each monitor will have it's Id or'ed with this to get the actual key used. You will not normally need to change this value unless it clashes with another instance of ZoneMinder on the same machine. Only the first four hex digits are used, the lower four will be masked out and ignored. SHM_KEY - ZoneMinder uses shared memory to speed up communication between modules. To identify the right area to use shared memory keys are used. This option controls what the base key is, each monitor will have it's Id or'ed with this to get the actual key used. You will not normally need to change this value unless it clashes with another instance of ZoneMinder on the same machine. Only the first four hex digits are used, the lower four will be masked out and ignored.

21
misc/apache-cors.conf Normal file
View File

@ -0,0 +1,21 @@
# This configuration is only needed for compatibility with zmninja
# If not using VirtualHosts, copy or symlink this file into the Apache config folder
# If using VirtualHosts, then this config must be placed inside the appropriate
# <VirtualHost> directive.
# Make sure you have enabled/loaded header manipulation modules
# For example, in Debian based distros the command is "sudo a2enmod headers"
# zmNinja header permissions. Tweak to your needs
Header always set Access-Control-Allow-Credentials true
#zmNinja's WKWebView will set the origin header as localhost:8080
Header always set Access-Control-Allow-Origin "http://localhost:8080"
Header always set Access-Control-Request-Methods "Authorization"
Header always set Access-Control-Methods "OPTIONS,GET,POST,DELETE,PUT"
Header always set Access-Control-Allow-Headers "X-Requested-With, Content-Type, Authorization, Origin, Accept, client-security-token"
Header always set Access-Control-Expose-Headers "Content-Security-Policy, Location"
Header always set Access-Control-Max-Age "1000"
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ $1 [R=200,L]

View File

@ -1,27 +1,3 @@
# ==========================================================================
#
# ZoneMinder Base Module, $Date$, $Revision$
# Copyright (C) 2001-2008 Philip Coombes
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# ==========================================================================
#
# This module contains the common definitions and functions used by the rest
# of the ZoneMinder scripts
#
package ZoneMinder::Base; package ZoneMinder::Base;
use 5.006; use 5.006;
@ -82,11 +58,18 @@ Philip Coombes, E<lt>philip.coombes@zoneminder.comE<gt>
=head1 COPYRIGHT AND LICENSE =head1 COPYRIGHT AND LICENSE
Copyright (C) 2001-2008 Philip Coombes This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This library is free software; you can redistribute it and/or modify This program is distributed in the hope that it will be useful,
it under the same terms as Perl itself, either Perl version 5.8.3 or, but WITHOUT ANY WARRANTY; without even the implied warranty of
at your option, any later version of Perl 5 you may have available. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
=cut =cut

View File

@ -309,6 +309,8 @@ saving configuration is a convenient way to ensure that the configuration
held in the database corresponds with the most recent definitions and that held in the database corresponds with the most recent definitions and that
all components are using the same set of configuration. all components are using the same set of configuration.
=back
=head2 EXPORT =head2 EXPORT
None by default. None by default.

View File

@ -409,8 +409,8 @@ our @options = (
that is used to get notifications for alarms detected by ZoneMinder that is used to get notifications for alarms detected by ZoneMinder
in real time. zmNinja requires this server for push notifications to in real time. zmNinja requires this server for push notifications to
mobile phones. This option only enables the server if its already installed. mobile phones. This option only enables the server if its already installed.
Please visit https://github.com/pliablepixels/zmeventserver for installation Please visit the [zmeventserver project site](https://github.com/pliablepixels/zmeventserver)
instructions. for installation instructions.
`, `,
type => $types{boolean}, type => $types{boolean},
category => 'system', category => 'system',
@ -442,7 +442,7 @@ our @options = (
description => 'Your recaptcha site-key', description => 'Your recaptcha site-key',
help => q`You need to generate your keys from help => q`You need to generate your keys from
the Google reCaptcha website. the Google reCaptcha website.
Please refer to https://www.google.com/recaptcha/ Please refer to the [recaptcha project site](https://www.google.com/recaptcha/)
for more details. for more details.
`, `,
requires => [ requires => [
@ -457,7 +457,7 @@ our @options = (
description => 'Your recaptcha secret-key', description => 'Your recaptcha secret-key',
help => q`You need to generate your keys from help => q`You need to generate your keys from
the Google reCaptcha website. the Google reCaptcha website.
Please refer to https://www.google.com/recaptcha/ Please refer to the [recaptcha project site](https://www.google.com/recaptcha/)
for more details. for more details.
`, `,
requires => [ requires => [
@ -674,9 +674,9 @@ our @options = (
ZoneMinder uses to view image streams on browsers such as ZoneMinder uses to view image streams on browsers such as
Internet Explorer that don't natively support this format. If Internet Explorer that don't natively support this format. If
you use this browser it is highly recommended to install this you use this browser it is highly recommended to install this
from http://www.charliemouse.com/code/cambozola/ however if it from the [cambozola project site](http://www.charliemouse.com/code/cambozola/).
is not installed still images at a lower refresh rate can still However, if it is not installed still images at a lower refresh rate can
be viewed. still be viewed.
`, `,
type => $types{boolean}, type => $types{boolean},
category => 'images', category => 'images',
@ -690,9 +690,9 @@ our @options = (
ZoneMinder uses to view image streams on browsers such as ZoneMinder uses to view image streams on browsers such as
Internet Explorer that don't natively support this format. If Internet Explorer that don't natively support this format. If
you use this browser it is highly recommended to install this you use this browser it is highly recommended to install this
from http://www.charliemouse.com/code/cambozola/ however if it from the [cambozola project site](http://www.charliemouse.com/code/cambozola/).
is not installed still images at a lower refresh rate can still However if it is not installed still images at a lower refresh rate can
be viewed. Leave this as 'cambozola.jar' if cambozola is still be viewed. Leave this as 'cambozola.jar' if cambozola is
installed in the same directory as the ZoneMinder web client installed in the same directory as the ZoneMinder web client
files. files.
`, `,
@ -2721,7 +2721,8 @@ our @options = (
This is being done for the sole purpoase of creating a better This is being done for the sole purpoase of creating a better
product for our target audience. This script is intended to be product for our target audience. This script is intended to be
completely transparent to the end user, and can be disabled from completely transparent to the end user, and can be disabled from
the web console under Options. the web console under Options. For more details on what information
we collect, please refer to our [privacy](?view=privacy) statement.
`, `,
type => $types{boolean}, type => $types{boolean},
category => 'system', category => 'system',
@ -3882,6 +3883,15 @@ our @options = (
readonly => 1, readonly => 1,
category => 'dynamic', category => 'dynamic',
}, },
{
name => 'ZM_SHOW_PRIVACY',
default => 'yes',
description => 'Present the privacy statment',
help => '',
type => $types{boolean},
readonly => 1,
category => 'dynamic',
},
{ {
name => 'ZM_SSMTP_MAIL', name => 'ZM_SSMTP_MAIL',
default => 'no', default => 'no',
@ -3898,7 +3908,7 @@ our @options = (
SSMTP is a lightweight and efficient method to send email. SSMTP is a lightweight and efficient method to send email.
The SSMTP application is not installed by default. The SSMTP application is not installed by default.
NEW_MAIL_MODULES must also be enabled. NEW_MAIL_MODULES must also be enabled.
Please visit: http://www.zoneminder.com/wiki/index.php/How_to_get_ssmtp_working_with_Zoneminder Please visit the ZoneMinder [SSMTP Wiki page](http://www.zoneminder.com/wiki/index.php/How_to_get_ssmtp_working_with_Zoneminder)
for setup and configuration help. for setup and configuration help.
`, `,
type => $types{boolean}, type => $types{boolean},

View File

@ -0,0 +1,362 @@
package ZoneMinder::Control::Dahua;
use 5.8.0;
use strict;
use warnings;
require ZoneMinder::Base;
require ZoneMinder::Control;
our @ISA = qw(ZoneMinder::Control);
our $REALM = '';
our $USERNAME = '';
our $PASSWORD = '';
our $ADDRESS = '';
our $PROTOCOL = 'http://';
use Time::HiRes qw(usleep);
use ZoneMinder::Logger qw(:all);
use ZoneMinder::Config qw(:all);
use ZoneMinder::Database qw(zmDbConnect);
sub new
{
my $class = shift;
my $id = shift;
my $self = ZoneMinder::Control->new( $id );
bless( $self, $class );
srand( time() );
return $self;
}
our $AUTOLOAD;
sub AUTOLOAD
{
my $self = shift;
my $class = ref($self) || croak( "$self not object" );
my $name = $AUTOLOAD;
$name =~ s/.*://;
if ( exists($self->{$name}) )
{
return( $self->{$name} );
}
Fatal( "Can't access $name member of object of class $class" );
}
sub open
{
my $self = shift;
$self->loadMonitor();
# The Dahua camera firmware API supports the concept of having multiple
# channels on a single IP controller.
# As most cameras only have a single channel, and there is no similar
# information model in Zoneminder, I'm hardcoding the first and default
# channel "0", here.
$self->{dahua_channel_number} = "0";
if ( ( $self->{Monitor}->{ControlAddress} =~ /^(?<PROTOCOL>https?:\/\/)?(?<USERNAME>[^:@]+)?:?(?<PASSWORD>[^\/@]+)?@?(?<ADDRESS>.*)$/ ) ) {
$PROTOCOL = $+{PROTOCOL} if $+{PROTOCOL};
$USERNAME = $+{USERNAME} if $+{USERNAME};
$PASSWORD = $+{PASSWORD} if $+{PASSWORD};
$ADDRESS = $+{ADDRESS} if $+{ADDRESS};
} else {
Error('Failed to parse auth from address ' . $self->{Monitor}->{ControlAddress});
$ADDRESS = $self->{Monitor}->{ControlAddress};
}
if ( !($ADDRESS =~ /:/) ) {
Error('You generally need to also specify the port. I will append :80');
$ADDRESS .= ':80';
}
use LWP::UserAgent;
$self->{ua} = LWP::UserAgent->new;
$self->{ua}->agent("ZoneMinder Control Agent/".$ZoneMinder::Base::ZM_VERSION);
$self->{state} = 'closed';
# credentials: ("ip:port" (no prefix!), realm (string), username (string), password (string)
Debug("sendCmd credentials control address:'".$ADDRESS
."' realm:'" . $REALM
. "' username:'" . $USERNAME
. "' password:'".$PASSWORD
."'"
);
$self->{ua}->credentials($ADDRESS, $REALM, $USERNAME, $PASSWORD);
# Detect REALM
my $get_config_url = $PROTOCOL . $ADDRESS . "/cgi-bin/configManager.cgi?action=getConfig&name=Ptz";
my $req = HTTP::Request->new(GET=>$get_config_url);
my $res = $self->{ua}->request($req);
if ($res->is_success) {
$self->{state} = 'open';
return;
}
if ( $res->status_line() eq '401 Unauthorized' ) {
my $headers = $res->headers();
foreach my $k (keys %$headers) {
Debug("Initial Header $k => $$headers{$k}");
}
if ($$headers{'www-authenticate'}) {
my ($auth, $tokens) = $$headers{'www-authenticate'} =~ /^(\w+)\s+(.*)$/;
if ($tokens =~ /\w+="([^"]+)"/i) {
if ($REALM ne $1) {
$REALM = $1;
Debug("Changing REALM to '" . $REALM . "'");
$self->{ua}->credentials($ADDRESS, $REALM, $USERNAME, $PASSWORD);
my $req = HTTP::Request->new(GET=>$get_config_url);
$res = $self->{ua}->request($req);
if ($res->is_success()) {
$self->{state} = 'open';
return;
}
Debug('Authentication still failed after updating REALM' . $res->status_line);
$headers = $res->headers();
foreach my $k ( keys %$headers ) {
Debug("Initial Header $k => $$headers{$k}");
} # end foreach
} else {
Error('Authentication failed, not a REALM problem');
}
} else {
Error('Failed to match realm in tokens');
} # end if
} else {
Error('No WWW-Authenticate Header');
} # end if headers
} # end if $res->status_line() eq '401 Unauthorized'
}
sub close
{
my $self = shift;
$self->{state} = 'closed';
}
sub printMsg
{
my $self = shift;
my $msg = shift;
my $msg_len = length($msg);
Debug( $msg."[".$msg_len."]" );
}
sub sendGetRequest {
my $self = shift;
my $url_path = shift;
my $result = undef;
my $url = $PROTOCOL . $ADDRESS . $url_path;
my $req = HTTP::Request->new(GET=>$url);
my $res = $self->{ua}->request($req);
if ($res->is_success) {
$result = !undef;
} else {
if ($res->status_line() eq '401 Unauthorized') {
Debug("Error check failed, trying again: USERNAME: $USERNAME realm: $REALM password: " . $PASSWORD);
Debug("Content was " . $res->content() );
my $res = $self->{ua}->request($req);
if ($res->is_success) {
$result = !undef;
} else {
Error("Content was " . $res->content() );
}
}
if ( ! $result ) {
Error("Error check failed: '".$res->status_line());
}
}
return($result);
}
sub sendPtzCommand
{
my $self = shift;
my $action = shift;
my $command_code = shift;
my $arg1 = shift;
my $arg2 = shift;
my $arg3 = shift;
my $channel = $self->{dahua_channel_number};
my $url_path = "/cgi-bin/ptz.cgi?";
$url_path .= "action=" . $action . "&";
$url_path .= "channel=" . $channel . "&";
$url_path .= "code=" . $command_code . "&";
$url_path .= "arg1=" . $arg1 . "&";
$url_path .= "arg2=" . $arg2 . "&";
$url_path .= "arg3=" . $arg3;
$self->sendGetRequest($url_path);
}
sub sendMomentaryPtzCommand
{
my $self = shift;
my $command_code = shift;
my $arg1 = shift;
my $arg2 = shift;
my $arg3 = shift;
my $duration_ms = shift;
$self->sendPtzCommand("start", $command_code, $arg1, $arg2, $arg3);
my $duration_ns = $duration_ms * 1000;
usleep($duration_ns);
$self->sendPtzCommand("stop", $command_code, $arg1, $arg2, $arg3);
}
sub moveRelUpLeft
{
my $self = shift;
Debug("Move Up Left");
$self->sendMomentaryPtzCommand("LeftUp", 4, 4, 0, 500);
}
sub moveRelUp
{
my $self = shift;
Debug("Move Up");
$self->sendMomentaryPtzCommand("Up", 0, 4, 0, 500);
}
sub moveRelUpRight
{
my $self = shift;
Debug("Move Up Right");
$self->sendMomentaryPtzCommand("RightUp", 0, 4, 0, 500);
}
sub moveRelLeft
{
my $self = shift;
Debug("Move Left");
$self->sendMomentaryPtzCommand("Left", 0, 4, 0, 500);
}
sub moveRelRight
{
my $self = shift;
Debug("Move Right");
$self->sendMomentaryPtzCommand("Right", 0, 4, 0, 500);
}
sub moveRelDownLeft
{
my $self = shift;
Debug("Move Down Left");
$self->sendMomentaryPtzCommand("LeftDown", 4, 4, 0, 500);
}
sub moveRelDown
{
my $self = shift;
Debug("Move Down");
$self->sendMomentaryPtzCommand("Down", 0, 4, 0, 500);
}
sub moveRelDownRight
{
my $self = shift;
Debug("Move Down Right");
$self->sendMomentaryPtzCommand("RightDown", 4, 4, 0, 500);
}
sub zoomRelTele
{
my $self = shift;
Debug("Zoom Relative Tele");
$self->sendMomentaryPtzCommand("ZoomTele", 0, 0, 0, 500);
}
sub zoomRelWide
{
my $self = shift;
Debug("Zoom Relative Wide");
$self->sendMomentaryPtzCommand("ZoomWide", 0, 0, 0, 500);
}
sub presetClear
{
my $self = shift;
my $params = shift;
my $preset_id = $self->getParam($params, 'preset');
$self->sendPtzCommand("start", "ClearPreset", 0, $preset_id, 0);
}
sub presetSet
{
my $self = shift;
my $params = shift;
my $preset_id = $self->getParam($params, 'preset');
my $dbh = zmDbConnect(1);
my $sql = 'SELECT * FROM ControlPresets WHERE MonitorId = ? AND Preset = ?';
my $sth = $dbh->prepare($sql)
or Fatal("Can't prepare sql '$sql': " . $dbh->errstr());
my $res = $sth->execute($self->{Monitor}->{Id}, $preset_id)
or Fatal("Can't execute sql '$sql': " . $sth->errstr());
my $control_preset_row = $sth->fetchrow_hashref();
my $new_label_name = $control_preset_row->{'Label'};
$self->sendPtzCommand("start", "SetPreset", 0, $preset_id, 0);
$self->sendPtzCommand("start", "SetPresetName", $preset_id, $new_label_name, 0);
}
sub presetGoto
{
my $self = shift;
my $params = shift;
my $preset_id = $self->getParam($params, 'preset');
$self->sendPtzCommand("start", "GotoPreset", 0, $preset_id, 0);
}
1;
__END__
=head1 NAME
ZoneMinder::Control::Dahua - Perl module for Dahua cameras
=head1 SYNOPSIS
use ZoneMinder::Control::Dahua;
place this in /usr/share/perl5/ZoneMinder/Control
=head1 DESCRIPTION
This module is an implementation of the Dahua IP camera HTTP control API.
=head2 EXPORT
None by default.
=head1 COPYRIGHT AND LICENSE
Copyright (C) 2018 ZoneMinder LLC
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
=cut

View File

@ -16,7 +16,7 @@
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
# #
# ========================================================================== # ==========================================================================
# #

View File

@ -0,0 +1,365 @@
package ZoneMinder::Control::PSIA;
use 5.006;
use strict;
use warnings;
require ZoneMinder::Base;
require ZoneMinder::Control;
our @ISA = qw(ZoneMinder::Control);
our $REALM = 'TV-IP450PI';
our $USERNAME = 'admin';
our $PASSWORD = '';
our $ADDRESS = '';
our $PROTOCOL = 'http://';
use ZoneMinder::Logger qw(:all);
use ZoneMinder::Config qw(:all);
use ZoneMinder::Database qw(zmDbConnect);
sub open
{
my $self = shift;
$self->loadMonitor();
if ( ( $self->{Monitor}->{ControlAddress} =~ /^(?<PROTOCOL>https?:\/\/)?(?<USERNAME>[^:@]+)?:?(?<PASSWORD>[^\/@]+)?@?(?<ADDRESS>.*)$/ ) ) {
$PROTOCOL = $+{PROTOCOL} if $+{PROTOCOL};
$USERNAME = $+{USERNAME} if $+{USERNAME};
$PASSWORD = $+{PASSWORD} if $+{PASSWORD};
$ADDRESS = $+{ADDRESS} if $+{ADDRESS};
} else {
Error('Failed to parse auth from address ' . $self->{Monitor}->{ControlAddress});
$ADDRESS = $self->{Monitor}->{ControlAddress};
}
if ( !($ADDRESS =~ /:/) ) {
Error('You generally need to also specify the port. I will append :80');
$ADDRESS .= ':80';
}
use LWP::UserAgent;
$self->{ua} = LWP::UserAgent->new;
$self->{ua}->agent( "ZoneMinder Control Agent/".$ZoneMinder::Base::ZM_VERSION );
$self->{state} = 'closed';
Debug( "sendCmd credentials control address:'".$ADDRESS
."' realm:'" . $REALM
. "' username:'" . $USERNAME
. "' password:'".$PASSWORD
."'"
);
$self->{ua}->credentials($ADDRESS, $REALM, $USERNAME, $PASSWORD);
# Detect REALM
my $req = HTTP::Request->new(GET=>$PROTOCOL . $ADDRESS . "/PSIA/PTZ/channels");
my $res = $self->{ua}->request($req);
if ($res->is_success) {
$self->{state} = 'open';
return;
} elsif (! $res->is_success) {
Debug("Need newer REALM");
if ( $res->status_line() eq '401 Unauthorized' ) {
my $headers = $res->headers();
foreach my $k ( keys %$headers ) {
Debug("Initial Header $k => $$headers{$k}");
} # end foreach
if ( $$headers{'www-authenticate'} ) {
my ($auth, $tokens) = $$headers{'www-authenticate'} =~ /^(\w+)\s+(.*)$/;
if ($tokens =~ /\w+="([^"]+)"/i) {
$REALM = $1;
Debug("Changing REALM to $REALM");
$self->{ua}->credentials($ADDRESS, $REALM, $USERNAME, $PASSWORD);
} # end if
} else {
Debug("No WWW-Authenticate header");
} # end if www-authenticate header
} # end if $res->status_line() eq '401 Unauthorized'
} # end elsif ! $res->is_success
}
sub close
{
my $self = shift;
$self->{state} = 'closed';
}
sub printMsg
{
my $self = shift;
my $msg = shift;
my $msg_len = length($msg);
Debug( $msg."[".$msg_len."]" );
}
sub sendGetRequest {
my $self = shift;
my $url_path = shift;
my $result = undef;
my $url = $PROTOCOL . $ADDRESS . $url_path;
my $req = HTTP::Request->new(GET=>$url);
my $res = $self->{ua}->request($req);
if ($res->is_success) {
$result = !undef;
} else {
if ( $res->status_line() eq '401 Unauthorized' ) {
Error( "Error check failed, trying again: USERNAME: $USERNAME realm: $REALM password: " . $PASSWORD );
Error("Content was " . $res->content() );
my $res = $self->{ua}->request($req);
if ( $res->is_success ) {
$result = !undef;
} else {
Error("Content was " . $res->content() );
}
}
if ( ! $result ) {
Error("Error check failed: '".$res->status_line());
}
}
return($result);
}
sub sendPutRequest {
my $self = shift;
my $url_path = shift;
my $content = shift;
my $result = undef;
my $url = $PROTOCOL . $ADDRESS . $url_path;
my $req = HTTP::Request->new(PUT=>$url);
if(defined($content)) {
$req->content_type("application/x-www-form-urlencoded; charset=UTF-8");
$req->content('<?xml version="1.0" encoding="UTF-8"?>' . "\n" . $content);
}
my $res = $self->{ua}->request($req);
if ($res->is_success) {
$result = !undef;
} else {
if ( $res->status_line() eq '401 Unauthorized' ) {
Error( "Error check failed, trying again: USERNAME: $USERNAME realm: $REALM password: " . $PASSWORD );
Error("Content was " . $res->content() );
my $res = $self->{ua}->request($req);
if ( $res->is_success ) {
$result = !undef;
} else {
Error("Content was " . $res->content() );
}
}
if ( ! $result ) {
Error( "Error check failed: '".$res->status_line()."' cmd:'".$cmd."'" );
}
}
return($result);
}
sub sendDeleteRequest {
my $self = shift;
my $url_path = shift;
my $result = undef;
my $url = $PROTOCOL . $ADDRESS . $url_path;
my $req = HTTP::Request->new(DELETE=>$url);
my $res = $self->{ua}->request($req);
if ($res->is_success) {
$result = !undef;
} else {
if ( $res->status_line() eq '401 Unauthorized' ) {
Error( "Error check failed, trying again: USERNAME: $USERNAME realm: $REALM password: " . $PASSWORD );
Error("Content was " . $res->content() );
my $res = $self->{ua}->request($req);
if ( $res->is_success ) {
$result = !undef;
} else {
Error("Content was " . $res->content() );
}
}
if ( ! $result ) {
Error( "Error check failed: '".$res->status_line()."' cmd:'".$cmd."'" );
}
}
return($result);
}
sub move
{
my $self = shift;
my $panPercentage = shift;
my $tiltPercentage = shift;
my $zoomPercentage = shift;
my $cmd = "set_relative_pos&posX=$panSteps&posY=$tiltSteps";
my $ptzdata = '<PTZData version="1.0" xmlns="urn:psialliance-org">';
$ptzdata .= '<pan>' . $panPercentage . '</pan>';
$ptzdata .= '<tilt>' . $tiltPercentage . '</tilt>';
$ptzdata .= '<zoom>' . $zoomPercentage . '</zoom>';
$ptzdata .= '<Momentary><duration>500</duration></Momentary>';
$ptzdata .= '</PTZData>';
$self->sendPutRequest("/PSIA/PTZ/channels/1/momentary", $ptzdata);
}
sub moveRelUpLeft
{
my $self = shift;
Debug( "Move Up Left" );
$self->move(-50, 50, 0);
}
sub moveRelUp
{
my $self = shift;
Debug( "Move Up" );
$self->move(0, 50, 0);
}
sub moveRelUpRight
{
my $self = shift;
Debug( "Move Up Right" );
$self->move(50, 50, 0);
}
sub moveRelLeft
{
my $self = shift;
Debug( "Move Left" );
$self->move(-50, 0, 0);
}
sub moveRelRight
{
my $self = shift;
Debug( "Move Right" );
$self->move(50, 0, 0);
}
sub moveRelDownLeft
{
my $self = shift;
Debug( "Move Down Left" );
$self->move(-50, -50, 0);
}
sub moveRelDown
{
my $self = shift;
Debug( "Move Down" );
$self->move(0, -50, 0);
}
sub moveRelDownRight
{
my $self = shift;
Debug( "Move Down Right" );
$self->move(50, -50, 0);
}
sub zoomRelTele
{
my $self = shift;
Debug("Zoom Relative Tele");
$self->move(0, 0, 50);
}
sub zoomRelWide
{
my $self = shift;
Debug("Zoom Relative Wide");
$self->move(0, 0, -50);
}
sub presetClear
{
my $self = shift;
my $params = shift;
my $preset_id = $self->getParam($params, 'preset');
my $url_path = "/PSIA/PTZ/channels/1/presets/" . $preset_id;
$self->sendDeleteRequest($url_path);
}
sub presetSet
{
my $self = shift;
my $params = shift;
my $preset_id = $self->getParam($params, 'preset');
my $dbh = zmDbConnect(1);
my $sql = 'SELECT * FROM ControlPresets WHERE MonitorId = ? AND Preset = ?';
my $sth = $dbh->prepare($sql)
or Fatal("Can't prepare sql '$sql': " . $dbh->errstr());
my $res = $sth->execute($self->{Monitor}->{Id}, $preset_id)
or Fatal("Can't execute sql '$sql': " . $sth->errstr());
my $control_preset_row = $sth->fetchrow_hashref();
my $new_label_name = $control_preset_row->{'Label'};
my $url_path = "/PSIA/PTZ/channels/1/presets/" . $preset_id;
my $ptz_preset_data = '<PTZPreset>';
$ptz_preset_data .= '<id>' . $preset_id . '</id>';
$ptz_preset_data .= '<presetName>' . $new_label_name . '</presetName>';
$ptz_preset_data .= '</PTZPreset>';
$self->sendPutRequest($url_path, $ptz_preset_data);
}
sub presetGoto
{
my $self = shift;
my $params = shift;
my $preset_id = $self->getParam($params, 'preset');
my $url_path = '/PSIA/PTZ/channels/1/presets/' . $preset_id . '/goto';
$self->sendPutRequest($url_path);
}
1;
__END__
=head1 NAME
ZoneMinder::Control::PSIA - Perl module for cameras implementing the PSIA
(Physical Security Interoperability Alliance), IP Media Devices API
specification
=head1 SYNOPSIS
use ZoneMinder::Control::PSIA;
place this in /usr/share/perl5/ZoneMinder/Control
=head1 DESCRIPTION
This has so far been tested with:
- Trendnet TV-IP450PI
=head2 EXPORT
None by default.
=head1 COPYRIGHT AND LICENSE
Copyright (C) 2018 ZoneMinder LLC
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
=cut

View File

@ -16,7 +16,7 @@
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
# #
# ========================================================================== # ==========================================================================
# #

View File

@ -41,17 +41,18 @@ our @ISA = qw(Exporter ZoneMinder::Base);
# If you do not need this, moving things directly into @EXPORT or @EXPORT_OK # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
# will save memory. # will save memory.
our %EXPORT_TAGS = ( our %EXPORT_TAGS = (
'functions' => [ qw( functions => [ qw(
zmDbConnect zmDbConnect
zmDbDisconnect zmDbDisconnect
zmDbGetMonitors zmDbGetMonitors
zmDbGetMonitor zmDbGetMonitor
zmDbGetMonitorAndControl zmDbGetMonitorAndControl
zmDbDo
) ] ) ]
); );
push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS;
our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } );
our @EXPORT = qw(); our @EXPORT = qw();
@ -66,8 +67,6 @@ our $VERSION = $ZoneMinder::Base::VERSION;
use ZoneMinder::Logger qw(:all); use ZoneMinder::Logger qw(:all);
use ZoneMinder::Config qw(:all); use ZoneMinder::Config qw(:all);
use Carp;
our $dbh = undef; our $dbh = undef;
sub zmDbConnect { sub zmDbConnect {
@ -93,7 +92,7 @@ sub zmDbConnect {
my $sslOptions = ''; my $sslOptions = '';
if ( $Config{ZM_DB_SSL_CA_CERT} ) { if ( $Config{ZM_DB_SSL_CA_CERT} ) {
$sslOptions = ';'.join(';', $sslOptions = join(';','',
'mysql_ssl=1', 'mysql_ssl=1',
'mysql_ssl_ca_file='.$Config{ZM_DB_SSL_CA_CERT}, 'mysql_ssl_ca_file='.$Config{ZM_DB_SSL_CA_CERT},
'mysql_ssl_client_key='.$Config{ZM_DB_SSL_CLIENT_KEY}, 'mysql_ssl_client_key='.$Config{ZM_DB_SSL_CLIENT_KEY},
@ -102,8 +101,9 @@ sub zmDbConnect {
} }
eval { eval {
$dbh = DBI->connect( 'DBI:mysql:database='.$Config{ZM_DB_NAME} $dbh = DBI->connect(
.$socket . $sslOptions . ($options?';'.join(';', map { $_.'='.$$options{$_} } keys %{$options} ) : '') 'DBI:mysql:database='.$Config{ZM_DB_NAME}
.$socket . $sslOptions . ($options?join(';', '', map { $_.'='.$$options{$_} } keys %{$options} ) : '')
, $Config{ZM_DB_USER} , $Config{ZM_DB_USER}
, $Config{ZM_DB_PASS} , $Config{ZM_DB_PASS}
); );
@ -125,7 +125,7 @@ sub zmDbConnect {
sub zmDbDisconnect { sub zmDbDisconnect {
if ( defined( $dbh ) ) { if ( defined( $dbh ) ) {
$dbh->disconnect(); $dbh->disconnect() or Error('Error disconnecting db? ' . $dbh->errstr());
$dbh = undef; $dbh = undef;
} }
} }
@ -141,7 +141,7 @@ sub zmDbGetMonitors {
zmDbConnect(); zmDbConnect();
my $function = shift || DB_MON_ALL; my $function = shift || DB_MON_ALL;
my $sql = "select * from Monitors"; my $sql = 'SELECT * FROM Monitors';
if ( $function ) { if ( $function ) {
if ( $function == DB_MON_CAPT ) { if ( $function == DB_MON_CAPT ) {
@ -156,26 +156,38 @@ sub zmDbGetMonitors {
$sql .= " where Function = 'Nodect'"; $sql .= " where Function = 'Nodect'";
} }
} }
my $sth = $dbh->prepare_cached( $sql ) my $sth = $dbh->prepare_cached( $sql );
or croak( "Can't prepare '$sql': ".$dbh->errstr() ); if ( ! $sth ) {
my $res = $sth->execute() Error("Can't prepare '$sql': ".$dbh->errstr());
or croak( "Can't execute '$sql': ".$sth->errstr() ); return undef;
}
my $res = $sth->execute();
if ( ! $res ) {
Error("Can't execute '$sql': ".$sth->errstr());
return undef;
}
my @monitors; my @monitors;
while( my $monitor = $sth->fetchrow_hashref() ) { while( my $monitor = $sth->fetchrow_hashref() ) {
push( @monitors, $monitor ); push( @monitors, $monitor );
} }
$sth->finish(); $sth->finish();
return( \@monitors ); return \@monitors;
} }
sub zmSQLExecute { sub zmSQLExecute {
my $sql = shift; my $sql = shift;
my $sth = $dbh->prepare_cached( $sql ) my $sth = $dbh->prepare_cached( $sql );
or croak( "Can't prepare '$sql': ".$dbh->errstr() ); if ( ! $sth ) {
my $res = $sth->execute( @_ ) Error("Can't prepare '$sql': ".$dbh->errstr());
or croak( "Can't execute '$sql': ".$sth->errstr() ); return undef;
}
my $res = $sth->execute( @_ );
if ( ! $res ) {
Error("Can't execute '$sql': ".$sth->errstr());
return undef;
}
return 1; return 1;
} }
@ -185,17 +197,22 @@ sub zmDbGetMonitor {
my $id = shift; my $id = shift;
if ( !defined($id) ) { if ( !defined($id) ) {
croak("Undefined id in zmDbgetMonitor"); Error('Undefined id in zmDbgetMonitor');
return undef ; return undef ;
} }
my $sql = 'SELECT * FROM Monitors WHERE Id = ?'; my $sql = 'SELECT * FROM Monitors WHERE Id = ?';
my $sth = $dbh->prepare_cached($sql) my $sth = $dbh->prepare_cached($sql);
or croak("Can't prepare '$sql': ".$dbh->errstr()); if ( !$sth ) {
my $res = $sth->execute($id) Error("Can't prepare '$sql': ".$dbh->errstr());
or croak("Can't execute '$sql': ".$sth->errstr()); return undef;
}
my $res = $sth->execute($id);
if ( $res ) {
Error("Can't execute '$sql': ".$sth->errstr());
return undef;
}
my $monitor = $sth->fetchrow_hashref(); my $monitor = $sth->fetchrow_hashref();
return $monitor; return $monitor;
} }
@ -204,25 +221,28 @@ sub zmDbGetMonitorAndControl {
my $id = shift; my $id = shift;
return( undef ) if ( !defined($id) ); return undef if !defined($id);
my $sql = "SELECT C.*,M.*,C.Protocol my $sql = 'SELECT C.*,M.*,C.Protocol
FROM Monitors as M FROM Monitors as M
INNER JOIN Controls as C on (M.ControlId = C.Id) INNER JOIN Controls as C on (M.ControlId = C.Id)
WHERE M.Id = ?" WHERE M.Id = ?'
; ;
my $sth = $dbh->prepare_cached( $sql ) my $sth = $dbh->prepare_cached($sql);
or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); if ( !$sth ) {
my $res = $sth->execute( $id ) Error("Can't prepare '$sql': ".$dbh->errstr());
or Fatal( "Can't execute '$sql': ".$sth->errstr() ); return undef;
}
my $res = $sth->execute( $id );
if ( !$res ) {
Error("Can't execute '$sql': ".$sth->errstr());
return undef;
}
my $monitor = $sth->fetchrow_hashref(); my $monitor = $sth->fetchrow_hashref();
return $monitor;
return( $monitor );
} }
sub start_transaction { sub start_transaction {
#my ( $caller, undef, $line ) = caller;
#$openprint::log->debug("Called start_transaction from $caller : $line");
my $d = shift; my $d = shift;
$d = $dbh if ! $d; $d = $dbh if ! $d;
my $ac = $d->{AutoCommit}; my $ac = $d->{AutoCommit};
@ -231,68 +251,54 @@ sub start_transaction {
} # end sub start_transaction } # end sub start_transaction
sub end_transaction { sub end_transaction {
#my ( $caller, undef, $line ) = caller;
#$openprint::log->debug("Called end_transaction from $caller : $line");
my ( $d, $ac ) = @_; my ( $d, $ac ) = @_;
if ( ! defined $ac ) { if ( ! defined $ac ) {
Error("Undefined ac"); Error("Undefined ac");
} }
$d = $dbh if ! $d; $d = $dbh if ! $d;
if ( $ac ) { if ( $ac ) {
#$log->debug("Committing");
$d->commit(); $d->commit();
} # end if } # end if
$d->{AutoCommit} = $ac; $d->{AutoCommit} = $ac;
} # end sub end_transaction } # end sub end_transaction
# Basic execution of $dbh->do but with some pretty logging of the sql on error.
# Returns 1 on success, 0 on error
sub zmDbDo {
my $sql = shift;
if ( ! $dbh->do($sql, undef, @_) ) {
$sql =~ s/\?/'%s'/;
Error(sprintf("Failed $sql :", @_).$dbh->errstr());
return 0;
}
return 1;
}
1; 1;
__END__ __END__
# Below is stub documentation for your module. You'd better edit it!
=head1 NAME =head1 NAME
ZoneMinder::Database - Perl extension for blah blah blah ZoneMinder::Database - Perl module containing database functions used in ZM
=head1 SYNOPSIS =head1 SYNOPSIS
use ZoneMinder::Database; use ZoneMinder::Database;
blah blah blah
=head1 DESCRIPTION =head1 DESCRIPTION
Stub documentation for ZoneMinder, created by h2xs. It looks like the
author of the extension was negligent enough to leave the stub
unedited.
Blah blah blah.
=head2 EXPORT =head2 EXPORT
None by default. zmDbConnect
zmDbDisconnect
zmDbGetMonitors
zmDbGetMonitor
=head1 SEE ALSO zmDbGetMonitorAndControl
zmDbDo
Mention other useful documentation such as the documentation of
related modules or operating system documentation (such as man pages
in UNIX), or any relevant external documentation such as RFCs or
standards.
If you have a mailing list set up for your module, mention it here.
If you have a web site set up for your module, mention it here.
=head1 AUTHOR =head1 AUTHOR
Philip Coombes, E<lt>philip.coombes@zoneminder.comE<gt> Philip Coombes, E<lt>philip.coombes@zoneminder.comE<gt>
=head1 COPYRIGHT AND LICENSE
Copyright (C) 2001-2008 Philip Coombes
This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.8.3 or,
at your option, any later version of Perl 5 you may have available.
=cut =cut

View File

@ -35,7 +35,6 @@ require Date::Manip;
require File::Find; require File::Find;
require File::Path; require File::Path;
require File::Copy; require File::Copy;
require File::Slurp;
require File::Basename; require File::Basename;
require Number::Bytes::Human; require Number::Bytes::Human;
@ -71,6 +70,7 @@ $serial = $primary_key = 'Id';
Frames Frames
AlarmFrames AlarmFrames
DefaultVideo DefaultVideo
SaveJPEGs
TotScore TotScore
AvgScore AvgScore
MaxScore MaxScore
@ -84,6 +84,7 @@ $serial = $primary_key = 'Id';
StateId StateId
Orientation Orientation
DiskSpace DiskSpace
Scheme
); );
use POSIX; use POSIX;
@ -169,6 +170,8 @@ sub Path {
sub Scheme { sub Scheme {
my $self = shift; my $self = shift;
$$self{Scheme} = shift if @_;
if ( ! $$self{Scheme} ) { if ( ! $$self{Scheme} ) {
if ( $$self{RelativePath} ) { if ( $$self{RelativePath} ) {
if ( $$self{RelativePath} =~ /^\d+\/\d{4}\-\d{2}\-\d{2}\/\d+$/ ) { if ( $$self{RelativePath} =~ /^\d+\/\d{4}\-\d{2}\-\d{2}\/\d+$/ ) {
@ -239,7 +242,7 @@ sub LinkPath {
'.'.$$event{Id} '.'.$$event{Id}
); );
} elsif ( $$event{Path} ) { } elsif ( $$event{Path} ) {
if ( ( $$event{Path} =~ /^(\d+\/\d{4}\/\d{2}\/\d{2})/ ) ) { if ( ( $event->RelativePath() =~ /^(\d+\/\d{4}\/\d{2}\/\d{2})/ ) ) {
$$event{LinkPath} = $1.'/.'.$$event{Id}; $$event{LinkPath} = $1.'/.'.$$event{Id};
} else { } else {
Error("Unable to get LinkPath from Path for $$event{Id} $$event{Path}"); Error("Unable to get LinkPath from Path for $$event{Id} $$event{Path}");
@ -255,6 +258,33 @@ sub LinkPath {
return $$event{LinkPath}; return $$event{LinkPath};
} # end sub LinkPath } # end sub LinkPath
sub createPath {
makePath($_[0]->Path());
}
sub createLinkPath {
my $LinkPath = $_[0]->LinkPath();
my $EventPath = $_[0]->EventPath();
if ( $LinkPath ) {
if ( !symlink($EventPath, $LinkPath) ) {
Error("Failed symlinking $EventPath to $LinkPath");
}
}
}
sub idPath {
return sprintf('%s/.%d', $_[0]->Path(), $_[0]->{Id});
}
sub createIdFile {
my $event = shift;
my $idFile = $event->idPath();
open( my $ID_FP, '>', $idFile )
or Error("Can't open $idFile: $!");
close($ID_FP);
setFileOwner($idFile);
}
sub GenerateVideo { sub GenerateVideo {
my ( $self, $rate, $fps, $scale, $size, $overwrite, $format ) = @_; my ( $self, $rate, $fps, $scale, $size, $overwrite, $format ) = @_;
@ -290,11 +320,11 @@ sub GenerateVideo {
my $file_size = 'S'.$size; my $file_size = 'S'.$size;
push( @file_parts, $file_size ); push( @file_parts, $file_size );
} }
my $video_file = "$video_name-".$file_parts[0]."-".$file_parts[1].".$format"; my $video_file = join('-', $video_name, $file_parts[0], $file_parts[1] ).'.'.$format;
if ( $overwrite || !-s $video_file ) { if ( $overwrite || !-s $video_file ) {
Info( "Creating video file $video_file for event $self->{Id}\n" ); Info("Creating video file $video_file for event $self->{Id}");
my $frame_rate = sprintf( "%.2f", $self->{Frames}/$self->{FullLength} ); my $frame_rate = sprintf('%.2f', $self->{Frames}/$self->{FullLength});
if ( $rate ) { if ( $rate ) {
if ( $rate != 1.0 ) { if ( $rate != 1.0 ) {
$frame_rate *= $rate; $frame_rate *= $rate;
@ -325,19 +355,19 @@ sub GenerateVideo {
.$Config{ZM_FFMPEG_OUTPUT_OPTIONS} .$Config{ZM_FFMPEG_OUTPUT_OPTIONS}
." '$video_file' > ffmpeg.log 2>&1" ." '$video_file' > ffmpeg.log 2>&1"
; ;
Debug( $command."\n" ); Debug($command);
my $output = qx($command); my $output = qx($command);
my $status = $? >> 8; my $status = $? >> 8;
if ( $status ) { if ( $status ) {
Error( "Unable to generate video, check $event_path/ffmpeg.log for details"); Error("Unable to generate video, check $event_path/ffmpeg.log for details");
return; return;
} }
Info( "Finished $video_file\n" ); Info("Finished $video_file");
return $event_path.'/'.$video_file; return $event_path.'/'.$video_file;
} else { } else {
Info( "Video file $video_file already exists for event $self->{Id}\n" ); Info("Video file $video_file already exists for event $self->{Id}");
return $event_path.'/'.$video_file; return $event_path.'/'.$video_file;
} }
return; return;
@ -345,57 +375,49 @@ sub GenerateVideo {
sub delete { sub delete {
my $event = $_[0]; my $event = $_[0];
my $in_zmaudit = ( $0 =~ 'zmaudit.pl$');
if ( ! $in_zmaudit ) {
if ( ! ( $event->{Id} and $event->{MonitorId} and $event->{StartTime} ) ) { if ( ! ( $event->{Id} and $event->{MonitorId} and $event->{StartTime} ) ) {
# zmfilter shouldn't delete anything in an odd situation. zmaudit will though.
my ( $caller, undef, $line ) = caller; my ( $caller, undef, $line ) = caller;
Warning("Can't Delete event $event->{Id} from Monitor $event->{MonitorId} StartTime:$event->{StartTime} from $caller:$line\n"); Warning("$0 Can't Delete event $event->{Id} from Monitor $event->{MonitorId} StartTime:".
(defined($event->{StartTime})?$event->{StartTime}:'undef')." from $caller:$line");
return; return;
} }
if ( ! -e $event->Storage()->Path() ) { if ( !($event->Storage()->Path() and -e $event->Storage()->Path()) ) {
Warning("Not deleting event because storage path doesn't exist"); Warning('Not deleting event because storage path doesn\'t exist');
return; return;
} }
Info("Deleting event $event->{Id} from Monitor $event->{MonitorId} StartTime:$event->{StartTime}\n"); }
if ( $$event{Id} ) {
# Need to have an event Id if we are to delete from the db.
Info("Deleting event $event->{Id} from Monitor $event->{MonitorId} StartTime:$event->{StartTime}");
$ZoneMinder::Database::dbh->ping(); $ZoneMinder::Database::dbh->ping();
$ZoneMinder::Database::dbh->begin_work(); $ZoneMinder::Database::dbh->begin_work();
#$event->lock_and_load(); #$event->lock_and_load();
{ ZoneMinder::Database::zmDbDo('DELETE FROM Frames WHERE EventId=?', $$event{Id});
my $sql = 'DELETE FROM Frames WHERE EventId=?'; if ( $ZoneMinder::Database::dbh->errstr() ) {
my $sth = $ZoneMinder::Database::dbh->prepare_cached($sql) $ZoneMinder::Database::dbh->commit();
or Error( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() ); return;
my $res = $sth->execute($event->{Id}) }
or Error( "Can't execute '$sql': ".$sth->errstr() ); ZoneMinder::Database::zmDbDo('DELETE FROM Stats WHERE EventId=?', $$event{Id});
$sth->finish();
if ( $ZoneMinder::Database::dbh->errstr() ) { if ( $ZoneMinder::Database::dbh->errstr() ) {
$ZoneMinder::Database::dbh->commit(); $ZoneMinder::Database::dbh->commit();
return; return;
} }
$sql = 'DELETE FROM Stats WHERE EventId=?'; # Do it individually to avoid locking up the table for new events
$sth = $ZoneMinder::Database::dbh->prepare_cached($sql) ZoneMinder::Database::zmDbDo('DELETE FROM Events WHERE Id=?', $$event{Id});
or Error("Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr());
$res = $sth->execute($event->{Id})
or Error("Can't execute '$sql': ".$sth->errstr());
$sth->finish();
if ( $ZoneMinder::Database::dbh->errstr() ) {
$ZoneMinder::Database::dbh->commit(); $ZoneMinder::Database::dbh->commit();
return;
}
} }
# Do it individually to avoid locking up the table for new events if ( ( $in_zmaudit or (!$Config{ZM_OPT_FAST_DELETE})) and $event->Storage()->DoDelete() ) {
{ $event->delete_files();
my $sql = 'DELETE FROM Events WHERE Id=?';
my $sth = $ZoneMinder::Database::dbh->prepare_cached($sql)
or Error("Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr());
my $res = $sth->execute($event->{Id})
or Error("Can't execute '$sql': ".$sth->errstr());
$sth->finish();
}
$ZoneMinder::Database::dbh->commit();
if ( (! $Config{ZM_OPT_FAST_DELETE}) and $event->Storage()->DoDelete() ) {
$event->delete_files( );
} else { } else {
Debug('Not deleting event files from '.$event->Path().' for speed.'); Debug('Not deleting event files from '.$event->Path().' for speed.');
} }
@ -404,11 +426,11 @@ sub delete {
sub delete_files { sub delete_files {
my $event = shift; my $event = shift;
my $Storage = @_ ? $_[0] : new ZoneMinder::Storage( $$event{StorageId} ); my $Storage = @_ ? $_[0] : new ZoneMinder::Storage($$event{StorageId});
my $storage_path = $Storage->Path(); my $storage_path = $Storage->Path();
if ( ! $storage_path ) { if ( ! $storage_path ) {
Error("Empty storage path when deleting files for event $$event{Id} with storage id $$event{StorageId} "); Error("Empty storage path when deleting files for event $$event{Id} with storage id $$event{StorageId}");
return; return;
} }
@ -417,13 +439,13 @@ sub delete_files {
return; return;
} }
my $event_path = $event->RelativePath(); my $event_path = $event->RelativePath();
Debug("Deleting files for Event $$event{Id} from $storage_path/$event_path."); Debug("Deleting files for Event $$event{Id} from $storage_path/$event_path, scheme is $$event{Scheme}.");
if ( $event_path ) { if ( $event_path ) {
( $storage_path ) = ( $storage_path =~ /^(.*)$/ ); # De-taint ( $storage_path ) = ( $storage_path =~ /^(.*)$/ ); # De-taint
( $event_path ) = ( $event_path =~ /^(.*)$/ ); # De-taint ( $event_path ) = ( $event_path =~ /^(.*)$/ ); # De-taint
my $deleted = 0; my $deleted = 0;
if ( $$Storage{Type} eq 's3fs' ) { if ( $$Storage{Type} and ( $$Storage{Type} eq 's3fs' ) ) {
my ( $aws_id, $aws_secret, $aws_host, $aws_bucket ) = ( $$Storage{Url} =~ /^\s*([^:]+):([^@]+)@([^\/]*)\/(.+)\s*$/ ); my ( $aws_id, $aws_secret, $aws_host, $aws_bucket ) = ( $$Storage{Url} =~ /^\s*([^:]+):([^@]+)@([^\/]*)\/(.+)\s*$/ );
eval { eval {
require Net::Amazon::S3; require Net::Amazon::S3;
@ -440,23 +462,23 @@ sub delete_files {
if ( $bucket->delete_key($event_path) ) { if ( $bucket->delete_key($event_path) ) {
$deleted = 1; $deleted = 1;
} else { } else {
Error("Failed to delete from S3:".$s3->err . ": " . $s3->errstr); Error('Failed to delete from S3:'.$s3->err . ': ' . $s3->errstr);
} }
}; };
Error($@) if $@; Error($@) if $@;
} }
if ( ! $deleted ) { if ( !$deleted ) {
my $command = "/bin/rm -rf $storage_path/$event_path"; my $command = "/bin/rm -rf $storage_path/$event_path";
ZoneMinder::General::executeShellCommand( $command ); ZoneMinder::General::executeShellCommand($command);
} }
} }
if ( $event->Scheme() eq 'Deep' ) { if ( $event->Scheme() eq 'Deep' ) {
my $link_path = $event->LinkPath(); my $link_path = $event->LinkPath();
Debug("Deleting files for Event $$event{Id} from $storage_path/$link_path."); Debug("Deleting link for Event $$event{Id} from $storage_path/$link_path.");
if ( $link_path ) { if ( $link_path ) {
( $link_path ) = ( $link_path =~ /^(.*)$/ ); # De-taint ( $link_path ) = ( $link_path =~ /^(.*)$/ ); # De-taint
unlink($storage_path.'/'.$link_path) or Error( "Unable to unlink '$storage_path/$link_path': $!" ); unlink($storage_path.'/'.$link_path) or Error("Unable to unlink '$storage_path/$link_path': $!");
} }
} }
} # end sub delete_files } # end sub delete_files
@ -476,7 +498,7 @@ sub check_for_in_filesystem {
if ( $path ) { if ( $path ) {
if ( -e $path ) { if ( -e $path ) {
my @files = glob "$path/*"; my @files = glob "$path/*";
Debug("Checking for files for event $_[0]{Id} at $path using glob $path/* found " . scalar @files . " files"); Debug("Checking for files for event $_[0]{Id} at $path using glob $path/* found " . scalar @files . ' files');
return 1 if @files; return 1 if @files;
} else { } else {
Warning("Path not found for Event $_[0]{Id} at $path"); Warning("Path not found for Event $_[0]{Id} at $path");
@ -547,7 +569,7 @@ sub MoveTo {
if ( $$OldStorage{Id} != $$self{StorageId} ) { if ( $$OldStorage{Id} != $$self{StorageId} ) {
$ZoneMinder::Database::dbh->commit(); $ZoneMinder::Database::dbh->commit();
return "Old Storage path changed, Event has moved somewhere else."; return 'Old Storage path changed, Event has moved somewhere else.';
} }
$$self{Storage} = $NewStorage; $$self{Storage} = $NewStorage;
@ -564,6 +586,7 @@ sub MoveTo {
my ( $aws_id, $aws_secret, $aws_host, $aws_bucket ) = ( $$NewStorage{Url} =~ /^\s*([^:]+):([^@]+)@([^\/]*)\/(.+)\s*$/ ); my ( $aws_id, $aws_secret, $aws_host, $aws_bucket ) = ( $$NewStorage{Url} =~ /^\s*([^:]+):([^@]+)@([^\/]*)\/(.+)\s*$/ );
eval { eval {
require Net::Amazon::S3; require Net::Amazon::S3;
require File::Slurp;
my $s3 = Net::Amazon::S3->new( { my $s3 = Net::Amazon::S3->new( {
aws_access_key_id => $aws_id, aws_access_key_id => $aws_id,
aws_secret_access_key => $aws_secret, aws_secret_access_key => $aws_secret,
@ -590,11 +613,11 @@ Debug("Files to move @files");
Debug("Moving file $file to $NewPath"); Debug("Moving file $file to $NewPath");
my $size = -s $file; my $size = -s $file;
if ( ! $size ) { if ( ! $size ) {
Info("Not moving file with 0 size"); Info('Not moving file with 0 size');
} }
my $file_contents = File::Slurp::read_file($file); my $file_contents = File::Slurp::read_file($file);
if ( ! $file_contents ) { if ( ! $file_contents ) {
die "Loaded empty file, but it had a size. Giving up"; die 'Loaded empty file, but it had a size. Giving up';
} }
my $filename = $event_path.'/'.File::Basename::basename($file); my $filename = $event_path.'/'.File::Basename::basename($file);
@ -602,7 +625,7 @@ Debug("Files to move @files");
die "Unable to add key for $filename"; die "Unable to add key for $filename";
} }
my $duration = time - $starttime; my $duration = time - $starttime;
Debug("PUT to S3 " . Number::Bytes::Human::format_bytes($size) . " in $duration seconds = " . Number::Bytes::Human::format_bytes($duration?$size/$duration:$size) . '/sec'); Debug('PUT to S3 ' . Number::Bytes::Human::format_bytes($size) . " in $duration seconds = " . Number::Bytes::Human::format_bytes($duration?$size/$duration:$size) . '/sec');
} # end foreach file. } # end foreach file.
$moved = 1; $moved = 1;

View File

@ -1,27 +1,3 @@
# ==========================================================================
#
# ZoneMinder General Utility Module, $Date$, $Revision$
# Copyright (C) 2001-2008 Philip Coombes
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# ==========================================================================
#
# This module contains the common definitions and functions used by the rest
# of the ZoneMinder scripts
#
package ZoneMinder::General; package ZoneMinder::General;
use 5.006; use 5.006;
@ -42,15 +18,13 @@ our @ISA = qw(Exporter ZoneMinder::Base);
# If you do not need this, moving things directly into @EXPORT or @EXPORT_OK # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
# will save memory. # will save memory.
our %EXPORT_TAGS = ( our %EXPORT_TAGS = (
'functions' => [ qw( functions => [ qw(
executeShellCommand executeShellCommand
getCmdFormat getCmdFormat
runCommand runCommand
setFileOwner setFileOwner
getEventPath
createEventPath createEventPath
createEvent createEvent
deleteEventFiles
makePath makePath
jsonEncode jsonEncode
jsonDecode jsonDecode
@ -58,7 +32,7 @@ our %EXPORT_TAGS = (
); );
push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS;
our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } );
our @EXPORT = qw(); our @EXPORT = qw();
@ -82,74 +56,74 @@ sub executeShellCommand {
my $output = qx( $command ); my $output = qx( $command );
my $status = $? >> 8; my $status = $? >> 8;
if ( $status || logDebugging() ) { if ( $status || logDebugging() ) {
Debug( "Command: $command\n" ); Debug("Command: $command");
chomp( $output ); chomp( $output );
Debug( "Output: $output\n" ); Debug("Output: $output");
} }
return( $status ); return $status;
} }
sub getCmdFormat { sub getCmdFormat {
Debug( "Testing valid shell syntax\n" ); Debug("Testing valid shell syntax");
my ( $name ) = getpwuid( $> ); my ( $name ) = getpwuid( $> );
if ( $name eq $Config{ZM_WEB_USER} ) { if ( $name eq $Config{ZM_WEB_USER} ) {
Debug( "Running as '$name', su commands not needed\n" ); Debug("Running as '$name', su commands not needed");
return( "" ); return '';
} }
my $null_command = "true"; my $null_command = 'true';
my $prefix = "sudo -u ".$Config{ZM_WEB_USER}." "; my $prefix = 'sudo -u '.$Config{ZM_WEB_USER}.' ';
my $suffix = ""; my $suffix = '';
my $command = $prefix.$null_command.$suffix; my $command = $prefix.$null_command.$suffix;
Debug( "Testing \"$command\"\n" ); Debug("Testing \"$command\"");
my $output = qx($command 2>&1); my $output = qx($command 2>&1);
my $status = $? >> 8; my $status = $? >> 8;
$output //= $!; $output //= $!;
if ( !$status ) { if ( !$status ) {
Debug( "Test ok, using format \"$prefix<command>$suffix\"\n" ); Debug("Test ok, using format \"$prefix<command>$suffix\"");
return( $prefix, $suffix ); return( $prefix, $suffix );
} else { } else {
chomp( $output ); chomp( $output );
Debug( "Test failed, '$output'\n" ); Debug("Test failed, '$output'");
$prefix = "su ".$Config{ZM_WEB_USER}." --shell=/bin/sh --command='"; $prefix = 'su '.$Config{ZM_WEB_USER}.q` --shell=/bin/sh --command='`;
$suffix = "'"; $suffix = q`'`;
$command = $prefix.$null_command.$suffix; $command = $prefix.$null_command.$suffix;
Debug( "Testing \"$command\"\n" ); Debug("Testing \"$command\"");
my $output = qx($command 2>&1); my $output = qx($command 2>&1);
my $status = $? >> 8; my $status = $? >> 8;
$output //= $!; $output //= $!;
if ( !$status ) { if ( !$status ) {
Debug( "Test ok, using format \"$prefix<command>$suffix\"\n" ); Debug("Test ok, using format \"$prefix<command>$suffix\"");
return( $prefix, $suffix ); return( $prefix, $suffix );
} else { } else {
chomp( $output ); chomp($output);
Debug( "Test failed, '$output'\n" ); Debug("Test failed, '$output'");
$prefix = "su ".$Config{ZM_WEB_USER}." -c '"; $prefix = "su ".$Config{ZM_WEB_USER}." -c '";
$suffix = "'"; $suffix = "'";
$command = $prefix.$null_command.$suffix; $command = $prefix.$null_command.$suffix;
Debug( "Testing \"$command\"\n" ); Debug("Testing \"$command\"");
$output = qx($command 2>&1); $output = qx($command 2>&1);
$status = $? >> 8; $status = $? >> 8;
$output //= $!; $output //= $!;
if ( !$status ) { if ( !$status ) {
Debug( "Test ok, using format \"$prefix<command>$suffix\"\n" ); Debug("Test ok, using format \"$prefix<command>$suffix\"");
return( $prefix, $suffix ); return( $prefix, $suffix );
} else { } else {
chomp( $output ); chomp($output);
Debug( "Test failed, '$output'\n" ); Debug("Test failed, '$output'");
} }
} }
} }
Error( "Unable to find valid 'su' syntax\n" ); Error("Unable to find valid 'su' syntax");
exit( -1 ); exit -1;
} } # end sub getCmdFormat
our $testedShellSyntax = 0; our $testedShellSyntax = 0;
our ( $cmdPrefix, $cmdSuffix ); our ( $cmdPrefix, $cmdSuffix );
@ -163,88 +137,32 @@ sub runCommand {
} }
my $command = shift; my $command = shift;
$command = $Config{ZM_PATH_BIN}."/".$command; $command = $Config{ZM_PATH_BIN}.'/'.$command;
if ( $cmdPrefix ) { if ( $cmdPrefix ) {
$command = $cmdPrefix.$command.$cmdSuffix; $command = $cmdPrefix.$command.$cmdSuffix;
} }
Debug( "Command: $command\n" ); Debug("Command: $command");
my $output = qx($command); my $output = qx($command);
my $status = $? >> 8; my $status = $? >> 8;
chomp( $output ); chomp($output);
if ( $status || logDebugging() ) { if ( $status || logDebugging() ) {
if ( $status ) { if ( $status ) {
Error( "Unable to run \"$command\", output is \"$output\", status is $status\n" ); Error("Unable to run \"$command\", output is \"$output\", status is $status");
} else { } else {
Debug( "Output: $output\n" ); Debug("Output: $output");
} }
} }
return( $output ); return $output;
} } # end sub runCommand
sub getEventPath {
my $event = shift;
my $Storage = new ZoneMinder::Storage( $$event{StorageId} );
my $event_path = join( '/',
$Storage->Path(),
$event->{MonitorId},
( $Config{ZM_USE_DEEP_STORAGE} ? strftime( "%y/%m/%d/%H/%M/%S", localtime($event->{Time}) ) : $event->{Id} ),
);
return( $event_path );
}
sub createEventPath { sub createEventPath {
#
# WARNING assumes running from events directory
#
my $event = shift; my $event = shift;
my $Storage = new ZoneMinder::Storage( $$event{Id} ); my $eventPath = $event->Path();
my $eventPath = $Storage->Path() . '/'.$event->{MonitorId}; $event->createPath();
$event->createIdFile();
$event->createLinkPath();
if ( $Config{ZM_USE_DEEP_STORAGE} ) { return $eventPath;
my @startTime = localtime( $event->{StartTime} );
my @datetimeParts = ();
$datetimeParts[0] = sprintf( "%02d", $startTime[5]-100 );
$datetimeParts[1] = sprintf( "%02d", $startTime[4]+1 );
$datetimeParts[2] = sprintf( "%02d", $startTime[3] );
$datetimeParts[3] = sprintf( "%02d", $startTime[2] );
$datetimeParts[4] = sprintf( "%02d", $startTime[1] );
$datetimeParts[5] = sprintf( "%02d", $startTime[0] );
my $datePath = join('/',@datetimeParts[0..2]);
my $timePath = join('/',@datetimeParts[3..5]);
makePath( $datePath, $eventPath );
$eventPath .= '/'.$datePath;
# Create event id symlink
my $idFile = sprintf( "%s/.%d", $eventPath, $event->{Id} );
symlink( $timePath, $idFile )
or Fatal( "Can't symlink $idFile -> $eventPath: $!" );
makePath( $timePath, $eventPath );
$eventPath .= '/'.$timePath;
setFileOwner( $idFile ); # Must come after directory has been created
# Create empty id tag file
$idFile = sprintf( "%s/.%d", $eventPath, $event->{Id} );
open( my $ID_FP, ">", $idFile )
or Fatal( "Can't open $idFile: $!" );
close( $ID_FP );
setFileOwner( $idFile );
} else {
makePath( $event->{Id}, $eventPath );
$eventPath .= '/'.$event->{Id};
my $idFile = sprintf( "%s/.%d", $eventPath, $event->{Id} );
open( my $ID_FP, ">", $idFile )
or Fatal( "Can't open $idFile: $!" );
close( $ID_FP );
setFileOwner( $idFile );
}
return( $eventPath );
} }
use Data::Dumper; use Data::Dumper;
@ -268,7 +186,7 @@ sub _checkProcessOwner {
$_setFileOwner = 0; $_setFileOwner = 0;
} }
} }
return( $_setFileOwner ); return $_setFileOwner;
} }
sub setFileOwner { sub setFileOwner {
@ -277,7 +195,7 @@ sub setFileOwner {
if ( _checkProcessOwner() ) { if ( _checkProcessOwner() ) {
chown( $_ownerUid, $_ownerGid, $file ) chown( $_ownerUid, $_ownerGid, $file )
or Fatal( "Can't change ownership of file '$file' to '" or Fatal( "Can't change ownership of file '$file' to '"
.$Config{ZM_WEB_USER}.":".$Config{ZM_WEB_GROUP}."': $!" .$Config{ZM_WEB_USER}.':'.$Config{ZM_WEB_GROUP}."': $!"
); );
} }
} }
@ -292,13 +210,13 @@ sub _checkForImageInfo {
}; };
$_hasImageInfo = $@?0:1; $_hasImageInfo = $@?0:1;
} }
return( $_hasImageInfo ); return $_hasImageInfo;
} }
sub createEvent { sub createEvent {
my $event = shift; my $event = shift;
Debug( "Creating event" ); Debug('Creating event');
#print( Dumper( $event )."\n" ); #print( Dumper( $event )."\n" );
_checkForImageInfo(); _checkForImageInfo();
@ -481,53 +399,6 @@ sub updateEvent {
or Fatal( "Can't execute sql '$sql': ".$sth->errstr() ); or Fatal( "Can't execute sql '$sql': ".$sth->errstr() );
} }
sub deleteEventFiles {
#
# WARNING assumes running from events directory
#
my $event_id = shift;
my $monitor_id = shift;
$monitor_id = '*' if ( !defined($monitor_id) );
if ( $Config{ZM_USE_DEEP_STORAGE} ) {
my $link_path = $monitor_id."/*/*/*/.".$event_id;
#Debug( "LP1:$link_path" );
my @links = glob($link_path);
#Debug( "L:".$links[0].": $!" );
if ( @links ) {
( $link_path ) = ( $links[0] =~ /^(.*)$/ ); # De-taint
#Debug( "LP2:$link_path" );
( my $day_path = $link_path ) =~ s/\.\d+//;
#Debug( "DP:$day_path" );
my $event_path = $day_path.readlink( $link_path );
( $event_path ) = ( $event_path =~ /^(.*)$/ ); # De-taint
#Debug( "EP:$event_path" );
my $command = "/bin/rm -rf ".$event_path;
#Debug( "C:$command" );
executeShellCommand( $command );
unlink( $link_path ) or Error( "Unable to unlink '$link_path': $!" );
my @path_parts = split( /\//, $event_path );
for ( my $i = int(@path_parts)-2; $i >= 1; $i-- ) {
my $delete_path = join( '/', @path_parts[0..$i] );
#Debug( "DP$i:$delete_path" );
my @has_files = glob( $delete_path."/*" );
#Debug( "HF1:".$has_files[0] ) if ( @has_files );
last if ( @has_files );
@has_files = glob( $delete_path."/.[0-9]*" );
#Debug( "HF2:".$has_files[0] ) if ( @has_files );
last if ( @has_files );
my $command = "/bin/rm -rf ".$delete_path;
executeShellCommand( $command );
}
}
} else {
my $command = "/bin/rm -rf $monitor_id/$event_id";
executeShellCommand( $command );
}
}
sub makePath { sub makePath {
my $path = shift; my $path = shift;
my $root = shift; my $root = shift;
@ -654,7 +525,7 @@ sub jsonDecode {
$out =~ s/=>null/=>undef/g; $out =~ s/=>null/=>undef/g;
$out =~ s/`/'/g; $out =~ s/`/'/g;
$out =~ s/qx/qq/g; $out =~ s/qx/qq/g;
( $out ) = $out =~ m/^({.+})$/; # Detaint and check it's a valid object syntax ( $out ) = $out =~ m/^(\{.+\})$/; # Detaint and check it's a valid object syntax
my $result = eval $out; my $result = eval $out;
Fatal( $@ ) if ( $@ ); Fatal( $@ ) if ( $@ );
return( $result ); return( $result );
@ -666,38 +537,33 @@ __END__
=head1 NAME =head1 NAME
ZoneMinder::Database - Perl extension for blah blah blah ZoneMinder::General - Utility Functions for ZoneMinder
=head1 SYNOPSIS =head1 SYNOPSIS
use ZoneMinder::Database; use ZoneMinder::General;
blah blah blah blah blah blah
=head1 DESCRIPTION =head1 DESCRIPTION
Stub documentation for ZoneMinder, created by h2xs. It looks like the This module contains the common definitions and functions used by the rest
author of the extension was negligent enough to leave the stub of the ZoneMinder scripts
unedited.
Blah blah blah.
=head2 EXPORT =head2 EXPORT
None by default. functions => [ qw(
executeShellCommand
getCmdFormat
runCommand
setFileOwner
createEventPath
createEvent
makePath
jsonEncode
jsonDecode
) ]
=head1 SEE ALSO
Mention other useful documentation such as the documentation of
related modules or operating system documentation (such as man pages
in UNIX), or any relevant external documentation such as RFCs or
standards.
If you have a mailing list set up for your module, mention it here.
If you have a web site set up for your module, mention it here.
=head1 AUTHOR =head1 AUTHOR
Philip Coombes, E<lt>philip.coombes@zoneminder.comE<gt> Philip Coombes, E<lt>philip.coombes@zoneminder.comE<gt>
@ -706,9 +572,18 @@ Philip Coombes, E<lt>philip.coombes@zoneminder.comE<gt>
Copyright (C) 2001-2008 Philip Coombes Copyright (C) 2001-2008 Philip Coombes
This library is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or
it under the same terms as Perl itself, either Perl version 5.8.3 or, modify it under the terms of the GNU General Public License
at your option, any later version of Perl 5 you may have available. as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
=cut =cut

View File

@ -440,12 +440,12 @@ sub databaseLevel {
if ( defined($databaseLevel) ) { if ( defined($databaseLevel) ) {
$databaseLevel = $this->limit($databaseLevel); $databaseLevel = $this->limit($databaseLevel);
if ( $this->{databaseLevel} != $databaseLevel ) { if ( $this->{databaseLevel} != $databaseLevel ) {
if ( $databaseLevel > NOLOG and $this->{databaseLevel} <= NOLOG ) { if ( ( $databaseLevel > NOLOG ) and ( $this->{databaseLevel} <= NOLOG ) ) {
if ( !$this->{dbh} ) { if ( ! ( $ZoneMinder::Database::dbh or ZoneMinder::Database::zmDbConnect() ) ) {
$this->{dbh} = ZoneMinder::Database::zmDbConnect(); Warning("Failed connecting to db. Not using database logging.");
$this->{databaseLevel} = NOLOG;
return NOLOG;
} }
} elsif ( $databaseLevel <= NOLOG && $this->{databaseLevel} > NOLOG ) {
undef($this->{dbh});
} }
$this->{databaseLevel} = $databaseLevel; $this->{databaseLevel} = $databaseLevel;
} }
@ -558,34 +558,39 @@ sub logPrint {
} }
if ( $level <= $this->{databaseLevel} ) { if ( $level <= $this->{databaseLevel} ) {
if ( ! ( $this->{dbh} and $this->{dbh}->ping() ) ) { if ( ! ( $ZoneMinder::Database::dbh and $ZoneMinder::Database::dbh->ping() ) ) {
$this->{sth} = undef; $this->{sth} = undef;
if ( ! ( $this->{dbh} = ZoneMinder::Database::zmDbConnect() ) ) { # Turn this off because zDbConnect will do logging calls.
#print(STDERR "Can't log to database: "); my $oldlevel = $this->{databaseLevel};
$this->{databaseLevel} = NOLOG; $this->{databaseLevel} = NOLOG;
if ( ! ZoneMinder::Database::zmDbConnect() ) {
#print(STDERR "Can't log to database: ");
return; return;
} }
$this->{databaseLevel} = $oldlevel;
} }
my $sql = 'INSERT INTO Logs ( TimeKey, Component, Pid, Level, Code, Message, File, Line ) VALUES ( ?, ?, ?, ?, ?, ?, ?, NULL )'; my $sql = 'INSERT INTO Logs ( TimeKey, Component, ServerId, Pid, Level, Code, Message, File, Line ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, NULL )';
$this->{sth} = $this->{dbh}->prepare_cached($sql) if ! $this->{sth}; $this->{sth} = $ZoneMinder::Database::dbh->prepare_cached($sql) if ! $this->{sth};
if ( !$this->{sth} ) { if ( !$this->{sth} ) {
$this->{databaseLevel} = NOLOG; $this->{databaseLevel} = NOLOG;
Error("Can't prepare log entry '$sql': ".$this->{dbh}->errstr()); Error("Can't prepare log entry '$sql': ".$ZoneMinder::Database::dbh->errstr());
return; return;
} }
my $res = $this->{sth}->execute($seconds+($microseconds/1000000.0) my $res = $this->{sth}->execute(
, $this->{id} $seconds+($microseconds/1000000.0),
, $$ $this->{id},
, $level ($Config{ZM_SERVER_ID} ? $Config{ZM_SERVER_ID} : undef),
, $codes{$level} $$,
, $string $level,
, $this->{fileName} $codes{$level},
$string,
$this->{fileName},
); );
if ( !$res ) { if ( !$res ) {
$this->{databaseLevel} = NOLOG; $this->{databaseLevel} = NOLOG;
Error("Can't execute log entry '$sql': ".$this->{dbh}->errstr()); Error("Can't execute log entry '$sql': ".$ZoneMinder::Database::dbh->errstr());
} }
} # end if doing db logging } # end if doing db logging
} # end if level < effectivelevel } # end if level < effectivelevel
@ -699,6 +704,8 @@ sub Fatal( @ ) {
if ( $SIG{TERM} and ( $SIG{TERM} ne 'DEFAULT' ) ) { if ( $SIG{TERM} and ( $SIG{TERM} ne 'DEFAULT' ) ) {
$SIG{TERM}(); $SIG{TERM}();
} }
# I think if we don't disconnect we will leave sockets around in TIME_WAIT
ZoneMinder::Database::zmDbDisconnect();
exit(-1); exit(-1);
} }

View File

@ -161,7 +161,6 @@ our $mem_data = {
format => { type=>'uint8', seq=>$mem_seq++ }, format => { type=>'uint8', seq=>$mem_seq++ },
imagesize => { type=>'uint32', seq=>$mem_seq++ }, imagesize => { type=>'uint32', seq=>$mem_seq++ },
epadding1 => { type=>'uint32', seq=>$mem_seq++ }, epadding1 => { type=>'uint32', seq=>$mem_seq++ },
epadding2 => { type=>'uint32', seq=>$mem_seq++ },
startup_time => { type=>'time_t64', seq=>$mem_seq++ }, startup_time => { type=>'time_t64', seq=>$mem_seq++ },
last_write_time => { type=>'time_t64', seq=>$mem_seq++ }, last_write_time => { type=>'time_t64', seq=>$mem_seq++ },
last_read_time => { type=>'time_t64', seq=>$mem_seq++ }, last_read_time => { type=>'time_t64', seq=>$mem_seq++ },

View File

@ -456,7 +456,7 @@ sub transform {
sub to_string { sub to_string {
my $type = ref($_[0]); my $type = ref($_[0]);
my $fields = eval '\%'.$type.'::fields'; my $fields = eval '\%'.$type.'::fields';
return $type . ': '. join(' ' , map { $_[0]{$_} ? "$_ => $_[0]{$_}" : () } keys %$fields ); return $type . ': '. join(' ' , map { $_[0]{$_} ? "$_ => $_[0]{$_}" : () } sort { $a cmp $b } keys %$fields );
} }
1; 1;

View File

@ -15,7 +15,7 @@
# #
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
# #
# ========================================================================== # ==========================================================================
# #
@ -69,12 +69,12 @@ sub find {
push @sql_values, $sql_filters{Name}; push @sql_values, $sql_filters{Name};
} }
if ( exists $sql_filters{ServerId} ) { if ( exists $sql_filters{ServerId} ) {
push @sql_filters, ' Id IN ( SELECT StorageId FROM Monitors WHERE ServerId=? )'; push @sql_filters, ' ServerId = ?';
push @sql_values, $sql_filters{ServerId}; push @sql_values, $sql_filters{ServerId};
} }
$sql .= ' WHERE ' . join(' AND ', @sql_filters ) if @sql_filters; $sql .= ' WHERE ' . join(' AND ', @sql_filters) if @sql_filters;
$sql .= ' LIMIT ' . $sql_filters{limit} if $sql_filters{limit}; $sql .= ' LIMIT ' . $sql_filters{limit} if $sql_filters{limit};
my $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql ) my $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql )
@ -88,6 +88,7 @@ sub find {
my $filter = new ZoneMinder::Storage( $$db_filter{Id}, $db_filter ); my $filter = new ZoneMinder::Storage( $$db_filter{Id}, $db_filter );
push @results, $filter; push @results, $filter;
} # end while } # end while
Debug("SQL: $sql returned " . @results . ' results');
return @results; return @results;
} }

View File

@ -67,6 +67,7 @@ my $level = 1;
my $monitor_id = 0; my $monitor_id = 0;
my $version; my $version;
my $force = 0; my $force = 0;
my $server_id = undef;
my $storage_id = undef; my $storage_id = undef;
logInit(); logInit();
@ -78,6 +79,7 @@ GetOptions(
level =>\$level, level =>\$level,
'monitor_id=i' =>\$monitor_id, 'monitor_id=i' =>\$monitor_id,
report =>\$report, report =>\$report,
'server_id=i' =>\$server_id,
'storage_id=i' =>\$storage_id, 'storage_id=i' =>\$storage_id,
version =>\$version version =>\$version
) or pod2usage(-exitstatus => -1); ) or pod2usage(-exitstatus => -1);
@ -181,13 +183,15 @@ MAIN: while( $loop ) {
Term(); Term();
} }
Info("Auditing Storage Area $Storage_Areas[0]{Id} $Storage_Areas[0]{Name} at $Storage_Areas[0]{Path}"); Info("Auditing Storage Area $Storage_Areas[0]{Id} $Storage_Areas[0]{Name} at $Storage_Areas[0]{Path}");
} elsif ( $Config{ZM_SERVER_ID} ) { } elsif ( $server_id ) {
@Storage_Areas = ZoneMinder::Storage->find( ServerId => $Config{ZM_SERVER_ID} ); @Storage_Areas = ZoneMinder::Storage->find( ServerId => $server_id );
if ( ! @Storage_Areas ) { if ( ! @Storage_Areas ) {
Error("No Storage Area found with ServerId =" . $Config{ZM_SERVER_ID}); Error("No Storage Area found with ServerId =" . $server_id);
Term(); Term();
} }
Info("Auditing All Storage Areas on Server " . $Storage_Areas[0]->Server()->Name()); foreach my $Storage ( @Storage_Areas ) {
Info('Auditing ' . $Storage->Name() . ' at ' . $Storage->Path() . ' on ' . $Storage->Server()->Name() );
}
} else { } else {
@Storage_Areas = ZoneMinder::Storage->find(); @Storage_Areas = ZoneMinder::Storage->find();
Info("Auditing All Storage Areas"); Info("Auditing All Storage Areas");
@ -260,33 +264,39 @@ MAIN: while( $loop ) {
Error("Can't open directory '$$Storage{Path}/$day_dir': $!"); Error("Can't open directory '$$Storage{Path}/$day_dir': $!");
next; next;
} }
my %event_ids_by_path;
my @event_links = sort { $b <=> $a } grep { -l $_ } readdir( DIR ); my @event_links = sort { $b <=> $a } grep { -l $_ } readdir( DIR );
Debug("Have " . @event_links . ' event links'); Debug("Have " . @event_links . ' event links');
closedir(DIR); closedir(DIR);
my $count = 0; my $count = 0;
foreach my $event_link ( @event_links ) { foreach my $event_link ( @event_links ) {
if ( $event_link =~ /[^\d\.]/ ) { # Event links start with a period and consist of the digits of the event id. Anything else is not an event link
my ($event_id) = $event_link =~ /^\.(\d+)$/;
if ( !$event_id ) {
Warning("Non-event link found $event_link in $day_dir, skipping"); Warning("Non-event link found $event_link in $day_dir, skipping");
next; next;
} }
Debug("Checking link $event_link"); Debug("Checking link $event_link");
( my $event = $event_link ) =~ s/^.*\.//;
#Event path is hour/minute/sec #Event path is hour/minute/sec
my $event_path = readlink($event_link); my $event_path = readlink($event_link);
if ( !($event_path and -e $event_path) ) { if ( !($event_path and -e $event_path) ) {
aud_print("Event link $day_dir/$event_link does not point to valid target"); aud_print("Event link $day_dir/$event_link does not point to valid target at $event_path");
if ( confirm() ) { if ( confirm() ) {
( $event_link ) = ( $event_link =~ /^(.*)$/ ); # De-taint ( $event_link ) = ( $event_link =~ /^(.*)$/ ); # De-taint
unlink($event_link); unlink($event_link);
$cleaned = 1; $cleaned = 1;
} }
} else { } else {
$event_ids_by_path{$event_path} = $event_id;
Debug("Checking link $event_link points to $event_path "); Debug("Checking link $event_link points to $event_path ");
my $Event = $fs_events->{$event} = new ZoneMinder::Event(); my $Event = $fs_events->{$event_id} = new ZoneMinder::Event();
$$Event{Id} = $event; $$Event{Id} = $event_id;
$$Event{Path} = join('/', $Storage->Path(), $day_dir,$event_path); $$Event{Path} = join('/', $Storage->Path(), $day_dir, $event_path);
$$Event{RelativePath} = join('/', $day_dir,$event_path); $$Event{RelativePath} = join('/', $day_dir, $event_path);
$$Event{Scheme} = 'Deep'; $$Event{Scheme} = 'Deep';
$Event->MonitorId( $monitor_dir ); $Event->MonitorId( $monitor_dir );
$Event->StorageId( $Storage->Id() ); $Event->StorageId( $Storage->Id() );
@ -303,15 +313,34 @@ MAIN: while( $loop ) {
my $event_id = undef; my $event_id = undef;
my @mp4_files = glob("$event_dir/[0-9]+\-video.mp4"); if ( ! opendir(DIR, $event_dir) ) {
Error("Can't open directory '$$Storage{Path}/$day_dir': $!");
next;
}
my @contents = readdir( DIR );
Debug("Have " . @contents . " files in $day_dir/$event_dir");
closedir(DIR);
my @mp4_files = grep( /^\d+\-video.mp4$/, @contents);
foreach my $mp4_file ( @mp4_files ) { foreach my $mp4_file ( @mp4_files ) {
my ( $id ) = $mp4_file =~ /^([0-9]+)\-video\.mp4$/; my ( $id ) = $mp4_file =~ /^([0-9]+)\-video\.mp4$/;
if ( $id ) { if ( $id ) {
$event_id = $id; $event_id = $id;
Debug("Got event id from mp4 file $mp4_file => $event_id");
last; last;
} }
} }
if ( $event_id ) {
if ( ! $event_id ) {
# Look for .id file
my @hidden_files = grep( /^\.\d+$/, @contents);
Debug("Have " . @hidden_files . ' hidden files');
if ( @hidden_files ) {
( $event_id ) = $hidden_files[0] =~ /^.(\d+)$/;
}
}
if ( $event_id and ! $fs_events->{$event_id} ) {
my $Event = $fs_events->{$event_id} = new ZoneMinder::Event(); my $Event = $fs_events->{$event_id} = new ZoneMinder::Event();
$$Event{Id} = $event_id; $$Event{Id} = $event_id;
$$Event{Path} = join('/', $Storage->Path(), $day_dir, $event_dir); $$Event{Path} = join('/', $Storage->Path(), $day_dir, $event_dir);
@ -320,7 +349,26 @@ MAIN: while( $loop ) {
$Event->MonitorId( $monitor_dir ); $Event->MonitorId( $monitor_dir );
$Event->StorageId( $Storage->Id() ); $Event->StorageId( $Storage->Id() );
$Event->DiskSpace( undef ); $Event->DiskSpace( undef );
if ( ! $event_ids_by_path{$event_dir} ) {
Warning("No event link found at ".$Event->LinkPath() ." for " . $Event->to_string());
}
} else { } else {
if ( $event_ids_by_path{$event_dir} ) {
Debug("Have an event link, leaving dir alone.");
next;
}
my ( undef, $year, $month, $day ) = split('/', $day_dir);
$year += 2000;
my ( $hour, $minute, $second ) = split('/', $event_dir);
my $StartTime =sprintf('%.4d-%.2d-%.2d %.2d:%.2d:%.2d', $year, $month, $day, $hour, $minute, $second);
my $Event = ZoneMinder::Event->find_one(
MonitorId=>$monitor_dir,
StartTime=>$StartTime,
);
if ( $Event ) {
Debug("Found event matching starttime on monitor $monitor_dir at $StartTime: " . $Event->to_string());
next;
}
aud_print("Deleting event directories with no event id information at $day_dir/$event_dir"); aud_print("Deleting event directories with no event id information at $day_dir/$event_dir");
if ( confirm() ) { if ( confirm() ) {
my $command = "rm -rf $event_dir"; my $command = "rm -rf $event_dir";
@ -336,7 +384,7 @@ MAIN: while( $loop ) {
Debug("Checking for Medium Scheme Events under $$Storage{Path}/$monitor_dir"); Debug("Checking for Medium Scheme Events under $$Storage{Path}/$monitor_dir");
{ {
my @event_dirs = glob("$monitor_dir/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/*"); my @event_dirs = glob("$monitor_dir/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/*");
Debug(qq`glob("$monitor_dir/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/*") returned ` . scalar @event_dirs . " entries." ); Debug(qq`glob("$monitor_dir/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/*") returned ` . scalar @event_dirs . ' entries.' );
foreach my $event_dir ( @event_dirs ) { foreach my $event_dir ( @event_dirs ) {
if ( ! -d $event_dir ) { if ( ! -d $event_dir ) {
Debug( "$event_dir is not a dir. Skipping" ); Debug( "$event_dir is not a dir. Skipping" );
@ -355,11 +403,12 @@ MAIN: while( $loop ) {
$$Event{RelativePath} = $event_dir; $$Event{RelativePath} = $event_dir;
$Event->MonitorId( $monitor_dir ); $Event->MonitorId( $monitor_dir );
$Event->StorageId( $Storage->Id() ); $Event->StorageId( $Storage->Id() );
$Event->StartTime( POSIX::strftime('%Y-%m-%d %H:%M:%S', gmtime(time_of_youngest_file($$Event{Path})) ) );
} # end foreach event } # end foreach event
} }
if ( ! $$Storage{Scheme} ) { if ( ! $$Storage{Scheme} ) {
Debug("Storage Scheme not set on $$Storage{Name}"); Error("Storage Scheme not set on $$Storage{Name}");
if ( ! chdir( $monitor_dir ) ) { if ( ! chdir( $monitor_dir ) ) {
Error( "Can't chdir directory '$$Storage{Path}/$monitor_dir': $!" ); Error( "Can't chdir directory '$$Storage{Path}/$monitor_dir': $!" );
next; next;
@ -380,9 +429,9 @@ MAIN: while( $loop ) {
} # end foreach event } # end foreach event
chdir( $Storage->Path() ); chdir( $Storage->Path() );
} # if USE_DEEP_STORAGE } # if USE_DEEP_STORAGE
Debug( 'Got '.int(keys(%$fs_events))." filesystem events for monitor $monitor_dir\n" ); Debug( 'Got '.int(keys(%$fs_events))." filesystem events for monitor $monitor_dir" );
#delete_empty_directories( $monitor_dir ); delete_empty_subdirs($$Storage{Path}.'/'.$monitor_dir);
} # end foreach monitor } # end foreach monitor
if ( $cleaned ) { if ( $cleaned ) {
@ -398,7 +447,7 @@ MAIN: while( $loop ) {
next; next;
} }
my @event_ids = keys %$fs_events; my @event_ids = keys %$fs_events;
Debug("Have " .scalar @event_ids . " events for monitor $monitor_id"); Debug('Have ' .scalar @event_ids . " events for monitor $monitor_id");
foreach my $fs_event_id ( sort { $a <=> $b } keys %$fs_events ) { foreach my $fs_event_id ( sort { $a <=> $b } keys %$fs_events ) {
@ -442,7 +491,11 @@ MAIN: while( $loop ) {
} }
} }
} # end foreach Storage Area } # end foreach Storage Area
redo MAIN if ( $cleaned );
if ( $cleaned ) {
Debug("Events were deleted, starting again.");
redo MAIN;
}
$cleaned = 0; $cleaned = 0;
my $deleteMonitorSql = 'DELETE LOW_PRIORITY FROM Monitors WHERE Id = ?'; my $deleteMonitorSql = 'DELETE LOW_PRIORITY FROM Monitors WHERE Id = ?';
@ -460,21 +513,30 @@ MAIN: while( $loop ) {
# Foreach database monitor and it's list of events. # Foreach database monitor and it's list of events.
while ( my ( $db_monitor, $db_events ) = each(%$db_monitors) ) { while ( my ( $db_monitor, $db_events ) = each(%$db_monitors) ) {
Debug("Checking db events for monitor $db_monitor");
if ( ! $db_events ) {
Debug("Skipping db events for $db_monitor because there are none");
next;
}
# If we found the monitor in the file system # If we found the monitor in the file system
if ( my $fs_events = $fs_monitors->{$db_monitor} ) { my $fs_events = $fs_monitors->{$db_monitor};
next if ! $db_events;
while ( my ( $db_event, $age ) = each( %$db_events ) ) { while ( my ( $db_event, $age ) = each( %$db_events ) ) {
if ( ! defined( $fs_events->{$db_event} ) ) { if ( ! ($fs_events and defined( $fs_events->{$db_event} ) ) ) {
Debug("Don't have an fs event for $db_event");
my $Event = ZoneMinder::Event->find_one( Id=>$db_event ); my $Event = ZoneMinder::Event->find_one( Id=>$db_event );
if ( ! $Event ) { if ( ! $Event ) {
Debug("Event $db_event is no longer in db. Filter probably deleted it while we were auditing."); Debug("Event $db_event is no longer in db. Filter probably deleted it while we were auditing.");
next; next;
} }
Debug("Event $db_event is not in fs. Should have been at ".$Event->Path()); Debug("Event $db_event is not in fs. Should have been at ".$Event->Path());
if ( $Event->Archived() ) {
Warning("Event $$Event{Id} is Archived. Taking no further action on it.");
next;
}
if ( ! $Event->StartTime() ) { if ( ! $Event->StartTime() ) {
Info("Event $$Event{Id} has no start time. deleting it."); Info("Event $$Event{Id} has no start time.");
if ( confirm() ) { if ( confirm() ) {
$Event->delete(); $Event->delete();
$cleaned = 1; $cleaned = 1;
@ -504,20 +566,35 @@ MAIN: while( $loop ) {
aud_print( "Database event '".$Event->Path()." monitor:$db_monitor event:$db_event' does not exist in filesystem but too young to delete age: $age > MIN $Config{ZM_AUDIT_MIN_AGE}.\n" ); aud_print( "Database event '".$Event->Path()." monitor:$db_monitor event:$db_event' does not exist in filesystem but too young to delete age: $age > MIN $Config{ZM_AUDIT_MIN_AGE}.\n" );
} }
} # end if exists in filesystem } # end if exists in filesystem
} else {
Debug("Found fs event for $db_event, $age at " . $$fs_events{$db_event}->Path());
my $Event = new ZoneMinder::Event( $db_event );
if ( ! $Event->check_for_in_filesystem() ) {
Warning("Not found at " . $Event->Path() . ' was found at ' . $$fs_events{$db_event}->Path() );
Warning($Event->to_string());
Warning($$fs_events{$db_event}->to_string());
if ( $$fs_events{$db_event}->Scheme() ne $Event->Scheme() ) {
Info("Updating scheme on event $$Event{Id} from $$Event{Scheme} to $$fs_events{$db_event}{Scheme}");
$Event->Scheme($$fs_events{$db_event}->Scheme());
}
if ( $$fs_events{$db_event}->StorageId() != $Event->StorageId() ) {
Info("Updating storage area on event $$Event{Id} from $$Event{StorageId} to $$fs_events{$db_event}{StorageId}");
$Event->StorageId($$fs_events{$db_event}->StorageId());
}
if ( $$fs_events{$db_event}->StartTime() ne $Event->StartTime() ) {
Info("Updating StartTime on event $$Event{Id} from $$Event{StartTime} to $$fs_events{$db_event}{StartTime}");
if ( $$Event{Scheme} eq 'Deep' ) {
$Event->StartTime($$fs_events{$db_event}->StartTime());
} else {
$Event->StartTime($$fs_events{$db_event}->StartTime());
}
$Event->save();
}
$Event->save();
}
} # end if ! in fs_events } # end if ! in fs_events
} # foreach db_event } # foreach db_event
#} else {
#my $Monitor = new ZoneMinder::Monitor( $db_monitor );
#my $Storage = $Monitor->Storage();
#aud_print( "Database monitor '$db_monitor' does not exist in filesystem, should have been at ".$Storage->Path().'/'.$Monitor->Id()."\n" );
#if ( confirm() )
#{
# We don't actually do this in case it's new
#my $res = $deleteMonitorSth->execute( $db_monitor )
# or Fatal( "Can't execute: ".$deleteMonitorSth->errstr() );
#$cleaned = 1;
#}
}
} # end foreach db_monitor } # end foreach db_monitor
if ( $cleaned ) { if ( $cleaned ) {
Debug("Have done some cleaning, restarting."); Debug("Have done some cleaning, restarting.");
@ -877,30 +954,71 @@ sub deleteSwapImage {
} }
} }
sub delete_empty_directories { # Deletes empty sub directories of the given path.
# Does not delete the path if empty. Is not meant to be recursive.
sub delete_empty_subdirs {
my $DIR; my $DIR;
Debug("delete_empty_directories $_[0]"); if ( !opendir($DIR, $_[0]) ) {
if ( ! opendir( $DIR, $_[0] ) ) { Error("delete_empty_directories: Can't open directory '".getcwd()."/$_[0]': $!" );
Error( "delete_empty_directories: Can't open directory '".getcwd()."/$_[0]': $!" );
return; return;
} }
my @contents = map { $_ eq '.' or $_ eq '..' ? () : $_ } readdir( $DIR ); my @contents = map { ( $_ eq '.' or $_ eq '..' ) ? () : $_ } readdir( $DIR );
Debug("delete_empty_subdirectories $_[0] has " . @contents .' entries:' . ( @contents < 2 ? join(',',@contents) : '' ));
my @dirs = map { -d $_[0].'/'.$_ ? $_ : () } @contents;
Debug("Have " . @dirs . " dirs");
foreach ( @dirs ) {
delete_empty_directories( $_[0].'/'.$_ );
}
closedir($DIR);
}
sub delete_empty_directories {
my $DIR;
if ( !opendir($DIR, $_[0]) ) {
Error("delete_empty_directories: Can't open directory '".getcwd()."/$_[0]': $!" );
return;
}
my @contents = map { ( $_ eq '.' or $_ eq '..' ) ? () : $_ } readdir( $DIR );
#Debug("delete_empty_directories $_[0] has " . @contents .' entries:' . ( @contents <= 2 ? join(',',@contents) : '' ));
my @dirs = map { -d $_[0].'/'.$_ ? $_ : () } @contents; my @dirs = map { -d $_[0].'/'.$_ ? $_ : () } @contents;
if ( @dirs ) { if ( @dirs ) {
Debug("Have " . @dirs . " dirs");
foreach ( @dirs ) { foreach ( @dirs ) {
delete_empty_directories( $_[0].'/'.$_ ); delete_empty_directories( $_[0].'/'.$_ );
} }
#Reload, since we may now be empty #Reload, since we may now be empty
rewinddir $DIR; rewinddir $DIR;
@contents = map { $_ eq '.' or $_ eq '..' ? () : $_ } readdir( $DIR ); @contents = map { ($_ eq '.' or $_ eq '..') ? () : $_ } readdir( $DIR );
} }
closedir($DIR);
if ( ! @contents ) { if ( ! @contents ) {
( my $dir ) = ( $_[0] =~ /^(.*)$/ ); ( my $dir ) = ( $_[0] =~ /^(.*)$/ );
unlink $dir; Debug("Unlinking $dir because it's empty");
if ( ! rmdir $dir ) {
Error("Unable to unlink $dir: $!");
}
} }
closedir( $DIR );
} # end sub delete_empty_directories } # end sub delete_empty_directories
sub time_of_youngest_file {
my $dir = shift;
if ( ! opendir(DIR, $dir) ) {
Error("Can't open directory '$dir': $!");
return;
}
my $youngest = (stat($dir))[9];
Debug("stat of $dir is $youngest");
foreach my $file ( readdir( DIR ) ) {
next if $file =~ /^\./;
$_ = (stat($dir))[9];
$youngest = $_ if $_ and ( $_ < $youngest );
#Debug("stat of $dir is $_ < $youngest");
}
Debug("stat of $dir is $youngest");
return $youngest;
} # end sub time_of_youngest_file
1; 1;
__END__ __END__

View File

@ -272,6 +272,8 @@ sub run {
."\n" ."\n"
); );
# We don't want to leave killall zombies, so ignore SIGCHLD
$SIG{CHLD} = 'IGNORE';
# Tell any existing processes to die, wait 1 second between TERM and KILL # Tell any existing processes to die, wait 1 second between TERM and KILL
killAll(1); killAll(1);
@ -436,7 +438,7 @@ sub start {
$dbh = zmDbConnect(1); $dbh = zmDbConnect(1);
# This logReinit is required. Not sure why. # This logReinit is required. Not sure why.
#logReinit(); logReinit();
$process->{pid} = $cpid; $process->{pid} = $cpid;
$process->{started} = time(); $process->{started} = time();
@ -480,7 +482,7 @@ sub start {
logTerm(); logTerm();
zmDbDisconnect(); zmDbDisconnect();
my $fd = 0; my $fd = 3; # leave stdin,stdout,stderr open. Closing them causes problems with libx264
while( $fd < POSIX::sysconf(&POSIX::_SC_OPEN_MAX) ) { while( $fd < POSIX::sysconf(&POSIX::_SC_OPEN_MAX) ) {
POSIX::close($fd++); POSIX::close($fd++);
} }

View File

@ -69,6 +69,7 @@ use Time::HiRes qw/gettimeofday/;
use Getopt::Long; use Getopt::Long;
use autouse 'Pod::Usage'=>qw(pod2usage); use autouse 'Pod::Usage'=>qw(pod2usage);
use autouse 'Data::Dumper'=>qw(Dumper); use autouse 'Data::Dumper'=>qw(Dumper);
use File::Path qw(make_path);
my $daemon = 0; my $daemon = 0;
my $filter_name = ''; my $filter_name = '';
@ -162,7 +163,7 @@ my $delay = $Config{ZM_FILTER_EXECUTE_INTERVAL};
my $event_id = 0; my $event_id = 0;
if ( ! EVENT_PATH ) { if ( ! EVENT_PATH ) {
Error("No event path defined. Config was $Config{ZM_DIR_EVENTS}\n"); Error("No event path defined. Config was $Config{ZM_DIR_EVENTS}");
die; die;
} }
@ -173,11 +174,11 @@ chdir( EVENT_PATH );
my $dbh = zmDbConnect(); my $dbh = zmDbConnect();
if ( $filter_name ) { if ( $filter_name ) {
Info("Scanning for events using filter '$filter_name'\n"); Info("Scanning for events using filter '$filter_name'");
} elsif ( $filter_id ) { } elsif ( $filter_id ) {
Info("Scanning for events using filter id '$filter_id'\n"); Info("Scanning for events using filter id '$filter_id'");
} else { } else {
Info("Scanning for events using all filters\n"); Info("Scanning for events using all filters");
} }
if ( ! ( $filter_name or $filter_id ) ) { if ( ! ( $filter_name or $filter_id ) ) {
@ -191,7 +192,7 @@ my $last_action = 0;
while( !$zm_terminate ) { while( !$zm_terminate ) {
my $now = time; my $now = time;
if ( ($now - $last_action) > $Config{ZM_FILTER_RELOAD_DELAY} ) { if ( ($now - $last_action) > $Config{ZM_FILTER_RELOAD_DELAY} ) {
Debug("Reloading filters\n"); Debug("Reloading filters");
$last_action = $now; $last_action = $now;
@filters = getFilters({ Name=>$filter_name, Id=>$filter_id }); @filters = getFilters({ Name=>$filter_name, Id=>$filter_id });
} }
@ -211,7 +212,7 @@ while( !$zm_terminate ) {
last if (!$daemon and ($filter_name or $filter_id)) or $zm_terminate; last if (!$daemon and ($filter_name or $filter_id)) or $zm_terminate;
Debug("Sleeping for $delay seconds\n"); Debug("Sleeping for $delay seconds");
sleep($delay); sleep($delay);
} }
@ -292,48 +293,48 @@ sub checkFilter {
last if $zm_terminate; last if $zm_terminate;
my $Event = new ZoneMinder::Event($$event{Id}, $event); my $Event = new ZoneMinder::Event($$event{Id}, $event);
Debug("Checking event $event->{Id}"); Debug("Checking event $Event->{Id}");
my $delete_ok = !undef; my $delete_ok = !undef;
$dbh->ping(); $dbh->ping();
if ( $filter->{AutoArchive} ) { if ( $filter->{AutoArchive} ) {
Info("Archiving event $event->{Id}"); Info("Archiving event $Event->{Id}");
# Do it individually to avoid locking up the table for new events # Do it individually to avoid locking up the table for new events
my $sql = 'UPDATE Events SET Archived = 1 WHERE Id = ?'; my $sql = 'UPDATE Events SET Archived = 1 WHERE Id = ?';
my $sth = $dbh->prepare_cached( $sql ) my $sth = $dbh->prepare_cached( $sql )
or Fatal("Unable to prepare '$sql': ".$dbh->errstr()); or Fatal("Unable to prepare '$sql': ".$dbh->errstr());
my $res = $sth->execute( $event->{Id} ) my $res = $sth->execute( $Event->{Id} )
or Error("Unable to execute '$sql': ".$dbh->errstr()); or Error("Unable to execute '$sql': ".$dbh->errstr());
} }
if ( $Config{ZM_OPT_FFMPEG} && $filter->{AutoVideo} ) { if ( $Config{ZM_OPT_FFMPEG} && $filter->{AutoVideo} ) {
if ( !$event->{Videoed} ) { if ( !$Event->{Videoed} ) {
$delete_ok = undef if !generateVideo($filter, $event); $delete_ok = undef if !generateVideo($filter, $Event);
} }
} }
if ( $Config{ZM_OPT_EMAIL} && $filter->{AutoEmail} ) { if ( $Config{ZM_OPT_EMAIL} && $filter->{AutoEmail} ) {
if ( !$event->{Emailed} ) { if ( !$Event->{Emailed} ) {
$delete_ok = undef if !sendEmail($filter, $Event); $delete_ok = undef if !sendEmail($filter, $Event);
} }
} }
if ( $Config{ZM_OPT_MESSAGE} && $filter->{AutoMessage} ) { if ( $Config{ZM_OPT_MESSAGE} && $filter->{AutoMessage} ) {
if ( !$event->{Messaged} ) { if ( !$Event->{Messaged} ) {
$delete_ok = undef if !sendMessage($filter, $Event); $delete_ok = undef if !sendMessage($filter, $Event);
} }
} }
if ( $Config{ZM_OPT_UPLOAD} && $filter->{AutoUpload} ) { if ( $Config{ZM_OPT_UPLOAD} && $filter->{AutoUpload} ) {
if ( !$event->{Uploaded} ) { if ( !$Event->{Uploaded} ) {
$delete_ok = undef if !uploadArchFile($filter, $event); $delete_ok = undef if !uploadArchFile($filter, $Event);
} }
} }
if ( $filter->{AutoExecute} ) { if ( $filter->{AutoExecute} ) {
if ( !$event->{Executed} ) { if ( !$Event->{Executed} ) {
$delete_ok = undef if !executeCommand($filter, $event); $delete_ok = undef if !executeCommand($filter, $Event);
} }
} }
if ( $filter->{AutoDelete} ) { if ( $filter->{AutoDelete} ) {
if ( $delete_ok ) { if ( $delete_ok ) {
$Event->delete(); $Event->delete();
} else { } else {
Error("Unable toto delete event $event->{Id} as previous operations failed"); Error("Unable to delete event $Event->{Id} as previous operations failed");
} }
} # end if AutoDelete } # end if AutoDelete
@ -364,11 +365,11 @@ sub checkFilter {
sub generateVideo { sub generateVideo {
my $filter = shift; my $filter = shift;
my $event = shift; my $Event = shift;
my $phone = shift; my $phone = shift;
my $rate = $event->{DefaultRate}/100; my $rate = $Event->{DefaultRate}/100;
my $scale = $event->{DefaultScale}/100; my $scale = $Event->{DefaultScale}/100;
my $format; my $format;
my @ffmpeg_formats = split(/\s+/, $Config{ZM_FFMPEG_FORMATS}); my @ffmpeg_formats = split(/\s+/, $Config{ZM_FFMPEG_FORMATS});
@ -393,7 +394,7 @@ sub generateVideo {
my $command = join('', my $command = join('',
$Config{ZM_PATH_BIN}, $Config{ZM_PATH_BIN},
'/zmvideo.pl -e ', '/zmvideo.pl -e ',
$event->{Id}, $Event->{Id},
' -r ', ' -r ',
$rate, $rate,
' -s ', ' -s ',
@ -405,10 +406,10 @@ sub generateVideo {
chomp($output); chomp($output);
my $status = $? >> 8; my $status = $? >> 8;
if ( $status || logDebugging() ) { if ( $status || logDebugging() ) {
Debug("Output: $output\n"); Debug("Output: $output");
} }
if ( $status ) { if ( $status ) {
Error("Video generation '$command' failed with status: $status\n"); Error("Video generation '$command' failed with status: $status");
if ( wantarray() ) { if ( wantarray() ) {
return( undef, undef ); return( undef, undef );
} }
@ -417,8 +418,8 @@ sub generateVideo {
my $sql = 'UPDATE Events SET Videoed = 1 WHERE Id = ?'; my $sql = 'UPDATE Events SET Videoed = 1 WHERE Id = ?';
my $sth = $dbh->prepare_cached($sql) my $sth = $dbh->prepare_cached($sql)
or Fatal("Unable to prepare '$sql': ".$dbh->errstr()); or Fatal("Unable to prepare '$sql': ".$dbh->errstr());
my $res = $sth->execute($event->{Id}) my $res = $sth->execute($Event->{Id})
or Fatal("Unable toexecute '$sql': ".$dbh->errstr()); or Fatal("Unable to execute '$sql': ".$dbh->errstr());
if ( wantarray() ) { if ( wantarray() ) {
return( $format, $output ); return( $format, $output );
} }
@ -453,7 +454,7 @@ sub generateImage {
chomp( $output ); chomp( $output );
my $status = $? >> 8; my $status = $? >> 8;
if ( $status || logDebugging() ) { if ( $status || logDebugging() ) {
Debug("Output: $output\n"); Debug("Output: $output");
} }
if ( $status ) { if ( $status ) {
Error("Failed $command status $status"); Error("Failed $command status $status");
@ -467,22 +468,30 @@ sub generateImage {
sub uploadArchFile { sub uploadArchFile {
my $filter = shift; my $filter = shift;
my $event = shift; my $Event = shift;
if ( ! $Config{ZM_UPLOAD_HOST} ) { if ( ! $Config{ZM_UPLOAD_HOST} ) {
Error('Cannot upload archive as no upload host defined'); Error('Cannot upload archive as no upload host defined');
return( 0 ); return( 0 );
} }
my $archFile = $event->{MonitorName}.'-'.$event->{Id}; # Try to make the path to the upload folder if it doesn't already exist
my $archImagePath = getEventPath($event) make_path( $Config{ZM_UPLOAD_LOC_DIR} );
# Complain if the upload folder is still not writable
if ( ! -w $Config{ZM_UPLOAD_LOC_DIR} ) {
Error("Upload folder either does not exist or is not writable: $Config{ZM_UPLOAD_LOC_DIR}");
return(0);
}
my $archFile = $Event->{MonitorName}.'-'.$Event->{Id};
my $archImagePath = $Event->Path()
.'/' .'/'
.( .(
( $Config{ZM_UPLOAD_ARCH_ANALYSE} ) ( $Config{ZM_UPLOAD_ARCH_ANALYSE} )
? '{*analyse,*capture}' ? '{*analyse.jpg,*capture.jpg,snapshot.jpg,*.mp4}'
: '*capture' : '{*capture.jpg,snapshot.jpg,*.mp4}'
) )
.'.jpg'
; ;
my @archImageFiles = glob($archImagePath); my @archImageFiles = glob($archImagePath);
my $archLocPath; my $archLocPath;
@ -492,11 +501,11 @@ sub uploadArchFile {
$archFile .= '.zip'; $archFile .= '.zip';
$archLocPath = $Config{ZM_UPLOAD_LOC_DIR}.'/'.$archFile; $archLocPath = $Config{ZM_UPLOAD_LOC_DIR}.'/'.$archFile;
my $zip = Archive::Zip->new(); my $zip = Archive::Zip->new();
Info("Creating upload file '$archLocPath', ".int(@archImageFiles)." files\n"); Info("Creating upload file '$archLocPath', ".int(@archImageFiles).' files');
my $status = &AZ_OK; my $status = &AZ_OK;
foreach my $imageFile ( @archImageFiles ) { foreach my $imageFile ( @archImageFiles ) {
Debug("Adding $imageFile\n"); Debug("Adding $imageFile");
my $member = $zip->addFile($imageFile); my $member = $zip->addFile($imageFile);
if ( !$member ) { if ( !$member ) {
Error("Unable toto add image file $imageFile to zip archive $archLocPath"); Error("Unable toto add image file $imageFile to zip archive $archLocPath");
@ -524,7 +533,7 @@ sub uploadArchFile {
$archFile .= '.tar'; $archFile .= '.tar';
} }
$archLocPath = $Config{ZM_UPLOAD_LOC_DIR}.'/'.$archFile; $archLocPath = $Config{ZM_UPLOAD_LOC_DIR}.'/'.$archFile;
Info("Creating upload file '$archLocPath', ".int(@archImageFiles)." files\n"); Info("Creating upload file '$archLocPath', ".int(@archImageFiles).' files');
if ( $archError = !Archive::Tar->create_archive( if ( $archError = !Archive::Tar->create_archive(
$archLocPath, $archLocPath,
@ -540,7 +549,7 @@ sub uploadArchFile {
return( 0 ); return( 0 );
} else { } else {
if ( $Config{ZM_UPLOAD_PROTOCOL} eq 'ftp' ) { if ( $Config{ZM_UPLOAD_PROTOCOL} eq 'ftp' ) {
Info('Uploading to '.$Config{ZM_UPLOAD_HOST}." using FTP"); Info('Uploading to '.$Config{ZM_UPLOAD_HOST}.' using FTP');
my $ftp = Net::FTP->new( my $ftp = Net::FTP->new(
$Config{ZM_UPLOAD_HOST}, $Config{ZM_UPLOAD_HOST},
Timeout=>$Config{ZM_UPLOAD_TIMEOUT}, Timeout=>$Config{ZM_UPLOAD_TIMEOUT},
@ -548,26 +557,27 @@ sub uploadArchFile {
Debug=>$Config{ZM_UPLOAD_DEBUG} Debug=>$Config{ZM_UPLOAD_DEBUG}
); );
if ( !$ftp ) { if ( !$ftp ) {
Error("Unable tocreate FTP connection: $@"); Error("Unable to create FTP connection: $@");
return 0; return 0;
} }
$ftp->login($Config{ZM_UPLOAD_USER}, $Config{ZM_UPLOAD_PASS}) $ftp->login($Config{ZM_UPLOAD_USER}, $Config{ZM_UPLOAD_PASS})
or Error("FTP - Unable tologin"); or Error("FTP - Unable to login");
$ftp->binary() $ftp->binary()
or Error("FTP - Unable togo binary"); or Error("FTP - Unable to go binary");
$ftp->cwd($Config{ZM_UPLOAD_REM_DIR}) $ftp->cwd($Config{ZM_UPLOAD_REM_DIR})
or Error("FTP - Unable tocwd") or Error("FTP - Unable to cwd")
if ( $Config{ZM_UPLOAD_REM_DIR} ); if ( $Config{ZM_UPLOAD_REM_DIR} );
$ftp->put( $archLocPath ) $ftp->put( $archLocPath )
or Error("FTP - Unable toupload '$archLocPath'"); or Error("FTP - Unable to upload '$archLocPath'");
$ftp->quit() $ftp->quit()
or Error("FTP - Unable toquit"); or Error("FTP - Unable to quit");
} else { } else {
my $host = $Config{ZM_UPLOAD_HOST}; my $host = $Config{ZM_UPLOAD_HOST};
$host .= ':'.$Config{ZM_UPLOAD_PORT} if $Config{ZM_UPLOAD_PORT}; $host .= ':'.$Config{ZM_UPLOAD_PORT} if $Config{ZM_UPLOAD_PORT};
Info('Uploading to '.$host." using SFTP\n"); Info('Uploading to '.$host.' using SFTP');
my %sftpOptions = ( my %sftpOptions = (
host=>$Config{ZM_UPLOAD_HOST}, user=>$Config{ZM_UPLOAD_USER} host=>$Config{ZM_UPLOAD_HOST},
user=>$Config{ZM_UPLOAD_USER},
($Config{ZM_UPLOAD_PASS} ? (password=>$Config{ZM_UPLOAD_PASS}) : ()), ($Config{ZM_UPLOAD_PASS} ? (password=>$Config{ZM_UPLOAD_PASS}) : ()),
($Config{ZM_UPLOAD_PORT} ? (port=>$Config{ZM_UPLOAD_PORT}) : ()), ($Config{ZM_UPLOAD_PORT} ? (port=>$Config{ZM_UPLOAD_PORT}) : ()),
($Config{ZM_UPLOAD_TIMEOUT} ? (timeout=>$Config{ZM_UPLOAD_TIMEOUT}) : ()), ($Config{ZM_UPLOAD_TIMEOUT} ? (timeout=>$Config{ZM_UPLOAD_TIMEOUT}) : ()),
@ -580,7 +590,7 @@ sub uploadArchFile {
$sftpOptions{more} = [@more_ssh_args]; $sftpOptions{more} = [@more_ssh_args];
my $sftp = Net::SFTP::Foreign->new($Config{ZM_UPLOAD_HOST}, %sftpOptions); my $sftp = Net::SFTP::Foreign->new($Config{ZM_UPLOAD_HOST}, %sftpOptions);
if ( $sftp->error ) { if ( $sftp->error ) {
Error("Unable tocreate SFTP connection: ".$sftp->error); Error("Unable to create SFTP connection: ".$sftp->error);
return 0; return 0;
} }
$sftp->setcwd($Config{ZM_UPLOAD_REM_DIR}) $sftp->setcwd($Config{ZM_UPLOAD_REM_DIR})
@ -592,9 +602,9 @@ sub uploadArchFile {
unlink($archLocPath); unlink($archLocPath);
my $sql = 'UPDATE Events SET Uploaded = 1 WHERE Id = ?'; my $sql = 'UPDATE Events SET Uploaded = 1 WHERE Id = ?';
my $sth = $dbh->prepare_cached($sql) my $sth = $dbh->prepare_cached($sql)
or Fatal("Unable toprepare '$sql': ".$dbh->errstr()); or Fatal("Unable to prepare '$sql': ".$dbh->errstr());
my $res = $sth->execute($event->{Id}) my $res = $sth->execute($Event->{Id})
or Fatal("Unable toexecute '$sql': ".$dbh->errstr()); or Fatal("Unable to execute '$sql': ".$dbh->errstr());
} }
return 1; return 1;
} # end sub uploadArchFile } # end sub uploadArchFile
@ -622,9 +632,9 @@ sub substituteTags {
WHERE EventId = ? AND Type = 'Alarm' WHERE EventId = ? AND Type = 'Alarm'
ORDER BY FrameId`; ORDER BY FrameId`;
my $sth = $dbh->prepare_cached($sql) my $sth = $dbh->prepare_cached($sql)
or Fatal("Unable toprepare '$sql': ".$dbh->errstr()); or Fatal("Unable to prepare '$sql': ".$dbh->errstr());
my $res = $sth->execute($Event->{Id}) my $res = $sth->execute($Event->{Id})
or Fatal( "Unable toexecute '$sql': ".$dbh->errstr()); or Fatal("Unable to execute '$sql': ".$dbh->errstr());
my $rows = 0; my $rows = 0;
while( my $frame = $sth->fetchrow_hashref() ) { while( my $frame = $sth->fetchrow_hashref() ) {
if ( !$first_alarm_frame ) { if ( !$first_alarm_frame ) {
@ -713,14 +723,18 @@ sub substituteTags {
} }
} # end if $first_alarm_frame } # end if $first_alarm_frame
if ( $attachments_ref && $Config{ZM_OPT_FFMPEG} ) { if ( $attachments_ref ) {
if ( $text =~ s/%EV%//g ) { if ( $text =~ s/%EV%//g ) {
if ( $$Event{DefaultVideo} ) {
push @$attachments_ref, { type=>'video/mp4', path=>join('/',$Event->Path(), $Event->DefaultVideo()) };
} elsif ( $Config{ZM_OPT_FFMPEG} ) {
my ( $format, $path ) = generateVideo($filter, $Event); my ( $format, $path ) = generateVideo($filter, $Event);
if ( !$format ) { if ( !$format ) {
return undef; return undef;
} }
push( @$attachments_ref, { type=>"video/$format", path=>$path } ); push( @$attachments_ref, { type=>"video/$format", path=>$path } );
} }
}
if ( $text =~ s/%EVM%//g ) { if ( $text =~ s/%EVM%//g ) {
my ( $format, $path ) = generateVideo($filter, $Event, 1); my ( $format, $path ) = generateVideo($filter, $Event, 1);
if ( !$format ) { if ( !$format ) {
@ -792,7 +806,7 @@ sub sendEmail {
$ssmtp_location = qx('which ssmtp'); $ssmtp_location = qx('which ssmtp');
} }
if ( !$ssmtp_location ) { if ( !$ssmtp_location ) {
Debug('Unable tofind ssmtp, trying MIME::Lite->send'); Debug('Unable to find ssmtp, trying MIME::Lite->send');
MIME::Lite->send('smtp', $Config{ZM_EMAIL_HOST}, Timeout=>60); MIME::Lite->send('smtp', $Config{ZM_EMAIL_HOST}, Timeout=>60);
$mail->send(); $mail->send();
} else { } else {
@ -824,16 +838,16 @@ sub sendEmail {
} }
}; };
if ( $@ ) { if ( $@ ) {
Error("Unable tosend email: $@"); Error("Unable to send email: $@");
return 0; return 0;
} else { } else {
Info('Notification email sent'); Info('Notification email sent');
} }
my $sql = 'UPDATE Events SET Emailed = 1 WHERE Id = ?'; my $sql = 'UPDATE Events SET Emailed = 1 WHERE Id = ?';
my $sth = $dbh->prepare_cached($sql) my $sth = $dbh->prepare_cached($sql)
or Fatal("Unable toprepare '$sql': ".$dbh->errstr()); or Fatal("Unable to prepare '$sql': ".$dbh->errstr());
my $res = $sth->execute($Event->{Id}) my $res = $sth->execute($Event->{Id})
or Fatal("Unable toexecute '$sql': ".$dbh->errstr()); or Fatal("Unable to execute '$sql': ".$dbh->errstr());
return 1; return 1;
} }
@ -894,7 +908,7 @@ sub sendMessage {
$ssmtp_location = qx('which ssmtp'); $ssmtp_location = qx('which ssmtp');
} }
if ( !$ssmtp_location ) { if ( !$ssmtp_location ) {
Debug('Unable tofind ssmtp, trying MIME::Lite->send'); Debug('Unable to find ssmtp, trying MIME::Lite->send');
MIME::Lite->send(smtp=>$Config{ZM_EMAIL_HOST}, Timeout=>60); MIME::Lite->send(smtp=>$Config{ZM_EMAIL_HOST}, Timeout=>60);
$mail->send(); $mail->send();
} else { } else {
@ -929,29 +943,29 @@ sub sendMessage {
} }
}; };
if ( $@ ) { if ( $@ ) {
Error("Unable tosend email: $@"); Error("Unable to send email: $@");
return 0; return 0;
} else { } else {
Info('Notification message sent'); Info('Notification message sent');
} }
my $sql = 'UPDATE Events SET Messaged = 1 WHERE Id = ?'; my $sql = 'UPDATE Events SET Messaged = 1 WHERE Id = ?';
my $sth = $dbh->prepare_cached($sql) my $sth = $dbh->prepare_cached($sql)
or Fatal("Unable toprepare '$sql': ".$dbh->errstr()); or Fatal("Unable to prepare '$sql': ".$dbh->errstr());
my $res = $sth->execute($Event->{Id}) my $res = $sth->execute($Event->{Id})
or Fatal("Unable toexecute '$sql': ".$dbh->errstr()); or Fatal("Unable to execute '$sql': ".$dbh->errstr());
return 1; return 1;
} # end sub sendMessage } # end sub sendMessage
sub executeCommand { sub executeCommand {
my $filter = shift; my $filter = shift;
my $event = shift; my $Event = shift;
my $event_path = getEventPath($event); my $event_path = $Event->Path();
my $command = $filter->{AutoExecuteCmd}; my $command = $filter->{AutoExecuteCmd};
$command .= " $event_path"; $command .= " $event_path";
$command = substituteTags($command, $filter, $event); $command = substituteTags($command, $filter, $Event);
Info("Executing '$command'"); Info("Executing '$command'");
my $output = qx($command); my $output = qx($command);
@ -967,7 +981,7 @@ sub executeCommand {
my $sql = 'UPDATE Events SET Executed = 1 WHERE Id = ?'; my $sql = 'UPDATE Events SET Executed = 1 WHERE Id = ?';
my $sth = $dbh->prepare_cached($sql) my $sth = $dbh->prepare_cached($sql)
or Fatal("Unable to prepare '$sql': ".$dbh->errstr()); or Fatal("Unable to prepare '$sql': ".$dbh->errstr());
my $res = $sth->execute( $event->{Id} ) my $res = $sth->execute( $Event->{Id} )
or Fatal("Unable to execute '$sql': ".$dbh->errstr()); or Fatal("Unable to execute '$sql': ".$dbh->errstr());
} }
return( 1 ); return( 1 );

View File

@ -44,7 +44,6 @@ delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
my $store_state=''; # PP - will remember state name passed my $store_state=''; # PP - will remember state name passed
logInit(); logInit();
Info("Aftere LogInit");
my $command = $ARGV[0]||''; my $command = $ARGV[0]||'';
if ( $command eq 'version' ) { if ( $command eq 'version' ) {
@ -316,12 +315,14 @@ sub isActiveSanityCheck {
if ( $sth->rows != 1 ) { if ( $sth->rows != 1 ) {
# PP - no row, or too many rows. Either case is an error # PP - no row, or too many rows. Either case is an error
Info( 'Fixing States table - either no default state or duplicate default states' ); Info( 'Fixing States table - either no default state or duplicate default states' );
$sql = "DELETE FROM States WHERE Name='default'"; if ( $sth->rows ) {
$sql = q`DELETE FROM States WHERE Name='default'`;
$sth = $dbh->prepare_cached( $sql ) $sth = $dbh->prepare_cached( $sql )
or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
$res = $sth->execute() $res = $sth->execute()
or Fatal( "Can't execute: ".$sth->errstr() ); or Fatal( "Can't execute: ".$sth->errstr() );
$sql = q`"INSERT INTO States (Name,Definition,IsActive) VALUES ('default','','1');`; }
$sql = q`INSERT INTO States (Name,Definition,IsActive) VALUES ('default','','1');`;
$sth = $dbh->prepare_cached($sql) $sth = $dbh->prepare_cached($sql)
or Fatal("Can't prepare '$sql': ".$dbh->errstr()); or Fatal("Can't prepare '$sql': ".$dbh->errstr());
$res = $sth->execute() $res = $sth->execute()

View File

@ -1,4 +1,63 @@
#!/usr/bin/perl -wT #!/usr/bin/perl -wT
use strict;
use bytes;
# ==========================================================================
#
# These are the elements you can edit to suit your installation
#
# ==========================================================================
use constant START_DELAY => 30; # To give everything else time to start
# ==========================================================================
#
# Don't change anything below here
#
# ==========================================================================
@EXTRA_PERL_LIB@
use ZoneMinder;
use POSIX;
use DBI;
use autouse 'Data::Dumper'=>qw(Dumper);
$| = 1;
$ENV{PATH} = '/bin:/usr/bin:/usr/local/bin';
$ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL};
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
logInit();
logSetSignal();
Info("Stats Daemon starting in ".START_DELAY." seconds");
sleep(START_DELAY);
my $dbh = zmDbConnect();
while( 1 ) {
while ( ! ( $dbh and $dbh->ping() ) ) {
Info("Reconnecting to db");
if ( ! ( $dbh = zmDbConnect() ) ) {
#What we do here is not that important, so just skip this interval
sleep($Config{ZM_STATS_UPDATE_INTERVAL});
}
}
$dbh->do('DELETE FROM Events_Hour WHERE StartTime < DATE_SUB(NOW(), INTERVAL 1 hour)') or Error($dbh->errstr());
$dbh->do('DELETE FROM Events_Day WHERE StartTime < DATE_SUB(NOW(), INTERVAL 1 day)') or Error($dbh->errstr());
$dbh->do('DELETE FROM Events_Week WHERE StartTime < DATE_SUB(NOW(), INTERVAL 1 week)') or Error($dbh->errstr());
$dbh->do('DELETE FROM Events_Month WHERE StartTime < DATE_SUB(NOW(), INTERVAL 1 month)') or Error($dbh->errstr());
sleep($Config{ZM_STATS_UPDATE_INTERVAL});
} # end while (1)
Info("Stats Daemon exiting");
exit();
1;
__END__
# #
# ========================================================================== # ==========================================================================
# #
@ -23,7 +82,7 @@
=head1 NAME =head1 NAME
zmwatch.pl - ZoneMinder Stats Updating Script zmstats.pl - ZoneMinder Stats Updating Script
=head1 SYNOPSIS =head1 SYNOPSIS
@ -34,64 +93,3 @@ zmstats.pl
This does background updating various stats in the db like event counts, diskspace, etc. This does background updating various stats in the db like event counts, diskspace, etc.
=cut =cut
use strict;
use bytes;
# ==========================================================================
#
# These are the elements you can edit to suit your installation
#
# ==========================================================================
use constant START_DELAY => 30; # To give everything else time to start
# ==========================================================================
#
# Don't change anything below here
#
# ==========================================================================
@EXTRA_PERL_LIB@
use ZoneMinder;
use ZoneMinder::Storage;
use POSIX;
use DBI;
use autouse 'Data::Dumper'=>qw(Dumper);
$| = 1;
$ENV{PATH} = '/bin:/usr/bin:/usr/local/bin';
$ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL};
delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
logInit();
logSetSignal();
Info( "Stats Daemon starting in ".START_DELAY." seconds\n" );
sleep( START_DELAY );
my $dbh = zmDbConnect();
my $sql = $Config{ZM_SERVER_ID} ? 'SELECT * FROM Monitors WHERE ServerId=?' : 'SELECT * FROM Monitors';
my $sth = $dbh->prepare_cached( $sql )
or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
while( 1 ) {
while ( ! ( $dbh and $dbh->ping() ) ) {
Info("Reconnecting to db");
$dbh = zmDbConnect();
}
$dbh->do('DELETE FROM Events_Hour WHERE StartTime < DATE_SUB(NOW(), INTERVAL 1 hour)') or Error($dbh->errstr());
$dbh->do('DELETE FROM Events_Day WHERE StartTime < DATE_SUB(NOW(), INTERVAL 1 day)') or Error($dbh->errstr());
$dbh->do('DELETE FROM Events_Week WHERE StartTime < DATE_SUB(NOW(), INTERVAL 1 week)') or Error($dbh->errstr());
$dbh->do('DELETE FROM Events_Month WHERE StartTime < DATE_SUB(NOW(), INTERVAL 1 month)') or Error($dbh->errstr());
sleep( $Config{ZM_STATS_UPDATE_INTERVAL} );
} # end while (1)
Info( "Stats Daemon exiting\n" );
exit();
1;
__END__

View File

@ -88,13 +88,13 @@ delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
logInit(); logInit();
logSetSignal(); logSetSignal();
Info( "Trigger daemon starting\n" ); Info( "Trigger daemon starting" );
my $dbh = zmDbConnect(); my $dbh = zmDbConnect();
my $base_rin = ''; my $base_rin = '';
foreach my $connection ( @connections ) { foreach my $connection ( @connections ) {
Info( "Opening connection '$connection->{name}'\n" ); Info( "Opening connection '$connection->{name}'" );
$connection->open(); $connection->open();
} }
@ -109,7 +109,7 @@ foreach my $connection ( @in_select_connections ) {
my %spawned_connections; my %spawned_connections;
my %monitors; my %monitors;
my $monitor_reload_time = 0; my $monitor_reload_time = 0;
my $needsReload = 0; my @needsReload;
loadMonitors(); loadMonitors();
$! = undef; $! = undef;
@ -127,14 +127,14 @@ while( 1 ) {
my $nfound = select( my $rout = $rin, undef, my $eout = $ein, $timeout ); my $nfound = select( my $rout = $rin, undef, my $eout = $ein, $timeout );
if ( $nfound > 0 ) { if ( $nfound > 0 ) {
Debug( "Got input from $nfound connections\n" ); Debug( "Got input from $nfound connections" );
foreach my $connection ( @in_select_connections ) { foreach my $connection ( @in_select_connections ) {
if ( vec( $rout, $connection->fileno(), 1 ) ) { if ( vec( $rout, $connection->fileno(), 1 ) ) {
Debug( 'Got input from connection ' Debug( 'Got input from connection '
.$connection->name() .$connection->name()
.' (' .' ('
.$connection->fileno() .$connection->fileno()
.")\n" .")"
); );
if ( $connection->spawns() ) { if ( $connection->spawns() ) {
my $new_connection = $connection->accept(); my $new_connection = $connection->accept();
@ -143,7 +143,7 @@ while( 1 ) {
.$new_connection->fileno() .$new_connection->fileno()
.'), ' .'), '
.int(keys(%spawned_connections)) .int(keys(%spawned_connections))
." spawned connections\n" ." spawned connections"
); );
} else { } else {
my $messages = $connection->getMessages(); my $messages = $connection->getMessages();
@ -162,7 +162,7 @@ while( 1 ) {
.$connection->name() .$connection->name()
.' (' .' ('
.$connection->fileno() .$connection->fileno()
.")\n" .")"
); );
my $messages = $connection->getMessages(); my $messages = $connection->getMessages();
if ( defined($messages) ) { if ( defined($messages) ) {
@ -175,7 +175,7 @@ while( 1 ) {
.$connection->fileno() .$connection->fileno()
.'), ' .'), '
.int(keys(%spawned_connections)) .int(keys(%spawned_connections))
." spawned connections\n" ." spawned connections"
); );
$connection->close(); $connection->close();
} }
@ -206,7 +206,7 @@ while( 1 ) {
if ( ! zmMemVerify($monitor) ) { if ( ! zmMemVerify($monitor) ) {
# Our attempt to verify the memory handle failed. We should reload the monitors. # Our attempt to verify the memory handle failed. We should reload the monitors.
# Don't need to zmMemInvalidate because the monitor reload will do it. # Don't need to zmMemInvalidate because the monitor reload will do it.
$needsReload = 1; push @needsReload, $monitor;
next; next;
} }
@ -217,8 +217,8 @@ while( 1 ) {
] ]
); );
#print( "$monitor->{Id}: S:$state, LE:$last_event\n" ); #print( "$monitor->{Id}: S:$state, LE:$last_event" );
#print( "$monitor->{Id}: mS:$monitor->{LastState}, mLE:$monitor->{LastEvent}\n" ); #print( "$monitor->{Id}: mS:$monitor->{LastState}, mLE:$monitor->{LastEvent}" );
if ( $state == STATE_ALARM || $state == STATE_ALERT ) { if ( $state == STATE_ALARM || $state == STATE_ALERT ) {
# In alarm state # In alarm state
if ( !defined($monitor->{LastEvent}) if ( !defined($monitor->{LastEvent})
@ -261,14 +261,14 @@ while( 1 ) {
} }
if ( my @action_times = keys(%actions) ) { if ( my @action_times = keys(%actions) ) {
Debug( "Checking for timed actions\n" ); Debug( "Checking for timed actions" );
my $now = time(); my $now = time();
foreach my $action_time ( sort( grep { $_ < $now } @action_times ) ) { foreach my $action_time ( sort( grep { $_ < $now } @action_times ) ) {
Info( "Found actions expiring at $action_time\n" ); Info( "Found actions expiring at $action_time" );
foreach my $action ( @{$actions{$action_time}} ) { foreach my $action ( @{$actions{$action_time}} ) {
my $connection = $action->{connection}; my $connection = $action->{connection};
my $message = $action->{message}; my $message = $action->{message};
Info( "Found action '$message'\n" ); Info( "Found action '$message'" );
handleMessage( $connection, $message ); handleMessage( $connection, $message );
} }
delete( $actions{$action_time} ); delete( $actions{$action_time} );
@ -293,23 +293,41 @@ while( 1 ) {
} }
} }
# If necessary reload monitors # Reload all monitors from the dB every MONITOR_RELOAD_INTERVAL
if ( $needsReload || ((time() - $monitor_reload_time) > MONITOR_RELOAD_INTERVAL )) { if ( (time() - $monitor_reload_time) > MONITOR_RELOAD_INTERVAL ) {
foreach my $monitor ( values(%monitors) ) { foreach my $monitor ( values(%monitors) ) {
# Free up any used memory handle zmMemInvalidate( $monitor ); # Free up any used memory handle
zmMemInvalidate( $monitor );
} }
loadMonitors(); loadMonitors();
$needsReload = 0; @needsReload = (); # We just reloaded all monitors so no need reload a specific monitor
# If we have NOT just reloaded all monitors, reload a specific monitor if its shared mem changed
} elsif ( @needsReload ) {
foreach my $monitor ( @needsReload ) {
loadMonitor($monitor);
} }
@needsReload = ();
}
# zmDbConnect will ping and reconnect if neccessary # zmDbConnect will ping and reconnect if neccessary
$dbh = zmDbConnect(); $dbh = zmDbConnect();
} # end while ( 1 ) } # end while ( 1 )
Info( "Trigger daemon exiting\n" ); Info( "Trigger daemon exiting" );
exit; exit;
sub loadMonitor {
my $monitor = shift;
Debug( "Loading monitor $monitor" );
zmMemInvalidate( $monitor );
if ( zmMemVerify( $monitor ) ) { # This will re-init shared memory
$monitor->{LastState} = zmGetMonitorState( $monitor );
$monitor->{LastEvent} = zmGetLastEvent( $monitor );
}
}
sub loadMonitors { sub loadMonitors {
Debug( "Loading monitors\n" ); Debug( "Loading monitors" );
$monitor_reload_time = time(); $monitor_reload_time = time();
my %new_monitors = (); my %new_monitors = ();
@ -323,14 +341,10 @@ sub loadMonitors {
my $res = $sth->execute( $Config{ZM_SERVER_ID} ? $Config{ZM_SERVER_ID} : () ) my $res = $sth->execute( $Config{ZM_SERVER_ID} ? $Config{ZM_SERVER_ID} : () )
or Fatal( "Can't execute: ".$sth->errstr() ); or Fatal( "Can't execute: ".$sth->errstr() );
while( my $monitor = $sth->fetchrow_hashref() ) { while( my $monitor = $sth->fetchrow_hashref() ) {
# Check shared memory ok if ( zmMemVerify( $monitor ) ) { # This will re-init shared memory
if ( !zmMemVerify( $monitor ) ) {
zmMemInvalidate( $monitor );
next;
}
$monitor->{LastState} = zmGetMonitorState( $monitor ); $monitor->{LastState} = zmGetMonitorState( $monitor );
$monitor->{LastEvent} = zmGetLastEvent( $monitor ); $monitor->{LastEvent} = zmGetLastEvent( $monitor );
}
$new_monitors{$monitor->{Id}} = $monitor; $new_monitors{$monitor->{Id}} = $monitor;
} # end while fetchrow } # end while fetchrow
%monitors = %new_monitors; %monitors = %new_monitors;
@ -348,14 +362,14 @@ sub handleMessage {
my $monitor = $monitors{$id}; my $monitor = $monitors{$id};
if ( !$monitor ) { if ( !$monitor ) {
Warning( "Can't find monitor '$id' for message '$message'\n" ); Warning( "Can't find monitor '$id' for message '$message'" );
return; return;
} }
Debug( "Found monitor for id '$id'\n" ); Debug( "Found monitor for id '$id'" );
next if ( !zmMemVerify( $monitor ) ); next if ( !zmMemVerify( $monitor ) );
Debug( "Handling action '$action'\n" ); Debug( "Handling action '$action'" );
if ( $action =~ /^(enable|disable)(?:\+(\d+))?$/ ) { if ( $action =~ /^(enable|disable)(?:\+(\d+))?$/ ) {
my $state = $1; my $state = $1;
my $delay = $2; my $delay = $2;
@ -366,7 +380,7 @@ sub handleMessage {
} }
# Force a reload # Force a reload
$monitor_reload_time = 0; $monitor_reload_time = 0;
Info( "Set monitor to $state\n" ); Info( "Set monitor to $state" );
if ( $delay ) { if ( $delay ) {
my $action_text = $id.'|'.( ($state eq 'enable') my $action_text = $id.'|'.( ($state eq 'enable')
? 'disable' ? 'disable'
@ -383,7 +397,7 @@ sub handleMessage {
if ( $trigger eq 'on' ) { if ( $trigger eq 'on' ) {
zmTriggerEventOn( $monitor, $score, $cause, $text ); zmTriggerEventOn( $monitor, $score, $cause, $text );
zmTriggerShowtext( $monitor, $showtext ) if defined($showtext); zmTriggerShowtext( $monitor, $showtext ) if defined($showtext);
Info( "Trigger '$trigger' '$cause'\n" ); Info( "Trigger '$trigger' '$cause'" );
if ( $delay ) { if ( $delay ) {
my $action_text = $id.'|cancel'; my $action_text = $id.'|cancel';
handleDelay($delay, $connection, $action_text); handleDelay($delay, $connection, $action_text);
@ -396,7 +410,7 @@ sub handleMessage {
my $last_event = zmGetLastEvent( $monitor ); my $last_event = zmGetLastEvent( $monitor );
zmTriggerEventOff( $monitor ); zmTriggerEventOff( $monitor );
zmTriggerShowtext( $monitor, $showtext ) if defined($showtext); zmTriggerShowtext( $monitor, $showtext ) if defined($showtext);
Info( "Trigger '$trigger'\n" ); Info( "Trigger '$trigger'" );
# Wait til it's finished # Wait til it's finished
while( zmInAlarm( $monitor ) while( zmInAlarm( $monitor )
&& ($last_event == zmGetLastEvent( $monitor )) && ($last_event == zmGetLastEvent( $monitor ))
@ -410,12 +424,12 @@ sub handleMessage {
} elsif( $action eq 'cancel' ) { } elsif( $action eq 'cancel' ) {
zmTriggerEventCancel( $monitor ); zmTriggerEventCancel( $monitor );
zmTriggerShowtext( $monitor, $showtext ) if defined($showtext); zmTriggerShowtext( $monitor, $showtext ) if defined($showtext);
Info( "Cancelled event\n" ); Info( "Cancelled event" );
} elsif( $action eq 'show' ) { } elsif( $action eq 'show' ) {
zmTriggerShowtext( $monitor, $showtext ); zmTriggerShowtext( $monitor, $showtext );
Info( "Updated show text to '$showtext'\n" ); Info( "Updated show text to '$showtext'" );
} else { } else {
Error( "Unrecognised action '$action' in message '$message'\n" ); Error( "Unrecognised action '$action' in message '$message'" );
} }
} # end sub handleMessage } # end sub handleMessage
@ -430,7 +444,7 @@ sub handleDelay {
$action_array = $actions{$action_time} = []; $action_array = $actions{$action_time} = [];
} }
push( @$action_array, { connection=>$connection, message=>$action_text } ); push( @$action_array, { connection=>$connection, message=>$action_text } );
Debug( "Added timed event '$action_text', expires at $action_time (+$delay secs)\n" ); Debug( "Added timed event '$action_text', expires at $action_time (+$delay secs)" );
} }
1; 1;
__END__ __END__

View File

@ -922,6 +922,11 @@ if ( $version ) {
zmDbDisconnect(); zmDbDisconnect();
die( "Can't find upgrade from version '$version'" ); die( "Can't find upgrade from version '$version'" );
} }
# Re-enable the privacy popup after each upgrade
my $sql = "update Config set Value = 1 where Name = 'ZM_SHOW_PRIVACY'";
my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() );
my $res = $sth->execute( ) or die( "Can't execute: ".$sth->errstr() );
$sth->finish();
print( "\nDatabase upgrade to version ".ZM_VERSION." successful.\n\n" ); print( "\nDatabase upgrade to version ".ZM_VERSION." successful.\n\n" );
} }
zmDbDisconnect(); zmDbDisconnect();

View File

@ -78,6 +78,11 @@ my $sth = $dbh->prepare_cached($sql)
or Fatal("Can't prepare '$sql': ".$dbh->errstr()); or Fatal("Can't prepare '$sql': ".$dbh->errstr());
while( 1 ) { while( 1 ) {
while ( ! ( $dbh and $dbh->ping() ) ) {
if ( ! ( $dbh = zmDbConnect() ) ) {
sleep($Config{ZM_WATCH_CHECK_INTERVAL});
}
}
my $res = $sth->execute( $Config{ZM_SERVER_ID} ? $Config{ZM_SERVER_ID} : () ) my $res = $sth->execute( $Config{ZM_SERVER_ID} ? $Config{ZM_SERVER_ID} : () )
or Fatal('Can\'t execute: '.$sth->errstr()); or Fatal('Can\'t execute: '.$sth->errstr());
@ -98,7 +103,7 @@ while( 1 ) {
Debug("Monitor $$monitor{Id} LastWriteTime is $capture_time."); Debug("Monitor $$monitor{Id} LastWriteTime is $capture_time.");
if ( !$capture_time ) { if ( !$capture_time ) {
my $startup_time = zmGetStartupTime($monitor); my $startup_time = zmGetStartupTime($monitor);
if ( $now - $startup_time > $Config{ZM_WATCH_MAX_DELAY} ) { if ( ( $now - $startup_time ) > $Config{ZM_WATCH_MAX_DELAY} ) {
Info( Info(
"Restarting capture daemon for $$monitor{Name}, no image since startup. ". "Restarting capture daemon for $$monitor{Name}, no image since startup. ".
"Startup time was $startup_time - now $now > $Config{ZM_WATCH_MAX_DELAY}" "Startup time was $startup_time - now $now > $Config{ZM_WATCH_MAX_DELAY}"
@ -111,7 +116,8 @@ while( 1 ) {
} }
} }
if ( ! $restart ) { if ( ! $restart ) {
my $max_image_delay = ( $monitor->{MaxFPS} my $max_image_delay = (
$monitor->{MaxFPS}
&&($monitor->{MaxFPS}>0) &&($monitor->{MaxFPS}>0)
&&($monitor->{MaxFPS}<1) &&($monitor->{MaxFPS}<1)
) ? (3/$monitor->{MaxFPS}) ) ? (3/$monitor->{MaxFPS})

View File

@ -33,8 +33,7 @@ class Camera;
// Abstract base class for cameras. This is intended just to express // Abstract base class for cameras. This is intended just to express
// common attributes // common attributes
// //
class Camera class Camera {
{
protected: protected:
typedef enum { LOCAL_SRC, REMOTE_SRC, FILE_SRC, FFMPEG_SRC, LIBVLC_SRC, CURL_SRC } SourceType; typedef enum { LOCAL_SRC, REMOTE_SRC, FILE_SRC, FFMPEG_SRC, LIBVLC_SRC, CURL_SRC } SourceType;
@ -55,47 +54,46 @@ protected:
bool record_audio; bool record_audio;
unsigned int bytes; unsigned int bytes;
public: public:
Camera( unsigned int p_monitor_id, SourceType p_type, unsigned int p_width, unsigned int p_height, int p_colours, int p_subpixelorder, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio ); Camera( unsigned int p_monitor_id, SourceType p_type, unsigned int p_width, unsigned int p_height, int p_colours, int p_subpixelorder, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture, bool p_record_audio );
virtual ~Camera(); virtual ~Camera();
unsigned int getId() const { return( monitor_id ); } unsigned int getId() const { return monitor_id; }
Monitor *getMonitor(); Monitor *getMonitor();
void setMonitor( Monitor *p_monitor ); void setMonitor( Monitor *p_monitor );
SourceType Type() const { return( type ); } SourceType Type() const { return type; }
bool IsLocal() const { return( type == LOCAL_SRC ); } bool IsLocal() const { return type == LOCAL_SRC; }
bool IsRemote() const { return( type == REMOTE_SRC ); } bool IsRemote() const { return type == REMOTE_SRC; }
bool IsFile() const { return( type == FILE_SRC ); } bool IsFile() const { return type == FILE_SRC; }
bool IsFfmpeg() const { return( type == FFMPEG_SRC ); } bool IsFfmpeg() const { return type == FFMPEG_SRC; }
bool IsLibvlc() const { return( type == LIBVLC_SRC ); } bool IsLibvlc() const { return type == LIBVLC_SRC; }
bool IscURL() const { return( type == CURL_SRC ); } bool IscURL() const { return type == CURL_SRC; }
unsigned int Width() const { return( width ); } unsigned int Width() const { return width; }
unsigned int Height() const { return( height ); } unsigned int Height() const { return height; }
unsigned int Colours() const { return( colours ); } unsigned int Colours() const { return colours; }
unsigned int SubpixelOrder() const { return( subpixelorder ); } unsigned int SubpixelOrder() const { return subpixelorder; }
unsigned int Pixels() const { return( pixels ); } unsigned int Pixels() const { return pixels; }
unsigned int ImageSize() const { return( imagesize ); } unsigned int ImageSize() const { return imagesize; }
unsigned int Bytes() const { return bytes; }; unsigned int Bytes() const { return bytes; };
virtual int Brightness( int/*p_brightness*/=-1 ) { return( -1 ); } virtual int Brightness( int/*p_brightness*/=-1 ) { return -1; }
virtual int Hue( int/*p_hue*/=-1 ) { return( -1 ); } virtual int Hue( int/*p_hue*/=-1 ) { return -1; }
virtual int Colour( int/*p_colour*/=-1 ) { return( -1 ); } virtual int Colour( int/*p_colour*/=-1 ) { return -1; }
virtual int Contrast( int/*p_contrast*/=-1 ) { return( -1 ); } virtual int Contrast( int/*p_contrast*/=-1 ) { return -1; }
bool CanCapture() const { return( capture ); } bool CanCapture() const { return capture; }
bool SupportsNativeVideo() const { bool SupportsNativeVideo() const {
return (type == FFMPEG_SRC); return (type == FFMPEG_SRC);
//return (type == FFMPEG_SRC )||(type == REMOTE_SRC); //return (type == FFMPEG_SRC )||(type == REMOTE_SRC);
} }
virtual int PrimeCapture() { return( 0 ); } virtual int PrimeCapture() { return 0; }
virtual int PreCapture()=0; virtual int PreCapture() = 0;
virtual int Capture( Image &image )=0; virtual int Capture(Image &image) = 0;
virtual int PostCapture()=0; virtual int PostCapture() = 0;
virtual int CaptureAndRecord( Image &image, timeval recording, char* event_directory ) = 0; virtual int CaptureAndRecord(Image &image, timeval recording, char* event_directory) = 0;
virtual int Close()=0; virtual int Close() = 0;
}; };
#endif // ZM_CAMERA_H #endif // ZM_CAMERA_H

View File

@ -120,9 +120,15 @@ Event::Event(
char id_file[PATH_MAX]; char id_file[PATH_MAX];
if ( storage->Scheme() == Storage::DEEP ) {
char *path_ptr = path; char *path_ptr = path;
path_ptr += snprintf(path_ptr, sizeof(path), "%s/%d", storage->Path(), monitor->Id()); path_ptr += snprintf(path_ptr, sizeof(path), "%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, 0755) ) {
if ( errno != EEXIST )
Error("Can't mkdir %s: %s", path, strerror(errno));
}
if ( storage->Scheme() == Storage::DEEP ) {
int dt_parts[6]; int dt_parts[6];
dt_parts[0] = stime->tm_year-100; dt_parts[0] = stime->tm_year-100;
@ -141,10 +147,9 @@ Event::Event(
errno = 0; errno = 0;
if ( mkdir(path, 0755) ) { if ( mkdir(path, 0755) ) {
// FIXME This should not be fatal. Should probably move to a different storage area. // FIXME This should not be fatal. Should probably move to a different storage area.
if ( errno != EEXIST ) { if ( errno != EEXIST )
Error("Can't mkdir %s: %s", path, strerror(errno)); Error("Can't mkdir %s: %s", path, strerror(errno));
} }
}
if ( i == 2 ) if ( i == 2 )
strncpy(date_path, path, sizeof(date_path)); strncpy(date_path, path, sizeof(date_path));
else if ( i >= 3 ) else if ( i >= 3 )
@ -155,29 +160,25 @@ Event::Event(
if ( symlink(time_path, id_file) < 0 ) if ( symlink(time_path, id_file) < 0 )
Error("Can't symlink %s -> %s: %s", id_file, path, strerror(errno)); Error("Can't symlink %s -> %s: %s", id_file, path, strerror(errno));
} else if ( storage->Scheme() == Storage::MEDIUM ) { } else if ( storage->Scheme() == Storage::MEDIUM ) {
char *path_ptr = path;
path_ptr += snprintf( path_ptr += snprintf(
path_ptr, sizeof(path), "%s/%d/%04d-%02d-%02d", path_ptr, sizeof(path), "/%04d-%02d-%02d",
storage->Path(), monitor->Id(), stime->tm_year+1900, stime->tm_mon+1, stime->tm_mday stime->tm_year+1900, stime->tm_mon+1, stime->tm_mday
); );
if ( mkdir(path, 0755) ) { if ( mkdir(path, 0755) ) {
// FIXME This should not be fatal. Should probably move to a different storage area.
if ( errno != EEXIST ) if ( errno != EEXIST )
Error("Can't mkdir %s: %s", path, strerror(errno)); Error("Can't mkdir %s: %s", path, strerror(errno));
} }
path_ptr += snprintf(path_ptr, sizeof(path), "/%" PRIu64, id); path_ptr += snprintf(path_ptr, sizeof(path), "/%" PRIu64, id);
if ( mkdir(path, 0755) ) { if ( mkdir(path, 0755) ) {
// FIXME This should not be fatal. Should probably move to a different storage area.
if ( errno != EEXIST ) if ( errno != EEXIST )
Error("Can't mkdir %s: %s", path, strerror(errno)); Error("Can't mkdir %s: %s", path, strerror(errno));
} }
} else { } else {
snprintf(path, sizeof(path), "%s/%d/%" PRIu64, storage->Path(), monitor->Id(), id); snprintf(path, sizeof(path), "/%" PRIu64, id);
if ( mkdir(path, 0755) ) { if ( mkdir(path, 0755) ) {
if ( errno != EEXIST ) { if ( errno != EEXIST )
Error("Can't mkdir %s: %s", path, strerror(errno)); Error("Can't mkdir %s: %s", path, strerror(errno));
} }
}
// Create empty id tag file // Create empty id tag file
snprintf(id_file, sizeof(id_file), "%s/.%" PRIu64, path, id); snprintf(id_file, sizeof(id_file), "%s/.%" PRIu64, path, id);
@ -540,7 +541,7 @@ Debug(3, "Writing video");
if ( score < 0 ) if ( score < 0 )
score = 0; score = 0;
bool db_frame = ( frame_type != BULK ) || ((frames%config.bulk_frame_interval)==0) || !frames; bool db_frame = ( frame_type != BULK ) || (!frames) || ((frames%config.bulk_frame_interval)==0) ;
if ( db_frame ) { if ( db_frame ) {
Debug( 1, "Adding frame %d of type \"%s\" to DB", frames, Event::frame_type_names[frame_type] ); Debug( 1, "Adding frame %d of type \"%s\" to DB", frames, Event::frame_type_names[frame_type] );

View File

@ -279,8 +279,8 @@ void EventStream::processCommand(const CmdMsg *msg) {
} }
// If we are in single event mode and at the last frame, replay the current event // If we are in single event mode and at the last frame, replay the current event
if ( (mode == MODE_SINGLE) && ((unsigned int)curr_frame_id == event_data->frame_count) ) { if ( (mode == MODE_SINGLE || mode == MODE_NONE) && ((unsigned int)curr_frame_id == event_data->frame_count) ) {
Debug(1, "Was in single_mode, and last frame, so jumping to 1st frame"); Debug(1, "Was in single or no replay mode, and at last frame, so jumping to 1st frame");
curr_frame_id = 1; curr_frame_id = 1;
} else { } else {
Debug(1, "mode is %s, current frame is %d, frame count is %d", (mode == MODE_SINGLE ? "single" : "not single" ), curr_frame_id, event_data->frame_count ); Debug(1, "mode is %s, current frame is %d, frame count is %d", (mode == MODE_SINGLE ? "single" : "not single" ), curr_frame_id, event_data->frame_count );
@ -501,7 +501,7 @@ void EventStream::checkEventLoaded() {
} }
if ( reload_event ) { if ( reload_event ) {
if ( forceEventChange || mode != MODE_SINGLE ) { if ( forceEventChange || ( mode != MODE_SINGLE && mode != MODE_NONE ) ) {
//Info( "SQL:%s", sql ); //Info( "SQL:%s", sql );
if ( mysql_query( &dbconn, sql ) ) { if ( mysql_query( &dbconn, sql ) ) {
Error( "Can't run query: %s", mysql_error( &dbconn ) ); Error( "Can't run query: %s", mysql_error( &dbconn ) );
@ -755,6 +755,12 @@ void EventStream::runStream() {
// commands may set send_frame to true // commands may set send_frame to true
while(checkCommandQueue()); while(checkCommandQueue());
// Update modified time of the socket .lock file so that we can tell which ones are stale.
if ( now.tv_sec - last_comm_update.tv_sec > 3600 ) {
touch(sock_path_lock);
last_comm_update = now;
}
if ( step != 0 ) if ( step != 0 )
curr_frame_id += step; curr_frame_id += step;
@ -812,7 +818,7 @@ void EventStream::runStream() {
step = 0; step = 0;
send_frame = true; send_frame = true;
} else if ( !send_frame ) { } else if ( !send_frame ) {
// We are paused, and doing nothing // We are paused, not stepping and doing nothing
double actual_delta_time = TV_2_FLOAT(now) - last_frame_sent; double actual_delta_time = TV_2_FLOAT(now) - last_frame_sent;
if ( actual_delta_time > MAX_STREAM_DELAY ) { if ( actual_delta_time > MAX_STREAM_DELAY ) {
// Send keepalive // Send keepalive
@ -828,9 +834,11 @@ void EventStream::runStream() {
curr_stream_time = frame_data->timestamp; curr_stream_time = frame_data->timestamp;
if ( !paused ) { if ( !paused ) {
curr_frame_id += replay_rate>0?1:-1; curr_frame_id += (replay_rate>0) ? 1 : -1;
if ( (mode == MODE_SINGLE) && ((unsigned int)curr_frame_id == event_data->frame_count) ) if ( (mode == MODE_SINGLE) && ((unsigned int)curr_frame_id == event_data->frame_count) ) {
Debug(2, "Have mode==MODE_SINGLE and at end of event, looping back to start");
curr_frame_id = 1; curr_frame_id = 1;
}
if ( send_frame && type != STREAM_MPEG ) { if ( send_frame && type != STREAM_MPEG ) {
Debug( 3, "dUs: %d", delta_us ); Debug( 3, "dUs: %d", delta_us );
if ( delta_us ) if ( delta_us )

View File

@ -42,7 +42,7 @@ extern "C" {
class EventStream : public StreamBase { class EventStream : public StreamBase {
public: public:
typedef enum { MODE_SINGLE, MODE_ALL, MODE_ALL_GAPLESS } StreamMode; typedef enum { MODE_NONE, MODE_SINGLE, MODE_ALL, MODE_ALL_GAPLESS } StreamMode;
protected: protected:
struct FrameData { struct FrameData {

View File

@ -225,8 +225,9 @@ static void zm_log_fps(double d, const char *postfix) {
Debug(1, "%3.2f %s", d, postfix); Debug(1, "%3.2f %s", d, postfix);
} else if (v % (100 * 1000)) { } else if (v % (100 * 1000)) {
Debug(1, "%1.0f %s", d, postfix); Debug(1, "%1.0f %s", d, postfix);
} else } else {
Debug(1, "%1.0fk %s", d / 1000, postfix); Debug(1, "%1.0fk %s", d / 1000, postfix);
}
} }
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
@ -355,9 +356,8 @@ int check_sample_fmt(AVCodec *codec, enum AVSampleFormat sample_fmt) {
#if LIBAVCODEC_VERSION_CHECK(56, 8, 0, 60, 100) #if LIBAVCODEC_VERSION_CHECK(56, 8, 0, 60, 100)
#else #else
unsigned int zm_av_packet_ref( AVPacket *dst, AVPacket *src ) { unsigned int zm_av_packet_ref( AVPacket *dst, AVPacket *src ) {
dst->size = (src->size + FF_INPUT_BUFFER_PADDING_SIZE)/sizeof(uint64_t) + 1; av_new_packet(dst,src->size);
dst->data = reinterpret_cast<uint8_t*>(new uint64_t[dst->size]); memcpy(dst->data, src->data, src->size);
memcpy(dst->data, src->data, src->size );
dst->flags = src->flags; dst->flags = src->flags;
return 0; return 0;
} }

View File

@ -303,8 +303,8 @@ void zm_dump_codecpar ( const AVCodecParameters *par );
#define zm_av_packet_unref( packet ) av_packet_unref( packet ) #define zm_av_packet_unref( packet ) av_packet_unref( packet )
#define zm_av_packet_ref( dst, src ) av_packet_ref( dst, src ) #define zm_av_packet_ref( dst, src ) av_packet_ref( dst, src )
#else #else
unsigned int zm_av_packet_ref( AVPacket *dst, AVPacket *src );
#define zm_av_packet_unref( packet ) av_free_packet( packet ) #define zm_av_packet_unref( packet ) av_free_packet( packet )
unsigned int zm_av_packet_ref( AVPacket *dst, AVPacket *src );
#endif #endif
#if LIBAVCODEC_VERSION_CHECK(52, 23, 0, 23, 0) #if LIBAVCODEC_VERSION_CHECK(52, 23, 0, 23, 0)
#define zm_avcodec_decode_video( context, rawFrame, frameComplete, packet ) avcodec_decode_video2( context, rawFrame, frameComplete, packet ) #define zm_avcodec_decode_video( context, rawFrame, frameComplete, packet ) avcodec_decode_video2( context, rawFrame, frameComplete, packet )

View File

@ -331,6 +331,8 @@ int FfmpegCamera::OpenFfmpeg() {
ret = av_dict_set(&opts, "rtsp_transport", "tcp", 0); ret = av_dict_set(&opts, "rtsp_transport", "tcp", 0);
} else if ( method == "rtpRtspHttp" ) { } else if ( method == "rtpRtspHttp" ) {
ret = av_dict_set(&opts, "rtsp_transport", "http", 0); ret = av_dict_set(&opts, "rtsp_transport", "http", 0);
} else if ( method == "rtpUni" ) {
ret = av_dict_set(&opts, "rtsp_transport", "udp", 0);
} else { } else {
Warning("Unknown method (%s)", method.c_str() ); Warning("Unknown method (%s)", method.c_str() );
} }
@ -606,7 +608,8 @@ int FfmpegCamera::OpenFfmpeg() {
return -1; return -1;
} }
mConvertContext = sws_getContext(mVideoCodecContext->width, mConvertContext = sws_getContext(
mVideoCodecContext->width,
mVideoCodecContext->height, mVideoCodecContext->height,
mVideoCodecContext->pix_fmt, mVideoCodecContext->pix_fmt,
width, height, width, height,
@ -722,7 +725,8 @@ int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event
uint32_t video_writer_event_id = monitor->GetVideoWriterEventId(); uint32_t video_writer_event_id = monitor->GetVideoWriterEventId();
if ( last_event_id != video_writer_event_id ) { if ( last_event_id != video_writer_event_id ) {
Debug(2, "Have change of event. last_event(%d), our current (%d)", last_event_id, video_writer_event_id ); Debug(2, "Have change of event. last_event(%d), our current (%d)",
last_event_id, video_writer_event_id);
if ( videoStore ) { if ( videoStore ) {
Info("Re-starting video storage module"); Info("Re-starting video storage module");
@ -731,7 +735,7 @@ int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event
// Also don't know how much it matters for audio. // Also don't know how much it matters for audio.
if ( packet.stream_index == mVideoStreamId ) { if ( packet.stream_index == mVideoStreamId ) {
//Write the packet to our video store //Write the packet to our video store
int ret = videoStore->writeVideoFramePacket( &packet ); int ret = videoStore->writeVideoFramePacket(&packet);
if ( ret < 0 ) { //Less than zero and we skipped a frame if ( ret < 0 ) { //Less than zero and we skipped a frame
Warning("Error writing last packet to videostore."); Warning("Error writing last packet to videostore.");
} }

File diff suppressed because it is too large Load Diff

View File

@ -160,16 +160,16 @@ public:
static void Initialise(); static void Initialise();
static void Deinitialise(); static void Deinitialise();
inline unsigned int Width() const { return( width ); } inline unsigned int Width() const { return width; }
inline unsigned int Height() const { return( height ); } inline unsigned int Height() const { return height; }
inline unsigned int Pixels() const { return( pixels ); } inline unsigned int Pixels() const { return pixels; }
inline unsigned int Colours() const { return( colours ); } inline unsigned int Colours() const { return colours; }
inline unsigned int SubpixelOrder() const { return( subpixelorder ); } inline unsigned int SubpixelOrder() const { return subpixelorder; }
inline unsigned int Size() const { return( size ); } inline unsigned int Size() const { return size; }
/* Internal buffer should not be modified from functions outside of this class */ /* Internal buffer should not be modified from functions outside of this class */
inline const uint8_t* Buffer() const { return( buffer ); } inline const uint8_t* Buffer() const { return buffer; }
inline const uint8_t* Buffer( unsigned int x, unsigned int y= 0 ) const { return( &buffer[colours*((y*width)+x)] ); } inline const uint8_t* Buffer( unsigned int x, unsigned int y= 0 ) const { return &buffer[colours*((y*width)+x)]; }
/* Request writeable buffer */ /* Request writeable buffer */
uint8_t* WriteBuffer(const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder); uint8_t* WriteBuffer(const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder);
@ -196,7 +196,7 @@ public:
} }
inline Image &operator=( const unsigned char *new_buffer ) { inline Image &operator=( const unsigned char *new_buffer ) {
(*fptr_imgbufcpy)(buffer, new_buffer, size); (*fptr_imgbufcpy)(buffer, new_buffer, size);
return( *this ); return *this;
} }
bool ReadRaw( const char *filename ); bool ReadRaw( const char *filename );
@ -274,6 +274,7 @@ void std_delta8_rgba(const uint8_t* col1, const uint8_t* col2, uint8_t* result,
void std_delta8_bgra(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); void std_delta8_bgra(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count);
void std_delta8_argb(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); void std_delta8_argb(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count);
void std_delta8_abgr(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); void std_delta8_abgr(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count);
void neon32_armv7_delta8_gray8(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); void neon32_armv7_delta8_gray8(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count);
void neon32_armv7_delta8_rgba(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); void neon32_armv7_delta8_rgba(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count);
void neon32_armv7_delta8_bgra(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count); void neon32_armv7_delta8_bgra(const uint8_t* col1, const uint8_t* col2, uint8_t* result, unsigned long count);

File diff suppressed because it is too large Load Diff

View File

@ -38,7 +38,7 @@
#endif #endif
bool Logger::smInitialised = false; bool Logger::smInitialised = false;
Logger *Logger::smInstance = 0; Logger *Logger::smInstance = NULL;
Logger::StringMap Logger::smCodes; Logger::StringMap Logger::smCodes;
Logger::IntMap Logger::smSyslogPriorities; Logger::IntMap Logger::smSyslogPriorities;
@ -57,10 +57,10 @@ static void subtractTime( struct timeval * const tp1, struct timeval * const tp2
void Logger::usrHandler( int sig ) { void Logger::usrHandler( int sig ) {
Logger *logger = fetch(); Logger *logger = fetch();
if ( sig == SIGUSR1 ) if ( sig == SIGUSR1 ) {
logger->level( logger->level()+1 ); logger->level(logger->level()+1);
else if ( sig == SIGUSR2 ) } else if ( sig == SIGUSR2 )
logger->level( logger->level()-1 ); logger->level(logger->level()-1);
Info("Logger - Level changed to %d", logger->level()); Info("Logger - Level changed to %d", logger->level());
} }
@ -79,7 +79,7 @@ Logger::Logger() :
mFlush(false) { mFlush(false) {
if ( smInstance ) { if ( smInstance ) {
Panic( "Attempt to create second instance of Logger class" ); Panic("Attempt to create second instance of Logger class");
} }
if ( !smInitialised ) { if ( !smInitialised ) {
@ -133,11 +133,11 @@ void Logger::initialise(const std::string &id, const Options &options) {
std::string tempLogFile; std::string tempLogFile;
if ( (envPtr = getTargettedEnv("LOG_FILE")) ) if ( (envPtr = getTargettedEnv("LOG_FILE")) ) {
tempLogFile = envPtr; tempLogFile = envPtr;
else if ( options.mLogFile.size() ) } else if ( options.mLogFile.size() ) {
tempLogFile = options.mLogFile; tempLogFile = options.mLogFile;
else { } else {
if ( options.mLogPath.size() ) { if ( options.mLogPath.size() ) {
mLogPath = options.mLogPath; mLogPath = options.mLogPath;
} }
@ -169,7 +169,7 @@ void Logger::initialise(const std::string &id, const Options &options) {
tempSyslogLevel = config.log_level_syslog >= DEBUG1 ? DEBUG9 : config.log_level_syslog; tempSyslogLevel = config.log_level_syslog >= DEBUG1 ? DEBUG9 : config.log_level_syslog;
// Legacy // Legacy
if ( (envPtr = getenv( "LOG_PRINT" )) ) if ( (envPtr = getenv("LOG_PRINT")) )
tempTerminalLevel = atoi(envPtr) ? DEBUG9 : NOLOG; tempTerminalLevel = atoi(envPtr) ? DEBUG9 : NOLOG;
if ( (envPtr = getTargettedEnv("LOG_LEVEL")) ) if ( (envPtr = getTargettedEnv("LOG_LEVEL")) )
@ -218,7 +218,7 @@ void Logger::initialise(const std::string &id, const Options &options) {
mFlush = false; mFlush = false;
if ( (envPtr = getenv("LOG_FLUSH")) ) { if ( (envPtr = getenv("LOG_FLUSH")) ) {
mFlush = atoi( envPtr ); mFlush = atoi(envPtr);
} else if ( config.log_debug ) { } else if ( config.log_debug ) {
mFlush = true; mFlush = true;
} }
@ -335,6 +335,10 @@ Logger::Level Logger::level(Logger::Level level) {
mEffectiveLevel = mSyslogLevel; mEffectiveLevel = mSyslogLevel;
if ( mEffectiveLevel > mLevel) if ( mEffectiveLevel > mLevel)
mEffectiveLevel = mLevel; mEffectiveLevel = mLevel;
// DEBUG levels should flush
if ( mLevel > INFO )
mFlush = true;
} }
return mLevel; return mLevel;
} }
@ -577,12 +581,12 @@ void Logger::logPrint( bool hex, const char * const filepath, const int line, co
} }
} }
void logInit( const char *name, const Logger::Options &options ) { void logInit(const char *name, const Logger::Options &options) {
if ( !Logger::smInstance ) if ( !Logger::smInstance )
Logger::smInstance = new Logger(); Logger::smInstance = new Logger();
Logger::Options tempOptions = options; Logger::Options tempOptions = options;
tempOptions.mLogPath = staticConfig.PATH_LOGS; tempOptions.mLogPath = staticConfig.PATH_LOGS;
Logger::smInstance->initialise( name, tempOptions ); Logger::smInstance->initialise(name, tempOptions);
} }
void logTerm() { void logTerm() {

View File

@ -423,16 +423,10 @@ Monitor::Monitor(
snprintf(monitor_dir, sizeof(monitor_dir), "%s/%d", storage->Path(), id); snprintf(monitor_dir, sizeof(monitor_dir), "%s/%d", storage->Path(), id);
if ( purpose == CAPTURE ) { if ( purpose == CAPTURE ) {
struct stat statbuf;
if ( stat(monitor_dir, &statbuf) ) {
if ( errno == ENOENT || errno == ENOTDIR ) {
if ( mkdir(monitor_dir, 0755) ) { if ( mkdir(monitor_dir, 0755) ) {
if ( errno != EEXIST ) {
Error("Can't mkdir %s: %s", monitor_dir, strerror(errno)); Error("Can't mkdir %s: %s", monitor_dir, strerror(errno));
} }
} else {
Warning("Error stat'ing %s, may be fatal. error is %s", monitor_dir, strerror(errno));
}
} }
if ( ! this->connect() ) { if ( ! this->connect() ) {
@ -609,7 +603,8 @@ bool Monitor::connect() {
next_buffer.image = new Image( width, height, camera->Colours(), camera->SubpixelOrder()); next_buffer.image = new Image( width, height, camera->Colours(), camera->SubpixelOrder());
next_buffer.timestamp = new struct timeval; next_buffer.timestamp = new struct timeval;
} }
if ( ( purpose == ANALYSIS ) && analysis_fps ) { if ( purpose == ANALYSIS ) {
if ( analysis_fps ) {
// Size of pre event buffer must be greater than pre_event_count // Size of pre event buffer must be greater than pre_event_count
// if alarm_frame_count > 1, because in this case the buffer contains // if alarm_frame_count > 1, because in this case the buffer contains
// alarmed images that must be discarded when event is created // alarmed images that must be discarded when event is created
@ -617,9 +612,15 @@ bool Monitor::connect() {
pre_event_buffer = new Snapshot[pre_event_buffer_count]; pre_event_buffer = new Snapshot[pre_event_buffer_count];
for ( int i = 0; i < pre_event_buffer_count; i++ ) { for ( int i = 0; i < pre_event_buffer_count; i++ ) {
pre_event_buffer[i].timestamp = new struct timeval; pre_event_buffer[i].timestamp = new struct timeval;
*pre_event_buffer[i].timestamp = {0,0};
pre_event_buffer[i].image = new Image( width, height, camera->Colours(), camera->SubpixelOrder()); pre_event_buffer[i].image = new Image( width, height, camera->Colours(), camera->SubpixelOrder());
} }
} }
timestamps = new struct timeval *[pre_event_count];
images = new Image *[pre_event_count];
last_signal = shared_data->signal;
}
Debug(3, "Success connecting"); Debug(3, "Success connecting");
return true; return true;
} }
@ -1261,6 +1262,7 @@ bool Monitor::Analyse() {
int index; int index;
if ( adaptive_skip ) { if ( adaptive_skip ) {
// I think the idea behind adaptive skip is if we are falling behind, then skip a bunch, but not all
int read_margin = shared_data->last_read_index - shared_data->last_write_index; int read_margin = shared_data->last_read_index - shared_data->last_write_index;
if ( read_margin < 0 ) read_margin += image_buffer_count; if ( read_margin < 0 ) read_margin += image_buffer_count;
@ -1274,7 +1276,10 @@ bool Monitor::Analyse() {
int pending_frames = shared_data->last_write_index - shared_data->last_read_index; int pending_frames = shared_data->last_write_index - shared_data->last_read_index;
if ( pending_frames < 0 ) pending_frames += image_buffer_count; if ( pending_frames < 0 ) pending_frames += image_buffer_count;
Debug( 4, "ReadIndex:%d, WriteIndex: %d, PendingFrames = %d, ReadMargin = %d, Step = %d", shared_data->last_read_index, shared_data->last_write_index, pending_frames, read_margin, step ); Debug(4,
"ReadIndex:%d, WriteIndex: %d, PendingFrames = %d, ReadMargin = %d, Step = %d",
shared_data->last_read_index, shared_data->last_write_index, pending_frames, read_margin, step
);
if ( step <= pending_frames ) { if ( step <= pending_frames ) {
index = (shared_data->last_read_index+step)%image_buffer_count; index = (shared_data->last_read_index+step)%image_buffer_count;
} else { } else {
@ -1294,17 +1299,17 @@ bool Monitor::Analyse() {
if ( shared_data->action ) { if ( shared_data->action ) {
// Can there be more than 1 bit set in the action? Shouldn't these be elseifs? // Can there be more than 1 bit set in the action? Shouldn't these be elseifs?
if ( shared_data->action & RELOAD ) { if ( shared_data->action & RELOAD ) {
Info( "Received reload indication at count %d", image_count ); Info("Received reload indication at count %d", image_count);
shared_data->action &= ~RELOAD; shared_data->action &= ~RELOAD;
Reload(); Reload();
} }
if ( shared_data->action & SUSPEND ) { if ( shared_data->action & SUSPEND ) {
if ( Active() ) { if ( Active() ) {
Info( "Received suspend indication at count %d", image_count ); Info("Received suspend indication at count %d", image_count);
shared_data->active = false; shared_data->active = false;
//closeEvent(); //closeEvent();
} else { } else {
Info( "Received suspend indication at count %d, but wasn't active", image_count ); Info("Received suspend indication at count %d, but wasn't active", image_count);
} }
if ( config.max_suspend_time ) { if ( config.max_suspend_time ) {
auto_resume_time = now.tv_sec + config.max_suspend_time; auto_resume_time = now.tv_sec + config.max_suspend_time;
@ -1313,7 +1318,7 @@ bool Monitor::Analyse() {
} }
if ( shared_data->action & RESUME ) { if ( shared_data->action & RESUME ) {
if ( Enabled() && !Active() ) { if ( Enabled() && !Active() ) {
Info( "Received resume indication at count %d", image_count ); Info("Received resume indication at count %d", image_count);
shared_data->active = true; shared_data->active = true;
ref_image = *snap_image; ref_image = *snap_image;
ready_count = image_count+(warmup_count/2); ready_count = image_count+(warmup_count/2);
@ -1324,24 +1329,14 @@ bool Monitor::Analyse() {
} // end if shared_data->action } // end if shared_data->action
if ( auto_resume_time && (now.tv_sec >= auto_resume_time) ) { if ( auto_resume_time && (now.tv_sec >= auto_resume_time) ) {
Info( "Auto resuming at count %d", image_count ); Info("Auto resuming at count %d", image_count);
shared_data->active = true; shared_data->active = true;
ref_image = *snap_image; ref_image = *snap_image;
ready_count = image_count+(warmup_count/2); ready_count = image_count+(warmup_count/2);
auto_resume_time = 0; auto_resume_time = 0;
} }
static bool static_undef = true;
static int last_section_mod = 0; static int last_section_mod = 0;
static bool last_signal;
if ( static_undef ) {
// Sure would be nice to be able to assume that these were already initialized. It's just 1 compare/branch, but really not neccessary.
static_undef = false;
timestamps = new struct timeval *[pre_event_count];
images = new Image *[pre_event_count];
last_signal = shared_data->signal;
}
if ( Enabled() ) { if ( Enabled() ) {
bool signal = shared_data->signal; bool signal = shared_data->signal;
@ -1394,27 +1389,24 @@ bool Monitor::Analyse() {
} else if ( signal && Active() && (function == MODECT || function == MOCORD) ) { } else if ( signal && Active() && (function == MODECT || function == MOCORD) ) {
Event::StringSet zoneSet; Event::StringSet zoneSet;
int motion_score = last_motion_score;
if ( !(image_count % (motion_frame_skip+1) ) ) { if ( !(image_count % (motion_frame_skip+1) ) ) {
// Get new score. // Get new score.
motion_score = DetectMotion(*snap_image, zoneSet); int new_motion_score = DetectMotion(*snap_image, zoneSet);
Debug(3, Debug(3,
"After motion detection, last_motion_score(%d), new motion score(%d)", "After motion detection, last_motion_score(%d), new motion score(%d)",
last_motion_score, motion_score last_motion_score, new_motion_score
); );
// Why are we updating the last_motion_score too? last_motion_score = new_motion_score;
last_motion_score = motion_score;
} }
//int motion_score = DetectBlack( *snap_image, zoneSet ); if ( last_motion_score ) {
if ( motion_score ) {
if ( !event ) { if ( !event ) {
score += motion_score; score += last_motion_score;
if ( cause.length() ) if ( cause.length() )
cause += ", "; cause += ", ";
cause += MOTION_CAUSE; cause += MOTION_CAUSE;
} else { } else {
score += motion_score; score += last_motion_score;
} }
noteSetMap[MOTION_CAUSE] = zoneSet; noteSetMap[MOTION_CAUSE] = zoneSet;
} // end if motion_score } // end if motion_score
@ -1436,7 +1428,7 @@ bool Monitor::Analyse() {
first_link = false; first_link = false;
} }
} }
noteSet.insert( linked_monitors[i]->Name() ); noteSet.insert(linked_monitors[i]->Name());
score += 50; score += 50;
} }
} else { } else {
@ -1450,14 +1442,15 @@ bool Monitor::Analyse() {
//TODO: What happens is the event closes and sets recording to false then recording to true again so quickly that our capture daemon never picks it up. Maybe need a refresh flag? //TODO: What happens is the event closes and sets recording to false then recording to true again so quickly that our capture daemon never picks it up. Maybe need a refresh flag?
if ( (!signal_change && signal) && (function == RECORD || function == MOCORD) ) { if ( (!signal_change && signal) && (function == RECORD || function == MOCORD) ) {
if ( event ) { if ( event ) {
//TODO: We shouldn't have to do this every time. Not sure why it clears itself if this isn't here?? Debug(3, "Detected new event at (%d.%d)", timestamp->tv_sec, timestamp->tv_usec);
//snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "%s", event->getEventFile());
Debug( 3, "Detected new event at (%d.%d)", timestamp->tv_sec,timestamp->tv_usec );
if ( section_length && ( timestamp->tv_sec >= section_length ) ) { if ( section_length && ( timestamp->tv_sec >= section_length ) ) {
// TODO: Wouldn't this be clearer if we just did something like if now - event->start > section_length ? // TODO: Wouldn't this be clearer if we just did something like if now - event->start > section_length ?
int section_mod = timestamp->tv_sec % section_length; int section_mod = timestamp->tv_sec % section_length;
Debug( 3, "Section length (%d) Last Section Mod(%d), new section mod(%d)", section_length, last_section_mod, section_mod ); Debug(3,
"Section length (%d) Last Section Mod(%d), new section mod(%d)",
section_length, last_section_mod, section_mod
);
if ( section_mod < last_section_mod ) { if ( section_mod < last_section_mod ) {
//if ( state == IDLE || state == TAPE || event_close_mode == CLOSE_TIME ) { //if ( state == IDLE || state == TAPE || event_close_mode == CLOSE_TIME ) {
//if ( state == TAPE ) { //if ( state == TAPE ) {
@ -1480,7 +1473,7 @@ bool Monitor::Analyse() {
if ( ! event ) { if ( ! event ) {
// Create event // Create event
event = new Event( this, *timestamp, "Continuous", noteSetMap, videoRecording ); event = new Event(this, *timestamp, "Continuous", noteSetMap, videoRecording);
shared_data->last_event = event->Id(); shared_data->last_event = event->Id();
//set up video store data //set up video store data
snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "%s", event->getEventFile()); snprintf(video_store_data->event_file, sizeof(video_store_data->event_file), "%s", event->getEventFile());
@ -1554,6 +1547,10 @@ bool Monitor::Analyse() {
int pre_index; int pre_index;
int pre_event_images = pre_event_count; int pre_event_images = pre_event_count;
if ( event ) {
// SHouldn't be able to happen because
Error("Creating new event when one exists");
}
if ( analysis_fps && pre_event_count ) { if ( analysis_fps && pre_event_count ) {
// If analysis fps is set, // If analysis fps is set,
// compute the index for pre event images in the dedicated buffer // compute the index for pre event images in the dedicated buffer
@ -1576,8 +1573,8 @@ bool Monitor::Analyse() {
else else
pre_index = ((index + image_buffer_count) - pre_event_count)%image_buffer_count; pre_index = ((index + image_buffer_count) - pre_event_count)%image_buffer_count;
Debug(4,"Resulting pre_index(%d) from index(%d) + image_buffer_count(%d) - pre_event_count(%d) % %d", Debug(4,"Resulting pre_index(%d) from index(%d) + image_buffer_count(%d) - pre_event_count(%d)",
pre_index, index, image_buffer_count, pre_event_count, image_buffer_count); pre_index, index, image_buffer_count, pre_event_count);
// Seek forward the next filled slot in to the buffer (oldest data) // Seek forward the next filled slot in to the buffer (oldest data)
// from the current position // from the current position
@ -1622,7 +1619,6 @@ bool Monitor::Analyse() {
pre_index = (pre_index + 1)%image_buffer_count; pre_index = (pre_index + 1)%image_buffer_count;
} }
} }
event->AddFrames( pre_event_images, images, timestamps ); event->AddFrames( pre_event_images, images, timestamps );
} }
if ( alarm_frame_count ) { if ( alarm_frame_count ) {
@ -1735,6 +1731,7 @@ bool Monitor::Analyse() {
} }
shared_data->state = state = IDLE; shared_data->state = state = IDLE;
last_section_mod = 0; last_section_mod = 0;
trigger_data->trigger_state = TRIGGER_CANCEL;
} // end if ( trigger_data->trigger_state != TRIGGER_OFF ) } // end if ( trigger_data->trigger_state != TRIGGER_OFF )
if ( (!signal_change && signal) && (function == MODECT || function == MOCORD) ) { if ( (!signal_change && signal) && (function == MODECT || function == MOCORD) ) {
@ -2113,6 +2110,7 @@ Monitor *Monitor::Load(MYSQL_ROW dbrow, bool load_zones, Purpose purpose) {
Camera *camera = 0; Camera *camera = 0;
if ( type == "Local" ) { if ( type == "Local" ) {
#if ZM_HAS_V4L
int extras = (deinterlacing>>24)&0xff; int extras = (deinterlacing>>24)&0xff;
camera = new LocalCamera( camera = new LocalCamera(
@ -2135,6 +2133,9 @@ Monitor *Monitor::Load(MYSQL_ROW dbrow, bool load_zones, Purpose purpose) {
record_audio, record_audio,
extras extras
); );
#else
Fatal("ZoneMinder not built with Local Camera support");
#endif
} else if ( type == "Remote" ) { } else if ( type == "Remote" ) {
if ( protocol == "http" ) { if ( protocol == "http" ) {
camera = new RemoteCameraHttp( camera = new RemoteCameraHttp(
@ -2387,7 +2388,9 @@ int Monitor::Capture() {
} }
if ( captureResult < 0 ) { if ( captureResult < 0 ) {
Warning("Return from Capture (%d), signal loss", captureResult); Info("Return from Capture (%d), signal loss", captureResult);
// Tell zma to end the event. zma will reset TRIGGER
trigger_data->trigger_state = TRIGGER_OFF;
// Unable to capture image for temporary reason // Unable to capture image for temporary reason
// Fake a signal loss image // Fake a signal loss image
Rgb signalcolor; Rgb signalcolor;
@ -2473,9 +2476,7 @@ int Monitor::Capture() {
//Info( "%d -> %d -> %lf -> %lf", now-last_fps_time, fps_report_interval/(now-last_fps_time), double(fps_report_interval)/(now-last_fps_time), fps ); //Info( "%d -> %d -> %lf -> %lf", now-last_fps_time, fps_report_interval/(now-last_fps_time), double(fps_report_interval)/(now-last_fps_time), fps );
Info("%s: images:%d - Capturing at %.2lf fps, capturing bandwidth %ubytes/sec", name, image_count, new_fps, new_capture_bandwidth); Info("%s: images:%d - Capturing at %.2lf fps, capturing bandwidth %ubytes/sec", name, image_count, new_fps, new_capture_bandwidth);
last_fps_time = now; last_fps_time = now;
if ( new_fps != fps ) {
fps = new_fps; fps = new_fps;
db_mutex.lock(); db_mutex.lock();
static char sql[ZM_SQL_SML_BUFSIZ]; static char sql[ZM_SQL_SML_BUFSIZ];
snprintf(sql, sizeof(sql), snprintf(sql, sizeof(sql),
@ -2485,7 +2486,7 @@ int Monitor::Capture() {
Error("Can't run query: %s", mysql_error(&dbconn)); Error("Can't run query: %s", mysql_error(&dbconn));
} }
db_mutex.unlock(); db_mutex.unlock();
} // end if new_fps != fps Debug(4,sql);
} // end if time has changed since last update } // end if time has changed since last update
} // end if it might be time to report the fps } // end if it might be time to report the fps
} // end if captureResult } // end if captureResult

View File

@ -127,24 +127,25 @@ protected:
uint8_t format; /* +55 */ uint8_t format; /* +55 */
uint32_t imagesize; /* +56 */ uint32_t imagesize; /* +56 */
uint32_t epadding1; /* +60 */ uint32_t epadding1; /* +60 */
uint32_t epadding2; /* +64 */
/* /*
** This keeps 32bit time_t and 64bit time_t identical and compatible as long as time is before 2038. ** This keeps 32bit time_t and 64bit time_t identical and compatible as long as time is before 2038.
** Shared memory layout should be identical for both 32bit and 64bit and is multiples of 16. ** Shared memory layout should be identical for both 32bit and 64bit and is multiples of 16.
** Because startup_time is 64bit it may be aligned to a 64bit boundary. So it's offset SHOULD be a multiple
** of 8. Add or delete epadding's to achieve this.
*/ */
union { /* +68 */ union { /* +64 */
time_t startup_time; /* When the zmc process started. zmwatch uses this to see how long the process has been running without getting any images */ time_t startup_time; /* When the zmc process started. zmwatch uses this to see how long the process has been running without getting any images */
uint64_t extrapad1; uint64_t extrapad1;
}; };
union { /* +76 */ union { /* +72 */
time_t last_write_time; time_t last_write_time;
uint64_t extrapad2; uint64_t extrapad2;
}; };
union { /* +84 */ union { /* +80 */
time_t last_read_time; time_t last_read_time;
uint64_t extrapad3; uint64_t extrapad3;
}; };
uint8_t control_state[256]; /* +92 */ uint8_t control_state[256]; /* +88 */
char alarm_cause[256]; char alarm_cause[256];
@ -288,6 +289,8 @@ protected:
Rgb signal_check_colour; // The colour that the camera will emit when no video signal detected Rgb signal_check_colour; // The colour that the camera will emit when no video signal detected
bool embed_exif; // Whether to embed Exif data into each image frame or not bool embed_exif; // Whether to embed Exif data into each image frame or not
bool last_signal;
double fps; double fps;
unsigned int last_camera_bytes; unsigned int last_camera_bytes;

View File

@ -482,7 +482,7 @@ void MonitorStream::runStream() {
swap_path = staticConfig.PATH_SWAP; swap_path = staticConfig.PATH_SWAP;
Debug( 3, "Checking swap path folder: %s", swap_path.c_str() ); Debug( 3, "Checking swap path folder: %s", swap_path.c_str() );
if ( checkSwapPath(swap_path.c_str(), false) ) { if ( checkSwapPath(swap_path.c_str(), true) ) {
swap_path += stringtf("/zmswap-m%d", monitor->Id()); swap_path += stringtf("/zmswap-m%d", monitor->Id());
Debug(4, "Checking swap path subfolder: %s", swap_path.c_str()); Debug(4, "Checking swap path subfolder: %s", swap_path.c_str());
@ -531,6 +531,12 @@ void MonitorStream::runStream() {
Debug(2, "Have checking command Queue for connkey: %d", connkey ); Debug(2, "Have checking command Queue for connkey: %d", connkey );
got_command = true; got_command = true;
} }
// Update modified time of the socket .lock file so that we can tell which ones are stale.
if ( now.tv_sec - last_comm_update.tv_sec > 3600 ) {
touch(sock_path_lock);
last_comm_update = now;
}
} }
if ( paused ) { if ( paused ) {
@ -633,7 +639,7 @@ Debug(2, "Have checking command Queue for connkey: %d", connkey );
// Send the next frame // Send the next frame
Monitor::Snapshot *snap = &monitor->image_buffer[index]; Monitor::Snapshot *snap = &monitor->image_buffer[index];
//Debug(2, "sending Frame."); Debug(2, "sending Frame.");
if ( !sendFrame(snap->image, snap->timestamp) ) { if ( !sendFrame(snap->image, snap->timestamp) ) {
Debug(2, "sendFrame failed, quiting."); Debug(2, "sendFrame failed, quiting.");
zm_terminate = true; zm_terminate = true;
@ -687,7 +693,7 @@ Debug(2, "Have checking command Queue for connkey: %d", connkey );
} // end if buffered playback } // end if buffered playback
frame_count++; frame_count++;
} else { } else {
Debug(5,"Waiting for capture"); Debug(4,"Waiting for capture last_write_index=%u", monitor->shared_data->last_write_index);
} // end if ( (unsigned int)last_read_index != monitor->shared_data->last_write_index ) } // end if ( (unsigned int)last_read_index != monitor->shared_data->last_write_index )
unsigned long sleep_time = (unsigned long)((1000000 * ZM_RATE_BASE)/((base_fps?base_fps:1)*abs(replay_rate*2))); unsigned long sleep_time = (unsigned long)((1000000 * ZM_RATE_BASE)/((base_fps?base_fps:1)*abs(replay_rate*2)));

View File

@ -388,7 +388,7 @@ int RtspThread::run() {
std::string trackUrl = mUrl; std::string trackUrl = mUrl;
std::string controlUrl; std::string controlUrl;
_AVCODECID codecId; _AVCODECID codecId = AV_CODEC_ID_NONE;
if ( mFormatContext->nb_streams >= 1 ) { if ( mFormatContext->nb_streams >= 1 ) {
for ( unsigned int i = 0; i < mFormatContext->nb_streams; i++ ) { for ( unsigned int i = 0; i < mFormatContext->nb_streams; i++ ) {

View File

@ -267,7 +267,20 @@ bool StreamBase::sendTextFrame( const char *frame_text ) {
void StreamBase::openComms() { void StreamBase::openComms() {
if ( connkey > 0 ) { if ( connkey > 0 ) {
unsigned int length = snprintf(sock_path_lock, sizeof(sock_path_lock), "%s/zms-%06d.lock", staticConfig.PATH_SOCKS.c_str(), connkey); // Have to mkdir because systemd is now chrooting and the dir may not exist
if ( mkdir(staticConfig.PATH_SOCKS.c_str(), 0755) ) {
if ( errno != EEXIST ) {
Error("Can't mkdir ZM_PATH_SOCKS %s: %s.", staticConfig.PATH_SOCKS.c_str(), strerror(errno));
}
}
unsigned int length = snprintf(
sock_path_lock,
sizeof(sock_path_lock),
"%s/zms-%06d.lock",
staticConfig.PATH_SOCKS.c_str(),
connkey
);
if ( length >= sizeof(sock_path_lock) ) { if ( length >= sizeof(sock_path_lock) ) {
Warning("Socket lock path was truncated."); Warning("Socket lock path was truncated.");
} }
@ -275,14 +288,14 @@ void StreamBase::openComms() {
lock_fd = open(sock_path_lock, O_CREAT|O_WRONLY, S_IRUSR | S_IWUSR); lock_fd = open(sock_path_lock, O_CREAT|O_WRONLY, S_IRUSR | S_IWUSR);
if ( lock_fd <= 0 ) { if ( lock_fd <= 0 ) {
Error("Unable to open sock lock file %s: %s", sock_path_lock, strerror(errno) ); Error("Unable to open sock lock file %s: %s", sock_path_lock, strerror(errno));
lock_fd = 0; lock_fd = 0;
} else if ( flock(lock_fd, LOCK_EX) != 0 ) { } else if ( flock(lock_fd, LOCK_EX) != 0 ) {
Error("Unable to lock sock lock file %s: %s", sock_path_lock, strerror(errno) ); Error("Unable to lock sock lock file %s: %s", sock_path_lock, strerror(errno));
close(lock_fd); close(lock_fd);
lock_fd = 0; lock_fd = 0;
} else { } else {
Debug( 1, "We have obtained a lock on %s fd: %d", sock_path_lock, lock_fd); Debug(1, "We have obtained a lock on %s fd: %d", sock_path_lock, lock_fd);
} }
sd = socket(AF_UNIX, SOCK_DGRAM, 0); sd = socket(AF_UNIX, SOCK_DGRAM, 0);
@ -292,7 +305,13 @@ void StreamBase::openComms() {
Debug(1, "Have socket %d", sd); Debug(1, "Have socket %d", sd);
} }
length = snprintf(loc_sock_path, sizeof(loc_sock_path), "%s/zms-%06ds.sock", staticConfig.PATH_SOCKS.c_str(), connkey); length = snprintf(
loc_sock_path,
sizeof(loc_sock_path),
"%s/zms-%06ds.sock",
staticConfig.PATH_SOCKS.c_str(),
connkey
);
if ( length >= sizeof(loc_sock_path) ) { if ( length >= sizeof(loc_sock_path) ) {
Warning("Socket path was truncated."); Warning("Socket path was truncated.");
length = sizeof(loc_sock_path)-1; length = sizeof(loc_sock_path)-1;
@ -306,13 +325,15 @@ void StreamBase::openComms() {
strncpy(loc_addr.sun_path, loc_sock_path, sizeof(loc_addr.sun_path)); strncpy(loc_addr.sun_path, loc_sock_path, sizeof(loc_addr.sun_path));
loc_addr.sun_family = AF_UNIX; loc_addr.sun_family = AF_UNIX;
Debug(3, "Binding to %s", loc_sock_path); Debug(3, "Binding to %s", loc_sock_path);
if ( bind(sd, (struct sockaddr *)&loc_addr, strlen(loc_addr.sun_path)+sizeof(loc_addr.sun_family)+1) < 0 ) { if ( ::bind(sd, (struct sockaddr *)&loc_addr, strlen(loc_addr.sun_path)+sizeof(loc_addr.sun_family)+1) < 0 ) {
Fatal("Can't bind: %s", strerror(errno)); Fatal("Can't bind: %s", strerror(errno));
} }
snprintf(rem_sock_path, sizeof(rem_sock_path), "%s/zms-%06dw.sock", staticConfig.PATH_SOCKS.c_str(), connkey); snprintf(rem_sock_path, sizeof(rem_sock_path), "%s/zms-%06dw.sock", staticConfig.PATH_SOCKS.c_str(), connkey);
strncpy(rem_addr.sun_path, rem_sock_path, sizeof(rem_addr.sun_path)-1); strncpy(rem_addr.sun_path, rem_sock_path, sizeof(rem_addr.sun_path)-1);
rem_addr.sun_family = AF_UNIX; rem_addr.sun_family = AF_UNIX;
gettimeofday(&last_comm_update, NULL);
} // end if connKey > 0 } // end if connKey > 0
Debug(2, "comms open"); Debug(2, "comms open");
} // end void StreamBase::openComms() } // end void StreamBase::openComms()

View File

@ -85,6 +85,7 @@ protected:
int step; int step;
struct timeval now; struct timeval now;
struct timeval last_comm_update;
double base_fps; double base_fps;
double effective_fps; double effective_fps;

View File

@ -24,6 +24,8 @@
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
#include <stdarg.h> #include <stdarg.h>
#include <fcntl.h> /* Definition of AT_* constants */
#include <sys/stat.h>
#if defined(__arm__) #if defined(__arm__)
#include <sys/auxv.h> #include <sys/auxv.h>
#endif #endif
@ -414,3 +416,22 @@ Warning("ZM Compiled without LIBCURL. UriDecoding not implemented.");
#endif #endif
} }
void touch(const char *pathname) {
int fd = open(pathname,
O_WRONLY|O_CREAT|O_NOCTTY|O_NONBLOCK,
0666);
if ( fd < 0 ) {
// Couldn't open that path.
Error("Couldn't open() path \"%s in touch", pathname);
return;
}
int rc = utimensat(AT_FDCWD,
pathname,
nullptr,
0);
if ( rc ) {
Error("Couldn't utimensat() path %s in touch", pathname);
return;
}
}

View File

@ -63,5 +63,5 @@ extern unsigned int neonversion;
char *timeval_to_string( struct timeval tv ); char *timeval_to_string( struct timeval tv );
std::string UriDecode( const std::string &encoded ); std::string UriDecode( const std::string &encoded );
void touch( const char *pathname );
#endif // ZM_UTILS_H #endif // ZM_UTILS_H

View File

@ -64,7 +64,7 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in,
} }
// Couldn't deduce format from filename, trying from format name // Couldn't deduce format from filename, trying from format name
if (!oc) { if ( !oc ) {
avformat_alloc_output_context2(&oc, NULL, format, filename); avformat_alloc_output_context2(&oc, NULL, format, filename);
if (!oc) { if (!oc) {
Error( Error(
@ -108,7 +108,7 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in,
Debug(2, "Success creating video out stream"); Debug(2, "Success creating video out stream");
} }
if (!video_out_ctx->codec_tag) { if ( !video_out_ctx->codec_tag ) {
video_out_ctx->codec_tag = video_out_ctx->codec_tag =
av_codec_get_tag(oc->oformat->codec_tag, video_in_ctx->codec_id); av_codec_get_tag(oc->oformat->codec_tag, video_in_ctx->codec_id);
Debug(2, "No codec_tag, setting to %d", video_out_ctx->codec_tag); Debug(2, "No codec_tag, setting to %d", video_out_ctx->codec_tag);
@ -127,9 +127,10 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in,
#else #else
video_out_stream = video_out_stream =
avformat_new_stream(oc,(AVCodec *)(video_in_ctx->codec)); avformat_new_stream(oc, NULL);
//(AVCodec *)(video_in_ctx->codec));
//avformat_new_stream(oc,(const AVCodec *)(video_in_ctx->codec)); //avformat_new_stream(oc,(const AVCodec *)(video_in_ctx->codec));
if (!video_out_stream) { if ( !video_out_stream ) {
Fatal("Unable to create video out stream\n"); Fatal("Unable to create video out stream\n");
} else { } else {
Debug(2, "Success creating video out stream"); Debug(2, "Success creating video out stream");
@ -158,6 +159,9 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in,
// Just copy them from the in, no reason to choose different // Just copy them from the in, no reason to choose different
video_out_ctx->time_base = video_in_ctx->time_base; video_out_ctx->time_base = video_in_ctx->time_base;
if ( ! (video_out_ctx->time_base.num && video_out_ctx->time_base.den) ) {
video_out_ctx->time_base = AV_TIME_BASE_Q;
}
video_out_stream->time_base = video_in_stream->time_base; video_out_stream->time_base = video_in_stream->time_base;
Debug(3, Debug(3,
@ -178,7 +182,6 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in,
} }
Monitor::Orientation orientation = monitor->getOrientation(); Monitor::Orientation orientation = monitor->getOrientation();
Debug(3, "Have orientation");
if (orientation) { if (orientation) {
if (orientation == Monitor::ROTATE_0) { if (orientation == Monitor::ROTATE_0) {
} else if (orientation == Monitor::ROTATE_90) { } else if (orientation == Monitor::ROTATE_90) {
@ -245,18 +248,18 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in,
// Copy params from instream to ctx // Copy params from instream to ctx
ret = avcodec_parameters_to_context(audio_out_ctx, ret = avcodec_parameters_to_context(audio_out_ctx,
audio_in_stream->codecpar); audio_in_stream->codecpar);
if (ret < 0) { if ( ret < 0 ) {
Error("Unable to copy audio params to ctx %s\n", Error("Unable to copy audio params to ctx %s",
av_make_error_string(ret).c_str()); av_make_error_string(ret).c_str());
} }
ret = avcodec_parameters_from_context(audio_out_stream->codecpar, ret = avcodec_parameters_from_context(audio_out_stream->codecpar,
audio_out_ctx); audio_out_ctx);
if (ret < 0) { if ( ret < 0 ) {
Error("Unable to copy audio params to stream %s\n", Error("Unable to copy audio params to stream %s",
av_make_error_string(ret).c_str()); av_make_error_string(ret).c_str());
} }
if (!audio_out_ctx->codec_tag) { if ( !audio_out_ctx->codec_tag ) {
audio_out_ctx->codec_tag = av_codec_get_tag( audio_out_ctx->codec_tag = av_codec_get_tag(
oc->oformat->codec_tag, audio_in_ctx->codec_id); oc->oformat->codec_tag, audio_in_ctx->codec_id);
Debug(2, "Setting audio codec tag to %d", Debug(2, "Setting audio codec tag to %d",
@ -268,12 +271,12 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in,
ret = avcodec_copy_context(audio_out_ctx, audio_in_ctx); ret = avcodec_copy_context(audio_out_ctx, audio_in_ctx);
audio_out_ctx->codec_tag = 0; audio_out_ctx->codec_tag = 0;
#endif #endif
if (ret < 0) { if ( ret < 0 ) {
Error("Unable to copy audio ctx %s\n", Error("Unable to copy audio ctx %s",
av_make_error_string(ret).c_str()); av_make_error_string(ret).c_str());
audio_out_stream = NULL; audio_out_stream = NULL;
} else { } else {
if (audio_out_ctx->channels > 1) { if ( audio_out_ctx->channels > 1 ) {
Warning("Audio isn't mono, changing it."); Warning("Audio isn't mono, changing it.");
audio_out_ctx->channels = 1; audio_out_ctx->channels = 1;
} else { } else {
@ -283,7 +286,7 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in,
} // end if audio_out_stream } // end if audio_out_stream
} // end if is AAC } // end if is AAC
if (audio_out_stream) { if ( audio_out_stream ) {
if (oc->oformat->flags & AVFMT_GLOBALHEADER) { if (oc->oformat->flags & AVFMT_GLOBALHEADER) {
#if LIBAVCODEC_VERSION_CHECK(56, 35, 0, 64, 0) #if LIBAVCODEC_VERSION_CHECK(56, 35, 0, 64, 0)
audio_out_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; audio_out_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
@ -336,14 +339,17 @@ bool VideoStore::open() {
} else if (av_dict_count(opts) != 0) { } else if (av_dict_count(opts) != 0) {
Warning("some options not set\n"); Warning("some options not set\n");
} }
if (opts) av_dict_free(&opts);
if (ret < 0) { if (ret < 0) {
Error("Error occurred when writing out file header to %s: %s\n", Error("Error occurred when writing out file header to %s: %s\n",
filename, av_make_error_string(ret).c_str()); filename, av_make_error_string(ret).c_str());
/* free the stream */
avio_closep(&oc->pb);
//avformat_free_context(oc);
return false; return false;
} }
if (opts) av_dict_free(&opts);
return true; return true;
} } // end VideoStore::open()
VideoStore::~VideoStore() { VideoStore::~VideoStore() {
@ -353,6 +359,9 @@ VideoStore::~VideoStore() {
// The codec queues data. We need to send a flush command and out // The codec queues data. We need to send a flush command and out
// whatever we get. Failures are not fatal. // whatever we get. Failures are not fatal.
AVPacket pkt; AVPacket pkt;
// Without these we seg fault I don't know why.
pkt.data = NULL;
pkt.size = 0;
av_init_packet(&pkt); av_init_packet(&pkt);
while (1) { while (1) {
@ -402,16 +411,18 @@ VideoStore::~VideoStore() {
} // end if audio_out_codec } // end if audio_out_codec
// Flush Queues // Flush Queues
Debug(1,"Flushing interleaved queues");
av_interleaved_write_frame(oc, NULL); av_interleaved_write_frame(oc, NULL);
Debug(1,"Writing trailer");
/* Write the trailer before close */ /* Write the trailer before close */
if (int rc = av_write_trailer(oc)) { if (int rc = av_write_trailer(oc)) {
Error("Error writing trailer %s", av_err2str(rc)); Error("Error writing trailer %s", av_err2str(rc));
} else { } else {
Debug(3, "Sucess Writing trailer"); Debug(3, "Success Writing trailer");
} }
// WHen will be not using a file ? // When will we not be using a file ?
if ( !(out_format->flags & AVFMT_NOFILE) ) { if ( !(out_format->flags & AVFMT_NOFILE) ) {
/* Close the out file. */ /* Close the out file. */
Debug(2, "Closing"); Debug(2, "Closing");
@ -422,7 +433,7 @@ VideoStore::~VideoStore() {
} else { } else {
Debug(3, "Not closing avio because we are not writing to a file."); Debug(3, "Not closing avio because we are not writing to a file.");
} }
} } // end if ( oc->pb )
// I wonder if we should be closing the file first. // I wonder if we should be closing the file first.
// I also wonder if we really need to be doing all the ctx // I also wonder if we really need to be doing all the ctx
// allocation/de-allocation constantly, or whether we can just re-use it. // allocation/de-allocation constantly, or whether we can just re-use it.
@ -507,16 +518,23 @@ bool VideoStore::setup_resampler() {
} }
Debug(2, "Have audio out codec"); Debug(2, "Have audio out codec");
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
// audio_out_ctx = audio_out_stream->codec; // audio_out_ctx = audio_out_stream->codec;
audio_out_ctx = avcodec_alloc_context3(audio_out_codec); audio_out_ctx = avcodec_alloc_context3(audio_out_codec);
if (!audio_out_ctx) { if ( !audio_out_ctx ) {
Error("could not allocate codec ctx for AAC\n"); Error("could not allocate codec ctx for AAC");
audio_out_stream = NULL; audio_out_stream = NULL;
return false; return false;
} }
Debug(2, "Have audio_out_ctx"); Debug(2, "Have audio_out_ctx");
// Now copy them to the out stream
audio_out_stream = avformat_new_stream(oc, NULL);
#else
audio_out_stream = avformat_new_stream(oc, NULL);
audio_out_ctx = audio_out_stream->codec;
#endif
/* put sample parameters */ /* put sample parameters */
audio_out_ctx->bit_rate = audio_in_ctx->bit_rate; audio_out_ctx->bit_rate = audio_in_ctx->bit_rate;
@ -524,7 +542,17 @@ bool VideoStore::setup_resampler() {
audio_out_ctx->channels = audio_in_ctx->channels; audio_out_ctx->channels = audio_in_ctx->channels;
audio_out_ctx->channel_layout = audio_in_ctx->channel_layout; audio_out_ctx->channel_layout = audio_in_ctx->channel_layout;
audio_out_ctx->sample_fmt = audio_in_ctx->sample_fmt; audio_out_ctx->sample_fmt = audio_in_ctx->sample_fmt;
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
#else
audio_out_ctx->refcounted_frames = 1; audio_out_ctx->refcounted_frames = 1;
#endif
if ( ! audio_out_ctx->channel_layout ) {
Debug(3, "Correcting channel layout from (%d) to (%d)",
audio_out_ctx->channel_layout,
av_get_default_channel_layout(audio_out_ctx->channels)
);
audio_out_ctx->channel_layout = av_get_default_channel_layout(audio_out_ctx->channels);
}
if (audio_out_codec->supported_samplerates) { if (audio_out_codec->supported_samplerates) {
int found = 0; int found = 0;
@ -556,8 +584,6 @@ bool VideoStore::setup_resampler() {
audio_out_ctx->time_base = audio_out_ctx->time_base =
(AVRational){1, audio_out_ctx->sample_rate}; (AVRational){1, audio_out_ctx->sample_rate};
// Now copy them to the out stream
audio_out_stream = avformat_new_stream(oc, audio_out_codec);
#if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
ret = avcodec_parameters_from_context(audio_out_stream->codecpar, ret = avcodec_parameters_from_context(audio_out_stream->codecpar,

View File

@ -254,7 +254,11 @@ bool Zone::CheckAlarms(const Image *delta_image) {
return false; return false;
} }
if (max_alarm_pixels != 0)
score = (100*alarm_pixels)/max_alarm_pixels;
else
score = (100*alarm_pixels)/polygon.Area(); score = (100*alarm_pixels)/polygon.Area();
if ( score < 1 ) if ( score < 1 )
score = 1; /* Fix for score of 0 when frame meets thresholds but alarmed area is not big enough */ score = 1; /* Fix for score of 0 when frame meets thresholds but alarmed area is not big enough */
Debug(5, "Current score is %d", score); Debug(5, "Current score is %d", score);
@ -331,7 +335,11 @@ bool Zone::CheckAlarms(const Image *delta_image) {
return false; return false;
} }
if (max_filter_pixels != 0)
score = (100*alarm_filter_pixels)/max_filter_pixels;
else
score = (100*alarm_filter_pixels)/polygon.Area(); score = (100*alarm_filter_pixels)/polygon.Area();
if ( score < 1 ) if ( score < 1 )
score = 1; /* Fix for score of 0 when frame meets thresholds but alarmed area is not big enough */ score = 1; /* Fix for score of 0 when frame meets thresholds but alarmed area is not big enough */
Debug(5, "Current score is %d", score); Debug(5, "Current score is %d", score);
@ -598,7 +606,11 @@ bool Zone::CheckAlarms(const Image *delta_image) {
return false; return false;
} }
score = (100*alarm_blob_pixels)/(polygon.Area()); if (max_blob_pixels != 0)
score = (100*alarm_blob_pixels)/(max_blob_pixels);
else
score = (100*alarm_blob_pixels)/polygon.Area();
if ( score < 1 ) if ( score < 1 )
score = 1; /* Fix for score of 0 when frame meets thresholds but alarmed area is not big enough */ score = 1; /* Fix for score of 0 when frame meets thresholds but alarmed area is not big enough */
Debug(5, "Current score is %d", score); Debug(5, "Current score is %d", score);

View File

@ -83,7 +83,7 @@ int main( int argc, char *argv[] ) {
while (1) { while (1) {
int option_index = 0; int option_index = 0;
int c = getopt_long (argc, argv, "m:h:v", long_options, &option_index); int c = getopt_long(argc, argv, "m:h:v", long_options, &option_index);
if ( c == -1 ) { if ( c == -1 ) {
break; break;
} }
@ -144,7 +144,7 @@ int main( int argc, char *argv[] ) {
unsigned int analysis_update_delay = monitor->GetAnalysisUpdateDelay(); unsigned int analysis_update_delay = monitor->GetAnalysisUpdateDelay();
time_t last_analysis_update_time, cur_time; time_t last_analysis_update_time, cur_time;
monitor->UpdateAdaptiveSkip(); monitor->UpdateAdaptiveSkip();
last_analysis_update_time = time( 0 ); last_analysis_update_time = time(0);
while( (!zm_terminate) && monitor->ShmValid() ) { while( (!zm_terminate) && monitor->ShmValid() ) {
// Process the next image // Process the next image
@ -181,5 +181,5 @@ int main( int argc, char *argv[] ) {
Image::Deinitialise(); Image::Deinitialise();
logTerm(); logTerm();
zmDbClose(); zmDbClose();
return( 0 ); return 0;
} }

View File

@ -300,19 +300,22 @@ int main(int argc, char *argv[]) {
if ( next_delays[i] <= min_delay || next_delays[i] <= 0 ) { if ( next_delays[i] <= min_delay || next_delays[i] <= 0 ) {
if ( monitors[i]->PreCapture() < 0 ) { if ( monitors[i]->PreCapture() < 0 ) {
Error("Failed to pre-capture monitor %d %d (%d/%d)", monitors[i]->Id(), monitors[i]->Name(), i+1, n_monitors); Error("Failed to pre-capture monitor %d %d (%d/%d)",
monitors[i]->Id(), monitors[i]->Name(), i+1, n_monitors);
monitors[i]->Close(); monitors[i]->Close();
result = -1; result = -1;
break; break;
} }
if ( monitors[i]->Capture() < 0 ) { if ( monitors[i]->Capture() < 0 ) {
Error("Failed to capture image from monitor %d %s (%d/%d)", monitors[i]->Id(), monitors[i]->Name(), i+1, n_monitors); Info("Failed to capture image from monitor %d %s (%d/%d)",
monitors[i]->Id(), monitors[i]->Name(), i+1, n_monitors);
monitors[i]->Close(); monitors[i]->Close();
result = -1; result = -1;
break; break;
} }
if ( monitors[i]->PostCapture() < 0 ) { if ( monitors[i]->PostCapture() < 0 ) {
Error("Failed to post-capture monitor %d %s (%d/%d)", monitors[i]->Id(), monitors[i]->Name(), i+1, n_monitors); Error("Failed to post-capture monitor %d %s (%d/%d)",
monitors[i]->Id(), monitors[i]->Name(), i+1, n_monitors);
monitors[i]->Close(); monitors[i]->Close();
result = -1; result = -1;
break; break;

View File

@ -66,7 +66,7 @@ int main( int argc, const char *argv[] ) {
double maxfps = 10.0; double maxfps = 10.0;
unsigned int bitrate = 100000; unsigned int bitrate = 100000;
unsigned int ttl = 0; unsigned int ttl = 0;
EventStream::StreamMode replay = EventStream::MODE_SINGLE; EventStream::StreamMode replay = EventStream::MODE_NONE;
std::string username; std::string username;
std::string password; std::string password;
char auth[64] = ""; char auth[64] = "";
@ -137,8 +137,17 @@ int main( int argc, const char *argv[] ) {
} else if ( !strcmp( name, "ttl" ) ) { } else if ( !strcmp( name, "ttl" ) ) {
ttl = atoi(value); ttl = atoi(value);
} else if ( !strcmp( name, "replay" ) ) { } else if ( !strcmp( name, "replay" ) ) {
replay = !strcmp( value, "gapless" )?EventStream::MODE_ALL_GAPLESS:EventStream::MODE_SINGLE; if ( !strcmp(value, "gapless") ) {
replay = !strcmp( value, "all" )?EventStream::MODE_ALL:replay; replay = EventStream::MODE_ALL_GAPLESS;
} else if ( !strcmp(value, "all") ) {
replay = EventStream::MODE_ALL;
} else if ( !strcmp(value, "none") ) {
replay = EventStream::MODE_NONE;
} else if ( !strcmp(value, "single") ) {
replay = EventStream::MODE_SINGLE;
} else {
Error("Unsupported value %s for replay, defaulting to none", value);
}
} else if ( !strcmp( name, "connkey" ) ) { } else if ( !strcmp( name, "connkey" ) ) {
connkey = atoi(value); connkey = atoi(value);
} else if ( !strcmp( name, "buffer" ) ) { } else if ( !strcmp( name, "buffer" ) ) {

View File

@ -23,7 +23,7 @@ case $i in
shift # past argument=value shift # past argument=value
;; ;;
-d=*|--distro=*) -d=*|--distro=*)
DISTRO="${i#*=}" DISTROS="${i#*=}"
shift # past argument=value shift # past argument=value
;; ;;
-i=*|--interactive=*) -i=*|--interactive=*)
@ -74,11 +74,15 @@ else
echo "Doing $TYPE build" echo "Doing $TYPE build"
fi; fi;
if [ "$DISTRO" == "" ]; then if [ "$DISTROS" == "" ]; then
DISTRO=`lsb_release -a 2>/dev/null | grep Codename | awk '{print $2}'`; if [ "$RELEASE" != "" ]; then
echo "Defaulting to $DISTRO for distribution"; DISTROS="xenial,bionic,trusty"
else
DISTROS=`lsb_release -a 2>/dev/null | grep Codename | awk '{print $2}'`;
fi;
echo "Defaulting to $DISTROS for distribution";
else else
echo "Building for $DISTRO"; echo "Building for $DISTROS";
fi; fi;
# Release is a special mode... it uploads to the release ppa and cannot have a snapshot # Release is a special mode... it uploads to the release ppa and cannot have a snapshot
@ -93,7 +97,8 @@ if [ "$RELEASE" != "" ]; then
else else
GITHUB_FORK="ZoneMinder"; GITHUB_FORK="ZoneMinder";
fi fi
BRANCH="release-$RELEASE" # We use a tag instead of a branch atm.
BRANCH=$RELEASE
else else
if [ "$GITHUB_FORK" == "" ]; then if [ "$GITHUB_FORK" == "" ]; then
echo "Defaulting to ZoneMinder upstream git" echo "Defaulting to ZoneMinder upstream git"
@ -115,6 +120,18 @@ else
fi; fi;
fi fi
PPA="";
if [ "$RELEASE" != "" ]; then
# We need to use our official tarball for the original source, so grab it and overwrite our generated one.
IFS='.' read -r -a VERSION <<< "$RELEASE"
PPA="ppa:iconnor/zoneminder-${VERSION[0]}.${VERSION[1]}"
else
if [ "$BRANCH" == "" ]; then
PPA="ppa:iconnor/zoneminder-master";
else
PPA="ppa:iconnor/zoneminder-$BRANCH";
fi;
fi;
# Instead of cloning from github each time, if we have a fork lying around, update it and pull from there instead. # Instead of cloning from github each time, if we have a fork lying around, update it and pull from there instead.
if [ ! -d "${GITHUB_FORK}_zoneminder_release" ]; then if [ ! -d "${GITHUB_FORK}_zoneminder_release" ]; then
@ -153,6 +170,11 @@ if [ "$SNAPSHOT" != "stable" ] && [ "$SNAPSHOT" != "" ]; then
fi; fi;
DIRECTORY="zoneminder_$VERSION"; DIRECTORY="zoneminder_$VERSION";
if [ -d "$DIRECTORY.orig" ]; then
echo "$DIRECTORY.orig already exists. Please delete it."
exit 0;
fi;
echo "Doing $TYPE release $DIRECTORY"; echo "Doing $TYPE release $DIRECTORY";
mv "${GITHUB_FORK}_zoneminder_release" "$DIRECTORY.orig"; mv "${GITHUB_FORK}_zoneminder_release" "$DIRECTORY.orig";
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
@ -160,36 +182,57 @@ if [ $? -ne 0 ]; then
echo "Setting up build dir failed."; echo "Setting up build dir failed.";
exit $?; exit $?;
fi; fi;
cd "$DIRECTORY.orig"; cd "$DIRECTORY.orig";
# Init submodules
git submodule init git submodule init
git submodule update --init --recursive git submodule update --init --recursive
if [ "$DISTRO" == "trusty" ] || [ "$DISTRO" == "precise" ]; then
mv distros/ubuntu1204 debian # Cleanup
else rm -rf .git
if [ "$DISTRO" == "wheezy" ]; then rm .gitignore
mv distros/debian debian cd ../
else
mv distros/ubuntu1604 debian if [ ! -e "$DIRECTORY.orig.tar.gz" ]; then
fi; tar zcf $DIRECTORY.orig.tar.gz $DIRECTORY.orig
fi; fi;
if [ "$DEBEMAIL" != "" ] && [ "$DEBFULLNAME" != "" ]; then IFS=',' ;for DISTRO in `echo "$DISTROS"`; do
echo "Generating package for $DISTRO";
cd $DIRECTORY.orig
if [ -e "debian" ]; then
rm -rf debian
fi;
# Generate Changlog
if [ "$DISTRO" == "trusty" ] || [ "$DISTRO" == "precise" ]; then
cp -Rpd distros/ubuntu1204 debian
else
if [ "$DISTRO" == "wheezy" ]; then
cp -Rpd distros/debian debian
else
cp -Rpd distros/ubuntu1604 debian
fi;
fi;
if [ "$DEBEMAIL" != "" ] && [ "$DEBFULLNAME" != "" ]; then
AUTHOR="$DEBFULLNAME <$DEBEMAIL>" AUTHOR="$DEBFULLNAME <$DEBEMAIL>"
else else
if [ -z `hostname -d` ] ; then if [ -z `hostname -d` ] ; then
AUTHOR="`getent passwd $USER | cut -d ':' -f 5 | cut -d ',' -f 1` <`whoami`@`hostname`.local>" AUTHOR="`getent passwd $USER | cut -d ':' -f 5 | cut -d ',' -f 1` <`whoami`@`hostname`.local>"
else else
AUTHOR="`getent passwd $USER | cut -d ':' -f 5 | cut -d ',' -f 1` <`whoami`@`hostname`>" AUTHOR="`getent passwd $USER | cut -d ':' -f 5 | cut -d ',' -f 1` <`whoami`@`hostname`>"
fi fi
fi fi
if [ "$URGENCY" = "" ]; then if [ "$URGENCY" = "" ]; then
URGENCY="medium" URGENCY="medium"
fi; fi;
if [ "$SNAPSHOT" == "stable" ]; then if [ "$SNAPSHOT" == "stable" ]; then
cat <<EOF > debian/changelog cat <<EOF > debian/changelog
zoneminder ($VERSION-$DISTRO${PACKAGE_VERSION}) $DISTRO; urgency=$URGENCY zoneminder ($VERSION-$DISTRO${PACKAGE_VERSION}) $DISTRO; urgency=$URGENCY
* Release $VERSION * Release $VERSION
@ -197,43 +240,37 @@ zoneminder ($VERSION-$DISTRO${PACKAGE_VERSION}) $DISTRO; urgency=$URGENCY
-- $AUTHOR $DATE -- $AUTHOR $DATE
EOF EOF
cat <<EOF > debian/NEWS cat <<EOF > debian/NEWS
zoneminder ($VERSION-$DISTRO${PACKAGE_VERSION}) $DISTRO; urgency=$URGENCY zoneminder ($VERSION-$DISTRO${PACKAGE_VERSION}) $DISTRO; urgency=$URGENCY
* Release $VERSION * Release $VERSION
-- $AUTHOR $DATE -- $AUTHOR $DATE
EOF EOF
else else
cat <<EOF > debian/changelog cat <<EOF > debian/changelog
zoneminder ($VERSION-$DISTRO${PACKAGE_VERSION}) $DISTRO; urgency=$URGENCY zoneminder ($VERSION-$DISTRO${PACKAGE_VERSION}) $DISTRO; urgency=$URGENCY
* *
-- $AUTHOR $DATE -- $AUTHOR $DATE
EOF EOF
cat <<EOF > debian/changelog cat <<EOF > debian/changelog
zoneminder ($VERSION-$DISTRO${PACKAGE_VERSION}) $DISTRO; urgency=$URGENCY zoneminder ($VERSION-$DISTRO${PACKAGE_VERSION}) $DISTRO; urgency=$URGENCY
* *
-- $AUTHOR $DATE -- $AUTHOR $DATE
EOF EOF
fi; fi;
rm -rf .git if [ $TYPE == "binary" ]; then
rm .gitignore
cd ../
tar zcf $DIRECTORY.orig.tar.gz $DIRECTORY.orig
cd $DIRECTORY.orig
if [ $TYPE == "binary" ]; then
# Auto-install all ZoneMinder's depedencies using the Debian control file # Auto-install all ZoneMinder's depedencies using the Debian control file
sudo apt-get install devscripts equivs sudo apt-get install devscripts equivs
sudo mk-build-deps -ir ./debian/control sudo mk-build-deps -ir ./debian/control
echo "Status: $?" echo "Status: $?"
DEBUILD=debuild DEBUILD=debuild
else else
if [ $TYPE == "local" ]; then if [ $TYPE == "local" ]; then
# Auto-install all ZoneMinder's depedencies using the Debian control file # Auto-install all ZoneMinder's depedencies using the Debian control file
sudo apt-get install devscripts equivs sudo apt-get install devscripts equivs
@ -244,27 +281,20 @@ else
# Source build, don't need build depends. # Source build, don't need build depends.
DEBUILD="debuild -S -sa" DEBUILD="debuild -S -sa"
fi; fi;
fi; fi;
if [ "$DEBSIGN_KEYID" != "" ]; then if [ "$DEBSIGN_KEYID" != "" ]; then
DEBUILD="$DEBUILD -k$DEBSIGN_KEYID" DEBUILD="$DEBUILD -k$DEBSIGN_KEYID"
fi fi
$DEBUILD eval $DEBUILD
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
echo "Error status code is: $?" echo "Error status code is: $?"
echo "Build failed."; echo "Build failed.";
exit $?; exit $?;
fi; fi;
cd ../ cd ../
if [ "$INTERACTIVE" != "no" ]; then
read -p "Do you want to keep the checked out version of Zoneminder (incase you want to modify it later) [y/N]"
[[ $REPLY == [yY] ]] && { mv "$DIRECTORY.orig" zoneminder_release; echo "The checked out copy is preserved in zoneminder_release"; } || { rm -fr "$DIRECTORY.orig"; echo "The checked out copy has been deleted"; }
echo "Done!"
else
rm -fr "$DIRECTORY.orig"; echo "The checked out copy has been deleted";
fi
if [ $TYPE == "binary" ]; then if [ $TYPE == "binary" ]; then
if [ "$INTERACTIVE" != "no" ]; then if [ "$INTERACTIVE" != "no" ]; then
read -p "Not doing dput since it's a binary release. Do you want to install it? (y/N)" read -p "Not doing dput since it's a binary release. Do you want to install it? (y/N)"
if [[ $REPLY == [yY] ]]; then if [[ $REPLY == [yY] ]]; then
@ -283,18 +313,8 @@ if [ $TYPE == "binary" ]; then
fi; fi;
fi; fi;
fi; fi;
else else
SC="zoneminder_${VERSION}-${DISTRO}${PACKAGE_VERSION}_source.changes"; SC="zoneminder_${VERSION}-${DISTRO}${PACKAGE_VERSION}_source.changes";
PPA="";
if [ "$RELEASE" != "" ]; then
PPA="ppa:iconnor/zoneminder";
else
if [ "$BRANCH" == "" ]; then
PPA="ppa:iconnor/zoneminder-master";
else
PPA="ppa:iconnor/zoneminder-$BRANCH";
fi;
fi;
dput="Y"; dput="Y";
if [ "$INTERACTIVE" != "no" ]; then if [ "$INTERACTIVE" != "no" ]; then
@ -303,6 +323,15 @@ else
dput $PPA $SC dput $PPA $SC
fi; fi;
fi; fi;
fi; fi;
done; # foreach distro
if [ "$INTERACTIVE" != "no" ]; then
read -p "Do you want to keep the checked out version of Zoneminder (incase you want to modify it later) [y/N]"
[[ $REPLY == [yY] ]] && { mv "$DIRECTORY.orig" zoneminder_release; echo "The checked out copy is preserved in zoneminder_release"; } || { rm -fr "$DIRECTORY.orig"; echo "The checked out copy has been deleted"; }
echo "Done!"
else
rm -fr "$DIRECTORY.orig"; echo "The checked out copy has been deleted";
fi

View File

@ -154,7 +154,7 @@ movecrud () {
echo "Unpacking CakePHP-Enum-Behavior plugin..." echo "Unpacking CakePHP-Enum-Behavior plugin..."
tar -xzf build/cakephp-enum-behavior-${CEBVER}.tar.gz tar -xzf build/cakephp-enum-behavior-${CEBVER}.tar.gz
rmdir web/api/app/Plugin/CakePHP-Enum-Behavior rmdir web/api/app/Plugin/CakePHP-Enum-Behavior
mv -f crud-${CEBVER} web/api/app/Plugin/CakePHP-Enum-Behavior mv -f CakePHP-Enum-Behavior-${CEBVER} web/api/app/Plugin/CakePHP-Enum-Behavior
fi fi
} }

View File

@ -14,9 +14,17 @@ $| = 1;
my @monitors; my @monitors;
my $dbh = zmDbConnect(); my $dbh = zmDbConnect();
my $sql = "SELECT * FROM Monitors";
my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); my $sql = "SELECT * FROM Monitors
my $res = $sth->execute() or die( "Can't execute '$sql': ".$sth->errstr() ); WHERE find_in_set( Function, 'Modect,Mocord,Nodect' )".
( $Config{ZM_SERVER_ID} ? 'AND ServerId=?' : '' )
;
my $sth = $dbh->prepare_cached( $sql )
or die( "Can't prepare '$sql': ".$dbh->errstr() );
my $res = $sth->execute()
or die( "Can't execute '$sql': ".$sth->errstr() );
while ( my $monitor = $sth->fetchrow_hashref() ) { while ( my $monitor = $sth->fetchrow_hashref() ) {
push( @monitors, $monitor ); push( @monitors, $monitor );
@ -24,6 +32,12 @@ while ( my $monitor = $sth->fetchrow_hashref() ) {
while (1) { while (1) {
foreach my $monitor (@monitors) { foreach my $monitor (@monitors) {
# Check shared memory ok
if ( !zmMemVerify( $monitor ) ) {
zmMemInvalidate( $monitor );
next;
}
my $monitorState = zmGetMonitorState($monitor); my $monitorState = zmGetMonitorState($monitor);
printState($monitor->{Id}, $monitor->{Name}, $monitorState); printState($monitor->{Id}, $monitor->{Name}, $monitorState);
} }

View File

@ -1 +1 @@
1.31.47 1.32.2

Some files were not shown because too many files have changed in this diff Show More