diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..8804e639c --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: [connortechnology,pliablepixels] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: zoneminder # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.travis.yml b/.travis.yml index 8ab86bf7d..b085414b5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ addons: ssh_known_hosts: zmrepo.zoneminder.com apt: sources: - - sourceline: ppa:iconnor/zoneminder + - sourceline: ppa:iconnor/zoneminder-master - key_url: http://keyserver.ubuntu.com:11371/pks/lookup?op=get&search=0x4D0BF748776FFB04 packages: - gdebi @@ -33,24 +33,23 @@ install: env: - SMPFLAGS=-j4 OS=el DIST=7 - - SMPFLAGS=-j4 OS=el DIST=8 - - SMPFLAGS=-j4 OS=fedora DIST=29 DOCKER_REPO=knnniggett/packpack + - SMPFLAGS=-j4 OS=el DIST=8 DOCKER_REPO=knnniggett/packpack - SMPFLAGS=-j4 OS=fedora DIST=30 - - SMPFLAGS=-j4 OS=ubuntu DIST=trusty DOCKER_REPO=iconzm/packpack USE_SFTP=yes - - SMPFLAGS=-j4 OS=ubuntu DIST=xenial DOCKER_REPO=iconzm/packpack USE_SFTP=yes - - SMPFLAGS=-j4 OS=ubuntu DIST=bionic DOCKER_REPO=iconzm/packpack USE_SFTP=yes - - SMPFLAGS=-j4 OS=ubuntu DIST=disco DOCKER_REPO=iconzm/packpack USE_SFTP=yes - - SMPFLAGS=-j4 OS=ubuntu DIST=eoan DOCKER_REPO=iconzm/packpack USE_SFTP=yes - - SMPFLAGS=-j4 OS=debian DIST=jessie DOCKER_REPO=iconzm/packpack USE_SFTP=yes - - SMPFLAGS=-j4 OS=debian DIST=stretch DOCKER_REPO=iconzm/packpack USE_SFTP=yes - - SMPFLAGS=-j4 OS=debian DIST=buster DOCKER_REPO=iconzm/packpack USE_SFTP=yes + - SMPFLAGS=-j4 OS=fedora DIST=31 + - SMPFLAGS=-j4 OS=ubuntu DIST=trusty DOCKER_REPO=iconzm/packpack + - SMPFLAGS=-j4 OS=ubuntu DIST=xenial DOCKER_REPO=iconzm/packpack + - SMPFLAGS=-j4 OS=ubuntu DIST=bionic DOCKER_REPO=iconzm/packpack + - SMPFLAGS=-j4 OS=ubuntu DIST=disco DOCKER_REPO=iconzm/packpack + - SMPFLAGS=-j4 OS=ubuntu DIST=eoan DOCKER_REPO=iconzm/packpack + - SMPFLAGS=-j4 OS=debian DIST=jessie DOCKER_REPO=iconzm/packpack + - SMPFLAGS=-j4 OS=debian DIST=stretch DOCKER_REPO=iconzm/packpack + - SMPFLAGS=-j4 OS=debian DIST=buster DOCKER_REPO=iconzm/packpack - SMPFLAGS=-j4 OS=ubuntu DIST=trusty ARCH=i386 - SMPFLAGS=-j4 OS=ubuntu DIST=xenial ARCH=i386 - - SMPFLAGS=-j4 OS=ubuntu DIST=bionic ARCH=i386 - SMPFLAGS=-j4 OS=ubuntu DIST=disco ARCH=i386 - SMPFLAGS=-j4 OS=debian DIST=buster ARCH=i386 - SMPFLAGS=-j4 OS=debian DIST=stretch ARCH=i386 - - SMPFLAGS=-j4 OS=raspbian DIST=stretch ARCH=armhf DOCKER_REPO=knnniggett/packpack + - SMPFLAGS=-j4 OS=eslint DIST=eslint compiler: - gcc @@ -58,12 +57,6 @@ services: - mysql - docker -jobs: - include: - - name: eslint - install: npm install -g eslint@5.12.0 eslint-config-google@0.11.0 eslint-plugin-html@5.0.0 eslint-plugin-php-markup@0.2.5 - script: eslint --ext .php,.js . - script: - utils/packpack/startpackpack.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 9d882942d..eb58ed6db 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -140,9 +140,9 @@ set(ZM_TMPDIR "/var/tmp/zm" CACHE PATH "Location of temporary files, default: /tmp/zm") set(ZM_LOGDIR "/var/log/zm" CACHE PATH "Location of generated log files, default: /var/log/zm") -set(ZM_WEBDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_DATADIR}/zoneminder/www" CACHE PATH +set(ZM_WEBDIR "${CMAKE_INSTALL_FULL_DATADIR}/zoneminder/www" CACHE PATH "Location of the web files, default: /${CMAKE_INSTALL_DATADIR}/zoneminder/www") -set(ZM_CGIDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBEXECDIR}/zoneminder/cgi-bin" CACHE PATH +set(ZM_CGIDIR "${CMAKE_INSTALL_FULL_LIBEXECDIR}/zoneminder/cgi-bin" CACHE PATH "Location of the cgi-bin files, default: /${CMAKE_INSTALL_LIBEXECDIR}/zoneminder/cgi-bin") set(ZM_CACHEDIR "/var/cache/zoneminder" CACHE PATH "Location of the web server cache busting files, default: /var/cache/zoneminder") @@ -547,6 +547,7 @@ if(AVCODEC_LIBRARIES) check_include_file("libavcodec/avcodec.h" HAVE_LIBAVCODEC_AVCODEC_H) set(optlibsfound "${optlibsfound} AVCodec") else(AVCODEC_LIBRARIES) + message(WARNING "\nWhile it should be possible to build ZM without AVCODEC the result will pretty useless.") set(optlibsnotfound "${optlibsnotfound} AVCodec") endif(AVCODEC_LIBRARIES) @@ -905,7 +906,7 @@ message(STATUS "Optional libraries not found:${optlibsnotfound}") # Run ZM configuration generator message(STATUS "Running ZoneMinder configuration generator") -execute_process(COMMAND perl ./zmconfgen.pl RESULT_VARIABLE zmconfgen_result) +execute_process(COMMAND perl ${CMAKE_CURRENT_BINARY_DIR}/zmconfgen.pl RESULT_VARIABLE zmconfgen_result) if(zmconfgen_result EQUAL 0) message(STATUS "ZoneMinder configuration generator completed successfully") diff --git a/README.md b/README.md index 9be80d4b7..b9d215a40 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@ ZoneMinder ========== -[![Build Status](https://travis-ci.org/ZoneMinder/zoneminder.png)](https://travis-ci.org/ZoneMinder/zoneminder) [![Bountysource](https://api.bountysource.com/badge/team?team_id=204&style=bounties_received)](https://www.bountysource.com/teams/zoneminder/issues?utm_source=ZoneMinder&utm_medium=shield&utm_campaign=bounties_received) - +[![Build Status](https://travis-ci.org/ZoneMinder/zoneminder.png)](https://travis-ci.org/ZoneMinder/zoneminder) +[![Bounty Source](https://api.bountysource.com/badge/team?team_id=204&style=bounties_received)](https://www.bountysource.com/teams/zoneminder/issues?utm_source=ZoneMinder&utm_medium=shield&utm_campaign=bounties_received) [![Join Slack](https://github.com/ozonesecurity/ozonebase/blob/master/img/slacksm.png?raw=true)](https://join.slack.com/t/zoneminder-chat/shared_invite/enQtNTU0NDkxMDM5NDQwLTdhZmQ5Y2M2NWQyN2JkYTBiN2ZkMzIzZGQ0MDliMTRmM2FjZWRlYzUwYTQ2MjMwMTVjMzQ1NjYxOTdmMjE2MTE) +[![IRC Network](https://img.shields.io/badge/irc-%23zoneminder-blue.svg "IRC Freenode")](https://webchat.freenode.net/?channels=zoneminder) All documentation for ZoneMinder is now online at https://zoneminder.readthedocs.org diff --git a/db/zm_create.sql.in b/db/zm_create.sql.in index 351d2215e..35d613067 100644 --- a/db/zm_create.sql.in +++ b/db/zm_create.sql.in @@ -287,6 +287,9 @@ CREATE TABLE `Filters` ( `AutoVideo` tinyint(3) unsigned NOT NULL default '0', `AutoUpload` tinyint(3) unsigned NOT NULL default '0', `AutoEmail` tinyint(3) unsigned NOT NULL default '0', + `EmailTo` TEXT, + `EmailSubject` TEXT, + `EmailBody` TEXT, `AutoMessage` tinyint(3) unsigned NOT NULL default '0', `AutoExecute` tinyint(3) unsigned NOT NULL default '0', `AutoExecuteCmd` tinytext, @@ -434,6 +437,7 @@ DROP TABLE IF EXISTS `Monitors`; CREATE TABLE `Monitors` ( `Id` int(10) unsigned NOT NULL auto_increment, `Name` varchar(64) NOT NULL default '', + `Notes` TEXT, `ServerId` int(10) unsigned, `StorageId` smallint(5) unsigned default 0, `Type` enum('Local','Remote','File','Ffmpeg','Libvlc','cURL','WebSite','NVSocket') NOT NULL default 'Local', @@ -759,18 +763,91 @@ insert into Users VALUES (NULL,'admin','$2b$12$NHZsm6AM2f2LQVROriz79ul3D6DnmFiZC -- Add a sample filter to purge the oldest 100 events when the disk is 95% full -- -insert into Filters values (NULL,'PurgeWhenFull','{"sort_field":"Id","terms":[{"val":0,"attr":"Archived","op":"="},{"cnj":"and","val":95,"attr":"DiskPercent","op":">="}],"limit":100,"sort_asc":1}', +INSERT INTO `Filters` + ( + `Name`, + `Query_json`, + `AutoArchive`, + `AutoVideo`, + `AutoUpload`, + `AutoEmail`, + `EmailTo`, + `EmailSubject`, + `EmailBody`, + `AutoMessage`, + `AutoExecute`, + `AutoExecuteCmd`, + `AutoDelete`, + `AutoMove`, + `AutoMoveTo`, + `AutoCopy`, + `AutoCopyTo`, + `UpdateDiskSpace`, + `Background`, + `Concurrent` + ) + VALUES + ( + 'PurgeWhenFull', + '{"sort_field":"Id","terms":[{"val":0,"attr":"Archived","op":"="},{"cnj":"and","val":95,"attr":"DiskPercent","op":">="}],"limit":100,"sort_asc":1}', + 0/*AutoArchive*/, + 0/*AutoVideo*/, + 0/*AutoUpload*/, + 0/*AutoEmail*/, + ''/*EmailTo*/, + ''/*EmailSubject*/, + ''/*EmailBody*/, + 0/*AutoMessage*/, + 0/*AutoExecute*/,'', + 1/*AutoDelete*/, + 0/*AutoMove*/,0/*MoveTo*/, + 0/*AutoCopy*/,0/*CopyTo*/, + 0/*UpdateDiskSpace*/, + 1/*Background*/, + 0/*Concurrent*/ + ); +INSERT INTO `Filters` + ( + `Name`, + `Query_json`, + `AutoArchive`, + `AutoVideo`, + `AutoUpload`, + `AutoEmail`, + `EmailTo`, + `EmailSubject`, + `EmailBody`, + `AutoMessage`, + `AutoExecute`, + `AutoExecuteCmd`, + `AutoDelete`, + `AutoMove`, + `AutoMoveTo`, + `AutoCopy`, + `AutoCopyTo`, + `UpdateDiskSpace`, + `Background`, + `Concurrent` + ) +VALUES ( + 'Update DiskSpace', + '{"terms":[{"attr":"DiskSpace","op":"IS","val":"NULL"}]}', 0/*AutoArchive*/, 0/*AutoVideo*/, 0/*AutoUpload*/, 0/*AutoEmail*/, + ''/*EmailTo*/, + ''/*EmailSubject*/, + ''/*EmailBody*/, 0/*AutoMessage*/, 0/*AutoExecute*/,'', - 1/*AutoDelete*/, + 0/*AutoDelete*/, 0/*AutoMove*/,0/*MoveTo*/, 0/*AutoCopy*/,0/*CopyTo*/, - 0/*UpdateDiskSpace*/,1/*Background*/,0/*Concurrent*/); -insert into Filters values (NULL,'Update DiskSpace','{"terms":[{"attr":"DiskSpace","op":"IS","val":"NULL"}]}',0,0,0,0,0,0,'',0,0,0,0,0,1,1,0); + 1/*UpdateDiskSpace*/, + 1/*Background*/, + 0/*Concurrent*/ +); -- -- Add in some sample control protocol definitions @@ -808,6 +885,7 @@ INSERT INTO `Controls` VALUES (NULL,'Reolink RLC-423','Ffmpeg','Reolink',0,0,1,0 INSERT INTO `Controls` VALUES (NULL,'Reolink RLC-411','Ffmpeg','Reolink',0,0,1,0,1,0,0,0,1,0,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,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0); INSERT INTO `Controls` VALUES (NULL,'Reolink RLC-420','Ffmpeg','Reolink',0,0,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,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); INSERT INTO `Controls` VALUES (NULL,'D-LINK DCS-3415','Remote','DCS3415',0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,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,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,'D-Link DCS-5020L','Remote','DCS5020L',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,1,24,1,0,1,1,1,0,1,0,1,0,0,1,30,0,0,0,0,0,1,0,0,1,30,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,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,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,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); @@ -819,6 +897,9 @@ INSERT INTO `Controls` VALUES (NULL,'Amcrest HTTP API','Ffmpeg','Amcrest_HTTP',0 -- -- Add some monitor preset values -- + +INSERT into MonitorPresets VALUES (NULL,'Amcrest, IP8M-T2499EW 640x480, RTP/RTSP','Ffmpeg','rtsp',0,255,'rtsp','rtpRtsp','NULL',554,'rtsp://:@/cam/realmonitor?channel=1&subtype=1',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT into MonitorPresets VALUES (NULL,'Amcrest, IP8M-T2499EW 3840x2160, RTP/RTSP','Ffmpeg','rtsp',0,255,'rtsp','rtpRtsp','NULL',554,'rtsp://:@/cam/realmonitor?channel=1&subtype=0',NULL,3840,2160,3,NULL,0,NULL,NULL,NULL,100,100); INSERT INTO MonitorPresets VALUES (NULL,'Axis IP, 320x240, mpjpeg','Remote','http',0,0,'http','simple','',80,'/axis-cgi/mjpg/video.cgi?resolution=320x240',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100); INSERT INTO MonitorPresets VALUES (NULL,'Axis IP, 320x240, mpjpeg, max 5 FPS','Remote','http',0,0,'http','simple','',80,'/axis-cgi/mjpg/video.cgi?resolution=320x240&req_fps=5',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100); INSERT INTO MonitorPresets VALUES (NULL,'Axis IP, 320x240, jpeg','Remote','http',0,0,'http','simple','',80,'/axis-cgi/jpg/image.cgi?resolution=320x240',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100); @@ -842,6 +923,7 @@ INSERT into MonitorPresets VALUES (NULL,'Axis IP, mpeg4, multicast','Remote','rt INSERT into MonitorPresets VALUES (NULL,'Axis IP, mpeg4, RTP/RTSP','Remote','rtsp',0,255,'rtsp','rtpRtsp','',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','',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','',80,'/mjpeg.cgi',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100); +INSERT INTO MonitorPresets VALUES (NULL,'D-Link DCS-5020L, 640x480, mjpeg','Remote','http',0,0,'http','simple',':@','80','/video.cgi',NULL,640,480,0,NULL,1,'34',NULL,':@',100,100); INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP, 320x240, mpjpeg','Remote','http',0,0,'http','simple','',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','',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','',80,'/SnapshotJPEG?Resolution=320x240&Quality=Standard',NULL,320,240,3,5.0,0,NULL,NULL,NULL,100,100); diff --git a/db/zm_update-1.33.16.sql b/db/zm_update-1.33.16.sql new file mode 100644 index 000000000..87058e3f8 --- /dev/null +++ b/db/zm_update-1.33.16.sql @@ -0,0 +1,12 @@ + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Monitors' + AND column_name = 'Notes' + ) > 0, + "SELECT 'Column Notes already exists in Monitors'", + "ALTER TABLE `Monitors` ADD `Notes` TEXT AFTER `Name`" + )); + +PREPARE stmt FROM @s; +EXECUTE stmt; diff --git a/db/zm_update-1.34.0.sql b/db/zm_update-1.34.0.sql new file mode 100644 index 000000000..b8bd3e4ed --- /dev/null +++ b/db/zm_update-1.34.0.sql @@ -0,0 +1,5 @@ +-- +-- This updates a 1.33.16 database to 1.34.0 +-- +-- No changes required +-- diff --git a/db/zm_update-1.34.1.sql b/db/zm_update-1.34.1.sql new file mode 100644 index 000000000..65ba2e5ff --- /dev/null +++ b/db/zm_update-1.34.1.sql @@ -0,0 +1,5 @@ +-- +-- This updates a 1.34.0 database to 1.34.1 +-- +-- No changes required +-- diff --git a/db/zm_update-1.34.2.sql b/db/zm_update-1.34.2.sql new file mode 100644 index 000000000..1fcc882d2 --- /dev/null +++ b/db/zm_update-1.34.2.sql @@ -0,0 +1,5 @@ +-- +-- This updates a 1.34.1 database to 1.34.2 +-- +-- No changes required +-- diff --git a/db/zm_update-1.34.3.sql b/db/zm_update-1.34.3.sql new file mode 100644 index 000000000..b84207047 --- /dev/null +++ b/db/zm_update-1.34.3.sql @@ -0,0 +1,5 @@ +-- +-- This updates a 1.34.2 database to 1.34.3 +-- +-- No changes required +-- diff --git a/db/zm_update-1.34.4.sql b/db/zm_update-1.34.4.sql new file mode 100644 index 000000000..2910943a0 --- /dev/null +++ b/db/zm_update-1.34.4.sql @@ -0,0 +1,5 @@ +-- +-- This updates a 1.34.3 database to 1.34.4 +-- +-- No changes required +-- diff --git a/db/zm_update-1.34.5.sql b/db/zm_update-1.34.5.sql new file mode 100644 index 000000000..7e12b977f --- /dev/null +++ b/db/zm_update-1.34.5.sql @@ -0,0 +1,5 @@ +-- +-- This updates a 1.34.4 database to 1.34.5 +-- +-- No changes required +-- diff --git a/db/zm_update-1.35.0.sql b/db/zm_update-1.35.0.sql new file mode 100644 index 000000000..2e9132a63 --- /dev/null +++ b/db/zm_update-1.35.0.sql @@ -0,0 +1,41 @@ +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Filters' + AND column_name = 'EmailTo' + ) > 0, +"SELECT 'Column EmailTo already exists in Filters'", +"ALTER TABLE `Filters` ADD `EmailTo` TEXT AFTER `AutoEmail`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +UPDATE Filters SET EmailTo=(SELECT Value FROM Config WHERE Name='ZM_EMAIL_ADDRESS') WHERE AutoEmail=1; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Filters' + AND column_name = 'EmailSubject' + ) > 0, +"SELECT 'Column EmailSubject already exists in Filters'", +"ALTER TABLE `Filters` ADD `EmailSubject` TEXT AFTER `EmailTo`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +UPDATE Filters SET EmailSubject=(SELECT Value FROM Config WHERE Name='ZM_EMAIL_SUBJECT') WHERE AutoEmail=1; + +SET @s = (SELECT IF( + (SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_schema = DATABASE() + AND table_name = 'Filters' + AND column_name = 'EmailBody' + ) > 0, +"SELECT 'Column EmailBody already exists in Filters'", +"ALTER TABLE `Filters` ADD `EmailBody` TEXT AFTER `EmailSubject`" +)); + +PREPARE stmt FROM @s; +EXECUTE stmt; + +UPDATE Filters SET EmailBody=(SELECT Value FROM Config WHERE Name='ZM_EMAIL_BODY') WHERE AutoEmail=1; diff --git a/distros/redhat/readme/README b/distros/redhat/readme/README index 0b3e4bb9c..f0fff828f 100644 --- a/distros/redhat/readme/README +++ b/distros/redhat/readme/README @@ -22,14 +22,10 @@ What's New switching between httpd <-> nginx requires manaully changing ownership of all event folders and the php session folder after the change. -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 +4. The timezone must now be set from the ZoneMinder web console. See the + appropriate README, mentioned in the next step, for details. -5. Continue on to the next README that corresponds to the chosen webserver: +6. Continue on to the next README that corresponds to your chosen webserver: README.httpd - Follow these steps when using Apache README.nginx - Follow these steps when using Nginx diff --git a/distros/redhat/readme/README.httpd b/distros/redhat/readme/README.httpd index 5301850df..1ba0ee688 100644 --- a/distros/redhat/readme/README.httpd +++ b/distros/redhat/readme/README.httpd @@ -36,20 +36,17 @@ NOTE: EL7 users should replace "dnf" with "yum" in the instructions below. sudo chown root:apache *.conf sudo chmod 640 *.conf -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. +4. Manually setting the timezone in /etc/php.ini is deprecated. - If you are not sure of the proper timezone specification to use, look at - http://php.net/date.timezone + Instead, navigate to Options -> System from the ZoneMinder web console. + Do this after completing step 10, below. + + Note that timezone errors will appear in the ZoneMinder log until this + has been completed. 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. + SELinux must be disabled or put into permissive mode. This is not optional! To immediately disbale SELinux for the current seesion, issue the following from the command line: @@ -166,3 +163,11 @@ Upgrades sudo systemctl restart httpd sudo systemctl start zoneminder +6. Manually setting the timezone in /etc/php.ini is deprecated. + + Instead, navigate to Options -> System from the ZoneMinder web console. + Do this now. + + Note that timezone errors will appear in the ZoneMinder log until this + has been completed. + diff --git a/distros/redhat/readme/README.nginx b/distros/redhat/readme/README.nginx index cca4e72c2..8bc3bbdc1 100644 --- a/distros/redhat/readme/README.nginx +++ b/distros/redhat/readme/README.nginx @@ -34,13 +34,13 @@ New installs sudo chown root:nginx *.conf sudo chmod 640 *.conf -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. +4. Manually setting the timezone in /etc/php.ini is deprecated. - If you are not sure of the proper timezone specification to use, look at - http://php.net/date.timezone + Instead, navigate to Options -> System from the ZoneMinder web console. + Do this after completing step 10, below. + + Note that timezone errors will appear in the ZoneMinder log until this + has been completed. 5. Disable SELinux @@ -169,3 +169,11 @@ Upgrades sudo systemctl restart php-fpm sudo systemctl start zoneminder +6. Manually setting the timezone in /etc/php.ini is deprecated. + + Instead, navigate to Options -> System from the ZoneMinder web console. + Do this now. + + Note that timezone errors will appear in the ZoneMinder log until this + has been completed. + diff --git a/distros/redhat/zoneminder.spec b/distros/redhat/zoneminder.spec index e319f4c8f..c0ac0af70 100644 --- a/distros/redhat/zoneminder.spec +++ b/distros/redhat/zoneminder.spec @@ -14,16 +14,21 @@ # This will tell zoneminder's cmake process we are building against a known distro %global zmtargetdistro %{?rhel:el%{rhel}}%{!?rhel:fc%{fedora}} -# Fedora >= 25 needs apcu backwards compatibility module -%if 0%{?fedora} >= 25 +# Fedora needs apcu backwards compatibility module +%if 0%{?fedora} %global with_apcu_bc 1 %endif +# Newer php's keep json functions in a subpackage +%if 0%{?fedora} || 0%{?rhel} >= 8 +%global with_php_json 1 +%endif + # The default for everything but el7 these days %global _hardened_build 1 Name: zoneminder -Version: 1.33.14 +Version: 1.35.0 Release: 1%{?dist} Summary: A camera monitoring and analysis tool Group: System Environment/Daemons @@ -105,7 +110,7 @@ Summary: Common files for ZoneMinder, not tied to a specific web server Requires: php-mysqli Requires: php-common Requires: php-gd -%{?fedora:Requires: php-json} +%{?with_php_json:Requires: php-json} Requires: php-pecl-apcu %{?with_apcu_bc:Requires: php-pecl-apcu-bc} Requires: cambozola @@ -411,26 +416,26 @@ EOF %dir %attr(755,nginx,nginx) %{_localstatedir}/spool/zoneminder-upload %changelog -* Sun Aug 11 2019 Andrew Bauer - 1.33.14-1 -- Bump to 1.33.13 Development +* Tue Feb 04 2020 Andrew Bauer - 1.34.2-1 +- 1.34.2 Release -* Sun Jul 07 2019 Andrew Bauer - 1.33.12-1 -- Bump to 1.33.12 Development +* Fri Jan 31 2020 Andrew Bauer - 1.34.1-1 +- 1.34.1 Release -* Sun Jun 23 2019 Andrew Bauer - 1.33.9-1 -- Bump to 1.33.9 Development +* Sat Jan 18 2020 Andrew Bauer - 1.34.0-1 +- 1.34.0 Release -* Tue Apr 30 2019 Andrew Bauer - 1.33.8-1 -- Bump to 1.33.8 Development +* Tue Dec 17 2019 Leigh Scott - 1.32.3-5 +- Mass rebuild for x264 -* Sun Apr 07 2019 Andrew Bauer - 1.33.6-1 -- Bump to 1.33.6 Development +* Wed Aug 07 2019 Leigh Scott - 1.32.3-4 +- Rebuild for new ffmpeg version -* Sat Mar 30 2019 Andrew Bauer - 1.33.4-1 -- Bump to 1.33.4 Development +* Tue Mar 12 2019 Sérgio Basto - 1.32.3-3 +- Mass rebuild for x264 -* Tue Dec 11 2018 Andrew Bauer - 1.33.0-1 -- Bump to 1.33.0 Development +* Tue Mar 05 2019 RPM Fusion Release Engineering - 1.32.3-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_30_Mass_Rebuild * Sat Dec 08 2018 Andrew Bauer - 1.32.3-1 - 1.32.3 Release diff --git a/distros/ubuntu1604/conf/apache2/zoneminder.conf b/distros/ubuntu1604/conf/apache2/zoneminder.conf index 598996bc0..e3164d36c 100644 --- a/distros/ubuntu1604/conf/apache2/zoneminder.conf +++ b/distros/ubuntu1604/conf/apache2/zoneminder.conf @@ -6,6 +6,7 @@ ScriptAlias /zm/cgi-bin "/usr/lib/zoneminder/cgi-bin" Require all granted + # Order matters. This alias must come first. Alias /zm/cache /var/cache/zoneminder/cache diff --git a/distros/ubuntu1604/control b/distros/ubuntu1604/control index 7e57c98a8..617b3e852 100644 --- a/distros/ubuntu1604/control +++ b/distros/ubuntu1604/control @@ -61,7 +61,7 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} ,libjson-maybexs-perl ,libsys-mmap-perl [!hurd-any] ,liburi-encode-perl - ,libwww-perl + ,libwww-perl, liburi-perl ,libdata-dump-perl ,libdatetime-perl ,libclass-std-fast-perl @@ -74,7 +74,7 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} ,libfile-slurp-perl ,mysql-client | mariadb-client | virtual-mysql-client ,perl-modules - ,php5-mysql | php-mysql, php5-gd | php-gd , php5-apcu | php-apcu , php-apc | php-apcu-bc + ,php5-mysql | php-mysql, php5-gd | php-gd , php5-apcu | php-apcu , php-apc | php-apcu-bc, php-json | php5-json ,policykit-1 ,rsyslog | system-log-daemon ,zip diff --git a/docs/api.rst b/docs/api.rst index 2aaeafb3e..23effc0b7 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -127,7 +127,7 @@ If you are using the old credentials mechanism present in v1.0, then the credent Key lifetime (v2.0) ^^^^^^^^^^^^^^^^^^^^^^ -In version 2.0, it is easy to know when a key will expire before you use it. You can find that out from the ``access_token_expires`` and ``refresh_token_exipres`` values (in seconds) after you decode the JWT key (there are JWT decode libraries for every language you want). You should refresh the keys before the timeout occurs, or you will not be able to use the APIs. +In version 2.0, it is easy to know when a key will expire before you use it. You can find that out from the ``access_token_expires`` and ``refresh_token_expires`` values (in seconds) after you decode the JWT key (there are JWT decode libraries for every language you want). You should refresh the keys before the timeout occurs, or you will not be able to use the APIs. Understanding access/refresh tokens (v2.0) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -482,6 +482,7 @@ Create a Zone &Zone[Units]=Percent\ &Zone[NumCoords]=4\ &Zone[Coords]=0,0 639,0 639,479 0,479\ + &Zone[Area]=307200\ &Zone[AlarmRGB]=16711680\ &Zone[CheckMethod]=Blobs\ &Zone[MinPixelThreshold]=25\ diff --git a/docs/userguide/filterevents.rst b/docs/userguide/filterevents.rst index 8359aaf89..436034da0 100644 --- a/docs/userguide/filterevents.rst +++ b/docs/userguide/filterevents.rst @@ -68,9 +68,13 @@ Here is what the filter window looks like * %ESM% Maximum score of the event * %EP% Path to the event * %EPS% Path to the event stream + * %EPF1% Path to the frame view for the first alarmed event image + * %EPFM% Path to the frame view for the (first) event image with the highest score + * %EFMOD% Path to image containing object detection, in frame view * %EPI% Path to the event images - * %EPI1% Path to the first alarmed event image - * %EPIM% Path to the (first) event image with the highest score + * %EPI1% Path to the first alarmed event image, suitable for use in img tags + * %EPIM% Path to the (first) event image with the highest score, suitable for use in img tags + * %EIMOD% Path to image containing object detection, suitable for use in img tags * %EI1% Attach first alarmed event image * %EIM% Attach (first) event image with the highest score * %EV% Attach event mpeg video @@ -81,7 +85,6 @@ Here is what the filter window looks like * %MEW% Number of events for the monitor in the last week * %MEM% Number of events for the monitor in the last month * %MEA% Number of archived events for the monitor - * %MOD% Path to image containing object detection * %MP% Path to the monitor window * %MPS% Path to the monitor stream * %MPI% Path to the monitor recent image diff --git a/docs/userguide/gettingstarted.rst b/docs/userguide/gettingstarted.rst index 509f413a2..0b40d7fb9 100644 --- a/docs/userguide/gettingstarted.rst +++ b/docs/userguide/gettingstarted.rst @@ -109,7 +109,7 @@ This brings up the new monitor window: * In this example, the Function is 'Modect', which means it will start recording if motion is detected on that camera feed. The parameters for what constitutes motion detected is specific in :doc:`definezone` -* In Analytis FPS, we've put in 5FPS here. Note that you should not put an FPS that is greater than the camera FPS. In my case, 5FPS is sufficient for my needs +* In Analysis FPS, we've put in 5FPS here. Note that you should not put an FPS that is greater than the camera FPS. In my case, 5FPS is sufficient for my needs .. note:: Leave Maximum FPS and Alarm Maximum FPS **empty** if you are configuring an IP camera. In older versions of ZoneMinder, you were encouraged to put a value here, but that is no longer recommended. Infact, if you see your feed going much slower than the feed is supposed to go, or you get a lot of buffering/display issues, make sure this is empty. If you need to control camera FPS, please do it directly on the camera (via its own web interface, for example) @@ -123,6 +123,8 @@ This brings up the new monitor window: * Let's select a protocol of RTSP and a remote method of RTP/RTSP (this is an RTSP camera) * Note that starting ZM 1.34, GPUs are supported. In my case, I have an NVIDIA GeForce GTX1050i. These ``cuda`` and ``cuvid`` parameters are what my system supports to use the NVIDIA hardware decoder and GPU resources. If you don't have a GPU, or don't know how to configure your ffmpeg to support it, leave it empty for now. In future, we will add a section on how to set up a GPU +**NOTE**: It is entirely possible that ``cuda`` and ``cuvid`` don't work for you and you need different values. Isaac uses ``cuda`` in ``DecoderHWAccelName`` and leaves ``DecoderHWAccelDevice`` empty. Try that too. + .. todo:: add GPU docs diff --git a/misc/logcheck b/misc/logcheck index e59948e86..820108a66 100644 --- a/misc/logcheck +++ b/misc/logcheck @@ -28,3 +28,4 @@ zmtelemetry\[[[:digit:]]+\]: INF \[Telemetry data uploaded successfully.\]$ zmtelemetry\[[[:digit:]]+\]: INF \[Sending data to ZoneMinder Telemetry server.\]$ zmtelemetry\[[[:digit:]]+\]: INF \[Collec?ting data to send to ZoneMinder Telemetry server.\]$ web_php\[[[:digit:]]+\]: INF \[Login successful for user "[[:alnum:]]+"\]$ +zmeventnotification\[[[:digit:]]+\]: INF diff --git a/onvif/modules/lib/ONVIF/Client.pm b/onvif/modules/lib/ONVIF/Client.pm index 90bfdd512..9676dff85 100644 --- a/onvif/modules/lib/ONVIF/Client.pm +++ b/onvif/modules/lib/ONVIF/Client.pm @@ -76,7 +76,7 @@ my %soap_version_of :ATTR(:default<('1.1')>); sub service { my ($self, $serviceName, $attr) = @_; -#print "service: " . $services_of{${$self}}{$serviceName}{$attr} . "\n"; + #print "service: " . $services_of{${$self}}{$serviceName}{$attr} . "\n"; # Please note that the Std::Class::Fast docs say not to use ident. $services_of{ident $self}{$serviceName}{$attr}; } @@ -114,18 +114,18 @@ sub get_service_urls { my $result = $self->service('device', 'ep')->GetServices( { IncludeCapability => 'true', # boolean - },, + } ); if ( $result ) { - foreach my $svc ( @{ $result->get_Service() } ) { + foreach my $svc ( @{ $result->get_Service() } ) { my $short_name = $namespace_map{$svc->get_Namespace()}; my $url_svc = $svc->get_XAddr()->get_value(); - if(defined $short_name && defined $url_svc) { -# print "Got $short_name service\n"; + if ( defined $short_name && defined $url_svc ) { + #print "Got $short_name service\n"; $self->set_service($short_name, 'url', $url_svc); } } - # } else { + #} else { #print "No results from GetServices: $result\n"; } @@ -142,14 +142,14 @@ sub get_service_urls { if ( my $function = $capabilities->can( "get_$capability" ) ) { my $Services = $function->( $capabilities ); if ( !$Services ) { - print "Nothing returned ffrom get_$capability\n"; + #print "Nothing returned from get_$capability\n"; } else { foreach my $svc ( @{ $Services } ) { # The capability versions don't have a namespace, so just lowercase them. my $short_name = lc $capability; my $url_svc = $svc->get_XAddr()->get_value(); - if( defined $url_svc) { -# print "Got $short_name service\n"; + if ( defined $url_svc ) { + #print "Got $short_name service\n"; $self->set_service($short_name, 'url', $url_svc); } } # end foreach svr @@ -202,10 +202,10 @@ sub BUILD { # deserializer_args => { strict => 0 } }); - $services_of{$ident}{'device'} = { url => $url_svc_device, ep => $svc_device }; + $services_of{$ident}{device} = { url => $url_svc_device, ep => $svc_device }; # Can't, don't have credentials yet - #$self->get_service_urls(); + # $self->get_service_urls(); } sub get_users { @@ -260,7 +260,7 @@ sub set_credentials { sub create_services { my ($self) = @_; - #$self->get_service_urls(); + $self->get_service_urls(); if ( defined $self->service('media', 'url') ) { $self->set_service('media', 'ep', ONVIF::Media::Interfaces::Media::MediaPort->new({ diff --git a/onvif/modules/lib/WSDiscovery/TransportUDP.pm b/onvif/modules/lib/WSDiscovery/TransportUDP.pm index 33308db13..5125ac9f0 100644 --- a/onvif/modules/lib/WSDiscovery/TransportUDP.pm +++ b/onvif/modules/lib/WSDiscovery/TransportUDP.pm @@ -43,7 +43,7 @@ my %message_of :ATTR(:name :default<()>); my %is_success_of :ATTR(:name :default<()>); my %local_addr_of :ATTR(:name :init_arg :default<()>); - +my $net_interface; # create methods normally inherited from SOAP::Client SUBFACTORY: { @@ -60,14 +60,22 @@ sub _notify_response } +sub set_net_interface { + my $self = shift; + $net_interface = shift; +} + sub send_multi() { my ($self, $address, $port, $utf8_string) = @_; my $destination = $address . ':' . $port; - my $socket = IO::Socket::Multicast->new(PROTO => 'udp', - LocalPort=>$port, PeerAddr=>$destination, ReuseAddr=>1) - - or die 'Cannot open multicast socket to ' . ${address} . ':' . ${port}; + my $socket = IO::Socket::Multicast->new( + PROTO => 'udp', + LocalPort=>$port, + PeerAddr=>$destination, + ReuseAddr=>1 + ) or die 'Cannot open multicast socket to ' . ${address} . ':' . ${port}; + $_ = $socket->mcast_if($net_interface) if $net_interface; my $bytes = $utf8_string; utf8::encode($bytes); @@ -80,14 +88,16 @@ sub receive_multi() { my ($self, $address, $port) = @_; my $data = undef; - my $socket = IO::Socket::Multicast->new(PROTO => 'udp', - LocalPort=>$port, ReuseAddr=>1); - $socket->mcast_add($address); + my $socket = IO::Socket::Multicast->new( + PROTO => 'udp', + LocalPort=>$port, + ReuseAddr=>1); + $socket->mcast_add($address, $net_interface); my $readbits = ''; vec($readbits, $socket->fileno, 1) = 1; - if(select($readbits, undef, undef, WAIT_TIME/1000)) { + if ( select($readbits, undef, undef, WAIT_TIME/1000) ) { $socket->recv($data, 9999); return $data; } @@ -98,15 +108,19 @@ sub receive_uni() { my ($self, $address, $port, $localaddr) = @_; my $data = undef; - my $socket = IO::Socket::Multicast->new(PROTO => 'udp', - LocalAddr => $localaddr, LocalPort=>$port, ReuseAddr=>1); + my $socket = IO::Socket::Multicast->new( + PROTO => 'udp', + LocalAddr => $localaddr, + LocalPort=>$port, + ReuseAddr=>1 + ); - $socket->mcast_add($address); + $socket->mcast_add($address, $net_interface); my $readbits = ''; vec($readbits, $socket->fileno, 1) = 1; - if(select($readbits, undef, undef, WAIT_TIME/1000)) { + if ( select($readbits, undef, undef, WAIT_TIME/1000) ) { $socket->recv($data, 9999); return $data; } @@ -114,50 +128,51 @@ sub receive_uni() { } sub send_receive { - my ($self, %parameters) = @_; - my ($envelope, $soap_action, $endpoint, $encoding, $content_type) = - @parameters{qw(envelope action endpoint encoding content_type)}; + my ($self, %parameters) = @_; + my ($envelope, $soap_action, $endpoint, $encoding, $content_type) = + @parameters{qw(envelope action endpoint encoding content_type)}; - my ($address,$port) = ($endpoint =~ /([^:\/]+):([0-9]+)/); + my ($address,$port) = ($endpoint =~ /([^:\/]+):([0-9]+)/); - #warn "address = ${address}"; - #warn "port = ${port}"; +#warn "address = ${address}"; +#warn "port = ${port}"; - $self->send_multi($address, $port, $envelope); + $self->send_multi($address, $port, $envelope); - my $localaddr = $self->get_local_addr(); + my $localaddr = $self->get_local_addr(); +#warn "localddr $localaddr"; - my ($response, $last_response); - my $wait = WAIT_COUNT; - while ( $wait >= 0 ) { - if($localaddr) { - if($response = $self->receive_uni($address, $port, $localaddr)) { - $last_response = $response; - $self->_notify_response($response); - } - $wait --; - } - if($response = $self->receive_multi($address, $port)) { - $last_response = $response; - $self->_notify_response($response); - } - $wait --; - } - - if($last_response) { - $self->set_code(); - $self->set_message(""); - $self->set_is_success(1); - $self->set_status('OK'); - } - else{ - $self->set_code(); - $self->set_message("Timed out waiting for response"); - $self->set_is_success(0); - $self->set_status('TIMEOUT'); - } + my ($response, $last_response); + my $wait = WAIT_COUNT; + while ( $wait >= 0 ) { + if ( $localaddr ) { + if ( $response = $self->receive_uni($address, $port, $localaddr) ) { + $last_response = $response; + $self->_notify_response($response); + } + $wait --; + } + if ( $response = $self->receive_multi($address, $port) ) { + $last_response = $response; + $self->_notify_response($response); + } + $wait --; + } - return $last_response; + if ( $last_response ) { + $self->set_code(); + $self->set_message(''); + $self->set_is_success(1); + $self->set_status('OK'); + } else { + $self->set_code(); + $self->set_message('Timed out waiting for response'); + $self->set_is_success(0); + $self->set_status('TIMEOUT'); + } + + return $last_response; } 1; +__END__ diff --git a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in index 31fafc1bc..aaa412453 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in @@ -785,7 +785,7 @@ our @options = ( }, { name => 'ZM_TIMEZONE', - default => 'UTC', + default => '', description => 'The timezone that php should use.', help => q` This should be set equal to the system timezone of the mysql server`, @@ -1127,7 +1127,7 @@ our @options = ( }, { name => 'ZM_LOG_LEVEL_FILE', - default => '-5', + default => '1', description => 'Save logging output to component files', help => q` ZoneMinder logging is now more integrated between @@ -1312,7 +1312,7 @@ our @options = ( }, { name => 'ZM_LOG_DEBUG_FILE', - default => '@ZM_LOGDIR@/zm_debug.log+', + default => '', description => 'Where extra debug is output to', help => q` This option allows you to specify a different target for debug @@ -2213,7 +2213,7 @@ our @options = ( that match the appropriate filters will be sent to. `, type => $types{email}, - category => 'mail', + category => 'hidden', }, { name => 'ZM_EMAIL_TEXT', @@ -2253,7 +2253,7 @@ our @options = ( sent for any events that match the appropriate filters. `, type => $types{string}, - category => 'mail', + category => 'hidden', }, { name => 'ZM_EMAIL_BODY', @@ -2280,7 +2280,7 @@ our @options = ( sent for any events that match the appropriate filters. `, type => $types{text}, - category => 'mail', + category => 'hidden', }, { name => 'ZM_OPT_MESSAGE', @@ -2468,7 +2468,7 @@ our @options = ( }, { name => 'ZM_WATCH_MAX_DELAY', - default => '5', + default => '45', description => 'The maximum delay allowed since the last captured image', help => q` The zmwatch daemon checks the image capture performance of the diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control.pm index 896d10c07..06c116626 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control.pm @@ -1,6 +1,6 @@ # ========================================================================== # -# ZoneMinder Base Control Module, $Date$, $Revision$ +# ZoneMinder Base Control Module # Copyright (C) 2001-2008 Philip Coombes # # This program is free software; you can redistribute it and/or @@ -46,13 +46,13 @@ our $AUTOLOAD; sub new { my $class = shift; my $id = shift; + if ( !defined($id) ) { + Fatal('No monitor defined when invoking protocol '.$class); + } my $self = {}; $self->{name} = $class; - if ( !defined($id) ) { - Fatal('No monitor defined when invoking protocol '.$self->{name}); - } $self->{id} = $id; - bless( $self, $class ); + bless($self, $class); return $self; } @@ -78,7 +78,7 @@ sub AUTOLOAD { sub getKey { my $self = shift; - return( $self->{id} ); + return $self->{id}; } sub open { diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/Amcrest_HTTP.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/Amcrest_HTTP.pm index e95f86cba..59e4f7511 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/Amcrest_HTTP.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/Amcrest_HTTP.pm @@ -1,16 +1,6 @@ # ========================================================================== # -# ZoneMinder Acrest HTTP API Control Protocol Module, 20180214, Rev 3.0 -# -# Change Log -# -# Rev 3.0: -# - Fixes incorrect method names -# - Updates control sequences to Amcrest HTTP Protocol API v 2.12 -# - Extends control features -# -# Rev 2.0: -# - Fixed installation instructions text, no changes to functionality. +# ZoneMinder Amcrest HTTP API Control Protocol Module # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -38,6 +28,8 @@ use Time::HiRes qw( usleep ); require ZoneMinder::Base; require ZoneMinder::Control; +require LWP::UserAgent; +use URI; our @ISA = qw(ZoneMinder::Control); @@ -50,130 +42,130 @@ our @ISA = qw(ZoneMinder::Control); use ZoneMinder::Logger qw(:all); use ZoneMinder::Config qw(:all); -sub new -{ - my $class = shift; - my $id = shift; - my $self = ZoneMinder::Control->new( $id ); - bless( $self, $class ); - srand( time() ); - return $self; +sub new { + my $class = shift; + my $id = shift; + my $self = ZoneMinder::Control->new($id); + bless($self, $class); + return $self; } -our $AUTOLOAD; +sub open { + my $self = shift; -sub AUTOLOAD -{ - my $self = shift; - my $class = ref($self) || croak( "$self not object" ); - my $name = $AUTOLOAD; - $name =~ s/.*://; - Debug( "Received command: $name" ); - if ( exists($self->{$name}) ) - { - return( $self->{$name} ); - } - Fatal( "Can't access $name member of object of class $class" ); -} + $self->loadMonitor(); + if ( $self->{Monitor}->{ControlAddress} !~ /^\w+:\/\// ) { + # Has no scheme at the beginning, so won't parse as a URI + $self->{Monitor}->{ControlAddress} = 'http://'.$self->{Monitor}->{ControlAddress}; + } + my $uri = URI->new($self->{Monitor}->{ControlAddress}); -sub open -{ - my $self = shift; + $self->{ua} = LWP::UserAgent->new; + $self->{ua}->agent('ZoneMinder Control Agent/'.ZoneMinder::Base::ZM_VERSION); + my ( $username, $password ); + my $realm = 'Login to ' . $self->{Monitor}->{ControlDevice}; + if ( $self->{Monitor}->{ControlAddress} ) { + ( $username, $password ) = $uri->authority() =~ /^(.*):(.*)@(.*)$/; - $self->loadMonitor(); + $$self{address} = $uri->host_port(); + $self->{ua}->credentials($uri->host_port(), $realm, $username, $password); + # Testing seems to show that we need the username/password in each url as well as credentials + $$self{base_url} = $uri->canonical(); + Debug('Using initial credentials for '.$uri->host_port().", $realm, $username, $password, base_url: $$self{base_url} auth:".$uri->authority()); + } + + # Detect REALM, has to be /cgi-bin/ptz.cgi because just / accepts no auth + my $res = $self->{ua}->get($$self{base_url}.'cgi-bin/ptz.cgi'); + + if ( $res->is_success ) { $self->{state} = 'open'; -} + return; + } -sub initUA -{ - my $self = shift; - my $user = undef; - my $password = undef; - my $address = undef; + if ( $res->status_line() eq '401 Unauthorized' ) { - if ( $self->{Monitor}->{ControlAddress} =~ /(.*):(.*)@(.*)/ ) - { - $user = $1; - $password = $2; - $address = $3; + my $headers = $res->headers(); + foreach my $k ( keys %$headers ) { + Debug("Initial Header $k => $$headers{$k}"); } - use LWP::UserAgent; - $self->{ua} = LWP::UserAgent->new; - $self->{ua}->credentials("$address", "Login to " . $self->{Monitor}->{ControlDevice}, "$user", "$password"); - $self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION ); + if ( $$headers{'www-authenticate'} ) { + my ( $auth, $tokens ) = $$headers{'www-authenticate'} =~ /^(\w+)\s+(.*)$/; + if ( $tokens =~ /realm="([^"]+)"/i ) { + if ( $realm ne $1 ) { + $realm = $1; + Debug("Changing REALM to ($realm)"); + $self->{ua}->credentials($$self{address}, $realm, $username, $password); + $res = $self->{ua}->get($$self{base_url}.'cgi-bin/ptz.cgi'); + if ( $res->is_success() ) { + $self->{state} = 'open'; + return; + } elsif ( $res->status_line eq '400 Bad Request' ) { + # In testing, this second request fails with Bad Request, I assume because we didn't actually give it a command. + $self->{state} = 'open'; + return; + } else { + Error('Authentication still failed after updating REALM' . $res->status_line); + $headers = $res->headers(); + foreach my $k ( keys %$headers ) { + Debug("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 { + Debug('No headers line'); + } # end if headers + } else { + Error("Failed to get $$self{base_url}cgi-bin/ptz.cgi ".$res->status_line()); + + } # end if $res->status_line() eq '401 Unauthorized' + + $self->{state} = 'closed'; } -sub destroyUA -{ - my $self = shift; - - $self->{ua} = undef; +sub close { + my $self = shift; + $self->{state} = 'closed'; } -sub close -{ - my $self = shift; - $self->{state} = 'closed'; -} +sub sendCmd { + my $self = shift; + my $cmd = shift; + my $result = undef; -sub printMsg -{ - my $self = shift; - my $msg = shift; - my $msg_len = length($msg); + $self->printMsg($cmd, 'Tx'); - Debug( $msg."[".$msg_len."]" ); -} + my $res = $self->{ua}->get($$self{base_url}.$cmd); -sub sendCmd -{ - my $self = shift; - my $cmd = shift; - my $result = undef; - - destroyUA($self); - initUA($self); - - my $user = undef; - my $password = undef; - my $address = undef; - - if ( $self->{Monitor}->{ControlAddress} =~ /(.*):(.*)@(.*)/ ) - { - $user = $1; - $password = $2; - $address = $3; + if ( $res->is_success ) { + $result = !undef; + # Command to camera appears successful, write Info item to log + Info('Camera control: \''.$res->status_line().'\' for URL '.$$self{base_url}.$cmd); + # TODO: Add code to retrieve $res->message_decode or some such. Then we could do things like check the camera status. + } else { + # Try again + $res = $self->{ua}->get($$self{base_url}.$cmd); + if ( $res->is_success ) { + # Command to camera appears successful, write Info item to log + Info('Camera control 2: \''.$res->status_line().'\' for URL '.$$self{base_url}.$cmd); + } else { + Error('Camera control command FAILED: \''.$res->status_line().'\' for URL '.$$self{base_url}.$cmd); + $res = $self->{ua}->get('http://'.$self->{Monitor}->{ControlAddress}.'/'.$cmd); } + } - printMsg( $cmd, "Tx" ); - - my $req = HTTP::Request->new( GET=>"http://$address/$cmd" ); - my $res = $self->{ua}->request($req); - - if ( $res->is_success ) - { - $result = !undef; - # Command to camera appears successful, write Info item to log - Info( "Camera control: '".$res->status_line()."' for URL ".$self->{Monitor}->{ControlAddress}."/$cmd" ); - # TODO: Add code to retrieve $res->message_decode or some such. Then we could do things like check the camera status. - } - else - { - Error( "Camera control command FAILED: '".$res->status_line()."' for URL ".$self->{Monitor}->{ControlAddress}."/$cmd" ); - } - - return( $result ); + return $result; } -sub reset -{ - my $self = shift; - # This reboots the camera effectively resetting it - Debug( "Camera Reset" ); - $self->sendCmd( 'cgi-bin/magicBox.cgi?action=reboot' ); - ##FIXME: Exit is a bad idea as it appears to cause zmc to run away. - #Exit (0); +sub reset { + my $self = shift; + # This reboots the camera effectively resetting it + $self->sendCmd('cgi-bin/magicBox.cgi?action=reboot'); } # NOTE: I'm putting this in, but absolute camera movement does not seem to be well supported in the classic skin ATM. @@ -184,163 +176,148 @@ sub reset sub moveAbs ## Up, Down, Left, Right, etc. ??? Doesn't make sense here... { - my $self = shift; - my $pan_degrees = shift || 0; - my $tilt_degrees = shift || 0; - my $speed = shift || 1; - Debug( "Move ABS" ); - $self->sendCmd( 'cgi-bin/ptz.cgi?action=start&code=PositionABS&channel=0&arg1='.$pan_degrees.'&arg2='.$tilt_degrees.'&arg3=0&arg4='.$speed ); + my $self = shift; + my $pan_degrees = shift || 0; + my $tilt_degrees = shift || 0; + my $speed = shift || 1; + Debug('Move ABS'); + $self->sendCmd('cgi-bin/ptz.cgi?action=start&code=PositionABS&channel=0&arg1='.$pan_degrees.'&arg2='.$tilt_degrees.'&arg3=0&arg4='.$speed); } -sub moveConUp -{ - my $self = shift; - Debug( "Move Up" ); - $self->sendCmd( 'cgi-bin/ptz.cgi?action=start&code=Up&channel=0&arg1=0&arg2=1&arg3=0' ); - usleep (500); ##XXX Should this be passed in as a "speed" parameter? - $self->sendCmd( 'cgi-bin/ptz.cgi?action=stop&code=Up&channel=0&arg1=0&arg2=1&arg3=0' ); +sub moveConUp { + my $self = shift; + Debug('Move Up'); + $self->sendCmd('cgi-bin/ptz.cgi?action=start&code=Up&channel=0&arg1=0&arg2=1&arg3=0'); + usleep(500); ##XXX Should this be passed in as a "speed" parameter? + $self->sendCmd('cgi-bin/ptz.cgi?action=stop&code=Up&channel=0&arg1=0&arg2=1&arg3=0'); } -sub moveConDown -{ - my $self = shift; - Debug( "Move Down" ); - $self->sendCmd( 'cgi-bin/ptz.cgi?action=start&code=Down&channel=0&arg1=0&arg2=1&arg3=0' ); - usleep (500); - $self->sendCmd( 'cgi-bin/ptz.cgi?action=stop&code=Down&channel=0&arg1=0&arg2=1&arg3=0' ); +sub moveConDown { + my $self = shift; + Debug('Move Down'); + $self->sendCmd('cgi-bin/ptz.cgi?action=start&code=Down&channel=0&arg1=0&arg2=1&arg3=0'); + usleep(500); + $self->sendCmd('cgi-bin/ptz.cgi?action=stop&code=Down&channel=0&arg1=0&arg2=1&arg3=0'); } -sub moveConLeft -{ - my $self = shift; - Debug( "Move Left" ); - $self->sendCmd( 'cgi-bin/ptz.cgi?action=start&code=Left&channel=0&arg1=0&arg2=1&arg3=0' ); - usleep (500); - $self->sendCmd( 'cgi-bin/ptz.cgi?action=stop&code=Left&channel=0&arg1=0&arg2=1&arg3=0' ); +sub moveConLeft { + my $self = shift; + Debug('Move Left'); + $self->sendCmd('cgi-bin/ptz.cgi?action=start&code=Left&channel=0&arg1=0&arg2=1&arg3=0'); + usleep(500); + $self->sendCmd('cgi-bin/ptz.cgi?action=stop&code=Left&channel=0&arg1=0&arg2=1&arg3=0'); } -sub moveConRight -{ - my $self = shift; - Debug( "Move Right" ); -# $self->sendCmd( 'cgi-bin/ptz.cgi?action=start&code=PositionABS&channel=0&arg1=270&arg2=5&arg3=0' ); - $self->sendCmd( 'cgi-bin/ptz.cgi?action=start&code=Right&channel=0&arg1=0&arg2=1&arg3=0' ); - usleep (500); - Debug( "Move Right Stop" ); - $self->sendCmd( 'cgi-bin/ptz.cgi?action=stop&code=Right&channel=0&arg1=0&arg2=1&arg3=0' ); +sub moveConRight { + my $self = shift; + Debug('Move Right'); + # $self->sendCmd( 'cgi-bin/ptz.cgi?action=start&code=PositionABS&channel=0&arg1=270&arg2=5&arg3=0' ); + $self->sendCmd('cgi-bin/ptz.cgi?action=start&code=Right&channel=0&arg1=0&arg2=1&arg3=0'); + usleep(500); + Debug('Move Right Stop'); + $self->sendCmd('cgi-bin/ptz.cgi?action=stop&code=Right&channel=0&arg1=0&arg2=1&arg3=0'); } -sub moveConUpRight -{ - my $self = shift; - Debug( "Move Diagonally Up Right" ); - $self->sendCmd( 'cgi-bin/ptz.cgi?action=start&code=RightUp&channel=0&arg1=1&arg2=1&arg3=0' ); - usleep (500); - $self->sendCmd( 'cgi-bin/ptz.cgi?action=stop&code=RightUp&channel=0&arg1=0&arg2=1&arg3=0' ); +sub moveConUpRight { + my $self = shift; + Debug('Move Diagonally Up Right'); + $self->sendCmd('cgi-bin/ptz.cgi?action=start&code=RightUp&channel=0&arg1=1&arg2=1&arg3=0'); + usleep(500); + $self->sendCmd('cgi-bin/ptz.cgi?action=stop&code=RightUp&channel=0&arg1=0&arg2=1&arg3=0'); } -sub moveConDownRight -{ - my $self = shift; - Debug( "Move Diagonally Down Right" ); - $self->sendCmd( 'cgi-bin/ptz.cgi?action=start&code=RightDown&channel=0&arg1=1&arg2=1&arg3=0' ); - usleep (500); - $self->sendCmd( 'cgi-bin/ptz.cgi?action=stop&code=RightDown&channel=0&arg1=0&arg2=1&arg3=0' ); +sub moveConDownRight { + my $self = shift; + Debug('Move Diagonally Down Right'); + $self->sendCmd('cgi-bin/ptz.cgi?action=start&code=RightDown&channel=0&arg1=1&arg2=1&arg3=0'); + usleep(500); + $self->sendCmd('cgi-bin/ptz.cgi?action=stop&code=RightDown&channel=0&arg1=0&arg2=1&arg3=0'); } -sub moveConUpLeft -{ - my $self = shift; - Debug( "Move Diagonally Up Left" ); - $self->sendCmd( 'cgi-bin/ptz.cgi?action=start&code=LeftUp&channel=0&arg1=1&arg2=1&arg3=0' ); - usleep (500); - $self->sendCmd( 'cgi-bin/ptz.cgi?action=stop&code=LeftUp&channel=0&arg1=0&arg2=1&arg3=0' ); +sub moveConUpLeft { + my $self = shift; + Debug('Move Diagonally Up Left'); + $self->sendCmd('cgi-bin/ptz.cgi?action=start&code=LeftUp&channel=0&arg1=1&arg2=1&arg3=0'); + usleep(500); + $self->sendCmd('cgi-bin/ptz.cgi?action=stop&code=LeftUp&channel=0&arg1=0&arg2=1&arg3=0'); } -sub moveConDownLeft -{ - my $self = shift; - Debug( "Move Diagonally Down Left" ); - $self->sendCmd( 'cgi-bin/ptz.cgi?action=start&code=LeftDown&channel=0&arg1=1&arg2=1&arg3=0' ); - usleep (500); - $self->sendCmd( 'cgi-bin/ptz.cgi?action=stop&code=LeftDown&channel=0&arg1=0&arg2=1&arg3=0' ); +sub moveConDownLeft { + my $self = shift; + Debug('Move Diagonally Down Left'); + $self->sendCmd('cgi-bin/ptz.cgi?action=start&code=LeftDown&channel=0&arg1=1&arg2=1&arg3=0'); + usleep (500); + $self->sendCmd('cgi-bin/ptz.cgi?action=stop&code=LeftDown&channel=0&arg1=0&arg2=1&arg3=0'); } # Stop is not "correctly" implemented as control_functions.php translates this to "Center" # So we'll just send the camera to 0* Horz, 0* Vert, zoom out; Also, Amcrest does not seem to # support a generic stop-all-current-action command. -sub moveStop -{ - my $self = shift; - Debug( "Move Stop/Center" ); - $self->sendCmd( 'cgi-bin/ptz.cgi?action=start&code=PositionABS&channel=0&arg1=0&arg2=0&arg3=0&arg4=1' ); +sub moveStop { + my $self = shift; + Debug('Move Stop/Center'); + $self->sendCmd('cgi-bin/ptz.cgi?action=start&code=PositionABS&channel=0&arg1=0&arg2=0&arg3=0&arg4=1'); } # Move Camera to Home Position # The current API does not support a Home per se, so we'll just send the camera to preset #1 # NOTE: It goes without saying that the user must have set up preset #1 for this to work. -sub presetHome -{ - my $self = shift; - Debug( "Home Preset" ); - $self->sendCmd( 'cgi-bin/ptz.cgi?action=start&channel=0&code=GotoPreset&&arg1=0&arg2=1&arg3=0&arg4=0' ); +sub presetHome { + my $self = shift; + Debug('Home Preset'); + $self->sendCmd('cgi-bin/ptz.cgi?action=start&channel=0&code=GotoPreset&&arg1=0&arg2=1&arg3=0&arg4=0'); } -sub presetGoto -{ - my $self = shift; - my $params = shift; - my $preset = $self->getParam( $params, 'preset' ); - Debug( "Go To Preset $preset" ); - $self->sendCmd( 'cgi-bin/ptz.cgi?action=start&channel=0&code=GotoPreset&&arg1=0&arg2='.$preset.'&arg3=0&arg4=0' ); +sub presetGoto { + my $self = shift; + my $params = shift; + my $preset = $self->getParam($params, 'preset'); + Debug("Go To Preset $preset"); + $self->sendCmd('cgi-bin/ptz.cgi?action=start&channel=0&code=GotoPreset&&arg1=0&arg2='.$preset.'&arg3=0&arg4=0'); } -sub presetSet -{ - my $self = shift; - my $params = shift; - my $preset = $self->getParam( $params, 'preset' ); - Debug( "Set Preset" ); - $self->sendCmd( 'cgi-bin/ptz.cgi?action=start&channel=0&code=SetPreset&arg1=0&arg2='.$preset.'&arg3=0&arg4=0' ); +sub presetSet { + my $self = shift; + my $params = shift; + my $preset = $self->getParam($params, 'preset'); + Debug('Set Preset'); + $self->sendCmd('cgi-bin/ptz.cgi?action=start&channel=0&code=SetPreset&arg1=0&arg2='.$preset.'&arg3=0&arg4=0'); } # NOTE: This does not appear to be implemented in the classic skin. But we'll leave it here for later. -sub moveMap -{ - my $self = shift; - my $params = shift; +sub moveMap { + my $self = shift; + my $params = shift; - my $xcoord = $self->getParam( $params, 'xcoord', $self->{Monitor}{Width}/2 ); - my $ycoord = $self->getParam( $params, 'ycoord', $self->{Monitor}{Height}/2 ); - # if the camera is mounted upside down, you may have to inverse these coordinates - # just use 360 minus pan instead of pan, 90 minus tilt instead of tilt - # Convert xcoord into pan position 0 to 359 - my $pan = int(360 * $xcoord / $self->{Monitor}{Width}); - # Convert ycoord into tilt position 0 to 89 - my $tilt = 90 - int(90 * $ycoord / $self->{Monitor}{Height}); - # Now get the following url: - $self->sendCmd( 'cgi-bin/ptz.cgi?action=start&code=PositionABS&channel=0&arg1='.$pan.'&arg2='.$tilt.'&arg3=1&arg4=1'); + my $xcoord = $self->getParam( $params, 'xcoord', $self->{Monitor}{Width}/2 ); + my $ycoord = $self->getParam( $params, 'ycoord', $self->{Monitor}{Height}/2 ); + # if the camera is mounted upside down, you may have to inverse these coordinates + # just use 360 minus pan instead of pan, 90 minus tilt instead of tilt + # Convert xcoord into pan position 0 to 359 + my $pan = int(360 * $xcoord / $self->{Monitor}{Width}); + # Convert ycoord into tilt position 0 to 89 + my $tilt = 90 - int(90 * $ycoord / $self->{Monitor}{Height}); + # Now get the following url: + $self->sendCmd('cgi-bin/ptz.cgi?action=start&code=PositionABS&channel=0&arg1='.$pan.'&arg2='.$tilt.'&arg3=1&arg4=1'); } -sub zoomConTele -{ - my $self = shift; - Debug( "Zoom continuous tele" ); - $self->sendCmd( 'cgi-bin/ptz.cgi?action=start&channel=0&code=ZoomTele&arg1=0&arg2=0&arg3=0&arg4=0' ); - usleep (100000); - $self->sendCmd( 'cgi-bin/ptz.cgi?action=stop&channel=0&code=ZoomTele&arg1=0&arg2=0&arg3=0&arg4=0' ); +sub zoomConTele { + my $self = shift; + Debug('Zoom continuous tele'); + $self->sendCmd('cgi-bin/ptz.cgi?action=start&channel=0&code=ZoomTele&arg1=0&arg2=0&arg3=0&arg4=0'); + usleep(100000); + $self->sendCmd('cgi-bin/ptz.cgi?action=stop&channel=0&code=ZoomTele&arg1=0&arg2=0&arg3=0&arg4=0'); } -sub zoomConWide -{ - my $self = shift; - Debug( "Zoom continuous wide" ); - $self->sendCmd( 'cgi-bin/ptz.cgi?action=start&channel=0&code=ZoomWide&arg1=0&arg2=0&arg3=0&arg4=0' ); - usleep (100000); - $self->sendCmd( 'cgi-bin/ptz.cgi?action=stop&channel=0&code=ZoomWide&arg1=0&arg2=0&arg3=0&arg4=0' ); +sub zoomConWide { + my $self = shift; + Debug('Zoom continuous wide'); + $self->sendCmd('cgi-bin/ptz.cgi?action=start&channel=0&code=ZoomWide&arg1=0&arg2=0&arg3=0&arg4=0'); + usleep (100000); + $self->sendCmd('cgi-bin/ptz.cgi?action=stop&channel=0&code=ZoomWide&arg1=0&arg2=0&arg3=0&arg4=0'); } 1; @@ -355,7 +332,7 @@ ZoneMinder::Control::Amcrest_HTTP - Amcrest camera control =head1 DESCRIPTION -This module contains the implementation of the Amcrest Camera +This module contains the implementation of the Amcrest Camera controllable SDK API. NOTE: This module implements interaction with the camera in clear text. diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/AxisV2.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/AxisV2.pm index d63236c86..ddb1dbe54 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/AxisV2.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/AxisV2.pm @@ -1,6 +1,6 @@ # ========================================================================== # -# ZoneMinder Axis version 2 API Control Protocol Module, $Date$, $Revision$ +# ZoneMinder Axis version 2 API Control Protocol Module # Copyright (C) 2001-2008 Philip Coombes # # This program is free software; you can redistribute it and/or @@ -43,349 +43,359 @@ use ZoneMinder::Logger qw(:all); use ZoneMinder::Config qw(:all); use Time::HiRes qw( usleep ); +use URI; -sub open -{ - my $self = shift; +our $ADDRESS; - $self->loadMonitor(); +sub open { + my $self = shift; - use LWP::UserAgent; - $self->{ua} = LWP::UserAgent->new; - $self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION ); + $self->loadMonitor(); + if ( $self->{Monitor}->{ControlAddress} !~ /^\w+:\/\// ) { + # Has no scheme at the beginning, so won't parse as a URI + $self->{Monitor}->{ControlAddress} = 'http://'.$self->{Monitor}->{ControlAddress}; + } + my $uri = URI->new($self->{Monitor}->{ControlAddress}); + $ADDRESS = $uri->scheme.'://'.$uri->authority().$uri->path().($uri->port()?':'.$uri->port():''); + use LWP::UserAgent; + $self->{ua} = LWP::UserAgent->new; + $self->{ua}->cookie_jar( {} ); + $self->{ua}->agent('ZoneMinder Control Agent/'.ZoneMinder::Base::ZM_VERSION); + $self->{state} = 'closed'; + + my ( $username, $password, $host ) = ( $uri->authority() =~ /^([^:]+):([^@]*)@(.+)$/ ); + my $realm = $self->{Monitor}->{ControlDevice}; + + $self->{ua}->credentials($ADDRESS, $realm, $username, $password); + + # test auth + my $res = $self->{ua}->get($ADDRESS.'/cgi/ptdc.cgi'); + + if ( $res->is_success ) { $self->{state} = 'open'; -} + return; + } -sub printMsg -{ - my $self = shift; - my $msg = shift; - my $msg_len = length($msg); + if ( $res->status_line() eq '401 Unauthorized' ) { - Debug( $msg."[".$msg_len."]" ); -} - -sub sendCmd -{ - my $self = shift; - my $cmd = shift; - - my $result = undef; - - printMsg( $cmd, "Tx" ); - - #print( "http://$address/$cmd\n" ); - my $req = HTTP::Request->new( GET=>"http://".$self->{Monitor}->{ControlAddress}."/$cmd" ); - my $res = $self->{ua}->request($req); - - if ( $res->is_success ) - { - $result = !undef; - } - else - { - Error( "Error check failed: '".$res->status_line()."'" ); + my $headers = $res->headers(); + foreach my $k ( keys %$headers ) { + Debug("Initial Header $k => $$headers{$k}"); } - return( $result ); + if ( $$headers{'www-authenticate'} ) { + Debug('Authenticating'); + 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($host, $realm, $username, $password); + $res = $self->{ua}->get($ADDRESS); + if ( $res->is_success() ) { + $self->{state} = 'open'; + return; + } + Error('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 { + Debug('No headers line'); + } # end if headers + } # end if $res->status_line() eq '401 Unauthorized' +} # end sub open + +sub sendCmd { + my $self = shift; + my $cmd = shift; + + $self->printMsg($cmd, 'Tx'); + + my $url = $ADDRESS.$cmd; + my $res = $self->{ua}->get($url); + + if ( $res->is_success ) { + Debug('sndCmd command: ' . $url . ' content: '.$res->content); + return !undef; + } + + Error("Error cmd $url failed: '".$res->status_line()."'"); + + return undef; } -sub cameraReset -{ - my $self = shift; - Debug( "Camera Reset" ); - my $cmd = "/axis-cgi/admin/restart.cgi"; - $self->sendCmd( $cmd ); +sub cameraReset { + my $self = shift; + Debug('Camera Reset'); + my $cmd = '/axis-cgi/admin/restart.cgi'; + $self->sendCmd($cmd); } -sub moveConUp -{ - my $self = shift; - Debug( "Move Up" ); - my $cmd = "/axis-cgi/com/ptz.cgi?move=up"; - $self->sendCmd( $cmd ); +sub moveConUp { + my $self = shift; + Debug('Move Up'); + my $cmd = '/axis-cgi/com/ptz.cgi?move=up'; + $self->sendCmd($cmd); } -sub moveConDown -{ - my $self = shift; - Debug( "Move Down" ); - my $cmd = "/axis-cgi/com/ptz.cgi?move=down"; - $self->sendCmd( $cmd ); +sub moveConDown { + my $self = shift; + Debug('Move Down'); + my $cmd = '/axis-cgi/com/ptz.cgi?move=down'; + $self->sendCmd($cmd); } -sub moveConLeft -{ - my $self = shift; - Debug( "Move Left" ); - my $cmd = "/axis-cgi/com/ptz.cgi?move=left"; - $self->sendCmd( $cmd ); +sub moveConLeft { + my $self = shift; + Debug('Move Left'); + my $cmd = '/axis-cgi/com/ptz.cgi?move=left'; + $self->sendCmd($cmd); } -sub moveConRight -{ - my $self = shift; - Debug( "Move Right" ); - my $cmd = "/axis-cgi/com/ptz.cgi?move=right"; - $self->sendCmd( $cmd ); +sub moveConRight { + my $self = shift; + Debug('Move Right'); + my $cmd = '/axis-cgi/com/ptz.cgi?move=right'; + $self->sendCmd($cmd); } -sub moveConUpRight -{ - my $self = shift; - Debug( "Move Up/Right" ); - my $cmd = "/axis-cgi/com/ptz.cgi?move=upright"; - $self->sendCmd( $cmd ); +sub moveConUpRight { + my $self = shift; + Debug('Move Up/Right'); + my $cmd = '/axis-cgi/com/ptz.cgi?move=upright'; + $self->sendCmd($cmd); } -sub moveConUpLeft -{ - my $self = shift; - Debug( "Move Up/Left" ); - my $cmd = "/axis-cgi/com/ptz.cgi?move=upleft"; - $self->sendCmd( $cmd ); +sub moveConUpLeft { + my $self = shift; + Debug('Move Up/Left'); + my $cmd = '/axis-cgi/com/ptz.cgi?move=upleft'; + $self->sendCmd($cmd); } -sub moveConDownRight -{ - my $self = shift; - Debug( "Move Down/Right" ); - my $cmd = "/axis-cgi/com/ptz.cgi?move=downright"; - $self->sendCmd( $cmd ); +sub moveConDownRight { + my $self = shift; + Debug('Move Down/Right'); + my $cmd = '/axis-cgi/com/ptz.cgi?move=downright'; + $self->sendCmd( $cmd ); } -sub moveConDownLeft -{ - my $self = shift; - Debug( "Move Down/Left" ); - my $cmd = "/axis-cgi/com/ptz.cgi?move=downleft"; - $self->sendCmd( $cmd ); +sub moveConDownLeft { + my $self = shift; + Debug('Move Down/Left'); + my $cmd = '/axis-cgi/com/ptz.cgi?move=downleft'; + $self->sendCmd($cmd); } -sub moveMap -{ - my $self = shift; - my $params = shift; - my $xcoord = $self->getParam( $params, 'xcoord' ); - my $ycoord = $self->getParam( $params, 'ycoord' ); - Debug( "Move Map to $xcoord,$ycoord" ); - my $cmd = "/axis-cgi/com/ptz.cgi?center=$xcoord,$ycoord&imagewidth=".$self->{Monitor}->{Width}."&imageheight=".$self->{Monitor}->{Height}; - $self->sendCmd( $cmd ); +sub moveMap { + my $self = shift; + my $params = shift; + my $xcoord = $self->getParam($params, 'xcoord'); + my $ycoord = $self->getParam($params, 'ycoord'); + Debug("Move Map to $xcoord,$ycoord"); + my $cmd = "/axis-cgi/com/ptz.cgi?center=$xcoord,$ycoord&imagewidth=".$self->{Monitor}->{Width}.'&imageheight='.$self->{Monitor}->{Height}; + $self->sendCmd($cmd); } -sub moveRelUp -{ - my $self = shift; - my $params = shift; - my $step = $self->getParam( $params, 'tiltstep' ); - Debug( "Step Up $step" ); - my $cmd = "/axis-cgi/com/ptz.cgi?rtilt=$step"; - $self->sendCmd( $cmd ); +sub moveRelUp { + my $self = shift; + my $params = shift; + my $step = $self->getParam($params, 'tiltstep'); + Debug("Step Up $step"); + my $cmd = '/axis-cgi/com/ptz.cgi?rtilt='.$step; + $self->sendCmd($cmd); } -sub moveRelDown -{ - my $self = shift; - my $params = shift; - my $step = $self->getParam( $params, 'tiltstep' ); - Debug( "Step Down $step" ); - my $cmd = "/axis-cgi/com/ptz.cgi?rtilt=-$step"; - $self->sendCmd( $cmd ); +sub moveRelDown { + my $self = shift; + my $params = shift; + my $step = $self->getParam($params, 'tiltstep'); + Debug("Step Down $step"); + my $cmd = '/axis-cgi/com/ptz.cgi?rtilt=-'.$step; + $self->sendCmd($cmd); } -sub moveRelLeft -{ - my $self = shift; - my $params = shift; - my $step = $self->getParam( $params, 'panstep' ); - Debug( "Step Left $step" ); - my $cmd = "/axis-cgi/com/ptz.cgi?rpan=-$step"; - $self->sendCmd( $cmd ); +sub moveRelLeft { + my $self = shift; + my $params = shift; + my $step = $self->getParam($params, 'panstep'); + Debug("Step Left $step"); + my $cmd = '/axis-cgi/com/ptz.cgi?rpan=-'.$step; + $self->sendCmd($cmd); } -sub moveRelRight -{ - my $self = shift; - my $params = shift; - my $step = $self->getParam( $params, 'panstep' ); - Debug( "Step Right $step" ); - my $cmd = "/axis-cgi/com/ptz.cgi?rpan=$step"; - $self->sendCmd( $cmd ); +sub moveRelRight { + my $self = shift; + my $params = shift; + my $step = $self->getParam($params, 'panstep'); + Debug("Step Right $step"); + my $cmd = '/axis-cgi/com/ptz.cgi?rpan='.$step; + $self->sendCmd($cmd); } -sub moveRelUpRight -{ - my $self = shift; - my $params = shift; - my $panstep = $self->getParam( $params, 'panstep' ); - my $tiltstep = $self->getParam( $params, 'tiltstep' ); - Debug( "Step Up/Right $tiltstep/$panstep" ); - my $cmd = "/axis-cgi/com/ptz.cgi?rpan=$panstep&rtilt=$tiltstep"; - $self->sendCmd( $cmd ); +sub moveRelUpRight { + my $self = shift; + my $params = shift; + my $panstep = $self->getParam($params, 'panstep'); + my $tiltstep = $self->getParam($params, 'tiltstep'); + Debug("Step Up/Right $tiltstep/$panstep"); + my $cmd = "/axis-cgi/com/ptz.cgi?rpan=$panstep&rtilt=$tiltstep"; + $self->sendCmd($cmd); } -sub moveRelUpLeft -{ - my $self = shift; - my $params = shift; - my $panstep = $self->getParam( $params, 'panstep' ); - my $tiltstep = $self->getParam( $params, 'tiltstep' ); - Debug( "Step Up/Left $tiltstep/$panstep" ); - my $cmd = "/axis-cgi/com/ptz.cgi?rpan=-$panstep&rtilt=$tiltstep"; - $self->sendCmd( $cmd ); +sub moveRelUpLeft { + my $self = shift; + my $params = shift; + my $panstep = $self->getParam($params, 'panstep'); + my $tiltstep = $self->getParam($params, 'tiltstep'); + Debug("Step Up/Left $tiltstep/$panstep"); + my $cmd = "/axis-cgi/com/ptz.cgi?rpan=-$panstep&rtilt=$tiltstep"; + $self->sendCmd($cmd); } -sub moveRelDownRight -{ - my $self = shift; - my $params = shift; - my $panstep = $self->getParam( $params, 'panstep' ); - my $tiltstep = $self->getParam( $params, 'tiltstep' ); - Debug( "Step Down/Right $tiltstep/$panstep" ); - my $cmd = "/axis-cgi/com/ptz.cgi?rpan=$panstep&rtilt=-$tiltstep"; - $self->sendCmd( $cmd ); +sub moveRelDownRight { + my $self = shift; + my $params = shift; + my $panstep = $self->getParam($params, 'panstep'); + my $tiltstep = $self->getParam($params, 'tiltstep'); + Debug("Step Down/Right $tiltstep/$panstep"); + my $cmd = "/axis-cgi/com/ptz.cgi?rpan=$panstep&rtilt=-$tiltstep"; + $self->sendCmd($cmd); } -sub moveRelDownLeft -{ - my $self = shift; - my $params = shift; - my $panstep = $self->getParam( $params, 'panstep' ); - my $tiltstep = $self->getParam( $params, 'tiltstep' ); - Debug( "Step Down/Left $tiltstep/$panstep" ); - my $cmd = "/axis-cgi/com/ptz.cgi?rpan=-$panstep&rtilt=-$tiltstep"; - $self->sendCmd( $cmd ); +sub moveRelDownLeft { + my $self = shift; + my $params = shift; + my $panstep = $self->getParam($params, 'panstep'); + my $tiltstep = $self->getParam($params, 'tiltstep'); + Debug("Step Down/Left $tiltstep/$panstep"); + my $cmd = "/axis-cgi/com/ptz.cgi?rpan=-$panstep&rtilt=-$tiltstep"; + $self->sendCmd($cmd); } -sub zoomRelTele -{ - my $self = shift; - my $params = shift; - my $step = $self->getParam( $params, 'step' ); - Debug( "Zoom Tele" ); - my $cmd = "/axis-cgi/com/ptz.cgi?rzoom=$step"; - $self->sendCmd( $cmd ); +sub zoomRelTele { + my $self = shift; + my $params = shift; + my $step = $self->getParam($params, 'step'); + Debug('Zoom Tele'); + my $cmd = "/axis-cgi/com/ptz.cgi?rzoom=$step"; + $self->sendCmd($cmd); } -sub zoomRelWide -{ - my $self = shift; - my $params = shift; - my $step = $self->getParam( $params, 'step' ); - Debug( "Zoom Wide" ); - my $cmd = "/axis-cgi/com/ptz.cgi?rzoom=-$step"; - $self->sendCmd( $cmd ); +sub zoomRelWide { + my $self = shift; + my $params = shift; + my $step = $self->getParam($params, 'step'); + Debug('Zoom Wide'); + my $cmd = "/axis-cgi/com/ptz.cgi?rzoom=-$step"; + $self->sendCmd($cmd); } -sub focusRelNear -{ - my $self = shift; - my $params = shift; - my $step = $self->getParam( $params, 'step' ); - Debug( "Focus Near" ); - my $cmd = "/axis-cgi/com/ptz.cgi?rfocus=-$step"; - $self->sendCmd( $cmd ); +sub focusRelNear { + my $self = shift; + my $params = shift; + my $step = $self->getParam($params, 'step'); + Debug('Focus Near'); + my $cmd = "/axis-cgi/com/ptz.cgi?rfocus=-$step"; + $self->sendCmd($cmd); } -sub focusRelFar -{ - my $self = shift; - my $params = shift; - my $step = $self->getParam( $params, 'step' ); - Debug( "Focus Far" ); - my $cmd = "/axis-cgi/com/ptz.cgi?rfocus=$step"; - $self->sendCmd( $cmd ); +sub focusRelFar { + my $self = shift; + my $params = shift; + my $step = $self->getParam($params, 'step'); + Debug('Focus Far'); + my $cmd = "/axis-cgi/com/ptz.cgi?rfocus=$step"; + $self->sendCmd($cmd); } -sub focusAuto -{ - my $self = shift; - Debug( "Focus Auto" ); - my $cmd = "/axis-cgi/com/ptz.cgi?autofocus=on"; - $self->sendCmd( $cmd ); +sub focusAuto { + my $self = shift; + Debug('Focus Auto'); + my $cmd = '/axis-cgi/com/ptz.cgi?autofocus=on'; + $self->sendCmd($cmd); } -sub focusMan -{ - my $self = shift; - Debug( "Focus Manual" ); - my $cmd = "/axis-cgi/com/ptz.cgi?autofocus=off"; - $self->sendCmd( $cmd ); +sub focusMan { + my $self = shift; + Debug('Focus Manual'); + my $cmd = '/axis-cgi/com/ptz.cgi?autofocus=off'; + $self->sendCmd($cmd); } -sub irisRelOpen -{ - my $self = shift; - my $params = shift; - my $step = $self->getParam( $params, 'step' ); - Debug( "Iris Open" ); - my $cmd = "/axis-cgi/com/ptz.cgi?riris=$step"; - $self->sendCmd( $cmd ); +sub irisRelOpen { + my $self = shift; + my $params = shift; + my $step = $self->getParam($params, 'step'); + Debug('Iris Open'); + my $cmd = "/axis-cgi/com/ptz.cgi?riris=$step"; + $self->sendCmd($cmd); } -sub irisRelClose -{ - my $self = shift; - my $params = shift; - my $step = $self->getParam( $params, 'step' ); - Debug( "Iris Close" ); - my $cmd = "/axis-cgi/com/ptz.cgi?riris=-$step"; - $self->sendCmd( $cmd ); +sub irisRelClose { + my $self = shift; + my $params = shift; + my $step = $self->getParam($params, 'step'); + Debug('Iris Close'); + my $cmd = "/axis-cgi/com/ptz.cgi?riris=-$step"; + $self->sendCmd($cmd); } -sub irisAuto -{ - my $self = shift; - Debug( "Iris Auto" ); - my $cmd = "/axis-cgi/com/ptz.cgi?autoiris=on"; - $self->sendCmd( $cmd ); +sub irisAuto { + my $self = shift; + Debug('Iris Auto'); + my $cmd = '/axis-cgi/com/ptz.cgi?autoiris=on'; + $self->sendCmd($cmd); } -sub irisMan -{ - my $self = shift; - Debug( "Iris Manual" ); - my $cmd = "/axis-cgi/com/ptz.cgi?autoiris=off"; - $self->sendCmd( $cmd ); +sub irisMan { + my $self = shift; + Debug('Iris Manual'); + my $cmd = '/axis-cgi/com/ptz.cgi?autoiris=off'; + $self->sendCmd($cmd); } -sub presetClear -{ - my $self = shift; - my $params = shift; - my $preset = $self->getParam( $params, 'preset' ); - Debug( "Clear Preset $preset" ); - my $cmd = "/axis-cgi/com/ptz.cgi?removeserverpresetno=$preset"; - $self->sendCmd( $cmd ); +sub presetClear { + my $self = shift; + my $params = shift; + my $preset = $self->getParam($params, 'preset'); + Debug("Clear Preset $preset"); + my $cmd = "/axis-cgi/com/ptz.cgi?removeserverpresetno=$preset"; + $self->sendCmd($cmd); } -sub presetSet -{ - my $self = shift; - my $params = shift; - my $preset = $self->getParam( $params, 'preset' ); - Debug( "Set Preset $preset" ); - my $cmd = "/axis-cgi/com/ptz.cgi?setserverpresetno=$preset"; - $self->sendCmd( $cmd ); +sub presetSet { + my $self = shift; + my $params = shift; + my $preset = $self->getParam($params, 'preset'); + Debug("Set Preset $preset"); + my $cmd = "/axis-cgi/com/ptz.cgi?setserverpresetno=$preset"; + $self->sendCmd($cmd); } -sub presetGoto -{ - my $self = shift; - my $params = shift; - my $preset = $self->getParam( $params, 'preset' ); - Debug( "Goto Preset $preset" ); - my $cmd = "/axis-cgi/com/ptz.cgi?gotoserverpresetno=$preset"; - $self->sendCmd( $cmd ); +sub presetGoto { + my $self = shift; + my $params = shift; + my $preset = $self->getParam($params, 'preset'); + Debug("Goto Preset $preset"); + my $cmd = "/axis-cgi/com/ptz.cgi?gotoserverpresetno=$preset"; + $self->sendCmd($cmd); } -sub presetHome -{ - my $self = shift; - Debug( "Home Preset" ); - my $cmd = "/axis-cgi/com/ptz.cgi?move=home"; - $self->sendCmd( $cmd ); +sub presetHome { + my $self = shift; + Debug('Home Preset'); + my $cmd = '/axis-cgi/com/ptz.cgi?move=home'; + $self->sendCmd($cmd); } 1; diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/DCS5020L.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/DCS5020L.pm new file mode 100644 index 000000000..59d9e3550 --- /dev/null +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/DCS5020L.pm @@ -0,0 +1,356 @@ +# =========================================================================r +# +# ZoneMinder D-Link DCS-5020L IP Control Protocol Module, $Date: $, $Revision: $ +# Copyright (C) 2013 Art Scheel +# +# 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# ========================================================================== +# +# This module contains the implementation of the D-Link DCS-5020L IP camera control +# protocol. +# +package ZoneMinder::Control::DCS5020L; + +use 5.006; +use strict; +use warnings; + +require ZoneMinder::Base; +require ZoneMinder::Control; + +our @ISA = qw(ZoneMinder::Control); + +our $VERSION = $ZoneMinder::Base::VERSION; + +# ========================================================================== +# +# D-Link DCS-5020L Control Protocol +# +# ========================================================================== + +use ZoneMinder::Logger qw(:all); +use ZoneMinder::Config qw(:all); + +use Time::HiRes qw( usleep ); + +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(); + + use LWP::UserAgent; + $self->{ua} = LWP::UserAgent->new; + $self->{ua}->agent( "ZoneMinder Control Agent/" . ZoneMinder::Base::ZM_VERSION ); + $self->{state} = 'open'; +} + +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 sendCmd +{ + my $self = shift; + my $cmd = shift; + my $cgi = shift; + + my $result = undef; + + printMsg( $cmd, "Tx" ); + + my $req = HTTP::Request->new( POST=>"http://$self->{Monitor}->{ControlAddress}/$cgi.cgi" ); + $req->content($cmd); + my $res = $self->{ua}->request($req); + + if ( $res->is_success ) + { + $result = !undef; + } + else + { + Error( "Error check failed: '".$res->status_line()."'" ); + } + + return( $result ); +} + +sub move +{ + my $self = shift; + my $dir = shift; + my $panStep = shift; + my $tiltStep = shift; + my $cmd = "PanSingleMoveDegree=$panStep&TiltSingleMoveDegree=$tiltStep&PanTiltSingleMove=$dir"; + $self->sendCmd( $cmd, 'pantiltcontrol' ); +} + +sub moveRel +{ + my $self = shift; + my $params = shift; + my $panStep = $self->getParam($params, 'panstep', 0); + my $tiltStep = $self->getParam($params, 'tiltstep', 0); + my $dir = shift; + $self->move( $dir, $panStep, $tiltStep ); +} + +sub moveRelUpLeft +{ + my $self = shift; + my $params = shift; + $self->moveRel( $params, 0 ); +} + +sub moveRelUp +{ + my $self = shift; + my $params = shift; + $self->moveRel( $params, 1 ); +} + +sub moveRelUpRight +{ + my $self = shift; + my $params = shift; + $self->moveRel( $params, 2 ); +} + +sub moveRelLeft +{ + my $self = shift; + my $params = shift; + $self->moveRel( $params, 3 ); +} + +sub moveRelRight +{ + my $self = shift; + my $params = shift; + $self->moveRel( $params, 5 ); +} + +sub moveRelDownLeft +{ + my $self = shift; + my $params = shift; + $self->moveRel( $params, 6 ); +} + +sub moveRelDown +{ + my $self = shift; + my $params = shift; + $self->moveRel( $params, 7 ); +} + +sub moveRelDownRight +{ + my $self = shift; + my $params = shift; + $self->moveRel( $params, 8 ); +} + +# moves the camera to center on the point that the user clicked on in the video image. +# This isn't extremely accurate but good enough for most purposes +sub moveMap +{ + # if the camera moves too much or too little, try increasing or decreasing this value + my $f = 11; + + my $self = shift; + my $params = shift; + my $xcoord = $self->getParam( $params, 'xcoord' ); + my $ycoord = $self->getParam( $params, 'ycoord' ); + + my $hor = $xcoord * 100 / $self->{Monitor}->{Width}; + my $ver = $ycoord * 100 / $self->{Monitor}->{Height}; + + my $direction; + my $horSteps; + my $verSteps; + if ($hor < 50 && $ver < 50) { + # up left + $horSteps = (50 - $hor) / $f; + $verSteps = (50 - $ver) / $f; + $direction = 0; + } elsif ($hor >= 50 && $ver < 50) { + # up right + $horSteps = ($hor - 50) / $f; + $verSteps = (50 - $ver) / $f; + $direction = 2; + } elsif ($hor < 50 && $ver >= 50) { + # down left + $horSteps = (50 - $hor) / $f; + $verSteps = ($ver - 50) / $f; + $direction = 6; + } elsif ($hor >= 50 && $ver >= 50) { + # down right + $horSteps = ($hor - 50) / $f; + $verSteps = ($ver - 50) / $f; + $direction = 8; + } + my $v = int($verSteps + .5); + my $h = int($horSteps + .5); + Debug( "Move Map to $xcoord,$ycoord, hor=$h, ver=$v with direction $direction" ); + $self->move( $direction, $h, $v ); +} + +sub presetClear +{ + my $self = shift; + my $params = shift; + my $preset = $self->getParam( $params, 'preset' ); + Debug( "Clear Preset $preset" ); + my $cmd = "ClearPosition=$preset"; + $self->sendCmd( $cmd, 'pantiltcontrol' ); +} + +sub presetSet +{ + my $self = shift; + my $params = shift; + my $preset = $self->getParam( $params, 'preset' ); + Debug( "Set Preset $preset" ); + my $cmd = "SetCurrentPosition=$preset&SetName=preset_$preset"; + $self->sendCmd( $cmd, 'pantiltcontrol' ); +} + +sub presetGoto +{ + my $self = shift; + my $params = shift; + my $preset = $self->getParam( $params, 'preset' ); + Debug( "Goto Preset $preset" ); + my $cmd = "PanTiltPresetPositionMove=$preset"; + $self->sendCmd( $cmd, 'pantiltcontrol' ); +} + +sub presetHome +{ + my $self = shift; + Debug( "Home Preset" ); + $self->move( 4, 0, 0 ); +} + + +# IR Controls +# +# wake = IR on +# sleep = IR off +# reset = IR auto + +sub setDayNightMode { + my $self = shift; + my $mode = shift; + my $cmd = "DayNightMode=$mode&ConfigReboot=No"; + $self->sendCmd( $cmd, 'daynight' ); +} + +sub wake +{ + my $self = shift; + Debug( "Wake - IR on" ); + $self->setDayNightMode(2); +} + +sub sleep +{ + my $self = shift; + Debug( "Sleep - IR off" ); + $self->setDayNightMode(3); +} + +sub reset +{ + my $self = shift; + Debug( "Reset - IR auto" ); + $self->setDayNightMode(0); +} + +1; +__END__ +# Below is stub documentation for your module. You'd better edit it! + +=head1 NAME + +ZoneMinder::Database - Perl extension for DCS-5020L + +=head1 SYNOPSIS + + use ZoneMinder::Database; + DLINK DCS-5020L + +=head1 DESCRIPTION + +ZoneMinder driver for the D-Link consumer camera DCS-5020L. + +=head2 EXPORT + +None by default. + + + +=head1 SEE ALSO + +See if there are better instructions for the DCS-5020L at +http://www.zoneminder.com/wiki/index.php/Dlink + +=head1 AUTHOR + +Art Scheel ascheel (at) gmail + +=head1 COPYRIGHT AND LICENSE + +LGPLv3 + +=cut diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/HikVision.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/HikVision.pm index 8754500fa..a9f8d8f1b 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/HikVision.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/HikVision.pm @@ -95,9 +95,9 @@ sub PutCmd { my $self = shift; my $cmd = shift; my $content = shift; - my $req = HTTP::Request->new(PUT => "$self->{BaseURL}/$cmd"); + my $req = HTTP::Request->new(PUT => $self->{BaseURL}.'/'.$cmd); if ( defined($content) ) { - $req->content_type("application/x-www-form-urlencoded; charset=UTF-8"); + $req->content_type('application/x-www-form-urlencoded; charset=UTF-8'); $req->content('' . "\n" . $content); } my $res = $self->{UA}->request($req); @@ -135,13 +135,13 @@ sub PutCmd { # Check for username/password # if ( $self->{Monitor}{ControlAddress} =~ /.+:(.+)@.+/ ) { - Info("Check username/password is correct"); + Info('Check username/password is correct'); } elsif ( $self->{Monitor}{ControlAddress} =~ /^[^:]+@.+/ ) { - Info("No password in Control Address. Should there be one?"); + Info('No password in Control Address. Should there be one?'); } elsif ( $self->{Monitor}{ControlAddress} =~ /^:.+@.+/ ) { - Info("Password but no username in Control Address."); + Info('Password but no username in Control Address.'); } else { - Info("Missing username and password in Control Address."); + Info('Missing username and password in Control Address.'); } Fatal($res->status_line); } @@ -382,7 +382,7 @@ sub irisRelOpen { sub reset { my $self = shift; - $self->PutCmd("ISAPI/System/reboot"); + $self->PutCmd('ISAPI/System/reboot'); } 1; diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/SkyIPCam7xx.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/SkyIPCam7xx.pm index 7175d1e84..ebbaf3a8a 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/SkyIPCam7xx.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/SkyIPCam7xx.pm @@ -1,6 +1,6 @@ # ========================================================================== # -# ZoneMinder Airlink SkyIPCam AICN747/AICN747W Control Protocol Module, $Date: 2008-09-13 17:30:29 +0000 (Sat, 13 Sept 2008) $, $Revision: 2229 $ +# ZoneMinder Airlink SkyIPCam AICN747/AICN747W Control Protocol Module # Copyright (C) 2008 Brian Rudy (brudyNO@SPAMpraecogito.com) # # This program is free software; you can redistribute it and/or @@ -43,8 +43,6 @@ our @ISA = qw(ZoneMinder::Control); use ZoneMinder::Logger qw(:all); use ZoneMinder::Config qw(:all); -use Time::HiRes qw( usleep ); - sub open { my $self = shift; @@ -52,58 +50,50 @@ sub open { use LWP::UserAgent; $self->{ua} = LWP::UserAgent->new; - $self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION ); + $self->{ua}->agent('ZoneMinder Control Agent/'.ZoneMinder::Base::ZM_VERSION); $self->{state} = 'open'; } -sub printMsg { - my $self = shift; - my $msg = shift; - my $msg_len = length($msg); - - Debug( $msg."[".$msg_len."]" ); -} - sub sendCmd { my $self = shift; my $cmd = shift; my $result = undef; - printMsg( $cmd, "Tx" ); + $self->printMsg($cmd, 'Tx'); my $url; - if ( $self->{Monitor}->{ControlAddress} =~ /^http/ ) { + if ( $self->{Monitor}->{ControlAddress} =~ /^http/i ) { $url = $self->{Monitor}->{ControlAddress}.$cmd; } else { $url = 'http://'.$self->{Monitor}->{ControlAddress}.$cmd; - } # en dif - my $req = HTTP::Request->new( GET=>$url ); + } # end if + my $req = HTTP::Request->new(GET=>$url); my $res = $self->{ua}->request($req); if ( $res->is_success ) { $result = !undef; } else { - Error( "Error check failed: '".$res->status_line()."'" ); + Error('Error check failed: \''.$res->status_line().'\''); } - return( $result ); + return $result; } sub reset { my $self = shift; - Debug( "Camera Reset" ); - my $cmd = "/admin/ptctl.cgi?move=reset"; - $self->sendCmd( $cmd ); + Debug('Camera Reset'); + my $cmd = '/admin/ptctl.cgi?move=reset'; + $self->sendCmd($cmd); } sub moveMap { my $self = shift; my $params = shift; - my $xcoord = $self->getParam( $params, 'xcoord' ); - my $ycoord = $self->getParam( $params, 'ycoord' ); + my $xcoord = $self->getParam($params, 'xcoord'); + my $ycoord = $self->getParam($params, 'ycoord'); my $hor = $xcoord * 100 / $self->{Monitor}->{Width}; my $ver = $ycoord * 100 / $self->{Monitor}->{Height}; @@ -125,81 +115,81 @@ sub moveMap { elsif ( $hor > 50 ) { # right $horSteps = (($hor - 50) / 50) * $maxhor; - $horDir = "right"; + $horDir = 'right'; } # Vertical movement if ( $ver < 50 ) { # up $verSteps = ((50 - $ver) / 50) * $maxver; - $verDir = "up"; + $verDir = 'up'; } elsif ( $ver > 50 ) { # down $verSteps = (($ver - 50) / 50) * $maxver; - $verDir = "down"; + $verDir = 'down'; } my $v = int($verSteps); my $h = int($horSteps); - Debug( "Move Map to $xcoord,$ycoord, hor=$h $horDir, ver=$v $verDir"); + Debug("Move Map to $xcoord,$ycoord, hor=$h $horDir, ver=$v $verDir"); my $cmd = "/cgi/admin/ptctrl.cgi?action=movedegree&Cmd=$horDir&Degree=$h"; - $self->sendCmd( $cmd ); + $self->sendCmd($cmd); $cmd = "/cgi/admin/ptctrl.cgi?action=movedegree&Cmd=$verDir&Degree=$v"; - $self->sendCmd( $cmd ); + $self->sendCmd($cmd); } sub moveRelUp { my $self = shift; my $params = shift; - my $step = $self->getParam( $params, 'tiltstep' ); - Debug( "Step Up $step" ); - my $cmd = "/admin/ptctl.cgi?move=up"; - $self->sendCmd( $cmd ); + my $step = $self->getParam($params, 'tiltstep'); + Debug("Step Up $step"); + my $cmd = '/admin/ptctl.cgi?move=up'; + $self->sendCmd($cmd); } sub moveRelDown { my $self = shift; my $params = shift; - my $step = $self->getParam( $params, 'tiltstep' ); - Debug( "Step Down $step" ); - my $cmd = "/admin/ptctl.cgi?move=down"; - $self->sendCmd( $cmd ); + my $step = $self->getParam($params, 'tiltstep'); + Debug("Step Down $step"); + my $cmd = '/admin/ptctl.cgi?move=down'; + $self->sendCmd($cmd); } sub moveRelLeft { my $self = shift; my $params = shift; - my $step = $self->getParam( $params, 'panstep' ); + my $step = $self->getParam($params, 'panstep'); - if ( $self->{Monitor}->{Orientation} eq "hori" ) { - Debug( "Stepping Right because flipped horizontally " ); - $self->sendCmd( "/admin/ptctl.cgi?move=right" ); + if ( $self->{Monitor}->{Orientation} eq 'FLIP_HORI' ) { + Debug('Stepping Right because flipped horizontally'); + $self->sendCmd('/admin/ptctl.cgi?move=right'); } else { - Debug( "Step Left" ); - $self->sendCmd( "/admin/ptctl.cgi?move=left" ); + Debug('Step Left'); + $self->sendCmd('/admin/ptctl.cgi?move=left'); } } sub moveRelRight { my $self = shift; my $params = shift; - my $step = $self->getParam( $params, 'panstep' ); - if ( $self->{Monitor}->{Orientation} eq "hori" ) { - Debug( "Stepping Left because flipped horizontally " ); - $self->sendCmd( "/admin/ptctl.cgi?move=left" ); + my $step = $self->getParam($params, 'panstep'); + if ( $self->{Monitor}->{Orientation} eq 'FLIP_HORI' ) { + Debug('Stepping Left because flipped horizontally'); + $self->sendCmd('/admin/ptctl.cgi?move=left'); } else { - Debug( "Step Right" ); - $self->sendCmd( "/admin/ptctl.cgi?move=right" ); + Debug('Step Right'); + $self->sendCmd('/admin/ptctl.cgi?move=right'); } } sub presetClear { my $self = shift; my $params = shift; - my $preset = $self->getParam( $params, 'preset' ); - Debug( "Clear Preset $preset" ); + my $preset = $self->getParam($params, 'preset'); + Debug("Clear Preset $preset"); #my $cmd = "/axis-cgi/com/ptz.cgi?removeserverpresetno=$preset"; #$self->sendCmd( $cmd ); } @@ -207,26 +197,26 @@ sub presetClear { sub presetSet { my $self = shift; my $params = shift; - my $preset = $self->getParam( $params, 'preset' ); - Debug( "Set Preset $preset" ); - my $cmd = "/admin/ptctl.cgi?position=" . ($preset - 1) . "&positionname=zm$preset"; + my $preset = $self->getParam($params, 'preset'); + Debug("Set Preset $preset"); + my $cmd = '/admin/ptctl.cgi?position=' . ($preset - 1) . "&positionname=zm$preset"; $self->sendCmd( $cmd ); } sub presetGoto { my $self = shift; my $params = shift; - my $preset = $self->getParam( $params, 'preset' ); - Debug( "Goto Preset $preset" ); - my $cmd = "/admin/ptctl.cgi?move=p" . ($preset - 1); - $self->sendCmd( $cmd ); + my $preset = $self->getParam($params, 'preset'); + Debug("Goto Preset $preset"); + my $cmd = '/admin/ptctl.cgi?move=p'.($preset - 1); + $self->sendCmd($cmd); } sub presetHome { my $self = shift; - Debug( "Home Preset" ); - my $cmd = "/admin/ptctl.cgi?move=h"; - $self->sendCmd( $cmd ); + Debug('Home Preset'); + my $cmd = '/admin/ptctl.cgi?move=h'; + $self->sendCmd($cmd); } 1; diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm index 6a46ee5d4..21803d76a 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Filter.pm @@ -1,6 +1,6 @@ # ========================================================================== # -# ZoneMinder Filter Module, $Date$, $Revision$ +# ZoneMinder Filter Module # Copyright (C) 2001-2008 Philip Coombes # # This program is free software; you can redistribute it and/or @@ -162,7 +162,9 @@ sub Sql { my $value = $term->{val}; my @value_list; if ( $term->{attr} ) { - if ( $term->{attr} =~ /^Monitor/ ) { + if ( $term->{attr} eq 'AlarmedZoneId' ) { + $term->{op} = 'EXISTS'; + } elsif ( $term->{attr} =~ /^Monitor/ ) { my ( $temp_attr_name ) = $term->{attr} =~ /^Monitor(.+)$/; $self->{Sql} .= 'M.'.$temp_attr_name; } elsif ( $term->{attr} eq 'ServerId' or $term->{attr} eq 'MonitorServerId' ) { @@ -214,7 +216,10 @@ sub Sql { ( my $stripped_value = $value ) =~ s/^["\']+?(.+)["\']+?$/$1/; foreach my $temp_value ( split( /["'\s]*?,["'\s]*?/, $stripped_value ) ) { - if ( $term->{attr} =~ /^MonitorName/ ) { + + if ( $term->{attr} eq 'AlarmedZoneId' ) { + $value = '(SELECT * FROM Stats WHERE EventId=E.Id AND ZoneId='.$value.')'; + } elsif ( $term->{attr} =~ /^MonitorName/ ) { $value = "'$temp_value'"; } elsif ( $term->{attr} =~ /ServerId/) { Debug("ServerId, temp_value is ($temp_value) ($ZoneMinder::Config::Config{ZM_SERVER_ID})"); @@ -256,6 +261,8 @@ sub Sql { } elsif ( $term->{attr} eq 'Date' or $term->{attr} eq 'StartDate' or $term->{attr} eq 'EndDate' ) { if ( $temp_value eq 'NULL' ) { $value = $temp_value; + } elsif ( $temp_value eq 'CURDATE()' or $temp_value eq 'NOW()' ) { + $value = 'to_days('.$temp_value.')'; } else { $value = DateTimeToSQL($temp_value); if ( !$value ) { @@ -294,6 +301,8 @@ sub Sql { } else { $self->{Sql} .= " IS $value"; } + } elsif ( $term->{op} eq 'EXISTS' ) { + $self->{Sql} .= " EXISTS $value"; } elsif ( $term->{op} eq 'IS NOT' ) { $self->{Sql} .= " IS NOT $value"; } elsif ( $term->{op} eq '=[]' ) { diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm b/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm index dc843449a..5d05ae7c5 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Logger.pm @@ -503,11 +503,14 @@ sub openFile { $LOGFILE->autoflush() if $this->{autoFlush}; my $webUid = (getpwnam($ZoneMinder::Config::Config{ZM_WEB_USER}))[2]; + Error("Can't get uid for $ZoneMinder::Config::Config{ZM_WEB_USER}") if ! defined $webUid; my $webGid = (getgrnam($ZoneMinder::Config::Config{ZM_WEB_GROUP}))[2]; + Error("Can't get gid for $ZoneMinder::Config::Config{ZM_WEB_USER}") if ! defined $webGid; if ( $> == 0 ) { - chown( $webUid, $webGid, $this->{logFile} ) - or Fatal("Can't change permissions on log file $$this{logFile}: $!"); - } + # If we are root, we want to make sure that www-data or whatever owns the file + chown($webUid, $webGid, $this->{logFile} ) or + Error("Can't change permissions on log file $$this{logFile}: $!"); + } # end if are root } else { $this->fileLevel(NOLOG); $this->termLevel(INFO); diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm b/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm index 47ec7c557..963f75638 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm @@ -36,9 +36,172 @@ require ZoneMinder::Server; #our @ISA = qw(Exporter ZoneMinder::Base); use parent qw(ZoneMinder::Object); -use vars qw/ $table $primary_key /; +use vars qw/ $table $primary_key %fields $serial %defaults $debug/; $table = 'Monitors'; -$primary_key = 'Id'; +$serial = $primary_key = 'Id'; +%fields = map { $_ => $_ } qw( + Id + Name + Notes + ServerId + StorageId + Type + Function + Enabled + LinkedMonitors + Triggers + Device + Channel + Format + V4LMultiBuffer + V4LCapturesPerFrame + Protocol + Method + Host + Port + SubPath + Path + Options + User + Pass + Width + Height + Colours + Palette + Orientation + Deinterlacing + DecoderHWAccelName + DecoderHWAccelDevice + SaveJPEGs + VideoWriter + OutputCodec + OutputContainer + EncoderParameters + RecordAudio + RTSPDescribe + Brightness + Contrast + Hue + Colour + EventPrefix + LabelFormat + LabelX + LabelY + LabelSize + ImageBufferCount + WarmupCount + PreEventCount + PostEventCount + StreamReplayBuffer + AlarmFrameCount + SectionLength + MinSectionLength + FrameSkip + MotionFrameSkip + AnalysisFPSLimit + AnalysisUpdateDelay + MaxFPS + AlarmMaxFPS + FPSReportInterval + RefBlendPerc + AlarmRefBlendPerc + Controllable + ControlId + ControlDevice + ControlAddress + AutoStopTimeout + TrackMotion + TrackDelay + ReturnLocation + ReturnDelay + DefaultRate + DefaultScale + SignalCheckPoints + SignalCheckColour + WebColour + Exif + Sequence + ); + +%defaults = ( + ServerId => 0, + StorageId => 0, + Type => 'Ffmpeg', + Function => 'Mocord', + Enabled => 1, + LinkedMonitors => undef, + Device => '', + Channel => 0, + Format => 0, + V4LMultiBuffer => undef, + V4LCapturesPerFrame => 1, + Protocol => undef, + Method => '', + Host => undef, + Port => '', + SubPath => '', + Path => undef, + Options => undef, + User => undef, + Pass => undef, + Width => undef, + Height => undef, + Colours => 4, + Palette => 0, + Orientation => undef, + Deinterlacing => 0, + DecoderHWAccelName => undef, + DecoderHWAccelDevice => undef, + SaveJPEGs => 3, + VideoWriter => 0, + OutputCodec => undef, + OutputContainer => undef, + EncoderParameters => "# Lines beginning with # are a comment \n# For changing quality, use the crf option\n# 1 is best, 51 is worst quality\n#crf=23\n", + RecordAudio=>0, + RTSPDescribe=>0, + Brightness => -1, + Contrast => -1, + Hue => -1, + Colour => -1, + EventPrefix => 'Event-', + LabelFormat => '%N - %d/%m/%y %H:%M:%S', + LabelX => 0, + LabelY => 0, + LabelSize => 1, + ImageBufferCount => 20, + WarmupCount => 0, + PreEventCount => 5, + PostEventCount => 5, + StreamReplayBuffer => 0, + AlarmFrameCount => 1, + SectionLength => 600, + MinSectionLength => 10, + FrameSkip => 0, + MotionFrameSkip => 0, + AnalysisFPSLimit => undef, + AnalysisUpdateDelay => 0, + MaxFPS => undef, + AlarmMaxFPS => undef, + FPSReportInterval => 100, + RefBlendPerc => 6, + AlarmRefBlendPerc => 6, + Controllable => 0, + ControlId => undef, + ControlDevice => undef, + ControlAddress => undef, + AutoStopTimeout => undef, + TrackMotion => 0, + TrackDelay => undef, + ReturnLocation => -1, + ReturnDelay => undef, + DefaultRate => 100, + DefaultScale => 100, + SignalCheckPoints => 0, + SignalCheckColour => '#0000BE', + WebColour => '#ff0000', + Exif => 0, + Sequence => undef, + ); sub Server { return new ZoneMinder::Server( $_[0]{ServerId} ); diff --git a/scripts/ZoneMinder/lib/ZoneMinder/ONVIF.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/ONVIF.pm.in index f6863367d..9b9c6c794 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/ONVIF.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/ONVIF.pm.in @@ -172,7 +172,7 @@ sub interpret_messages { # functions sub discover { - my ( $soap_version ) = @_; + my ( $soap_version, $net_interface ) = @_; my @results; ## collect all responses @@ -190,22 +190,27 @@ sub discover { my $uuid_gen = Data::UUID->new(); if ( ( ! $soap_version ) or ( $soap_version eq '1.1' ) ) { - my %services; + my %services; - if($verbose) { - print "Probing for SOAP 1.1\n" + if ( $verbose ) { + print "Probing for SOAP 1.1\n"; } my $svc_discover = WSDiscovery10::Interfaces::WSDiscovery::WSDiscoveryPort->new({ # no_dispatch => '1', }); $svc_discover->set_soap_version('1.1'); + if ( $net_interface ) { + my $transport = $svc_discover->get_transport(); + print "Setting net interface for $transport to $net_interface\n"; + $transport->set_net_interface($net_interface); + } my $uuid = $uuid_gen->create_str(); my $result = $svc_discover->ProbeOp( - { # WSDiscovery::Types::ProbeType - Types => 'http://www.onvif.org/ver10/network/wsdl:NetworkVideoTransmitter http://www.onvif.org/ver10/device/wsdl:Device', # QNameListType - Scopes => { value => '' }, + { # WSDiscovery::Types::ProbeType + Types => 'http://www.onvif.org/ver10/network/wsdl:NetworkVideoTransmitter http://www.onvif.org/ver10/device/wsdl:Device', # QNameListType + Scopes => { value => '' }, }, WSDiscovery10::Elements::Header->new({ Action => { value => 'http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe' }, @@ -220,14 +225,19 @@ sub discover { } # end if doing soap 1.1 if ( ( ! $soap_version ) or ( $soap_version eq '1.2' ) ) { - my %services; - if($verbose) { - print "Probing for SOAP 1.2\n" + my %services; + if ( $verbose ) { + print "Probing for SOAP 1.2\n"; } my $svc_discover = WSDiscovery10::Interfaces::WSDiscovery::WSDiscoveryPort->new({ # no_dispatch => '1', }); $svc_discover->set_soap_version('1.2'); + if ( $net_interface ) { + my $transport = $svc_discover->get_transport(); + print "Setting net interface for $transport to $net_interface\n"; + $transport->set_net_interface($net_interface); + } # copies of the same Probe message must have the same MessageID. # This is not a copy. So we generate a new uuid. @@ -250,20 +260,20 @@ sub discover { push @results, interpret_messages($svc_discover, \%services, @responses); } # end if doing soap 1.2 return @results; -} +} # end sub discover sub profiles { my ( $client ) = @_; - my $endpoint = $client->get_endpoint('media'); - if ( ! $endpoint ) { - print "No media enpoint for client.\n"; + my $media = $client->get_endpoint('media'); + if ( ! $media ) { + print "No media endpoint for client.\n"; return; } - my $result = $endpoint->GetProfiles( { } ,, ); + my $result = $media->GetProfiles( { } ,, ); if ( ! $result ) { - print "No result from GetProfiles\n"; + print "No result from GetProfiles.\n"; return; } if ( $verbose ) { @@ -272,48 +282,52 @@ sub profiles { my $profiles = $result->get_Profiles(); - foreach my $profile ( @{ $profiles } ) { + foreach my $profile ( @{ $profiles } ) { my $token = $profile->attr()->get_token() ; - my $video_encoder_configuration = $profile->get_VideoEncoderConfiguration(); - if ( ! $video_encoder_configuration ) { - print "Unknown profile $token " . $profile->get_Name()."\n"; + my $Name = $profile->get_Name(); + + my $VideoEncoderConfiguration = $profile->get_VideoEncoderConfiguration(); + if ( ! $VideoEncoderConfiguration ) { + print "Unknown profile $token $Name.\n"; next; } - print $token . ", " . - $profile->get_Name() . ", " . - $profile->get_VideoEncoderConfiguration()->get_Encoding() . ", " . - $profile->get_VideoEncoderConfiguration()->get_Resolution()->get_Width() . ", " . - $profile->get_VideoEncoderConfiguration()->get_Resolution()->get_Height() . ", " . - $profile->get_VideoEncoderConfiguration()->get_RateControl()->get_FrameRateLimit() . - ", "; # Specification gives conflicting values for unicast stream types, try both. # http://www.onvif.org/onvif/ver10/media/wsdl/media.wsdl#op.GetStreamUri - foreach my $streamtype ( 'RTP_unicast', 'RTP-Unicast' ) { - $result = $client->get_endpoint('media')->GetStreamUri( { + foreach my $streamtype ( 'RTP_unicast', 'RTP-Unicast', 'RTP-multicast', 'RTP-Multicast' ) { + my $StreamUri = $media->GetStreamUri( { StreamSetup => { # ONVIF::Media::Types::StreamSetup - Stream => $streamtype, # StreamType - Transport => { # ONVIF::Media::Types::Transport - Protocol => 'RTSP', # TransportProtocol - }, + Stream => $streamtype, # StreamType + Transport => { # ONVIF::Media::Types::Transport + Protocol => 'RTSP', # TransportProtocol + }, }, ProfileToken => $token, # ReferenceToken - } ,, ); - last if $result; - } - die $result if not $result; -# print $result . "\n"; + } ); + next if ! ( $StreamUri and $StreamUri->can('get_MediaUri') ); + my $MediaUri = $StreamUri->get_MediaUri(); + next if ! $MediaUri; + my $Uri = $MediaUri->get_Uri(); + next if ! $Uri; + + print join(', ', $token, + $Name, + $VideoEncoderConfiguration->get_Encoding(), + $VideoEncoderConfiguration->get_Resolution()->get_Width(), + $VideoEncoderConfiguration->get_Resolution()->get_Height(), + $VideoEncoderConfiguration->get_RateControl()->get_FrameRateLimit(), + $Uri, + ) . "\n"; + } # end foreach streamtype - print $result->get_MediaUri()->get_Uri() . - "\n"; } # end foreach profile # # use message parser without schema validation ??? # -} +} # end sub profiles sub move { my ($client, $dir) = @_; @@ -326,13 +340,22 @@ sub move { sub metadata { my ( $client ) = @_; - my $result = $client->get_endpoint('media')->GetMetadataConfigurations( { } ,, ); - die $result if not $result; - print $result . "\n"; + my $media = $client->get_endpoint('media'); + die 'No media endpoint.' if !$media; - $result = $client->get_endpoint('media')->GetVideoAnalyticsConfigurations( { } ,, ); - die $result if not $result; - print $result . "\n"; + my $result = $media->GetMetadataConfigurations( { } ,, ); + if ( ! $result ) { + print "No MetaDataConfigurations\n" if $verbose; + } else { + print $result . "\n"; + } + + $result = $media->GetVideoAnalyticsConfigurations( { } ,, ); + if ( ! $result ) { + print "No VideoAnalyticsConfigurations\n" if $verbose; + } else { + print $result . "\n"; + } # $result = $client->get_endpoint('analytics')->GetServiceCapabilities( { } ,, ); # die $result if not $result; diff --git a/scripts/zmaudit.pl.in b/scripts/zmaudit.pl.in index 69680056e..e57d98add 100644 --- a/scripts/zmaudit.pl.in +++ b/scripts/zmaudit.pl.in @@ -420,15 +420,15 @@ MAIN: while( $loop ) { Debug("Checking for Medium Scheme Events under $$Storage{Path}/$monitor_dir"); { my @event_dirs = glob("$monitor_dir/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/*"); - Debug(qq`glob("$monitor_dir/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/*") returned ` . scalar @event_dirs . ' entries.' ); + Debug('glob("'.$monitor_dir.'/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/*") returned '.(scalar @event_dirs).' entries.'); foreach my $event_dir ( @event_dirs ) { if ( ! -d $event_dir ) { - Debug( "$event_dir is not a dir. Skipping" ); + Debug("$event_dir is not a dir. Skipping"); next; } my ( $date, $event_id ) = $event_dir =~ /^$monitor_dir\/(\d{4}\-\d{2}\-\d{2})\/(\d+)$/; - if ( ! $event_id ) { - Debug("Unable to parse date/event_id from $event_dir"); + if ( !$event_id ) { + Debug('Unable to parse date/event_id from '.$event_dir); next; } my $Event = $fs_events->{$event_id} = new ZoneMinder::Event(); @@ -438,8 +438,9 @@ MAIN: while( $loop ) { $Event->MonitorId( $monitor_dir ); $Event->StorageId( $Storage->Id() ); $Event->Path(); + $Event->age(); Debug("Have event $$Event{Id} at $$Event{Path}"); - $Event->StartTime( POSIX::strftime('%Y-%m-%d %H:%M:%S', gmtime(time_of_youngest_file($Event->Path())) ) ); + $Event->StartTime(POSIX::strftime('%Y-%m-%d %H:%M:%S', gmtime(time_of_youngest_file($Event->Path())))); } # end foreach event } @@ -466,13 +467,13 @@ MAIN: while( $loop ) { } # end foreach event chdir( $Storage->Path() ); } # if USE_DEEP_STORAGE - Debug( 'Got '.int(keys(%$fs_events))." filesystem events for monitor $monitor_dir" ); + Debug('Got '.int(keys(%$fs_events)).' filesystem events for monitor '.$monitor_dir); delete_empty_subdirs($$Storage{Path}.'/'.$monitor_dir); } # end foreach monitor if ( $cleaned ) { - Debug("First stage cleaning done. Restarting."); + Debug('First stage cleaning done. Restarting.'); redo MAIN; } @@ -484,7 +485,7 @@ MAIN: while( $loop ) { next; } 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 ) { @@ -499,8 +500,8 @@ MAIN: while( $loop ) { } my $age = $Event->age(); - if ( $age > $Config{ZM_AUDIT_MIN_AGE} ) { - aud_print( "Filesystem event $fs_event_id at $$Event{Path} does not exist in database and is $age seconds old" ); + if ( $age and ($age > $Config{ZM_AUDIT_MIN_AGE}) ) { + aud_print("Filesystem event $fs_event_id at $$Event{Path} does not exist in database and is $age seconds old"); if ( confirm() ) { $Event->delete_files(); $cleaned = 1; @@ -586,7 +587,7 @@ EVENT: while ( my ( $db_event, $age ) = each( %$db_events ) ) { } else { Debug("$$Event{Id} Not found at $path"); } - } + } # end foreach Storage if ( $Event->Archived() ) { Warning("Event $$Event{Id} is Archived. Taking no further action on it."); next; @@ -638,18 +639,13 @@ EVENT: while ( my ( $db_event, $age ) = each( %$db_events ) ) { 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() ) { + if ( ! $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->StartTime($$fs_events{$db_event}->StartTime()); } $Event->save(); - } + } # end if Event exists in db and not in filesystem } # end if ! in fs_events } # foreach db_event } # end foreach db_monitor @@ -944,6 +940,10 @@ FROM `Frames` WHERE `EventId`=?'; $eventcounts_day_sth->execute() or Error("Can't execute: ".$eventcounts_sth->errstr()); $eventcounts_week_sth->execute() or Error("Can't execute: ".$eventcounts_sth->errstr()); $eventcounts_month_sth->execute() or Error("Can't execute: ".$eventcounts_sth->errstr()); + + my $storage_diskspace_sth = $dbh->prepare_cached('UPDATE Storage SET DiskSpace=(SELECT SUM(DiskSpace) FROM Events WHERE StorageId=Storage.Id)'); + $storage_diskspace_sth->execute() or Error("Can't execute: ".$storage_diskspace_sth->errstr()); + sleep($Config{ZM_AUDIT_CHECK_INTERVAL}) if $continuous; }; diff --git a/scripts/zmcamtool.pl.in b/scripts/zmcamtool.pl.in index 603c979bb..3d68b1408 100644 --- a/scripts/zmcamtool.pl.in +++ b/scripts/zmcamtool.pl.in @@ -351,8 +351,14 @@ sub exportsql { } } - if ($ARGV[0]) { - $command .= qq( --where="Name = '$ARGV[0]'"); + my $name = $ARGV[0]; + if ( $name ) { + if ( $name =~ /^([A-Za-z0-9 ,.&()\/\-]+)$/ ) { # Allow alphanumeric and " ,.&()/-" + $name = $1; + $command .= qq( --where="Name = '$name'"); + } else { + print "Invalid characters in Name\n"; + } } $command .= " zm Controls MonitorPresets"; diff --git a/scripts/zmcontrol.pl.in b/scripts/zmcontrol.pl.in index b68697ebc..eb1718655 100644 --- a/scripts/zmcontrol.pl.in +++ b/scripts/zmcontrol.pl.in @@ -108,6 +108,9 @@ if ( $options{command} ) { Fatal("Unable to load control data for monitor $id"); } my $protocol = $monitor->{Protocol}; + if ( !$protocol ) { + Fatal('No protocol is set in monitor. Please edit the monitor, edit control type, select the control capability and fill in the Protocol field'); + } if ( -x $protocol ) { # Protocol is actually a script! diff --git a/scripts/zmfilter.pl.in b/scripts/zmfilter.pl.in index be9996fe2..18b08cdf1 100644 --- a/scripts/zmfilter.pl.in +++ b/scripts/zmfilter.pl.in @@ -196,7 +196,7 @@ my $last_action = 0; while( !$zm_terminate ) { my $now = time; if ( ($now - $last_action) > $Config{ZM_FILTER_RELOAD_DELAY} ) { - Debug("Reloading filters"); + Debug('Reloading filters'); $last_action = $now; @filters = getFilters({ Name=>$filter_name, Id=>$filter_id }); } @@ -699,8 +699,10 @@ sub substituteTags { $text =~ s/%ESM%/$Event->{MaxScore}/g; if ( $first_alarm_frame ) { - $text =~ s/%EPI1%/$url?view=frame&mid=$Event->{MonitorId}&eid=$Event->{Id}&fid=$first_alarm_frame->{FrameId}/g; - $text =~ s/%EPIM%/$url?view=frame&mid=$Event->{MonitorId}&eid=$Event->{Id}&fid=$max_alarm_frame->{FrameId}/g; + $text =~ s/%EPF1%/$url?view=frame&mid=$Event->{MonitorId}&eid=$Event->{Id}&fid=$first_alarm_frame->{FrameId}/g; + $text =~ s/%EPFM%/$url?view=frame&mid=$Event->{MonitorId}&eid=$Event->{Id}&fid=$max_alarm_frame->{FrameId}/g; + $text =~ s/%EPI1%/$url?view=image&mid=$Event->{MonitorId}&eid=$Event->{Id}&fid=$first_alarm_frame->{FrameId}/g; + $text =~ s/%EPIM%/$url?view=image&mid=$Event->{MonitorId}&eid=$Event->{Id}&fid=$max_alarm_frame->{FrameId}/g; if ( $attachments_ref ) { if ( $text =~ s/%EI1%//g ) { my $path = generateImage($Event, $first_alarm_frame); @@ -748,13 +750,14 @@ sub substituteTags { } } - if ( $text =~ s/%EIMOD%//g ) { - $text =~ s/%EIMOD%/$url?view=frame&mid=$Event->{MonitorId}&eid=$Event->{Id}&fid=objdetect/g; + if ( $text =~ s/%EIMOD%//g or $text =~ s/%EFMOD%//g ) { + $text =~ s/%EFMOD%/$url?view=frame&mid=$Event->{MonitorId}&eid=$Event->{Id}&fid=objdetect/g; + $text =~ s/%EIMOD%/$url?view=image&mid=$Event->{MonitorId}&eid=$Event->{Id}&fid=objdetect/g; my $path = $Event->Path().'/objdetect.jpg'; if ( -e $path ) { push @$attachments_ref, { type=>'image/jpeg', path=>$path }; } else { - Warning('No image for EIMOD at ' . $path); + Warning('No image for MOD at '.$path); } } @@ -796,17 +799,17 @@ sub sendEmail { Error('No from email address defined, not sending email'); return 0; } - if ( ! $Config{ZM_EMAIL_ADDRESS} ) { + if ( ! $$filter{EmailTo} ) { Error('No email address defined, not sending email'); return 0; } Info('Creating notification email'); - my $subject = substituteTags($Config{ZM_EMAIL_SUBJECT}, $filter, $Event); + my $subject = substituteTags($$filter{EmailSubject}, $filter, $Event); return 0 if !$subject; my @attachments; - my $body = substituteTags($Config{ZM_EMAIL_BODY}, $filter, $Event, \@attachments); + my $body = substituteTags($$filter{EmailBody}, $filter, $Event, \@attachments); return 0 if !$body; Info("Sending notification email '$subject'"); @@ -816,7 +819,7 @@ sub sendEmail { ### Create the multipart container my $mail = MIME::Lite->new ( From => $Config{ZM_FROM_EMAIL}, - To => $Config{ZM_EMAIL_ADDRESS}, + To => $$filter{EmailTo}, Subject => $subject, Type => 'multipart/mixed' ); @@ -827,7 +830,7 @@ sub sendEmail { ); ### Add the attachments foreach my $attachment ( @attachments ) { - Info( "Attaching '$attachment->{path}'" ); + Info("Attaching '$attachment->{path}'"); $mail->attach( Path => $attachment->{path}, Type => $attachment->{type}, @@ -849,7 +852,7 @@ sub sendEmail { $mail->send(); } else { ### Send using SSMTP - $mail->send('sendmail', $ssmtp_location, $Config{ZM_EMAIL_ADDRESS}); + $mail->send('sendmail', $ssmtp_location, $$filter{EmailTo}); } } else { MIME::Lite->send('smtp', $Config{ZM_EMAIL_HOST}, Timeout=>60); @@ -858,9 +861,9 @@ sub sendEmail { } else { my $mail = MIME::Entity->build( From => $Config{ZM_FROM_EMAIL}, - To => $Config{ZM_EMAIL_ADDRESS}, + To => $$filter{EmailTo}, Subject => $subject, - Type => (($body=~//)?'text/html':'text/plain'), + Type => (($body=~/ $body ); @@ -962,7 +965,7 @@ sub sendMessage { From => $Config{ZM_FROM_EMAIL}, To => $Config{ZM_MESSAGE_ADDRESS}, Subject => $subject, - Type => (($body=~//)?'text/html':'text/plain'), + Type => (($body=~/ $body ); diff --git a/scripts/zmonvif-probe.pl.in b/scripts/zmonvif-probe.pl.in old mode 100755 new mode 100644 index 6b4c5c1a6..c825f8447 --- a/scripts/zmonvif-probe.pl.in +++ b/scripts/zmonvif-probe.pl.in @@ -41,7 +41,7 @@ my $OPTIONS = 'v'; sub HELP_MESSAGE { my ($fh, $pkg, $ver, $opts) = @_; - print $fh "Usage: " . __FILE__ . " [-v] probe \n"; + print $fh "Usage: " . __FILE__ . " [-v] probe \n"; print $fh " " . __FILE__ . " [-v] \n"; print $fh <header('content-length' => length($msg)); $req->header('connection' => 'Close'); - $req->content($msg); + $req->content(encode('UTF-8',$msg)); my $resp = $ua->request($req); my $resp_msg = $resp->decoded_content; @@ -196,7 +198,7 @@ sub getUUID { $uuid = $Config{ZM_TELEMETRY_UUID} = $sth->fetchrow_array(); $sth->finish(); - $sql = q`UPDATE Config set Value = ? WHERE Name = 'ZM_TELEMETRY_UUID'`; + $sql = q`UPDATE Config SET Value = ? WHERE Name = 'ZM_TELEMETRY_UUID'`; $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); $res = $sth->execute( "$uuid" ) or die( "Can't execute: ".$sth->errstr() ); $sth->finish(); @@ -232,9 +234,9 @@ sub countQuery { my $dbh = shift; my $table = shift; - my $sql = "SELECT count(*) FROM $table"; - 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() ); + my $sql = "SELECT count(*) FROM `$table`"; + 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(); my $count = $sth->fetchrow_array(); $sth->finish(); @@ -245,7 +247,7 @@ sub countQuery { sub getMonitorRef { my $dbh = shift; - my $sql = 'SELECT Id,Name,Type,Function,Width,Height,Colours,MaxFPS,AlarmMaxFPS FROM Monitors'; + my $sql = 'SELECT `Id`,`Name`,`Type`,`Function`,`Width`,`Height`,`Colours`,`MaxFPS`,`AlarmMaxFPS` FROM `Monitors`'; 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() ); my $arrayref = $sth->fetchall_arrayref({}); diff --git a/scripts/zmtrigger.pl.in b/scripts/zmtrigger.pl.in index b1743aeae..c56107276 100644 --- a/scripts/zmtrigger.pl.in +++ b/scripts/zmtrigger.pl.in @@ -329,14 +329,13 @@ sub loadMonitor { } # end sub loadMonitor sub loadMonitors { - Debug('Loading monitors'); $monitor_reload_time = time(); my %new_monitors = (); - my $sql = q`SELECT * FROM Monitors - WHERE find_in_set( Function, 'Modect,Mocord,Nodect' )`. - ( $Config{ZM_SERVER_ID} ? ' AND ServerId=?' : '' ) + my $sql = 'SELECT * FROM `Monitors` + WHERE find_in_set( `Function`, \'Modect,Mocord,Nodect\' )'. + ( $Config{ZM_SERVER_ID} ? ' AND `ServerId`=?' : '' ) ; my $sth = $dbh->prepare_cached( $sql ) or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); diff --git a/scripts/zmx10.pl.in b/scripts/zmx10.pl.in index 89dee5d00..de1f74929 100644 --- a/scripts/zmx10.pl.in +++ b/scripts/zmx10.pl.in @@ -83,72 +83,64 @@ my $unit_code; my $version; GetOptions( - 'command=s' =>\$command, - 'unit-code=i' =>\$unit_code, - 'version' =>\$version + 'command=s' =>\$command, + 'unit-code=i' =>\$unit_code, + 'version' =>\$version ) or pod2usage(-exitstatus => -1); if ( $version ) { - print ZoneMinder::Base::ZM_VERSION; - exit(0); + print ZoneMinder::Base::ZM_VERSION; + exit(0); } -die( 'No command given' ) unless( $command ); -die( 'No unit code given' ) - unless( $unit_code || ($command =~ /(?:start|status|shutdown)/) ); +die 'No command given' unless $command; +die 'No unit code given' +unless( $unit_code || ($command =~ /(?:start|status|shutdown)/) ); -if ( $command eq 'start' ) -{ +if ( $command eq 'start' ) { + X10Server::runServer(); + exit(); +} + +socket(CLIENT, PF_UNIX, SOCK_STREAM, 0) + or Fatal("Can't open socket: $!"); + +my $saddr = sockaddr_un(SOCK_FILE); + +if ( !connect(CLIENT, $saddr) ) { + # The server isn't there + print("Unable to connect, starting server\n"); + close(CLIENT); + + if ( my $cpid = fork() ) { + # Parent process just sleep and fall through + sleep(2); + logReinit(); + socket(CLIENT, PF_UNIX, SOCK_STREAM, 0) + or Fatal("Can't open socket: $!"); + connect(CLIENT, $saddr) + or Fatal("Can't connect: $!"); + } elsif ( defined($cpid) ) { + setpgrp(); + + logReinit(); X10Server::runServer(); - exit(); -} - -socket( CLIENT, PF_UNIX, SOCK_STREAM, 0 ) - or Fatal( "Can't open socket: $!" ); - -my $saddr = sockaddr_un( SOCK_FILE ); - -if ( !connect( CLIENT, $saddr ) ) -{ - # The server isn't there - print( "Unable to connect, starting server\n" ); - close( CLIENT ); - - if ( my $cpid = fork() ) - { - # Parent process just sleep and fall through - sleep( 2 ); - logReinit(); - socket( CLIENT, PF_UNIX, SOCK_STREAM, 0 ) - or Fatal( "Can't open socket: $!" ); - connect( CLIENT, $saddr ) - or Fatal( "Can't connect: $!" ); - } - elsif ( defined($cpid) ) - { - setpgrp(); - - logReinit(); - X10Server::runServer(); - } - else - { - Fatal( "Can't fork: $!" ); - } + } else { + Fatal("Can't fork: $!"); + } } # The server is there, connect to it #print( "Writing commands\n" ); CLIENT->autoflush(); -my $message = "$command"; -$message .= ";$unit_code" if ( $unit_code ); -print( CLIENT $message ); -shutdown( CLIENT, 1 ); -while ( my $line = ) -{ - chomp( $line ); - print( "$line\n" ); +my $message = $command; +$message .= ';'.$unit_code if $unit_code; +print(CLIENT $message); +shutdown(CLIENT, 1); +while ( my $line = ) { + chomp($line); + print("$line\n"); } -close( CLIENT ); +close(CLIENT); #print( "Finished writing, bye\n" ); exit; @@ -178,605 +170,497 @@ our %monitor_hash; our %device_hash; our %pending_tasks; -sub runServer -{ - Info( "X10 server starting\n" ); +sub runServer { + Info('X10 server starting'); - socket( SERVER, PF_UNIX, SOCK_STREAM, 0 ) - or Fatal( "Can't open socket: $!" ); - unlink( main::SOCK_FILE ); - my $saddr = sockaddr_un( main::SOCK_FILE ); - bind( SERVER, $saddr ) or Fatal( "Can't bind: $!" ); - listen( SERVER, SOMAXCONN ) or Fatal( "Can't listen: $!" ); + socket(SERVER, PF_UNIX, SOCK_STREAM, 0) + or Fatal("Can't open socket: $!"); + unlink(main::SOCK_FILE); + my $saddr = sockaddr_un(main::SOCK_FILE); + bind(SERVER, $saddr) or Fatal("Can't bind: $!"); + listen(SERVER, SOMAXCONN) or Fatal("Can't listen: $!"); - $dbh = zmDbConnect(); + $dbh = zmDbConnect(); - $x10 = new X10::ActiveHome( port=>$Config{ZM_X10_DEVICE}, house_code=>$Config{ZM_X10_HOUSE_CODE}, debug=>0 ); + $x10 = new X10::ActiveHome( + port=>$Config{ZM_X10_DEVICE}, + house_code=>$Config{ZM_X10_HOUSE_CODE}, + debug=>0 + ); - loadTasks(); + loadTasks(); - $x10->register_listener( \&x10listen ); + $x10->register_listener(\&x10listen); - my $rin = ''; - vec( $rin, fileno(SERVER),1) = 1; - vec( $rin, $x10->select_fds(),1) = 1; - my $timeout = 0.2; - #print( 'F:'.fileno(SERVER)."\n" ); - my $reload = undef; - my $reload_count = 0; - my $reload_limit = $Config{ZM_X10_DB_RELOAD_INTERVAL} / $timeout; - while( 1 ) - { - my $nfound = select( my $rout = $rin, undef, undef, $timeout ); - #print( "Off select, NF:$nfound, ER:$!\n" ); - #print( vec( $rout, fileno(SERVER),1)."\n" ); - #print( vec( $rout, $x10->select_fds(),1)."\n" ); - if ( $nfound > 0 ) - { - if ( vec( $rout, fileno(SERVER),1) ) - { - my $paddr = accept( CLIENT, SERVER ); - my $message = ; + my $rin = ''; + vec($rin, fileno(SERVER),1) = 1; + vec($rin, $x10->select_fds(),1) = 1; + my $timeout = 0.2; + #print( 'F:'.fileno(SERVER)."\n" ); + my $reload = undef; + my $reload_count = 0; + my $reload_limit = $Config{ZM_X10_DB_RELOAD_INTERVAL} / $timeout; + while( 1 ) { + my $nfound = select(my $rout = $rin, undef, undef, $timeout); + #print( "Off select, NF:$nfound, ER:$!\n" ); + #print( vec( $rout, fileno(SERVER),1)."\n" ); + #print( vec( $rout, $x10->select_fds(),1)."\n" ); + if ( $nfound > 0 ) { + if ( vec($rout, fileno(SERVER),1) ) { + my $paddr = accept(CLIENT, SERVER); + my $message = ; - my ( $command, $unit_code ) = split( /;/, $message ); + my ($command, $unit_code) = split(';', $message); - my $device; - if ( defined($unit_code) ) - { - if ( $unit_code < 1 || $unit_code > 16 ) - { - dPrint( ZoneMinder::Logger::ERROR, "Invalid unit code '$unit_code'\n" ); - next; - } + my $device; + if ( defined($unit_code) ) { + if ( $unit_code < 1 || $unit_code > 16 ) { + dPrint(ZoneMinder::Logger::ERROR, "Invalid unit code '$unit_code'\n"); + next; + } - $device = $device_hash{$unit_code}; - if ( !$device ) - { - $device = $device_hash{$unit_code} = { appliance=>$x10->Appliance( unit_code=>$unit_code ), - status=>'unknown' - }; - } - } + $device = $device_hash{$unit_code}; + if ( !$device ) { + $device = $device_hash{$unit_code} = { + appliance=>$x10->Appliance(unit_code=>$unit_code), + status=>'unknown' + }; + } + } # end if defined($unit_code) - my $result; - if ( $command eq 'on' ) - { - $result = $device->{appliance}->on(); - } - elsif ( $command eq 'off' ) - { - $result = $device->{appliance}->off(); - } - #elsif ( $command eq 'dim' ) - #{ - #$result = $device->{appliance}->dim(); - #} - #elsif ( $command eq 'bright' ) - #{ - #$result = $device->{appliance}->bright(); - #} - elsif ( $command eq 'status' ) - { - if ( $device ) - { - dPrint( ZoneMinder::Logger::DEBUG, $unit_code.' '.$device->{status}."\n" ); - } - else - { - foreach my $unit_code ( sort( keys(%device_hash) ) ) - { - my $device = $device_hash{$unit_code}; - dPrint( ZoneMinder::Logger::DEBUG, $unit_code.' '.$device->{status}."\n" ); - } - } - } - elsif ( $command eq 'shutdown' ) - { - last; - } - else - { - dPrint( ZoneMinder::Logger::ERROR, "Invalid command '$command'\n" ); - } - if ( defined($result) ) - { - if ( 1 || $result ) - { - $device->{status} = uc($command); - dPrint( ZoneMinder::Logger::DEBUG, $device->{appliance}->address()." $command, ok\n" ); - #x10listen( new X10::Event( sprintf("%s %s", $device->{appliance}->address, uc($command) ) ) ); - } - else - { - dPrint( ZoneMinder::Logger::ERROR, $device->{appliance}->address()." $command, failed\n" ); - } - } - close( CLIENT ); - } - elsif ( vec( $rout, $x10->select_fds(),1) ) - { - $x10->handle_input(); - } - else - { - Fatal( 'Bogus descriptor' ); - } + my $result; + if ( $command eq 'on' ) { + $result = $device->{appliance}->on(); + } elsif ( $command eq 'off' ) { + $result = $device->{appliance}->off(); } - elsif ( $nfound < 0 ) - { - if ( $! != EINTR ) - { - Fatal( "Can't select: $!" ); + #elsif ( $command eq 'dim' ) + #{ + #$result = $device->{appliance}->dim(); + #} + #elsif ( $command eq 'bright' ) + #{ + #$result = $device->{appliance}->bright(); + #} + elsif ( $command eq 'status' ) { + if ( $device ) { + dPrint(ZoneMinder::Logger::DEBUG, $unit_code.' '.$device->{status}."\n"); + } else { + foreach my $unit_code ( sort( keys(%device_hash) ) ) { + my $device = $device_hash{$unit_code}; + dPrint(ZoneMinder::Logger::DEBUG, $unit_code.' '.$device->{status}."\n"); } + } + } elsif ( $command eq 'shutdown' ) { + last; + } else { + dPrint(ZoneMinder::Logger::ERROR, "Invalid command '$command'\n"); } - else - { - #print( "Select timed out\n" ); - # Check for state changes - foreach my $monitor_id ( sort(keys(%monitor_hash) ) ) - { - my $monitor = $monitor_hash{$monitor_id}; - my $state = zmGetMonitorState( $monitor ); - if ( !defined($state) ) - { - $reload = !undef; - next; - } - if ( defined( $monitor->{LastState} ) ) - { - my $task_list; - if ( ($state == STATE_ALARM || $state == STATE_ALERT) - && ($monitor->{LastState} == STATE_IDLE || $monitor->{LastState} == STATE_TAPE) - ) # Gone into alarm state - { - Debug( "Applying ON_list for $monitor_id\n" ); - $task_list = $monitor->{'ON_list'}; - } - elsif ( ($state == STATE_IDLE && $monitor->{LastState} != STATE_IDLE) - || ($state == STATE_TAPE && $monitor->{LastState} != STATE_TAPE) - ) # Come out of alarm state - { - Debug( "Applying OFF_list for $monitor_id\n" ); - $task_list = $monitor->{'OFF_list'}; - } - if ( $task_list ) - { - foreach my $task ( @$task_list ) - { - processTask( $task ); - } - } - } - $monitor->{LastState} = $state; - } - - # Check for pending tasks - my $now = time(); - foreach my $activation_time ( sort(keys(%pending_tasks) ) ) - { - last if ( $activation_time > $now ); - my $pending_list = $pending_tasks{$activation_time}; - foreach my $task ( @$pending_list ) - { - processTask( $task ); - } - delete( $pending_tasks{$activation_time} ); - } - if ( $reload || ++$reload_count >= $reload_limit ) - { - loadTasks(); - $reload = undef; - $reload_count = 0; - } - } - } - Info( "X10 server exiting\n" ); - close( SERVER ); - exit(); -} - -sub addToDeviceList -{ - my $unit_code = shift; - my $event = shift; - my $monitor = shift; - my $function = shift; - my $limit = shift; - - Debug( "Adding to device list, uc:$unit_code, ev:$event, mo:" - .$monitor->{Id}.", fu:$function, li:$limit\n" - ); - my $device = $device_hash{$unit_code}; - if ( !$device ) - { - $device = $device_hash{$unit_code} = { appliance=>$x10->Appliance( unit_code=>$unit_code ), - status=>'unknown' - }; - } - - my $task = { type=>'device', - monitor=>$monitor, - address=>$device->{appliance}->address(), - function=>$function - }; - if ( $limit ) - { - $task->{limit} = $limit - } - - my $task_list = $device->{$event.'_list'}; - if ( !$task_list ) - { - $task_list = $device->{$event.'_list'} = []; - } - push( @$task_list, $task ); -} - -sub addToMonitorList -{ - my $monitor = shift; - my $event = shift; - my $unit_code = shift; - my $function = shift; - my $limit = shift; - - Debug( "Adding to monitor list, uc:$unit_code, ev:$event, mo:".$monitor->{Id} - .", fu:$function, li:$limit\n" - ); - my $device = $device_hash{$unit_code}; - if ( !$device ) - { - $device = $device_hash{$unit_code} = { appliance=>$x10->Appliance( unit_code=>$unit_code ), - status=>'unknown' - }; - } - - my $task = { type=>'monitor', - device=>$device, - id=>$monitor->{Id}, - function=>$function - }; - if ( $limit ) - { - $task->{limit} = $limit; - } - - my $task_list = $monitor->{$event.'_list'}; - if ( !$task_list ) - { - $task_list = $monitor->{$event.'_list'} = []; - } - push( @$task_list, $task ); -} - -sub loadTasks -{ - %monitor_hash = (); - - Debug( "Loading tasks\n" ); - # Clear out all old device task lists - foreach my $unit_code ( sort( keys(%device_hash) ) ) - { - my $device = $device_hash{$unit_code}; - $device->{ON_list} = []; - $device->{OFF_list} = []; - } - - my $sql = "SELECT M.*,T.* from Monitors as M - INNER JOIN TriggersX10 as T on (M.Id = T.MonitorId) - WHERE find_in_set( M.Function, 'Modect,Record,Mocord,Nodect' ) - AND M.Enabled = 1 - AND find_IN_set( 'X10', M.Triggers )" - ; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute() - or Fatal( "Can't execute: ".$sth->errstr() ); - while( my $monitor = $sth->fetchrow_hashref() ) - { -# Check shared memory ok - if ( !zmMemVerify( $monitor ) ) { - zmMemInvalidate( $monitor ); - next ; + if ( defined($result) ) { + # FIXME + if ( 1 || $result ) { + $device->{status} = uc($command); + dPrint(ZoneMinder::Logger::DEBUG, $device->{appliance}->address()." $command, ok\n"); + #x10listen( new X10::Event( sprintf("%s %s", $device->{appliance}->address, uc($command) ) ) ); + } else { + dPrint(ZoneMinder::Logger::ERROR, $device->{appliance}->address()." $command, failed\n"); + } + } # end if defined result + close(CLIENT); + } elsif ( vec($rout, $x10->select_fds(),1) ) { + $x10->handle_input(); + } else { + Fatal('Bogus descriptor'); } - - $monitor_hash{$monitor->{Id}} = $monitor; - - if ( $monitor->{Activation} ) - { - Debug( "$monitor->{Name} has active string '$monitor->{Activation}'\n" ); - foreach my $code_string ( split( /,/, $monitor->{Activation} ) ) - { - #Debug( "Code string: $code_string\n" ); - my ( $invert, $unit_code, $modifier, $limit ) - = ( $code_string =~ /^([!~])?(\d+)(?:([+-])(\d+)?)?$/ ); - $limit = 0 if ( !$limit ); - if ( $unit_code ) - { - if ( !$modifier || $modifier eq '+' ) - { - addToDeviceList( $unit_code, - 'ON', - $monitor, - !$invert ? 'start_active' - : 'stop_active', - $limit - ); - } - if ( !$modifier || $modifier eq '-' ) - { - addToDeviceList( $unit_code, - 'OFF', - $monitor, - !$invert ? 'stop_active' - : 'start_active', - $limit - ); - } - } - } + } elsif ( $nfound < 0 ) { + if ( $! != EINTR ) { + Fatal("Can't select: $!"); + } + } else { + #print( "Select timed out\n" ); + # Check for state changes + foreach my $monitor_id ( sort(keys(%monitor_hash) ) ) { + my $monitor = $monitor_hash{$monitor_id}; + my $state = zmGetMonitorState($monitor); + if ( !defined($state) ) { + $reload = !undef; + next; } - if ( $monitor->{AlarmInput} ) - { - Debug( "$monitor->{Name} has alarm input string '$monitor->{AlarmInput}'\n" ); - foreach my $code_string ( split( /,/, $monitor->{AlarmInput} ) ) - { - #Debug( "Code string: $code_string\n" ); - my ( $invert, $unit_code, $modifier, $limit ) - = ( $code_string =~ /^([!~])?(\d+)(?:([+-])(\d+)?)?$/ ); - $limit = 0 if ( !$limit ); - if ( $unit_code ) - { - if ( !$modifier || $modifier eq '+' ) - { - addToDeviceList( $unit_code, - 'ON', - $monitor, - !$invert ? 'start_alarm' - : 'stop_alarm', - $limit - ); - } - if ( !$modifier || $modifier eq '-' ) - { - addToDeviceList( $unit_code, - 'OFF', - $monitor, - !$invert ? 'stop_alarm' - : 'start_alarm', - $limit - ); - } - } + if ( defined( $monitor->{LastState} ) ) { + my $task_list; + if ( ($state == STATE_ALARM || $state == STATE_ALERT) + && ($monitor->{LastState} == STATE_IDLE || $monitor->{LastState} == STATE_TAPE) + ) # Gone into alarm state + { + Debug("Applying ON_list for $monitor_id"); + $task_list = $monitor->{ON_list}; + } elsif ( ($state == STATE_IDLE && $monitor->{LastState} != STATE_IDLE) + || ($state == STATE_TAPE && $monitor->{LastState} != STATE_TAPE) + ) # Come out of alarm state + { + Debug("Applying OFF_list for $monitor_id"); + $task_list = $monitor->{OFF_list}; + } + if ( $task_list ) { + foreach my $task ( @$task_list ) { + processTask($task); } - } - if ( $monitor->{AlarmOutput} ) - { - Debug( "$monitor->{Name} has alarm output string '$monitor->{AlarmOutput}'\n" ); - foreach my $code_string ( split( /,/, $monitor->{AlarmOutput} ) ) - { - #Debug( "Code string: $code_string\n" ); - my ( $invert, $unit_code, $modifier, $limit ) - = ( $code_string =~ /^([!~])?(\d+)(?:([+-])(\d+)?)?$/ ); - $limit = 0 if ( !$limit ); - if ( $unit_code ) - { - if ( !$modifier || $modifier eq '+' ) - { - addToMonitorList( $monitor, - 'ON', - $unit_code, - !$invert ? 'on' - : 'off', - $limit - ); - } - if ( !$modifier || $modifier eq '-' ) - { - addToMonitorList( $monitor, - 'OFF', - $unit_code, - !$invert ? 'off' - : 'on', - $limit - ); - } - } - } - } - zmMemInvalidate( $monitor ); - } -} + } + } # end if defined laststate + $monitor->{LastState} = $state; + } # end foreach monitor -sub addPendingTask -{ - my $task = shift; - - # Check whether we are just extending a previous pending task - # and remove it if it's there - foreach my $activation_time ( sort(keys(%pending_tasks) ) ) - { + # Check for pending tasks + my $now = time(); + foreach my $activation_time ( sort(keys(%pending_tasks) ) ) { + last if ( $activation_time > $now ); my $pending_list = $pending_tasks{$activation_time}; - my $new_pending_list = []; - foreach my $pending_task ( @$pending_list ) - { - if ( $task->{type} ne $pending_task->{type} ) - { - push( @$new_pending_list, $pending_task ) - } - elsif ( $task->{type} eq 'device' ) - { - if (( $task->{monitor}->{Id} != $pending_task->{monitor}->{Id} ) - || ( $task->{function} ne $pending_task->{function} )) - { - push( @$new_pending_list, $pending_task ) - } - } - elsif ( $task->{type} eq 'monitor' ) - { - if (( $task->{device}->{appliance}->unit_code() - != $pending_task->{device}->{appliance}->unit_code() - ) - || ( $task->{function} ne $pending_task->{function} ) - ) - { - push( @$new_pending_list, $pending_task ) - } - } - } - if ( @$new_pending_list ) - { - $pending_tasks{$activation_time} = $new_pending_list; - } - else - { - delete( $pending_tasks{$activation_time} ); + foreach my $task ( @$pending_list ) { + processTask($task); } + delete $pending_tasks{$activation_time}; + } + if ( $reload or (++$reload_count >= $reload_limit) ) { + loadTasks(); + $reload = undef; + $reload_count = 0; + } + } + } + Info("X10 server exiting"); + close(SERVER); + exit(); +} + +sub addToDeviceList { + my $unit_code = shift; + my $event = shift; + my $monitor = shift; + my $function = shift; + my $limit = shift; + + Debug("Adding to device list, uc:$unit_code, ev:$event, mo:" + .$monitor->{Id}.", fu:$function, li:$limit" + ); + my $device = $device_hash{$unit_code}; + if ( !$device ) { + $device = $device_hash{$unit_code} = { + appliance=>$x10->Appliance(unit_code=>$unit_code), + status=>'unknown' + }; + } + + my $task = { + type=>'device', + monitor=>$monitor, + address=>$device->{appliance}->address(), + function=>$function + }; + + if ( $limit ) { + $task->{limit} = $limit + } + + my $task_list = $device->{$event.'_list'}; + if ( !$task_list ) { + $task_list = $device->{$event.'_list'} = []; + } + push @$task_list, $task; +} # end sub addToDeviceList + +sub addToMonitorList { + my $monitor = shift; + my $event = shift; + my $unit_code = shift; + my $function = shift; + my $limit = shift; + + Debug("Adding to monitor list, uc:$unit_code, ev:$event, mo:".$monitor->{Id} + .", fu:$function, li:$limit" + ); + my $device = $device_hash{$unit_code}; + if ( !$device ) { + $device = $device_hash{$unit_code} = { + appliance=>$x10->Appliance(unit_code=>$unit_code), + status=>'unknown' + }; + } + + my $task = { + type=>'monitor', + device=>$device, + id=>$monitor->{Id}, + function=>$function + }; + if ( $limit ) { + $task->{limit} = $limit; + } + + my $task_list = $monitor->{$event.'_list'}; + if ( !$task_list ) { + $task_list = $monitor->{$event.'_list'} = []; + } + push @$task_list, $task; +} # end sub addToMonitorList + +sub loadTasks { + %monitor_hash = (); + + Debug('Loading tasks'); + # Clear out all old device task lists + foreach my $unit_code ( sort keys(%device_hash) ) { + my $device = $device_hash{$unit_code}; + $device->{ON_list} = []; + $device->{OFF_list} = []; + } + + my $sql = 'SELECT M.*,T.* FROM Monitors as M + INNER JOIN TriggersX10 as T on (M.Id = T.MonitorId) + WHERE find_in_set(M.`Function`, \'Modect,Record,Mocord,Nodect\') + AND M.`Enabled` = 1 + AND find_IN_set(\'X10\', M.Triggers)'; + my $sth = $dbh->prepare_cached( $sql ) + or Fatal("Can't prepare '$sql': ".$dbh->errstr()); + my $res = $sth->execute() + or Fatal("Can't execute: ".$sth->errstr()); + while( my $monitor = $sth->fetchrow_hashref() ) { + # Check shared memory ok + if ( !zmMemVerify($monitor) ) { + zmMemInvalidate($monitor); + next; } - my $end_time = time() + $task->{limit}; - my $pending_list = $pending_tasks{$end_time}; - if ( !$pending_list ) - { - $pending_list = $pending_tasks{$end_time} = []; + $monitor_hash{$monitor->{Id}} = $monitor; + + if ( $monitor->{Activation} ) { + Debug("$monitor->{Name} has active string '$monitor->{Activation}'"); + foreach my $code_string ( split(',', $monitor->{Activation}) ) { + #Debug( "Code string: $code_string\n" ); + my ( $invert, $unit_code, $modifier, $limit ) + = ( $code_string =~ /^([!~])?(\d+)(?:([+-])(\d+)?)?$/ ); + $limit = 0 if !$limit; + if ( $unit_code ) { + if ( !$modifier || $modifier eq '+' ) { + addToDeviceList( $unit_code, + 'ON', + $monitor, + (!$invert ? 'start_active' : 'stop_active'), + $limit + ); + } + if ( !$modifier || $modifier eq '-' ) { + addToDeviceList( $unit_code, + 'OFF', + $monitor, + (!$invert ? 'stop_active' : 'start_active'), + $limit + ); + } + } # end if unit_code + } # end foreach code_string } - my $pending_task; - if ( $task->{type} eq 'device' ) - { - $pending_task = { type=>$task->{type}, - monitor=>$task->{monitor}, - function=>$task->{function} + if ( $monitor->{AlarmInput} ) { + Debug("$monitor->{Name} has alarm input string '$monitor->{AlarmInput}'"); + foreach my $code_string ( split(',', $monitor->{AlarmInput}) ) { + #Debug( "Code string: $code_string\n" ); + my ( $invert, $unit_code, $modifier, $limit ) + = ( $code_string =~ /^([!~])?(\d+)(?:([+-])(\d+)?)?$/ ); + $limit = 0 if !$limit; + if ( $unit_code ) { + if ( !$modifier || $modifier eq '+' ) { + addToDeviceList( $unit_code, + 'ON', + $monitor, + (!$invert ? 'start_alarm' : 'stop_alarm'), + $limit + ); + } + if ( !$modifier || $modifier eq '-' ) { + addToDeviceList( $unit_code, + 'OFF', + $monitor, + (!$invert ? 'stop_alarm' : 'start_alarm'), + $limit + ); + } + } # end if unit_code + } # end foreach code_string + } # end if AlarmInput + if ( $monitor->{AlarmOutput} ) { + Debug("$monitor->{Name} has alarm output string '$monitor->{AlarmOutput}'"); + foreach my $code_string ( split( ',', $monitor->{AlarmOutput} ) ) { + #Debug( "Code string: $code_string\n" ); + my ( $invert, $unit_code, $modifier, $limit ) + = ( $code_string =~ /^([!~])?(\d+)(?:([+-])(\d+)?)?$/ ); + $limit = 0 if !$limit; + if ( $unit_code ) { + if ( !$modifier || $modifier eq '+' ) { + addToMonitorList( $monitor, + 'ON', + $unit_code, + (!$invert ? 'on' : 'off'), + $limit + ); + } + if ( !$modifier || $modifier eq '-' ) { + addToMonitorList( $monitor, + 'OFF', + $unit_code, + (!$invert ? 'off' : 'on'), + $limit + ); + } + } # end if unit_code + } # end foreach code_string + } # end if AlarmOutput + zmMemInvalidate($monitor); + } +} # end sub loadTasks + +sub addPendingTask { + my $task = shift; + + # Check whether we are just extending a previous pending task + # and remove it if it's there + foreach my $activation_time ( sort keys(%pending_tasks) ) { + my $pending_list = $pending_tasks{$activation_time}; + my $new_pending_list = []; + foreach my $pending_task ( @$pending_list ) { + if ( $task->{type} ne $pending_task->{type} ) { + push( @$new_pending_list, $pending_task ) + } elsif ( $task->{type} eq 'device' ) { + if (( $task->{monitor}->{Id} != $pending_task->{monitor}->{Id} ) + || ( $task->{function} ne $pending_task->{function} )) + { + push @$new_pending_list, $pending_task; + } + } elsif ( $task->{type} eq 'monitor' ) { + if (( $task->{device}->{appliance}->unit_code() + != $pending_task->{device}->{appliance}->unit_code() + ) + || ( $task->{function} ne $pending_task->{function} ) + ) { + push @$new_pending_list, $pending_task; + } + } # end switch task->type + } # end foreach pending_task + + if ( @$new_pending_list ) { + $pending_tasks{$activation_time} = $new_pending_list; + } else { + delete $pending_tasks{$activation_time}; + } + } # end foreach activation_time + + my $end_time = time() + $task->{limit}; + my $pending_list = $pending_tasks{$end_time}; + if ( !$pending_list ) { + $pending_list = $pending_tasks{$end_time} = []; + } + my $pending_task; + if ( $task->{type} eq 'device' ) { + $pending_task = { + type=>$task->{type}, + monitor=>$task->{monitor}, + function=>$task->{function} + }; + $pending_task->{function} =~ s/start/stop/; + } elsif ( $task->{type} eq 'monitor' ) { + $pending_task = { + type=>$task->{type}, + device=>$task->{device}, + function=>$task->{function} + }; + $pending_task->{function} =~ s/on/off/; + } + push @$pending_list, $pending_task; +} # end sub addPendingTask + +sub processTask { + my $task = shift; + + if ( $task->{type} eq 'device' ) { + my ( $instruction, $class ) = ( $task->{function} =~ /^(.+)_(.+)$/ ); + + if ( $class eq 'active' ) { + if ( $instruction eq 'start' ) { + zmMonitorEnable($task->{monitor}); + if ( $task->{limit} ) { + addPendingTask($task); + } + } elsif( $instruction eq 'stop' ) { + zmMonitorDisable($task->{monitor}); + } + } elsif( $class eq 'alarm' ) { + if ( $instruction eq 'start' ) { + zmTriggerEventOn( + $task->{monitor}, + 0, + main::CAUSE_STRING, + $task->{address} + ); + if ( $task->{limit} ) { + addPendingTask($task); + } + } elsif( $instruction eq 'stop' ) { + zmTriggerEventCancel($task->{monitor}); + } + } # end switch class + } elsif( $task->{type} eq 'monitor' ) { + if ( $task->{function} eq 'on' ) { + $task->{device}->{appliance}->on(); + if ( $task->{limit} ) { + addPendingTask($task); + } + } elsif ( $task->{function} eq 'off' ) { + $task->{device}->{appliance}->off(); + } + } +} + +sub dPrint { + my $dbg_level = shift; + if ( fileno(CLIENT) ) { + print CLIENT @_ + } + if ( $dbg_level == ZoneMinder::Logger::DEBUG ) { + Debug(@_); + } elsif ( $dbg_level == ZoneMinder::Logger::INFO ) { + Info(@_); + } elsif ( $dbg_level == ZoneMinder::Logger::WARNING ) { + Warning(@_); + } + elsif ( $dbg_level == ZoneMinder::Logger::ERROR ) { + Error( @_ ); + } elsif ( $dbg_level == ZoneMinder::Logger::FATAL ) { + Fatal( @_ ); + } +} + +sub x10listen { + foreach my $event ( @_ ) { + #print( Data::Dumper( $_ )."\n" ); + if ( $event->house_code() eq $Config{ZM_X10_HOUSE_CODE} ) { + my $unit_code = $event->unit_code(); + my $device = $device_hash{$unit_code}; + if ( !$device ) { + $device = $device_hash{$unit_code} = { + appliance=>$x10->Appliance(unit_code=>$unit_code), + status=>'unknown' }; - $pending_task->{function} =~ s/start/stop/; - } - elsif ( $task->{type} eq 'monitor' ) - { - $pending_task = { type=>$task->{type}, - device=>$task->{device}, - function=>$task->{function} - }; - $pending_task->{function} =~ s/on/off/; - } - push( @$pending_list, $pending_task ); -} - -sub processTask -{ - my $task = shift; - - if ( $task->{type} eq 'device' ) - { - my ( $instruction, $class ) = ( $task->{function} =~ /^(.+)_(.+)$/ ); - - if ( $class eq 'active' ) - { - if ( $instruction eq 'start' ) - { - zmMonitorEnable( $task->{monitor} ); - if ( $task->{limit} ) - { - addPendingTask( $task ); - } - } - elsif( $instruction eq 'stop' ) - { - zmMonitorDisable( $task->{monitor} ); - } + } + next if ( $event->func() !~ /(?:ON|OFF)/ ); + $device->{status} = $event->func(); + my $task_list = $device->{$event->func().'_list'}; + if ( $task_list ) { + foreach my $task ( @$task_list ) { + processTask($task); } - elsif( $class eq 'alarm' ) - { - if ( $instruction eq 'start' ) - { - zmTriggerEventOn( $task->{monitor}, - 0, - main::CAUSE_STRING, - $task->{address} - ); - if ( $task->{limit} ) - { - addPendingTask( $task ); - } - } - elsif( $instruction eq 'stop' ) - { - zmTriggerEventCancel( $task->{monitor} ); - } - } - } - elsif( $task->{type} eq 'monitor' ) - { - if ( $task->{function} eq 'on' ) - { - $task->{device}->{appliance}->on(); - if ( $task->{limit} ) - { - addPendingTask( $task ); - } - } - elsif ( $task->{function} eq 'off' ) - { - $task->{device}->{appliance}->off(); - } - } -} - -sub dPrint -{ - my $dbg_level = shift; - if ( fileno(CLIENT) ) - { - print CLIENT @_ - } - if ( $dbg_level == ZoneMinder::Logger::DEBUG ) - { - Debug( @_ ); - } - elsif ( $dbg_level == ZoneMinder::Logger::INFO ) - { - Info( @_ ); - } - elsif ( $dbg_level == ZoneMinder::Logger::WARNING ) - { - Warning( @_ ); - } - elsif ( $dbg_level == ZoneMinder::Logger::ERROR ) - { - Error( @_ ); - } - elsif ( $dbg_level == ZoneMinder::Logger::FATAL ) - { - Fatal( @_ ); - } -} - -sub x10listen -{ - foreach my $event ( @_ ) - { - #print( Data::Dumper( $_ )."\n" ); - if ( $event->house_code() eq $Config{ZM_X10_HOUSE_CODE} ) - { - my $unit_code = $event->unit_code(); - my $device = $device_hash{$unit_code}; - if ( !$device ) - { - $device = $device_hash{$unit_code} = { appliance=>$x10->Appliance( unit_code=>$unit_code ), - status=>'unknown' - }; - } - next if ( $event->func() !~ /(?:ON|OFF)/ ); - $device->{status} = $event->func(); - my $task_list = $device->{$event->func().'_list'}; - if ( $task_list ) - { - foreach my $task ( @$task_list ) - { - processTask( $task ); - } - } - } - Info( "Got event - ".$event->as_string()."\n" ); - } -} + } + } # end if correct house code + Info('Got event - '.$event->as_string()); + } +} # end sub x10listen 1; +__END__ diff --git a/src/zm_box.h b/src/zm_box.h index fa377e593..efc12cb18 100644 --- a/src/zm_box.h +++ b/src/zm_box.h @@ -33,37 +33,33 @@ // Class used for storing a box, which is defined as a region // defined by two coordinates // -class Box -{ +class Box { private: Coord lo, hi; Coord size; public: - inline Box() - { - } + inline Box() { } explicit inline Box( int p_size ) : lo( 0, 0 ), hi ( p_size-1, p_size-1 ), size( Coord::Range( hi, lo ) ) { } inline Box( int p_x_size, int p_y_size ) : lo( 0, 0 ), hi ( p_x_size-1, p_y_size-1 ), size( Coord::Range( hi, lo ) ) { } inline Box( int lo_x, int lo_y, int hi_x, int hi_y ) : lo( lo_x, lo_y ), hi( hi_x, hi_y ), size( Coord::Range( hi, lo ) ) { } inline Box( const Coord &p_lo, const Coord &p_hi ) : lo( p_lo ), hi( p_hi ), size( Coord::Range( hi, lo ) ) { } - inline const Coord &Lo() const { return( lo ); } - inline int LoX() const { return( lo.X() ); } - inline int LoY() const { return( lo.Y() ); } - inline const Coord &Hi() const { return( hi ); } - inline int HiX() const { return( hi.X() ); } - inline int HiY() const { return( hi.Y() ); } - inline const Coord &Size() const { return( size ); } - inline int Width() const { return( size.X() ); } - inline int Height() const { return( size.Y() ); } - inline int Area() const { return( size.X()*size.Y() ); } + inline const Coord &Lo() const { return lo; } + inline int LoX() const { return lo.X(); } + inline int LoY() const { return lo.Y(); } + inline const Coord &Hi() const { return hi; } + inline int HiX() const { return hi.X(); } + inline int HiY() const { return hi.Y(); } + inline const Coord &Size() const { return size; } + inline int Width() const { return size.X(); } + inline int Height() const { return size.Y(); } + inline int Area() const { return size.X()*size.Y(); } - inline const Coord Centre() const - { + inline const Coord Centre() const { int mid_x = int(round(lo.X()+(size.X()/2.0))); int mid_y = int(round(lo.Y()+(size.Y()/2.0))); - return( Coord( mid_x, mid_y ) ); + return Coord( mid_x, mid_y ); } inline bool Inside( const Coord &coord ) const { diff --git a/src/zm_config.cpp b/src/zm_config.cpp index f576e0ed8..2cbc920ae 100644 --- a/src/zm_config.cpp +++ b/src/zm_config.cpp @@ -64,7 +64,9 @@ void zmLoadConfig() { closedir(configSubFolder); } - zmDbConnect(); + if ( !zmDbConnect() ) { + Fatal("Can't connect to db. Can't continue."); + } config.Load(); config.Assign(); diff --git a/src/zm_coord.h b/src/zm_coord.h index 9bf31144c..c6234fb1c 100644 --- a/src/zm_coord.h +++ b/src/zm_coord.h @@ -25,8 +25,7 @@ // // Class used for storing an x,y pair, i.e. a coordinate // -class Coord -{ +class Coord { private: int x, y; @@ -44,8 +43,7 @@ public: inline int &Y() { return( y ); } inline const int &Y() const { return( y ); } - inline static Coord Range( const Coord &coord1, const Coord &coord2 ) - { + inline static Coord Range( const Coord &coord1, const Coord &coord2 ) { Coord result( (coord1.x-coord2.x)+1, (coord1.y-coord2.y)+1 ); return( result ); } diff --git a/src/zm_event.cpp b/src/zm_event.cpp index e5b806148..479ded301 100644 --- a/src/zm_event.cpp +++ b/src/zm_event.cpp @@ -259,6 +259,20 @@ Event::~Event() { if ( frame_data.size() ) WriteDbFrames(); + // update frame deltas to refer to video start time which may be a few frames before event start + struct timeval video_offset = {0}; + struct timeval video_start_time = monitor->GetVideoWriterStartTime(); + if (video_start_time.tv_sec > 0) { + timersub(&video_start_time, &start_time, &video_offset); + Debug(1, "Updating frames delta by %d sec %d usec", + video_offset.tv_sec, video_offset.tv_usec); + UpdateFramesDelta(video_offset.tv_sec + video_offset.tv_usec*1e-6); + } + else { + Debug(3, "Video start_time %d sec %d usec not valid -- frame deltas not updated", + video_start_time.tv_sec, video_start_time.tv_usec); + } + // Should not be static because we might be multi-threaded char sql[ZM_SQL_LGE_BUFSIZ]; snprintf(sql, sizeof(sql), @@ -472,7 +486,7 @@ void Event::AddFramesInternal(int n_frames, int start_frame, Image **images, str // neccessarily be of the motion. But some events are less than 10 frames, // so I am changing this to 1, but we should overwrite it later with a better snapshot. if ( frames == 1 ) { - WriteFrameImage(images[i], *(timestamps[i]), snapshot_file); + WriteFrameImage(images[i], *(timestamps[i]), snapshot_file.c_str()); } struct DeltaTimeval delta_time; @@ -553,6 +567,27 @@ void Event::WriteDbFrames() { db_mutex.unlock(); } // end void Event::WriteDbFrames() +// Subtract an offset time from frames deltas to match with video start time +void Event::UpdateFramesDelta(double offset) { + char sql[ZM_SQL_MED_BUFSIZ]; + + if (offset == 0.0) return; + // the table is set to auto update timestamp so we force it to keep current value + snprintf(sql, sizeof(sql), + "UPDATE Frames SET timestamp = timestamp, Delta = Delta - (%.4f) WHERE EventId = %" PRIu64, + offset, id); + + db_mutex.lock(); + if (mysql_query(&dbconn, sql)) { + db_mutex.unlock(); + Error("Can't update frames: %s, sql was %s", mysql_error(&dbconn), sql); + return; + } + db_mutex.unlock(); + Info("Updating frames delta by %0.2f sec to match video file", offset); +} + + void Event::AddFrame(Image *image, struct timeval timestamp, int score, Image *alarm_image) { if ( !timestamp.tv_sec ) { Debug(1, "Not adding new frame, zero timestamp"); @@ -562,10 +597,6 @@ void Event::AddFrame(Image *image, struct timeval timestamp, int score, Image *a frames++; bool write_to_db = false; - FrameType frame_type = score>0?ALARM:(score<0?BULK:NORMAL); - // < 0 means no motion detection is being done. - if ( score < 0 ) - score = 0; if ( save_jpegs & 1 ) { static char event_file[PATH_MAX]; @@ -579,9 +610,14 @@ void Event::AddFrame(Image *image, struct timeval timestamp, int score, Image *a // If this is the first frame, we should add a thumbnail to the event directory if ( (frames == 1) || (score > (int)max_score) ) { write_to_db = true; // web ui might show this as thumbnail, so db needs to know about it. - WriteFrameImage(image, timestamp, snapshot_file); + WriteFrameImage(image, timestamp, snapshot_file.c_str()); } + FrameType frame_type = score>0?ALARM:(score<0?BULK:NORMAL); + // < 0 means no motion detection is being done. + if ( score < 0 ) + score = 0; + // We are writing an Alarm frame if ( frame_type == ALARM ) { // The first frame with a score will be the frame that alarmed the event @@ -590,17 +626,30 @@ void Event::AddFrame(Image *image, struct timeval timestamp, int score, Image *a alarm_frame_written = true; WriteFrameImage(image, timestamp, alarm_file.c_str()); } - if ( videowriter != NULL ) { - WriteFrameVideo(image, timestamp, videowriter); - } + alarm_frames++; - struct DeltaTimeval delta_time; - DELTA_TIMEVAL(delta_time, timestamp, start_time, DT_PREC_2); + tot_score += score; + if ( score > (int)max_score ) + max_score = score; + + if ( alarm_image ) { + if ( save_jpegs & 2 ) { + static char event_file[PATH_MAX]; + snprintf(event_file, sizeof(event_file), staticConfig.analyse_file_format, path.c_str(), frames); + Debug(1, "Writing analysis frame %d", frames); + if ( ! WriteFrameImage(alarm_image, timestamp, event_file, true) ) { + Error("Failed to write analysis frame image"); + } + } + } + } bool db_frame = ( frame_type != BULK ) || (frames==1) || ((frames%config.bulk_frame_interval)==0) ; if ( db_frame ) { static char sql[ZM_SQL_MED_BUFSIZ]; + struct DeltaTimeval delta_time; + DELTA_TIMEVAL(delta_time, timestamp, start_time, DT_PREC_2); frame_data.push(new Frame(id, frames, frame_type, timestamp, delta_time, score)); if ( write_to_db || ( frame_data.size() > 20 ) ) { @@ -635,23 +684,4 @@ void Event::AddFrame(Image *image, struct timeval timestamp, int score, Image *a end_time = timestamp; - // We are writing an Alarm frame - if ( frame_type == ALARM ) { - alarm_frames++; - - tot_score += score; - if ( score > (int)max_score ) - max_score = score; - - if ( alarm_image ) { - if ( save_jpegs & 2 ) { - static char event_file[PATH_MAX]; - snprintf(event_file, sizeof(event_file), staticConfig.analyse_file_format, path.c_str(), frames); - Debug(1, "Writing analysis frame %d", frames); - if ( ! WriteFrameImage(alarm_image, timestamp, event_file, true) ) { - Error("Failed to write analysis frame image"); - } - } - } - } // end if frame_type == ALARM } // end void Event::AddFrame(Image *image, struct timeval timestamp, int score, Image *alarm_image) diff --git a/src/zm_event.h b/src/zm_event.h index bd75f0dda..73886200d 100644 --- a/src/zm_event.h +++ b/src/zm_event.h @@ -94,8 +94,6 @@ class Event { std::string snapshot_file; std::string alarm_file; VideoStore *videoStore; - std::string snapshot_file; - std::string alarm_file; VideoWriter* videowriter; char video_name[PATH_MAX]; @@ -137,6 +135,7 @@ class Event { private: void AddFramesInternal( int n_frames, int start_frame, Image **images, struct timeval **timestamps ); void WriteDbFrames(); + void UpdateFramesDelta(double offset); public: static const char *getSubPath( struct tm *time ) { diff --git a/src/zm_eventstream.cpp b/src/zm_eventstream.cpp index 2ca12972d..034ff778a 100644 --- a/src/zm_eventstream.cpp +++ b/src/zm_eventstream.cpp @@ -117,7 +117,7 @@ bool EventStream::loadEventData(uint64_t event_id) { snprintf(sql, sizeof(sql), "SELECT `MonitorId`, `StorageId`, `Frames`, unix_timestamp( `StartTime` ) AS StartTimestamp, " "(SELECT max(`Delta`)-min(`Delta`) FROM `Frames` WHERE `EventId`=`Events`.`Id`) AS Duration, " - "`DefaultVideo`, `Scheme`, `SaveJPEGs` FROM `Events` WHERE `Id` = %" PRIu64, event_id); + "`DefaultVideo`, `Scheme`, `SaveJPEGs`, `Orientation`+0 FROM `Events` WHERE `Id` = %" PRIu64, event_id); if ( mysql_query(&dbconn, sql) ) { Error("Can't run query: %s", mysql_error(&dbconn)); @@ -160,6 +160,7 @@ bool EventStream::loadEventData(uint64_t event_id) { event_data->scheme = Storage::SHALLOW; } event_data->SaveJPEGs = dbrow[7] == NULL ? 0 : atoi(dbrow[7]); + event_data->Orientation = (Monitor::Orientation)(dbrow[8] == NULL ? 0 : atoi(dbrow[8])); mysql_free_result(result); Storage * storage = new Storage(event_data->storage_id); @@ -703,6 +704,34 @@ Debug(1, "Loading image"); Error("Failed getting a frame."); return false; } + + // when stored as an mp4, we just have the rotation as a flag in the headers + // so we need to rotate it before outputting + if ( + (monitor->GetOptVideoWriter() == Monitor::H264PASSTHROUGH) + and + (event_data->Orientation != Monitor::ROTATE_0) + ) { + Debug(2, "Rotating image %d", event_data->Orientation); + switch ( event_data->Orientation ) { + case Monitor::ROTATE_0 : + // No action required + break; + case Monitor::ROTATE_90 : + case Monitor::ROTATE_180 : + case Monitor::ROTATE_270 : + image->Rotate((event_data->Orientation-1)*90); + break; + case Monitor::FLIP_HORI : + case Monitor::FLIP_VERT : + image->Flip(event_data->Orientation==Monitor::FLIP_HORI); + break; + default: + Error("Invalid Orientation: %d", event_data->Orientation); + } + } else { + Debug(2, "Not Rotating image %d", event_data->Orientation); + } // end if have rotation } else { Error("Unable to get a frame"); return false; diff --git a/src/zm_eventstream.h b/src/zm_eventstream.h index 5e7d91bb2..d0b5827e7 100644 --- a/src/zm_eventstream.h +++ b/src/zm_eventstream.h @@ -66,6 +66,7 @@ class EventStream : public StreamBase { char video_file[PATH_MAX]; Storage::Schemes scheme; int SaveJPEGs; + Monitor::Orientation Orientation; }; protected: diff --git a/src/zm_ffmpeg.cpp b/src/zm_ffmpeg.cpp index 99e8d59ca..fbb1ab29f 100644 --- a/src/zm_ffmpeg.cpp +++ b/src/zm_ffmpeg.cpp @@ -81,7 +81,7 @@ void FFMPEGInit() { av_log_set_callback(log_libav_callback); Info("Enabling ffmpeg logs, as LOG_DEBUG+LOG_FFMPEG are enabled in options"); } else { - Info("Not enabling ffmpeg logs, as LOG_FFMPEG and/or LOG_DEBUG is disabled in options, or this monitor not part of your debug targets"); + Info("Not enabling ffmpeg logs, as LOG_FFMPEG and/or LOG_DEBUG is disabled in options, or this monitor is not part of your debug targets"); av_log_set_level(AV_LOG_QUIET); } #if !LIBAVFORMAT_VERSION_CHECK(58, 9, 0, 64, 0) @@ -291,17 +291,18 @@ static void zm_log_fps(double d, const char *postfix) { #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) void zm_dump_codecpar ( const AVCodecParameters *par ) { - Debug(1, "Dumping codecpar codec_type(%d) codec_id(%d %s) codec_tag(%d) width(%d) height(%d) bit_rate(%d) format(%d = %s)", - par->codec_type, - par->codec_id, - avcodec_get_name(par->codec_id), - par->codec_tag, - par->width, - par->height, - par->bit_rate, - par->format, - ((AVPixelFormat)par->format == AV_PIX_FMT_NONE ? "none" : av_get_pix_fmt_name((AVPixelFormat)par->format)) -); + Debug(1, "Dumping codecpar codec_type(%d %s) codec_id(%d %s) codec_tag(%" PRIu32 ") width(%d) height(%d) bit_rate(%" PRIu64 ") format(%d %s)", + par->codec_type, + av_get_media_type_string(par->codec_type), + par->codec_id, + avcodec_get_name(par->codec_id), + par->codec_tag, + par->width, + par->height, + par->bit_rate, + par->format, + (((AVPixelFormat)par->format == AV_PIX_FMT_NONE) ? "none" : av_get_pix_fmt_name((AVPixelFormat)par->format)) + ); } #endif diff --git a/src/zm_ffmpeg_camera.cpp b/src/zm_ffmpeg_camera.cpp index dee700cc7..90504087e 100644 --- a/src/zm_ffmpeg_camera.cpp +++ b/src/zm_ffmpeg_camera.cpp @@ -402,19 +402,19 @@ int FfmpegCamera::OpenFfmpeg() { Debug(1, "Selected hw_pix_fmt %d %s", hw_pix_fmt, av_get_pix_fmt_name(hw_pix_fmt)); - mVideoCodecContext->get_format = get_hw_format; - ret = av_hwdevice_ctx_create(&hw_device_ctx, type, (hwaccel_device != "" ? hwaccel_device.c_str(): NULL), NULL, 0); if ( ret < 0 ) { - Error("Failed to create hwaccel device."); - return -1; + Error("Failed to create hwaccel device. %s",av_make_error_string(ret).c_str()); + hw_pix_fmt = AV_PIX_FMT_NONE; + } else { + Debug(1, "Created hwdevice for %s", hwaccel_device.c_str()); + mVideoCodecContext->get_format = get_hw_format; + mVideoCodecContext->hw_device_ctx = av_buffer_ref(hw_device_ctx); + hwFrame = zm_av_frame_alloc(); } - Debug(1, "Created hwdevice for %s", hwaccel_device.c_str()); - mVideoCodecContext->hw_device_ctx = av_buffer_ref(hw_device_ctx); - hwFrame = zm_av_frame_alloc(); } else { - Debug(1, "Failed to setup hwaccel."); + Debug(1, "Failed to find suitable hw_pix_fmt."); } #else Debug(1, "AVCodec not new enough for hwaccel"); @@ -422,7 +422,7 @@ int FfmpegCamera::OpenFfmpeg() { #else Warning("HWAccel support not compiled in."); #endif - } // end if hwacel_name + } // end if hwaccel_name #if !LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 8, 0) ret = avcodec_open(mVideoCodecContext, mVideoCodec); @@ -542,7 +542,6 @@ int FfmpegCamera::Close() { return 0; } // end FfmpegCamera::Close - int FfmpegCamera::transfer_to_image( Image &image, AVFrame *output_frame, @@ -557,9 +556,12 @@ int FfmpegCamera::transfer_to_image( return -1; } #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) + // From what I've read, we should align the linesizes to 32bit so that ffmpeg can use SIMD instructions too. int size = av_image_fill_arrays( output_frame->data, output_frame->linesize, - directbuffer, imagePixFormat, width, height, 32); + directbuffer, imagePixFormat, width, height, + (AV_PIX_FMT_RGBA == imagePixFormat ? 32 : 1) + ); if ( size < 0 ) { Error("Problem setting up data pointers into image %s", av_make_error_string(size).c_str()); @@ -598,19 +600,31 @@ int FfmpegCamera::transfer_to_image( mConvertContext, input_frame->data, input_frame->linesize, 0, mVideoCodecContext->height, output_frame->data, output_frame->linesize); - if ( ret <= 0 ) { - Error("Unable to convert format %u %s linesize %d height %d to format %u %s linesize %d at frame %d codec %u %s : code: %d", + if ( ret < 0 ) { + Error("Unable to convert format %u %s linesize %d,%d height %d to format %u %s linesize %d,%d at frame %d codec %u %s lines %d: code: %d", input_frame->format, av_get_pix_fmt_name((AVPixelFormat)input_frame->format), - input_frame->linesize, mVideoCodecContext->height, + input_frame->linesize[0], input_frame->linesize[1], mVideoCodecContext->height, imagePixFormat, av_get_pix_fmt_name(imagePixFormat), - output_frame->linesize, + output_frame->linesize[0], output_frame->linesize[1], frameCount, mVideoCodecContext->pix_fmt, av_get_pix_fmt_name(mVideoCodecContext->pix_fmt), + mVideoCodecContext->height, ret ); return -1; } + Debug(4, "Able to convert format %u %s linesize %d,%d height %d to format %u %s linesize %d,%d at frame %d codec %u %s %dx%d ", + input_frame->format, av_get_pix_fmt_name((AVPixelFormat)input_frame->format), + input_frame->linesize[0], input_frame->linesize[1], mVideoCodecContext->height, + imagePixFormat, + av_get_pix_fmt_name(imagePixFormat), + output_frame->linesize[0], output_frame->linesize[1], + frameCount, + mVideoCodecContext->pix_fmt, av_get_pix_fmt_name(mVideoCodecContext->pix_fmt), + output_frame->width, + output_frame->height + ); #else // HAVE_LIBSWSCALE Fatal("You must compile ffmpeg with the --enable-swscale " "option to use ffmpeg cameras"); diff --git a/src/zm_ffmpeg_camera.h b/src/zm_ffmpeg_camera.h index bfd1b8394..2e8e73517 100644 --- a/src/zm_ffmpeg_camera.h +++ b/src/zm_ffmpeg_camera.h @@ -53,6 +53,7 @@ class FfmpegCamera : public Camera { AVCodec *mAudioCodec; AVFrame *mRawFrame; AVFrame *mFrame; + _AVPIXELFORMAT imagePixFormat; AVFrame *input_frame; // Use to point to mRawFrame or hwFrame; diff --git a/src/zm_ffmpeg_input.cpp b/src/zm_ffmpeg_input.cpp index be6b60cb9..1def60346 100644 --- a/src/zm_ffmpeg_input.cpp +++ b/src/zm_ffmpeg_input.cpp @@ -10,6 +10,7 @@ FFmpeg_Input::FFmpeg_Input() { FFMPEGInit(); streams = NULL; frame = NULL; + last_seek_request = -1; } FFmpeg_Input::~FFmpeg_Input() { @@ -22,6 +23,17 @@ FFmpeg_Input::~FFmpeg_Input() { } } +int FFmpeg_Input::Open( AVStream * video_in_stream, AVStream * audio_in_stream ) { + video_stream_id = video_in_stream->index; + int max_stream_index = video_in_stream->index; + + if ( audio_in_stream ) { + max_stream_index = video_in_stream->index > audio_in_stream->index ? video_in_stream->index : audio_in_stream->index; + audio_stream_id = audio_in_stream->index; + } + streams = new stream[max_stream_index]; +} + int FFmpeg_Input::Open(const char *filepath) { int error; @@ -127,7 +139,6 @@ int FFmpeg_Input::Close( ) { } // end int FFmpeg_Input::Close() AVFrame *FFmpeg_Input::get_frame(int stream_id) { - Debug(1, "Getting frame from stream %d", stream_id); int frameComplete = false; AVPacket packet; @@ -166,12 +177,14 @@ AVFrame *FFmpeg_Input::get_frame(int stream_id) { frame = zm_av_frame_alloc(); } ret = zm_send_packet_receive_frame(context, frame, packet); - if ( ret <= 0 ) { + if ( ret < 0 ) { Error("Unable to decode frame at frame %d: %s, continuing", streams[packet.stream_index].frame_count, av_make_error_string(ret).c_str()); zm_av_packet_unref(&packet); av_frame_free(&frame); continue; + } else { + zm_dump_frame(frame, "resulting frame"); } frameComplete = 1; @@ -201,9 +214,20 @@ AVFrame *FFmpeg_Input::get_frame(int stream_id, double at) { } // Have to grab a frame to update our current frame to know where we are get_frame(stream_id); - } // end if ! frame + } // end if ! frame - if ( frame->pts > seek_target ) { + if ( !frame ) { + Warning("Unable to get frame."); + return NULL; + } + + if ( + (last_seek_request >= 0) + && + (last_seek_request > seek_target ) + && + (frame->pts > seek_target) + ) { zm_dump_frame(frame, "frame->pts > seek_target, seek backwards"); // our frame must be beyond our seek target. so go backwards to before it if ( ( ret = av_seek_frame(input_format_context, stream_id, seek_target, @@ -217,6 +241,8 @@ AVFrame *FFmpeg_Input::get_frame(int stream_id, double at) { zm_dump_frame(frame, "frame->pts > seek_target, got"); } // end if frame->pts > seek_target + last_seek_request = seek_target; + // Seeking seems to typically seek to a keyframe, so then we have to decode until we get the frame we want. if ( frame->pts <= seek_target ) { zm_dump_frame(frame, "pts <= seek_target"); diff --git a/src/zm_ffmpeg_input.h b/src/zm_ffmpeg_input.h index 2f524ac45..900f14d4a 100644 --- a/src/zm_ffmpeg_input.h +++ b/src/zm_ffmpeg_input.h @@ -42,6 +42,7 @@ class FFmpeg_Input { int audio_stream_id; AVFormatContext *input_format_context; AVFrame *frame; + int64_t last_seek_request; }; #endif diff --git a/src/zm_image.cpp b/src/zm_image.cpp index 44f88959a..85f9ba68b 100644 --- a/src/zm_image.cpp +++ b/src/zm_image.cpp @@ -165,8 +165,10 @@ Image::Image( const AVFrame *frame ) { width = frame->width; height = frame->height; pixels = width*height; + colours = ZM_COLOUR_RGB32; subpixelorder = ZM_SUBPIX_ORDER_RGBA; + size = pixels*colours; buffer = 0; holdbuffer = 0; diff --git a/src/zm_logger.cpp b/src/zm_logger.cpp index 8423245fb..43327ba36 100644 --- a/src/zm_logger.cpp +++ b/src/zm_logger.cpp @@ -157,6 +157,7 @@ void Logger::initialise(const std::string &id, const Options &options) { if ( options.mTerminalLevel != NOOPT ) tempTerminalLevel = options.mTerminalLevel; + // DEBUG1 == 1. So >= DEBUG1, we set to DEBUG9?! Why? if ( options.mDatabaseLevel != NOOPT ) tempDatabaseLevel = options.mDatabaseLevel; else @@ -358,7 +359,7 @@ Logger::Level Logger::databaseLevel(Logger::Level databaseLevel) { if ( databaseLevel > NOOPT ) { databaseLevel = limit(databaseLevel); if ( mDatabaseLevel != databaseLevel ) { - if ( databaseLevel > NOLOG && mDatabaseLevel <= NOLOG ) { + if ( (databaseLevel > NOLOG) && (mDatabaseLevel <= NOLOG) ) { // <= NOLOG would be NOOPT if ( !zmDbConnect() ) { databaseLevel = NOLOG; } @@ -534,8 +535,11 @@ void Logger::logPrint(bool hex, const char * const filepath, const int line, con fflush(stdout); } if ( level <= mFileLevel ) { - if ( !mLogFileFP ) + if ( !mLogFileFP ) { + log_mutex.unlock(); openFile(); + log_mutex.lock(); + } if ( mLogFileFP ) { fputs(logString, mLogFileFP); if ( mFlush ) @@ -553,8 +557,10 @@ void Logger::logPrint(bool hex, const char * const filepath, const int line, con if ( level <= mDatabaseLevel ) { if ( !db_mutex.trylock() ) { + char escapedString[(strlen(syslogStart)*2)+1]; mysql_real_escape_string(&dbconn, escapedString, syslogStart, strlen(syslogStart)); + char sql[ZM_SQL_MED_BUFSIZ]; snprintf(sql, sizeof(sql), "INSERT INTO `Logs` " "( `TimeKey`, `Component`, `ServerId`, `Pid`, `Level`, `Code`, `Message`, `File`, `Line` )" @@ -572,7 +578,7 @@ void Logger::logPrint(bool hex, const char * const filepath, const int line, con } else { Level tempDatabaseLevel = mDatabaseLevel; databaseLevel(NOLOG); - Error("Can't insert log entry: sql(%s) error(%s)", sql, mysql_error(&dbconn)); + Error("Can't insert log entry: sql(%s) error(%s)", syslogStart, mysql_error(&dbconn)); databaseLevel(tempDatabaseLevel); } db_mutex.unlock(); diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index e1f729a3c..8c65ca22a 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -288,6 +288,8 @@ Monitor::Monitor() orientation(ROTATE_0), deinterlacing(0), deinterlacing_value(0), + decoder_hwaccel_name(""), + decoder_hwaccel_device(""), videoRecording(0), rtsp_describe(0), @@ -410,6 +412,12 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) { capture_delay = (dbrow[col]&&atof(dbrow[col])>0.0)?int(DT_PREC_3/atof(dbrow[col])):0; col++; alarm_capture_delay = (dbrow[col]&&atof(dbrow[col])>0.0)?int(DT_PREC_3/atof(dbrow[col])):0; col++; + if (analysis_fps > 0.0) { + uint64_t usec = round(1000000*pre_event_count/analysis_fps); + video_buffer_duration.tv_sec = usec/1000000; + video_buffer_duration.tv_usec = usec % 1000000; + } + if ( dbrow[col] ) strncpy(device, dbrow[col], sizeof(device)-1); else @@ -545,6 +553,8 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) { last_signal = false; camera = NULL; + uint64_t image_size = width * height * colours; + mem_size = sizeof(SharedData) + sizeof(TriggerData) + sizeof(VideoStoreData) //Information to pass back to the capture process @@ -556,7 +566,7 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) { sizeof(mem_size), sizeof(SharedData), sizeof(TriggerData), sizeof(VideoStoreData), (image_buffer_count*sizeof(struct timeval)), - image_buffer_count, camera->ImageSize(), (image_buffer_count*camera->ImageSize()), + image_buffer_count, image_size, (image_buffer_count*image_size), mem_size); mem_ptr = NULL; @@ -582,7 +592,7 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) { } } - //this0>delta_image( width, height, ZM_COLOUR_GRAY8, ZM_SUBPIX_ORDER_NONE ), + //this->delta_image( width, height, ZM_COLOUR_GRAY8, ZM_SUBPIX_ORDER_NONE ), //ref_image( width, height, p_camera->Colours(), p_camera->SubpixelOrder() ), Debug(1, "Loaded monitor %d(%s), %d zones", id, name, n_zones); getCamera(); @@ -657,7 +667,7 @@ Camera * Monitor::getCamera() { } #endif // HAVE_LIBAVFORMAT else { - Error( "Unexpected remote camera protocol '%s'", protocol.c_str() ); + Error("Unexpected remote camera protocol '%s'", protocol.c_str()); } } else if ( type == FILE ) { camera = new FileCamera( @@ -793,7 +803,7 @@ Monitor *Monitor::Load(unsigned int p_id, bool load_zones, Purpose purpose) { bool Monitor::connect() { - Debug(3, "Connecting to monitor. Purpose is %d", purpose ); + Debug(3, "Connecting to monitor. Purpose is %d", purpose); #if ZM_MEM_MAPPED snprintf(mem_file, sizeof(mem_file), "%s/zm.mmap.%d", staticConfig.PATH_MAP.c_str(), id); map_fd = open(mem_file, O_RDWR|O_CREAT, (mode_t)0600); diff --git a/src/zm_monitor.h b/src/zm_monitor.h index 0477a4c65..1bfbcb467 100644 --- a/src/zm_monitor.h +++ b/src/zm_monitor.h @@ -257,10 +257,10 @@ protected: Orientation orientation; // Whether the image has to be rotated at all unsigned int deinterlacing; unsigned int deinterlacing_value; - bool videoRecording; - bool rtsp_describe; std::string decoder_hwaccel_name; std::string decoder_hwaccel_device; + bool videoRecording; + bool rtsp_describe; int savejpegs; int colours; @@ -297,6 +297,7 @@ protected: int frame_skip; // How many frames to skip in continuous modes int motion_frame_skip; // How many frames to skip in motion detection double analysis_fps_limit; // Target framerate for video analysis + struct timeval video_buffer_duration; // How long a video segment to keep in buffer (set only if analysis fps != 0 ) unsigned int analysis_update_delay; // How long we wait before updating analysis parameters int capture_delay; // How long we wait between capture frames int alarm_capture_delay; // How long we wait between capture frames when in alarm state @@ -461,8 +462,12 @@ public: uint64_t GetVideoWriterEventId() const { return video_store_data->current_event; } void SetVideoWriterEventId( uint64_t p_event_id ) { video_store_data->current_event = p_event_id; } + struct timeval GetVideoWriterStartTime() const { return video_store_data->recording; } + void SetVideoWriterStartTime(struct timeval &t) { video_store_data->recording = t; } + unsigned int GetPreEventCount() const { return pre_event_count; }; - int GetImageBufferCount() const { return image_buffer_count; }; + struct timeval GetVideoBufferDuration() const { return video_buffer_duration; }; + int GetImageBufferCount() const { return image_buffer_count; }; State GetState() const; int GetImage( int index=-1, int scale=100 ); ZMPacket *getSnapshot( int index=-1 ) const; diff --git a/src/zm_monitorstream.cpp b/src/zm_monitorstream.cpp index 8541f19b5..dd07012ae 100644 --- a/src/zm_monitorstream.cpp +++ b/src/zm_monitorstream.cpp @@ -1,21 +1,21 @@ // // ZoneMinder Monitor Class Implementation, $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. -// +// #include "zm.h" #include "zm_db.h" @@ -73,7 +73,7 @@ bool MonitorStream::checkSwapPath(const char *path, bool create_path) { return false; } return true; -} // end bool MonitorStream::checkSwapPath( const char *path, bool create_path ) +} // end bool MonitorStream::checkSwapPath( const char *path, bool create_path ) void MonitorStream::processCommand(const CmdMsg *msg) { Debug(2, "Got message, type %d, msg %d", msg->msg_type, msg->msg_data[0]); @@ -265,7 +265,7 @@ void MonitorStream::processCommand(const CmdMsg *msg) { //status_data.enabled = monitor->shared_data->active; status_data.enabled = monitor->trigger_data->trigger_state!=Monitor::TRIGGER_OFF; status_data.forced = monitor->trigger_data->trigger_state==Monitor::TRIGGER_ON; - Debug(2, "Buffer Level:%d, Delayed:%d, Paused:%d, Rate:%d, delay:%.3f, Zoom:%d, Enabled:%d Forced:%d", + Debug(2, "Buffer Level:%d, Delayed:%d, Paused:%d, Rate:%d, delay:%.3f, Zoom:%d, Enabled:%d Forced:%d", status_data.buffer_level, status_data.delayed, status_data.paused, @@ -329,7 +329,7 @@ bool MonitorStream::sendFrame(const char *filepath, struct timeval *timestamp) { // Calculate how long it takes to actually send the frame struct timeval frameStartTime; gettimeofday(&frameStartTime, NULL); - + fputs("--ZoneMinderFrame\r\nContent-Type: image/jpeg\r\n", stdout); fprintf(stdout, "Content-Length: %d\r\n\r\n", img_buffer_size); if ( fwrite(img_buffer, img_buffer_size, 1, stdout) != 1 ) { @@ -385,7 +385,7 @@ bool MonitorStream::sendFrame(Image *image, struct timeval *timestamp) { // Calculate how long it takes to actually send the frame struct timeval frameStartTime; gettimeofday(&frameStartTime, NULL); - + fputs("--ZoneMinderFrame\r\n", stdout); switch( type ) { case STREAM_JPEG : @@ -414,7 +414,7 @@ bool MonitorStream::sendFrame(Image *image, struct timeval *timestamp) { } fprintf(stdout, "Content-Length: %d\r\n\r\n", img_buffer_size); if ( fwrite(img_buffer, img_buffer_size, 1, stdout) != 1 ) { - if ( !zm_terminate ) { + if ( !zm_terminate ) { // If the pipe was closed, we will get signalled SIGPIPE to exit, which will set zm_terminate Warning("Unable to send stream frame: %s", strerror(errno)); } @@ -432,7 +432,7 @@ bool MonitorStream::sendFrame(Image *image, struct timeval *timestamp) { Warning("Frame send time %d msec too slow, throttling maxfps to %.2f", frameSendTime, maxfps); } - } + } // Not mpeg last_frame_sent = TV_2_FLOAT(now); return true; } // end bool MonitorStream::sendFrame( Image *image, struct timeval *timestamp ) @@ -457,7 +457,7 @@ void MonitorStream::runStream() { fputs("Content-Type: multipart/x-mixed-replace;boundary=ZoneMinderFrame\r\n\r\n", stdout); // point to end which is theoretically not a valid value because all indexes are % image_buffer_count - unsigned int last_read_index = monitor->image_buffer_count; + unsigned int last_read_index = monitor->image_buffer_count; time_t stream_start_time; time(&stream_start_time); @@ -478,15 +478,14 @@ void MonitorStream::runStream() { Image *paused_image = NULL; struct timeval paused_timestamp; - // 15 is the max length for the swap path suffix, /zmswap-whatever, assuming max 6 digits for monitor id - const int max_swap_len_suffix = 15; - - int swap_path_length = staticConfig.PATH_SWAP.length() + 1; // +1 for NULL terminator - int subfolder1_length = snprintf(NULL, 0, "/zmswap-m%d", monitor->Id()) + 1; - int subfolder2_length = snprintf(NULL, 0, "/zmswap-q%06d", connkey) + 1; - int total_swap_path_length = swap_path_length + subfolder1_length + subfolder2_length; - if ( connkey && ( playback_buffer > 0 ) ) { + // 15 is the max length for the swap path suffix, /zmswap-whatever, assuming max 6 digits for monitor id + const int max_swap_len_suffix = 15; + + int swap_path_length = staticConfig.PATH_SWAP.length() + 1; // +1 for NULL terminator + int subfolder1_length = snprintf(NULL, 0, "/zmswap-m%d", monitor->Id()) + 1; + int subfolder2_length = snprintf(NULL, 0, "/zmswap-q%06d", connkey) + 1; + int total_swap_path_length = swap_path_length + subfolder1_length + subfolder2_length; if ( total_swap_path_length + max_swap_len_suffix > PATH_MAX ) { Error("Swap Path is too long. %d > %d ", total_swap_path_length+max_swap_len_suffix, PATH_MAX); @@ -529,7 +528,7 @@ void MonitorStream::runStream() { Debug(1, "Using %.3f for fps instead of current fps %.3f", capture_max_fps, capture_fps); capture_fps = capture_max_fps; } - + if ( capture_fps < 1 ) { max_secs_since_last_sent_frame = 10/capture_fps; Debug(1, "Adjusting max_secs_since_last_sent_frame to %.2f from current fps %.2f", @@ -566,7 +565,7 @@ void MonitorStream::runStream() { touch(sock_path_lock); last_comm_update = now; } - } // end if connkey + } // end if connkey if ( paused ) { if ( !was_paused ) { @@ -593,7 +592,7 @@ void MonitorStream::runStream() { } else { if ( !paused ) { int temp_index = MOD_ADD(temp_read_index, 0, temp_image_buffer_count); - //Debug( 3, "tri: %d, ti: %d", temp_read_index, temp_index ); + // Debug( 3, "tri: %d, ti: %d", temp_read_index, temp_index ); SwapImage *swap_image = &temp_image_buffer[temp_index]; if ( !swap_image->valid ) { @@ -601,51 +600,61 @@ void MonitorStream::runStream() { delayed = true; temp_read_index = MOD_ADD(temp_read_index, (replay_rate>=0?-1:1), temp_image_buffer_count); } else { - //Debug( 3, "siT: %f, lfT: %f", TV_2_FLOAT( swap_image->timestamp ), TV_2_FLOAT( last_frame_timestamp ) ); - double expected_delta_time = ((TV_2_FLOAT( swap_image->timestamp ) - TV_2_FLOAT( last_frame_timestamp )) * ZM_RATE_BASE)/replay_rate; - double actual_delta_time = TV_2_FLOAT( now ) - last_frame_sent; + // Debug( 3, "siT: %f, lfT: %f", TV_2_FLOAT( swap_image->timestamp ), TV_2_FLOAT( last_frame_timestamp ) ); + double expected_delta_time = ((TV_2_FLOAT(swap_image->timestamp) - TV_2_FLOAT(last_frame_timestamp)) * ZM_RATE_BASE)/replay_rate; + double actual_delta_time = TV_2_FLOAT(now) - last_frame_sent; - //Debug( 3, "eDT: %.3lf, aDT: %.3f, lFS:%.3f, NOW:%.3f", expected_delta_time, actual_delta_time, last_frame_sent, TV_2_FLOAT( now ) ); + // Debug( 3, "eDT: %.3lf, aDT: %.3f, lFS:%.3f, NOW:%.3f", expected_delta_time, actual_delta_time, last_frame_sent, TV_2_FLOAT( now ) ); // If the next frame is due if ( actual_delta_time > expected_delta_time ) { - //Debug( 2, "eDT: %.3lf, aDT: %.3f", expected_delta_time, actual_delta_time ); + // Debug( 2, "eDT: %.3lf, aDT: %.3f", expected_delta_time, actual_delta_time ); if ( temp_index%frame_mod == 0 ) { - Debug( 2, "Sending delayed frame %d", temp_index ); + Debug(2, "Sending delayed frame %d", temp_index); // Send the next frame - if ( ! sendFrame(temp_image_buffer[temp_index].file_name, &temp_image_buffer[temp_index].timestamp) ) + if ( ! sendFrame(temp_image_buffer[temp_index].file_name, &temp_image_buffer[temp_index].timestamp) ) { zm_terminate = true; + } memcpy(&last_frame_timestamp, &(swap_image->timestamp), sizeof(last_frame_timestamp)); - //frame_sent = true; + // frame_sent = true; } temp_read_index = MOD_ADD(temp_read_index, (replay_rate>0?1:-1), temp_image_buffer_count); } } } else if ( step != 0 ) { - temp_read_index = MOD_ADD( temp_read_index, (step>0?1:-1), temp_image_buffer_count ); + temp_read_index = MOD_ADD(temp_read_index, (step>0?1:-1), temp_image_buffer_count); SwapImage *swap_image = &temp_image_buffer[temp_read_index]; // Send the next frame - if ( !sendFrame( temp_image_buffer[temp_read_index].file_name, &temp_image_buffer[temp_read_index].timestamp ) ) + if ( !sendFrame( + temp_image_buffer[temp_read_index].file_name, + &temp_image_buffer[temp_read_index].timestamp + ) ) { zm_terminate = true; - memcpy( &last_frame_timestamp, &(swap_image->timestamp), sizeof(last_frame_timestamp) ); - //frame_sent = true; + } + memcpy( + &last_frame_timestamp, + &(swap_image->timestamp), + sizeof(last_frame_timestamp) + ); + // frame_sent = true; step = 0; } else { //paused? int temp_index = MOD_ADD(temp_read_index, 0, temp_image_buffer_count); - double actual_delta_time = TV_2_FLOAT( now ) - last_frame_sent; - if ( got_command || actual_delta_time > 5 ) { + double actual_delta_time = TV_2_FLOAT(now) - last_frame_sent; + if ( got_command || (actual_delta_time > 5) ) { // Send keepalive Debug(2, "Sending keepalive frame %d", temp_index); // Send the next frame - if ( !sendFrame( temp_image_buffer[temp_index].file_name, &temp_image_buffer[temp_index].timestamp ) ) + if ( !sendFrame(temp_image_buffer[temp_index].file_name, &temp_image_buffer[temp_index].timestamp) ) { zm_terminate = true; - //frame_sent = true; + } + // frame_sent = true; } - } // end if (!paused) or step or paused - } // end if have exceeded buffer or not + } // end if (!paused) or step or paused + } // end if have exceeded buffer or not if ( temp_read_index == temp_write_index ) { // Go back to live viewing @@ -656,24 +665,16 @@ void MonitorStream::runStream() { delayed = false; replay_rate = ZM_RATE_BASE; } - } // end if ( buffered_playback && delayed ) + } // end if ( buffered_playback && delayed ) + if ( last_read_index != monitor->shared_data->last_write_index ) { // have a new image to send int index = monitor->shared_data->last_write_index % monitor->image_buffer_count; // % shouldn't be neccessary -#if 0 - // I don't know what this is about - ZMPacket *snap = &monitor->image_buffer[index]; - if ( tvCmp(last_frame_time, *(snap->timestamp)) ) { - - last_read_index = monitor->shared_data->last_write_index; - Debug(2, "index: %d: frame_mod: %d frame count: %d paused(%d) delayed(%d)", - index, frame_mod, frame_count, paused, delayed ); -#endif if ( (frame_mod == 1) || ((frame_count%frame_mod) == 0) ) { if ( !paused && !delayed ) { last_read_index = monitor->shared_data->last_write_index; Debug(2, "index: %d: frame_mod: %d frame count: %d paused(%d) delayed(%d)", - index, frame_mod, frame_count, paused, delayed ); + index, frame_mod, frame_count, paused, delayed); // Send the next frame // ZMPacket *snap = &monitor->image_buffer[index]; @@ -711,7 +712,7 @@ void MonitorStream::runStream() { if ( !sendFrame(paused_image, &paused_timestamp) ) zm_terminate = true; } else { - Debug(2, "Would have sent keepalive frame, but had no paused_image "); + Debug(2, "Would have sent keepalive frame, but had no paused_image"); } } // end if actual_delta_time > 5 } // end if change in zoom @@ -737,22 +738,22 @@ void MonitorStream::runStream() { temp_write_index = MOD_ADD( temp_write_index, 1, temp_image_buffer_count ); if ( temp_write_index == temp_read_index ) { // Go back to live viewing - Warning( "Exceeded temporary buffer, resuming live play" ); + Warning("Exceeded temporary buffer, resuming live play"); paused = false; delayed = false; replay_rate = ZM_RATE_BASE; } } else { - Warning( "Unable to store frame as timestamp invalid" ); + Warning("Unable to store frame as timestamp invalid"); } } else { - Warning( "Unable to store frame as shared memory invalid" ); + Warning("Unable to store frame as shared memory invalid"); } } // end if buffered playback frame_count++; } else { Debug(3, "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))); Debug(3, "Sleeping for (%d)", sleep_time); @@ -769,10 +770,10 @@ void MonitorStream::runStream() { break; } } - if ( ! last_frame_sent ) { + if ( !last_frame_sent ) { // If we didn't capture above, because frame_mod was bad? Then last_frame_sent will not have a value. last_frame_sent = now.tv_sec; - Warning("no last_frame_sent. Shouldn't happen. frame_mod was (%d) frame_count (%d) ", + Warning("no last_frame_sent. Shouldn't happen. frame_mod was (%d) frame_count (%d)", frame_mod, frame_count); } else if ( (!paused) @@ -813,9 +814,9 @@ void MonitorStream::runStream() { } } } - globfree( &pglob ); + globfree(&pglob); if ( rmdir(swap_path.c_str()) < 0 ) { - Error( "Can't rmdir '%s': %s", swap_path.c_str(), strerror(errno) ); + Error("Can't rmdir '%s': %s", swap_path.c_str(), strerror(errno)); } } // end if checking for swap_path } // end if buffered_playback @@ -823,7 +824,7 @@ void MonitorStream::runStream() { closeComms(); } // end MonitorStream::runStream -void MonitorStream::SingleImage( int scale ) { +void MonitorStream::SingleImage(int scale) { int img_buffer_size = 0; static JOCTET img_buffer[ZM_MAX_IMAGE_SIZE]; Image scaled_image; @@ -831,42 +832,45 @@ void MonitorStream::SingleImage( int scale ) { Image *snap_image = snap->image; if ( scale != ZM_SCALE_BASE ) { - scaled_image.Assign( *snap_image ); - scaled_image.Scale( scale ); + scaled_image.Assign(*snap_image); + scaled_image.Scale(scale); snap_image = &scaled_image; } if ( !config.timestamp_on_capture ) { - monitor->TimestampImage( snap_image, snap->timestamp ); + monitor->TimestampImage(snap_image, snap->timestamp); } - snap_image->EncodeJpeg( img_buffer, &img_buffer_size ); - - fprintf( stdout, "Content-Length: %d\r\n", img_buffer_size ); - fprintf( stdout, "Content-Type: image/jpeg\r\n\r\n" ); - fwrite( img_buffer, img_buffer_size, 1, stdout ); + snap_image->EncodeJpeg(img_buffer, &img_buffer_size); + + fprintf(stdout, + "Content-Length: %d\r\n" + "Content-Type: image/jpeg\r\n\r\n", + img_buffer_size); + fwrite(img_buffer, img_buffer_size, 1, stdout); } -void MonitorStream::SingleImageRaw( int scale ) { +void MonitorStream::SingleImageRaw(int scale) { Image scaled_image; ZMPacket *snap = monitor->getSnapshot(); Image *snap_image = snap->image; if ( scale != ZM_SCALE_BASE ) { - scaled_image.Assign( *snap_image ); - scaled_image.Scale( scale ); + scaled_image.Assign(*snap_image); + scaled_image.Scale(scale); snap_image = &scaled_image; } if ( !config.timestamp_on_capture ) { - monitor->TimestampImage( snap_image, snap->timestamp ); + monitor->TimestampImage(snap_image, snap->timestamp); } - - fprintf( stdout, "Content-Length: %d\r\n", snap_image->Size() ); - fprintf( stdout, "Content-Type: image/x-rgb\r\n\r\n" ); - fwrite( snap_image->Buffer(), snap_image->Size(), 1, stdout ); + + fprintf(stdout, + "Content-Length: %d\r\n" + "Content-Type: image/x-rgb\r\n\r\n", + snap_image->Size()); + fwrite(snap_image->Buffer(), snap_image->Size(), 1, stdout); } - #ifdef HAVE_ZLIB_H -void MonitorStream::SingleImageZip( int scale ) { +void MonitorStream::SingleImageZip(int scale) { unsigned long img_buffer_size = 0; static Bytef img_buffer[ZM_MAX_IMAGE_SIZE]; Image scaled_image; @@ -875,17 +879,19 @@ void MonitorStream::SingleImageZip( int scale ) { Image *snap_image = snap->image; if ( scale != ZM_SCALE_BASE ) { - scaled_image.Assign( *snap_image ); - scaled_image.Scale( scale ); + scaled_image.Assign(*snap_image); + scaled_image.Scale(scale); snap_image = &scaled_image; } if ( !config.timestamp_on_capture ) { - monitor->TimestampImage( snap_image, snap->timestamp ); + monitor->TimestampImage(snap_image, snap->timestamp); } - snap_image->Zip( img_buffer, &img_buffer_size ); - - fprintf( stdout, "Content-Length: %ld\r\n", img_buffer_size ); - fprintf( stdout, "Content-Type: image/x-rgbz\r\n\r\n" ); - fwrite( img_buffer, img_buffer_size, 1, stdout ); + snap_image->Zip(img_buffer, &img_buffer_size); + + fprintf(stdout, + "Content-Length: %ld\r\n" + "Content-Type: image/x-rgbz\r\n\r\n", + img_buffer_size); + fwrite(img_buffer, img_buffer_size, 1, stdout); } #endif // HAVE_ZLIB_H diff --git a/src/zm_packetqueue.cpp b/src/zm_packetqueue.cpp index b7bf69989..558c5c148 100644 --- a/src/zm_packetqueue.cpp +++ b/src/zm_packetqueue.cpp @@ -235,6 +235,73 @@ void zm_packetqueue::clearQueue() { mutex.unlock(); } +// clear queue keeping only specified duration of video -- return number of pkts removed +unsigned int zm_packetqueue::clearQueue(struct timeval *duration, int streamId) { + + if ( pktQueue.empty() ) { + return 0; + } + struct timeval keep_from; + std::list::reverse_iterator it; + it = pktQueue.rbegin(); + + struct timeval *t = (*it)->timestamp; + timersub(t, duration, &keep_from); + ++it; + + Debug(3, "Looking for frame before queue keep time with stream id (%d), queue has %d packets", + streamId, pktQueue.size()); + for ( ; it != pktQueue.rend(); ++it) { + ZMPacket *zm_packet = *it; + AVPacket *av_packet = &(zm_packet->packet); + if (av_packet->stream_index == streamId + && timercmp( zm_packet->timestamp, &keep_from, <= )) { + Debug(3, "Found frame before keep time with stream index %d at %d.%d", + av_packet->stream_index, + zm_packet->timestamp->tv_sec, + zm_packet->timestamp->tv_usec); + break; + } + } + + if (it == pktQueue.rend()) { + Debug(1, "Didn't find a frame before queue preserve time. keeping all"); + return 0; + } + + Debug(3, "Looking for keyframe"); + for ( ; it != pktQueue.rend(); ++it) { + ZMPacket *zm_packet = *it; + AVPacket *av_packet = &(zm_packet->packet); + if (av_packet->flags & AV_PKT_FLAG_KEY + && av_packet->stream_index == streamId) { + Debug(3, "Found keyframe before start with stream index %d at %d.%d", + av_packet->stream_index, + zm_packet->timestamp->tv_sec, + zm_packet->timestamp->tv_usec ); + break; + } + } + if ( it == pktQueue.rend() ) { + Debug(1, "Didn't find a keyframe before event starttime. keeping all" ); + return 0; + } + + unsigned int deleted_frames = 0; + ZMPacket *zm_packet = NULL; + while (distance(it, pktQueue.rend()) > 1) { + zm_packet = pktQueue.front(); + pktQueue.pop_front(); + packet_counts[zm_packet->packet.stream_index] -= 1; + delete zm_packet; + deleted_frames += 1; + } + zm_packet = NULL; + Debug(3, "Deleted %d frames", deleted_frames); + + return deleted_frames; +} + unsigned int zm_packetqueue::size() { return pktQueue.size(); } diff --git a/src/zm_packetqueue.h b/src/zm_packetqueue.h index f108913c6..c168d06a2 100644 --- a/src/zm_packetqueue.h +++ b/src/zm_packetqueue.h @@ -54,6 +54,7 @@ class zm_packetqueue { bool popVideoPacket(ZMPacket* packet); bool popAudioPacket(ZMPacket* packet); unsigned int clearQueue(unsigned int video_frames_to_keep, int stream_id); + unsigned int clearQueue(struct timeval *duration, int streamid); void clearQueue(); void dumpQueue(); unsigned int size(); diff --git a/src/zm_poly.cpp b/src/zm_poly.cpp index 13a21e7e0..35319d6af 100644 --- a/src/zm_poly.cpp +++ b/src/zm_poly.cpp @@ -26,11 +26,9 @@ #include #endif -void Polygon::calcArea() -{ +void Polygon::calcArea() { double float_area = 0.0L; - for ( int i = 0, j = n_coords-1; i < n_coords; j = i++ ) - { + for ( int i = 0, j = n_coords-1; i < n_coords; j = i++ ) { double trap_area = ((coords[i].X()-coords[j].X())*((coords[i].Y()+coords[j].Y())))/2.0L; float_area += trap_area; //printf( "%.2f (%.2f)\n", float_area, trap_area ); @@ -38,13 +36,11 @@ void Polygon::calcArea() area = (int)round(fabs(float_area)); } -void Polygon::calcCentre() -{ +void Polygon::calcCentre() { if ( !area && n_coords ) calcArea(); double float_x = 0.0L, float_y = 0.0L; - for ( int i = 0, j = n_coords-1; i < n_coords; j = i++ ) - { + for ( int i = 0, j = n_coords-1; i < n_coords; j = i++ ) { float_x += ((coords[i].Y()-coords[j].Y())*((coords[i].X()*2)+(coords[i].X()*coords[j].X())+(coords[j].X()*2))); float_y += ((coords[j].X()-coords[i].X())*((coords[i].Y()*2)+(coords[i].Y()*coords[j].Y())+(coords[j].Y()*2))); } @@ -54,16 +50,14 @@ void Polygon::calcCentre() centre = Coord( (int)round(float_x), (int)round(float_y) ); } -Polygon::Polygon( int p_n_coords, const Coord *p_coords ) : n_coords( p_n_coords ) -{ +Polygon::Polygon(int p_n_coords, const Coord *p_coords) : n_coords( p_n_coords ) { coords = new Coord[n_coords]; int min_x = -1; int max_x = -1; int min_y = -1; int max_y = -1; - for( int i = 0; i < n_coords; i++ ) - { + for ( int i = 0; i < n_coords; i++ ) { coords[i] = p_coords[i]; if ( min_x == -1 || coords[i].X() < min_x ) min_x = coords[i].X(); @@ -79,38 +73,36 @@ Polygon::Polygon( int p_n_coords, const Coord *p_coords ) : n_coords( p_n_coords calcCentre(); } -Polygon::Polygon( const Polygon &p_polygon ) : n_coords( p_polygon.n_coords ), extent( p_polygon.extent ), area( p_polygon.area ), centre( p_polygon.centre ) +Polygon::Polygon( const Polygon &p_polygon ) : + n_coords(p_polygon.n_coords), + extent(p_polygon.extent), + area(p_polygon.area), + centre(p_polygon.centre) { coords = new Coord[n_coords]; - for( int i = 0; i < n_coords; i++ ) - { + for( int i = 0; i < n_coords; i++ ) { coords[i] = p_polygon.coords[i]; } } -Polygon &Polygon::operator=( const Polygon &p_polygon ) -{ - if ( n_coords < p_polygon.n_coords ) - { +Polygon &Polygon::operator=( const Polygon &p_polygon ) { + if ( n_coords < p_polygon.n_coords ) { delete[] coords; coords = new Coord[p_polygon.n_coords]; } n_coords = p_polygon.n_coords; - for( int i = 0; i < n_coords; i++ ) - { + for ( int i = 0; i < n_coords; i++ ) { coords[i] = p_polygon.coords[i]; } extent = p_polygon.extent; area = p_polygon.area; centre = p_polygon.centre; - return( *this ); + return *this ; } -bool Polygon::isInside( const Coord &coord ) const -{ +bool Polygon::isInside( const Coord &coord ) const { bool inside = false; - for ( int i = 0, j = n_coords-1; i < n_coords; j = i++ ) - { + for ( int i = 0, j = n_coords-1; i < n_coords; j = i++ ) { if ( (((coords[i].Y() <= coord.Y()) && (coord.Y() < coords[j].Y()) ) || ((coords[j].Y() <= coord.Y()) && (coord.Y() < coords[i].Y()))) && (coord.X() < (coords[j].X() - coords[i].X()) * (coord.Y() - coords[i].Y()) / (coords[j].Y() - coords[i].Y()) + coords[i].X())) @@ -118,5 +110,5 @@ bool Polygon::isInside( const Coord &coord ) const inside = !inside; } } - return( inside ); + return inside; } diff --git a/src/zm_poly.h b/src/zm_poly.h index 0fcf34a6e..be039ef3e 100644 --- a/src/zm_poly.h +++ b/src/zm_poly.h @@ -41,13 +41,13 @@ protected: static int CompareYX( const void *p1, const void *p2 ) { const Edge *e1 = reinterpret_cast(p1), *e2 = reinterpret_cast(p2); if ( e1->min_y == e2->min_y ) - return( int(e1->min_x - e2->min_x) ); + return int(e1->min_x - e2->min_x); else - return( int(e1->min_y - e2->min_y) ); + return int(e1->min_y - e2->min_y); } static int CompareX( const void *p1, const void *p2 ) { const Edge *e1 = reinterpret_cast(p1), *e2 = reinterpret_cast(p2); - return( int(e1->min_x - e2->min_x) ); + return int(e1->min_x - e2->min_x); } }; @@ -83,32 +83,32 @@ protected: void calcCentre(); public: - inline Polygon() : n_coords( 0 ), coords( 0 ), area( 0 ), edges(0), slices(0) { + inline Polygon() : n_coords(0), coords(0), area(0), edges(0), slices(0) { } - Polygon( int p_n_coords, const Coord *p_coords ); - Polygon( const Polygon &p_polygon ); + Polygon(int p_n_coords, const Coord *p_coords); + Polygon(const Polygon &p_polygon); ~Polygon() { delete[] coords; } Polygon &operator=( const Polygon &p_polygon ); - inline int getNumCoords() const { return( n_coords ); } + inline int getNumCoords() const { return n_coords; } inline const Coord &getCoord( int index ) const { - return( coords[index] ); + return coords[index]; } - inline const Box &Extent() const { return( extent ); } - inline int LoX() const { return( extent.LoX() ); } - inline int HiX() const { return( extent.HiX() ); } - inline int LoY() const { return( extent.LoY() ); } - inline int HiY() const { return( extent.HiY() ); } - inline int Width() const { return( extent.Width() ); } - inline int Height() const { return( extent.Height() ); } + inline const Box &Extent() const { return extent; } + inline int LoX() const { return extent.LoX(); } + inline int HiX() const { return extent.HiX(); } + inline int LoY() const { return extent.LoY(); } + inline int HiY() const { return extent.HiY(); } + inline int Width() const { return extent.Width(); } + inline int Height() const { return extent.Height(); } - inline int Area() const { return( area ); } + inline int Area() const { return area; } inline const Coord &Centre() const { - return( centre ); + return centre; } bool isInside( const Coord &coord ) const; }; diff --git a/src/zm_remote_camera_http.cpp b/src/zm_remote_camera_http.cpp index 05cef4d0e..f12dc01c9 100644 --- a/src/zm_remote_camera_http.cpp +++ b/src/zm_remote_camera_http.cpp @@ -235,7 +235,7 @@ int RemoteCameraHttp::ReadData( Buffer &buffer, unsigned int bytes_expected ) { } else { if ( ioctl( sd, FIONREAD, &total_bytes_to_read ) < 0 ) { Error( "Can't ioctl(): %s", strerror(errno) ); - return( -1 ); + return -1; } if ( total_bytes_to_read == 0 ) { @@ -243,20 +243,20 @@ int RemoteCameraHttp::ReadData( Buffer &buffer, unsigned int bytes_expected ) { int error = 0; socklen_t len = sizeof (error); int retval = getsockopt( sd, SOL_SOCKET, SO_ERROR, &error, &len ); - if(retval != 0 ) { + if ( retval != 0 ) { Debug( 1, "error getting socket error code %s", strerror(retval) ); } - if (error != 0) { + if ( error != 0 ) { return -1; } // Case where we are grabbing a single jpg, but no content-length was given, so the expectation is that we read until close. - return( 0 ); + return 0; } // If socket is closed locally, then select will fail, but if it is closed remotely // then we have an exception on our socket.. but no data. - Debug( 3, "Socket closed remotely" ); + Debug(3, "Socket closed remotely"); //Disconnect(); // Disconnect is done outside of ReadData now. - return( -1 ); + return -1; } // There can be lots of bytes available. I've seen 4MB or more. This will vastly inflate our buffer size unnecessarily. @@ -293,6 +293,18 @@ int RemoteCameraHttp::ReadData( Buffer &buffer, unsigned int bytes_expected ) { return total_bytes_read; } +int RemoteCameraHttp::GetData() { + time_t start_time = time(NULL); + int buffer_len = 0; + while ( !( buffer_len = ReadData(buffer) ) ) { + if ( zm_terminate || ( start_time - time(NULL) < ZM_WATCH_MAX_DELAY )) + return -1; + Debug(4, "Timeout waiting for REGEXP HEADER"); + usleep(100000); + } + return buffer_len; +} + int RemoteCameraHttp::GetResponse() { int buffer_len; #if HAVE_LIBPCRE @@ -315,9 +327,7 @@ int RemoteCameraHttp::GetResponse() { switch( state ) { case HEADER : { - while ( !( buffer_len = ReadData(buffer) ) && !zm_terminate ) { - Debug(4, "Timeout waiting for REGEXP HEADER"); - } + buffer_len = GetData(); if ( buffer_len < 0 ) { Error("Unable to read header data"); return -1; @@ -457,9 +467,7 @@ int RemoteCameraHttp::GetResponse() { state = CONTENT; } else { Debug( 3, "Unable to extract subheader from stream, retrying" ); - while ( !( buffer_len = ReadData(buffer) ) && !zm_terminate ) { - Debug(4, "Timeout waiting to extract subheader"); - } + buffer_len = GetData(); if ( buffer_len < 0 ) { Error( "Unable to extract subheader data" ); return( -1 ); @@ -491,7 +499,7 @@ int RemoteCameraHttp::GetResponse() { if ( content_length ) { while ( ((long)buffer.size() < content_length ) && ! zm_terminate ) { Debug(3, "Need more data buffer %d < content length %d", buffer.size(), content_length ); - int bytes_read = ReadData( buffer ); + int bytes_read = GetData(); if ( bytes_read < 0 ) { Error( "Unable to read content" ); @@ -502,9 +510,7 @@ int RemoteCameraHttp::GetResponse() { Debug( 3, "Got end of image by length, content-length = %d", content_length ); } else { while ( !content_length ) { - while ( !( buffer_len = ReadData(buffer) ) && !zm_terminate ) { - Debug(4, "Timeout waiting for content"); - } + buffer_len = GetData(); if ( buffer_len < 0 ) { Error( "Unable to read content" ); return( -1 ); @@ -616,9 +622,7 @@ int RemoteCameraHttp::GetResponse() { } case HEADERCONT : { - while ( !( buffer_len = ReadData(buffer) ) && !zm_terminate ) { - Debug(1, "Timeout waiting for HEADERCONT"); - } + buffer_len = GetData(); if ( buffer_len < 0 ) { Error("Unable to read header"); return -1; @@ -903,9 +907,7 @@ int RemoteCameraHttp::GetResponse() { state = CONTENT; } else { Debug( 3, "Unable to extract subheader from stream, retrying" ); - while ( !( buffer_len = ReadData(buffer) ) &&!zm_terminate ) { - Debug(1, "Timeout waiting to extra subheader non regexp"); - } + buffer_len = GetData(); if ( buffer_len < 0 ) { Error( "Unable to read subheader" ); return( -1 ); @@ -945,7 +947,7 @@ int RemoteCameraHttp::GetResponse() { if ( content_length ) { while ( ( (long)buffer.size() < content_length ) && ! zm_terminate ) { Debug(4, "getting more data"); - int bytes_read = ReadData(buffer); + int bytes_read = GetData(); if ( bytes_read < 0 ) { Error("Unable to read content"); return -1; @@ -958,8 +960,7 @@ int RemoteCameraHttp::GetResponse() { while ( !content_length && !zm_terminate ) { Debug(4, "!content_length, ReadData"); buffer_len = ReadData( buffer ); - if ( buffer_len < 0 ) - { + if ( buffer_len < 0 ) { Error( "Unable to read content" ); return( -1 ); } @@ -1024,7 +1025,6 @@ int RemoteCameraHttp::PreCapture() { if ( sd < 0 ) { Connect(); if ( sd < 0 ) { - Error("Unable to connect to camera"); return -1; } mode = SINGLE_IMAGE; diff --git a/src/zm_remote_camera_http.h b/src/zm_remote_camera_http.h index b4f9ebe3a..2da0d1917 100644 --- a/src/zm_remote_camera_http.h +++ b/src/zm_remote_camera_http.h @@ -68,6 +68,7 @@ public: int Disconnect(); int SendRequest(); int ReadData( Buffer &buffer, unsigned int bytes_expected=0 ); + int GetData(); int GetResponse(); int PreCapture(); int Capture( ZMPacket &p ); diff --git a/src/zm_remote_camera_rtsp.cpp b/src/zm_remote_camera_rtsp.cpp index 0f01f2103..de5a51c93 100644 --- a/src/zm_remote_camera_rtsp.cpp +++ b/src/zm_remote_camera_rtsp.cpp @@ -342,7 +342,7 @@ int RemoteCameraRtsp::Capture( ZMPacket &zm_packet ) { while ( !frameComplete && (buffer.size() > 0) ) { packet->data = buffer.head(); packet->size = buffer.size(); - bytes += packet.size; + bytes += packet->size; // So I think this is the magic decode step. Result is a raw image? #if LIBAVCODEC_VERSION_CHECK(52, 23, 0, 23, 0) diff --git a/src/zm_user.cpp b/src/zm_user.cpp index 1f88109e0..d11c609df 100644 --- a/src/zm_user.cpp +++ b/src/zm_user.cpp @@ -203,7 +203,7 @@ User *zmLoadTokenUser (std::string jwt_token_str, bool use_remote_addr ) { return NULL; } - Debug (1,"Got stored expiry time of %u",stored_iat); + Debug (1,"Got last token revoke time of: %u",stored_iat); Debug (1,"Authenticated user '%s' via token", username.c_str()); mysql_free_result(result); return user; diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index b3ceb8e6c..4c20daeea 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -58,6 +58,7 @@ VideoStore::VideoStore( packets_written = 0; frame_count = 0; in_frame = NULL; + video_in_frame = NULL; video_in_ctx = NULL; // In future, we should just pass in the codec context instead of the stream. Don't really need the stream. @@ -85,10 +86,8 @@ VideoStore::VideoStore( video_start_pts = 0; audio_next_pts = 0; - audio_next_dts = 0; out_format = NULL; oc = NULL; - ret = 0; } // VideoStore::VideoStore bool VideoStore::open() { @@ -100,8 +99,6 @@ bool VideoStore::open() { "Could not create video storage stream %s as no out ctx" " could be assigned based on filename: %s", filename, av_make_error_string(ret).c_str()); - } else { - Debug(4, "Success allocating out format ctx"); } // Couldn't deduce format from filename, trying from format name @@ -113,11 +110,8 @@ bool VideoStore::open() { " could not be assigned based on filename or format %s", filename, format); return false; - } else { - Debug(4, "Success allocating out ctx"); } } // end if ! oc - Debug(2, "Success opening output contect"); AVDictionary *pmetadata = NULL; ret = av_dict_set(&pmetadata, "title", "Zoneminder Security Recording", 0); @@ -137,7 +131,7 @@ bool VideoStore::open() { Error("Couldn't copy params to context"); return false; } else { - zm_dump_codecpar( video_in_stream->codecpar ); + zm_dump_codecpar(video_in_stream->codecpar); } #else video_in_ctx = video_in_stream->codec; @@ -145,6 +139,7 @@ bool VideoStore::open() { zm_dump_codec(video_in_ctx); #endif } else { + // FIXME delete? Debug(2, "No input ctx"); video_in_ctx = avcodec_alloc_context3(NULL); video_in_stream_index = 0; @@ -170,14 +165,14 @@ bool VideoStore::open() { } max_stream_index = video_out_stream->index; - // FIXME SHould check that we are set to passthrough. Might be same codec, but want privacy overlays + // FIXME Should check that we are set to passthrough. Might be same codec, but want privacy overlays if ( video_in_stream && ( video_in_ctx->codec_id == wanted_codec ) ) { + // Copy params from instream to ctx #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) ret = avcodec_parameters_to_context(video_out_ctx, video_in_stream->codecpar); #else ret = avcodec_copy_context(video_out_ctx, video_in_ctx); #endif - // Copy params from instream to ctx if ( ret < 0 ) { Error("Could not initialize ctx parameteres"); return false; @@ -219,7 +214,7 @@ bool VideoStore::open() { Warning("Unsupported Orientation(%d)", orientation); } } // end if orientation - } else { + } else { // Either no video in or not the desired codec for ( unsigned int i = 0; i < sizeof(codec_data) / sizeof(*codec_data); i++ ) { if ( codec_data[i].codec_id != monitor->OutputCodec() ) continue; @@ -244,7 +239,7 @@ bool VideoStore::open() { Debug(2,"No timebase found in video in context, defaulting to Q"); 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 ? video_in_stream->time_base : AV_TIME_BASE_Q; if ( video_out_ctx->codec_id == AV_CODEC_ID_H264 ) { video_out_ctx->max_b_frames = 1; @@ -254,10 +249,10 @@ bool VideoStore::open() { } else { Debug(2, "Not setting priv_data"); } - } else if (video_out_ctx->codec_id == AV_CODEC_ID_MPEG2VIDEO) { + } else if ( video_out_ctx->codec_id == AV_CODEC_ID_MPEG2VIDEO ) { /* just for testing, we also add B frames */ video_out_ctx->max_b_frames = 2; - } else if (video_out_ctx->codec_id == AV_CODEC_ID_MPEG1VIDEO) { + } else if ( video_out_ctx->codec_id == AV_CODEC_ID_MPEG1VIDEO ) { /* Needed to avoid using macroblocks in which some coeffs overflow. * This does not happen with normal video, it just happens here as * the motion of the chroma plane does not match the luma plane. */ @@ -272,7 +267,7 @@ bool VideoStore::open() { } else { AVDictionaryEntry *e = NULL; while ( (e = av_dict_get(opts, "", e, AV_DICT_IGNORE_SUFFIX)) != NULL ) { - Debug( 3, "Encoder Option %s=%s", e->key, e->value ); + Debug(3, "Encoder Option %s=%s", e->key, e->value); } } @@ -327,6 +322,7 @@ bool VideoStore::open() { } #else avcodec_copy_context(video_out_stream->codec, video_out_ctx); +#endif converted_in_samples = NULL; audio_out_codec = NULL; @@ -458,10 +454,7 @@ bool VideoStore::open() { for ( int i = 0; i <= max_stream_index; i++ ) { next_dts[i] = 0; } -} // VideoStore::VideoStore -bool VideoStore::open() { - int ret; /* open the out file, if needed */ if ( !(out_format->flags & AVFMT_NOFILE) ) { if ( (ret = avio_open2(&oc->pb, filename, AVIO_FLAG_WRITE, NULL, NULL) ) < 0 ) { @@ -501,27 +494,8 @@ bool VideoStore::open() { return true; } // end bool VideoStore::open() -void VideoStore::write_audio_packet( AVPacket &pkt ) { - //Debug(2, "writing audio packet pts(%d) dts(%d) duration(%d)", pkt.pts, - //pkt.dts, pkt.duration); - pkt.pts = audio_next_pts; - pkt.dts = audio_next_dts; - - Debug(2, "writing audio packet pts(%d) dts(%d) duration(%d)", pkt.pts, pkt.dts, pkt.duration); - if ( pkt.duration > 0 ) { - pkt.duration = - av_rescale_q(pkt.duration, audio_out_ctx->time_base, - audio_out_stream->time_base); - } - audio_next_pts += pkt.duration; - audio_next_dts += pkt.duration; - - Debug(2, "writing audio packet pts(%d) dts(%d) duration(%d)", pkt.pts, pkt.dts, pkt.duration); - pkt.stream_index = audio_out_stream->index; - av_interleaved_write_frame(oc, &pkt); -} // end void VideoStore::Write_audio_packet( AVPacket &pkt ) - void VideoStore::flush_codecs() { + int ret; // The codec queues data. We need to send a flush command and out // whatever we get. Failures are not fatal. @@ -560,52 +534,29 @@ void VideoStore::flush_codecs() { break; } #endif - write_video_packet(pkt); + write_packet(&pkt, video_out_stream); zm_av_packet_unref(&pkt); } // while have buffered frames } // end if have delay capability if ( audio_out_codec ) { - // The codec queues data. We need to send a flush command and out - // whatever we get. Failures are not fatal. - AVPacket pkt; - pkt.data = NULL; - pkt.size = 0; - av_init_packet(&pkt); + // The codec queues data. We need to send a flush command and out + // whatever we get. Failures are not fatal. + AVPacket pkt; + pkt.data = NULL; + pkt.size = 0; + av_init_packet(&pkt); - int frame_size = audio_out_ctx->frame_size; - /* - * At the end of the file, we pass the remaining samples to - * the encoder. */ - while ( zm_resample_get_delay(resample_ctx, audio_out_ctx->sample_rate) ) { - zm_resample_audio(resample_ctx, NULL, out_frame); + int frame_size = audio_out_ctx->frame_size; + /* + * At the end of the file, we pass the remaining samples to + * the encoder. */ + while ( zm_resample_get_delay(resample_ctx, audio_out_ctx->sample_rate) ) { + zm_resample_audio(resample_ctx, NULL, out_frame); - if ( zm_add_samples_to_fifo(fifo, out_frame) ) { - // Should probably set the frame size to what is reported FIXME - if ( zm_get_samples_from_fifo(fifo, out_frame) ) { - if ( zm_send_frame_receive_packet(audio_out_ctx, out_frame, pkt) ) { - pkt.stream_index = audio_out_stream->index; - - av_packet_rescale_ts(&pkt, - audio_out_ctx->time_base, - audio_out_stream->time_base); - write_packet(&pkt, audio_out_stream); - } - } // end if data returned from fifo - } - - } // end if have buffered samples in the resampler - - Debug(2, "av_audio_fifo_size = %d", av_audio_fifo_size(fifo)); - while ( av_audio_fifo_size(fifo) > 0 ) { - /* Take one frame worth of audio samples from the FIFO buffer, - * encode it and write it to the output file. */ - - Debug(1, "Remaining samples in fifo for AAC codec frame_size %d > fifo size %d", - frame_size, av_audio_fifo_size(fifo)); - - // SHould probably set the frame size to what is reported FIXME - if ( av_audio_fifo_read(fifo, (void **)out_frame->data, frame_size) ) { + if ( zm_add_samples_to_fifo(fifo, out_frame) ) { + // Should probably set the frame size to what is reported FIXME + if ( zm_get_samples_from_fifo(fifo, out_frame) ) { if ( zm_send_frame_receive_packet(audio_out_ctx, out_frame, pkt) ) { pkt.stream_index = audio_out_stream->index; @@ -615,29 +566,50 @@ void VideoStore::flush_codecs() { write_packet(&pkt, audio_out_stream); } } // end if data returned from fifo - } // end while still data in the fifo + } + + } // end if have buffered samples in the resampler + + Debug(2, "av_audio_fifo_size = %d", av_audio_fifo_size(fifo)); + while ( av_audio_fifo_size(fifo) > 0 ) { + /* Take one frame worth of audio samples from the FIFO buffer, + * encode it and write it to the output file. */ + + Debug(1, "Remaining samples in fifo for AAC codec frame_size %d > fifo size %d", + frame_size, av_audio_fifo_size(fifo)); + + // SHould probably set the frame size to what is reported FIXME + if ( av_audio_fifo_read(fifo, (void **)out_frame->data, frame_size) ) { + if ( zm_send_frame_receive_packet(audio_out_ctx, out_frame, pkt) ) { + pkt.stream_index = audio_out_stream->index; + + av_packet_rescale_ts(&pkt, + audio_out_ctx->time_base, + audio_out_stream->time_base); + write_packet(&pkt, audio_out_stream); + } + } // end if data returned from fifo + } // end while still data in the fifo #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0) - // Put encoder into flushing mode - avcodec_send_frame(audio_out_ctx, NULL); + // Put encoder into flushing mode + avcodec_send_frame(audio_out_ctx, NULL); #endif - while (1) { - if ( ! zm_receive_packet(audio_out_ctx, pkt) ) { - Debug(1, "No more packets"); - break; - } + while (1) { + if ( ! zm_receive_packet(audio_out_ctx, pkt) ) { + Debug(1, "No more packets"); + break; + } - dumpPacket(&pkt, "raw from encoder"); - av_packet_rescale_ts(&pkt, audio_out_ctx->time_base, audio_out_stream->time_base); - dumpPacket(audio_out_stream, &pkt, "writing flushed packet"); - write_packet(&pkt, audio_out_stream); - zm_av_packet_unref(&pkt); - } // while have buffered frames - } // end if audio_out_codec - - } // end if audio_out_codec -} // end flush_codecs + dumpPacket(&pkt, "raw from encoder"); + av_packet_rescale_ts(&pkt, audio_out_ctx->time_base, audio_out_stream->time_base); + dumpPacket(audio_out_stream, &pkt, "writing flushed packet"); + write_packet(&pkt, audio_out_stream); + zm_av_packet_unref(&pkt); + } // while have buffered frames + } // end if audio_out_codec +} // end flush_codecs VideoStore::~VideoStore() { if ( oc->pb ) { @@ -1057,6 +1029,7 @@ int VideoStore::writePacket( ZMPacket *ipkt ) { } int VideoStore::writeVideoFramePacket(ZMPacket *zm_packet) { + int ret; frame_count += 1; // if we have to transcode @@ -1191,7 +1164,7 @@ int VideoStore::writeVideoFramePacket(ZMPacket *zm_packet) { video_in_stream->time_base, video_out_stream->time_base); Debug(1, "duration from ipkt: pts(%" PRId64 ") - last_pts(%" PRId64 ") = (%" PRId64 ") => (%" PRId64 ") (%d/%d) (%d/%d)", - zm_packet->in_frame->pkt_pts, + zm_packet->in_frame->pts, video_last_pts, zm_packet->in_frame->pkt_duration, duration, @@ -1203,13 +1176,13 @@ int VideoStore::writeVideoFramePacket(ZMPacket *zm_packet) { } else { duration = av_rescale_q( - zm_packet->in_frame->pkt_pts - video_last_pts, + zm_packet->in_frame->pts - video_last_pts, video_in_stream->time_base, video_out_stream->time_base); Debug(1, "duration calc: pts(%" PRId64 ") - last_pts(%" PRId64 ") = (%" PRId64 ") => (%" PRId64 ")", - zm_packet->in_frame->pkt_pts, + zm_packet->in_frame->pts, video_last_pts, - zm_packet->in_frame->pkt_pts - video_last_pts, + zm_packet->in_frame->pts - video_last_pts, duration ); if ( duration <= 0 ) { @@ -1226,7 +1199,7 @@ int VideoStore::writeVideoFramePacket(ZMPacket *zm_packet) { opkt.data = ipkt->data; opkt.size = ipkt->size; opkt.flags = ipkt->flags; - opkt.duration = pkt->duration; + opkt.duration = ipkt->duration; if ( ipkt->dts != AV_NOPTS_VALUE ) { if ( !video_first_dts ) { @@ -1394,7 +1367,6 @@ int VideoStore::write_packet(AVPacket *pkt, AVStream *stream) { av_make_error_string(ret).c_str()); } else { Debug(2, "Success writing packet"); ->>>>>>> master } return ret; } // end int VideoStore::write_packet(AVPacket *pkt, AVStream *stream) diff --git a/src/zm_videostore.h b/src/zm_videostore.h index 21a878e4d..e6ea83ab9 100644 --- a/src/zm_videostore.h +++ b/src/zm_videostore.h @@ -41,11 +41,17 @@ static struct CodecData codec_data[]; int audio_in_stream_index; AVCodec *video_out_codec; + AVCodecContext *video_in_ctx; AVCodecContext *video_out_ctx; AVStream *video_in_stream; AVStream *audio_in_stream; + const AVCodec *audio_in_codec; + AVCodecContext *audio_in_ctx; + // The following are used when encoding the audio stream to AAC + AVCodec *audio_out_codec; + AVCodecContext *audio_out_ctx; // Move this into the object so that we aren't constantly allocating/deallocating it on the stack AVPacket opkt; // we are transcoding @@ -53,18 +59,10 @@ static struct CodecData codec_data[]; AVFrame *in_frame; AVFrame *out_frame; - AVCodecContext *video_in_ctx; - const AVCodec *audio_in_codec; - AVCodecContext *audio_in_ctx; - SWScale swscale; unsigned int packets_written; unsigned int frame_count; - // The following are used when encoding the audio stream to AAC - AVStream *audio_out_stream; - AVCodec *audio_out_codec; - AVCodecContext *audio_out_ctx; #ifdef HAVE_LIBSWRESAMPLE SwrContext *resample_ctx; #else diff --git a/src/zmc.cpp b/src/zmc.cpp index e16a3e09c..3d8ca2f0b 100644 --- a/src/zmc.cpp +++ b/src/zmc.cpp @@ -349,7 +349,7 @@ int main(int argc, char *argv[]) { if ( result < 0 ) { // Failure, try reconnecting - sleep(1); + sleep(5); break; } diff --git a/src/zms.cpp b/src/zms.cpp index 544aa9936..c2df27591 100644 --- a/src/zms.cpp +++ b/src/zms.cpp @@ -106,9 +106,9 @@ int main(int argc, const char *argv[]) { for ( int p = 0; p < parm_no; p++ ) { char *name = strtok(parms[p], "="); - char *value = strtok(NULL, "="); + char const *value = strtok(NULL, "="); if ( !value ) - value = (char *)""; + value = ""; if ( !strcmp(name, "source") ) { source = !strcmp(value, "event")?ZMS_EVENT:ZMS_MONITOR; if ( !strcmp(value, "fifo") ) @@ -127,10 +127,10 @@ int main(int argc, const char *argv[]) { } else if ( !strcmp(name, "time") ) { event_time = atoi(value); } else if ( !strcmp(name, "event") ) { - event_id = strtoull(value, (char **)NULL, 10); + event_id = strtoull(value, NULL, 10); source = ZMS_EVENT; } else if ( !strcmp(name, "frame") ) { - frame_id = strtoull(value, (char **)NULL, 10); + frame_id = strtoull(value, NULL, 10); source = ZMS_EVENT; } else if ( !strcmp(name, "scale") ) { scale = atoi(value); @@ -159,7 +159,7 @@ int main(int argc, const char *argv[]) { } else if ( !strcmp(name, "buffer") ) { playback_buffer = atoi(value); } else if ( !strcmp(name, "auth") ) { - strncpy( auth, value, sizeof(auth)-1 ); + strncpy(auth, value, sizeof(auth)-1); } else if ( !strcmp(name, "token") ) { jwt_token_str = value; Debug(1, "ZMS: JWT token found: %s", jwt_token_str.c_str()); @@ -237,11 +237,13 @@ int main(int argc, const char *argv[]) { time_t now = time(0); char date_string[64]; - strftime(date_string, sizeof(date_string)-1, "%a, %d %b %Y %H:%M:%S GMT", gmtime(&now)); + strftime(date_string, sizeof(date_string)-1, + "%a, %d %b %Y %H:%M:%S GMT", gmtime(&now)); - fprintf(stdout, "Last-Modified: %s\r\n", date_string); + fputs("Last-Modified: ", stdout); + fputs(date_string, stdout); fputs( - "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n" + "\r\nExpires: Mon, 26 Jul 1997 05:00:00 GMT\r\n" "Cache-Control: no-store, no-cache, must-revalidate\r\n" "Cache-Control: post-check=0, pre-check=0\r\n" "Pragma: no-cache\r\n", @@ -279,7 +281,9 @@ int main(int argc, const char *argv[]) { stream.setStreamType(MonitorStream::STREAM_MPEG); #else // HAVE_LIBAVCODEC Error("MPEG streaming of '%s' attempted while disabled", query); - fprintf(stderr, "MPEG streaming is disabled.\nYou should configure with the --with-ffmpeg option and rebuild to use this functionality.\n"); + fprintf(stderr, "MPEG streaming is disabled.\n" + "You should configure with the --with-ffmpeg" + " option and rebuild to use this functionality.\n"); logTerm(); zmDbClose(); return -1; diff --git a/src/zmu.cpp b/src/zmu.cpp index bd70f8337..b435b7483 100644 --- a/src/zmu.cpp +++ b/src/zmu.cpp @@ -430,7 +430,9 @@ int main(int argc, char *argv[]) { User *user = 0; if ( config.opt_use_auth ) { - if ( strcmp(config.auth_relay, "none") == 0 ) { + if ( jwt_token_str != "" ) { + user = zmLoadTokenUser(jwt_token_str, false); + } else if ( strcmp(config.auth_relay, "none") == 0 ) { if ( !username ) { Error("Username must be supplied"); exit_zmu(-1); @@ -444,13 +446,10 @@ int main(int argc, char *argv[]) { user = zmLoadUser(username); } else { - if ( !(username && password) && !auth && (jwt_token_str=="")) { + if ( !(username && password) && !auth ) { Error("Username and password or auth/token string must be supplied"); exit_zmu(-1); } - if (jwt_token_str != "") { - user = zmLoadTokenUser(jwt_token_str, false); - } if ( auth ) { user = zmLoadAuthUser(auth, false); } diff --git a/utils/do_debian_package.sh b/utils/do_debian_package.sh index 3e20b303a..539205e3b 100755 --- a/utils/do_debian_package.sh +++ b/utils/do_debian_package.sh @@ -128,14 +128,14 @@ else fi; fi +IFS='.' read -r -a VERSION_PARTS <<< "$RELEASE" if [ "$PPA" == "" ]; then if [ "$RELEASE" != "" ]; then # We need to use our official tarball for the original source, so grab it and overwrite our generated one. - IFS='.' read -r -a VERSION <<< "$RELEASE" - if [ "${VERSION[0]}.${VERSION[1]}" == "1.30" ]; then + if [ "${VERSION_PARTS[0]}.${VERSION_PARTS[1]}" == "1.30" ]; then PPA="ppa:iconnor/zoneminder-stable" else - PPA="ppa:iconnor/zoneminder-${VERSION[0]}.${VERSION[1]}" + PPA="ppa:iconnor/zoneminder-${VERSION_PARTS[0]}.${VERSION_PARTS[1]}" fi; else if [ "$BRANCH" == "" ]; then @@ -175,7 +175,7 @@ cd ../ VERSION=`cat ${GITHUB_FORK}_zoneminder_release/version` -if [ $VERSION == "" ]; then +if [ -z "$VERSION" ]; then exit 1; fi; if [ "$SNAPSHOT" != "stable" ] && [ "$SNAPSHOT" != "" ]; then @@ -316,7 +316,7 @@ EOF read -p "Do you want to upload this binary to zmrepo? (y/N)" if [[ $REPLY == [yY] ]]; then if [ "$RELEASE" != "" ]; then - scp "zoneminder_${VERSION}-${DISTRO}"* "zoneminder-doc_${VERSION}-${DISTRO}"* "zoneminder-dbg_${VERSION}-${DISTRO}"* "zoneminder_${VERSION}.orig.tar.gz" "zmrepo@zmrepo.connortechnology.com:debian/stable/mini-dinstall/incoming/" + scp "zoneminder_${VERSION}-${DISTRO}"* "zoneminder-doc_${VERSION}-${DISTRO}"* "zoneminder-dbg_${VERSION}-${DISTRO}"* "zoneminder_${VERSION}.orig.tar.gz" "zmrepo@zmrepo.connortechnology.com:debian/release-${VERSION_PARTS[0]}.${VERSION_PARTS[1]}/mini-dinstall/incoming/" else if [ "$BRANCH" == "" ]; then scp "zoneminder_${VERSION}-${DISTRO}"* "zoneminder-doc_${VERSION}-${DISTRO}"* "zoneminder-dbg_${VERSION}-${DISTRO}"* "zoneminder_${VERSION}.orig.tar.gz" "zmrepo@zmrepo.connortechnology.com:debian/master/mini-dinstall/incoming/" diff --git a/utils/packpack/rsync_xfer.sh b/utils/packpack/rsync_xfer.sh index 40f5235be..c9a737a03 100755 --- a/utils/packpack/rsync_xfer.sh +++ b/utils/packpack/rsync_xfer.sh @@ -1,5 +1,10 @@ #!/bin/bash +# We don't deploy during eslint checks, so exit immediately +if [ "${DIST}" == "eslint" ]; then + exit 0 +fi + # Check to see if this script has access to all the commands it needs for CMD in sshfs rsync find fusermount mkdir; do type $CMD 2>&1 > /dev/null @@ -12,53 +17,35 @@ for CMD in sshfs rsync find fusermount mkdir; do fi done -# We only want to deploy packages during cron events -# See https://docs.travis-ci.com/user/cron-jobs/ -if [ "${TRAVIS_EVENT_TYPE}" == "cron" ] || [ "${OS}" == "debian" ] || [ "${OS}" == "ubuntu" ]; then - - if [ "${OS}" == "debian" ] || [ "${OS}" == "ubuntu" ]; then - targetfolder="debian/master/mini-dinstall/incoming" +if [ "${OS}" == "debian" ] || [ "${OS}" == "ubuntu" ] || [ "${OS}" == "raspbian" ]; then + if [ "${RELEASE}" != "" ]; then + IFS='.' read -r -a VERSION_PARTS <<< "$RELEASE" + if [ "${VERSION_PARTS[0]}.${VERSION_PARTS[1]}" == "1.30" ]; then + targetfolder="debian/release/mini-dinstall/incoming" else - targetfolder="travis" - fi - - echo - echo "Target subfolder set to $targetfolder" - echo - if [ "${USE_SFTP}" == "yes" ]; then - echo "Running \$(rsync -v -e 'ssh -vvv' build/* zmrepo@zmrepo.zoneminder.com:${targetfolder}/ 2>&1)" - rsync -v -e 'ssh -vvv' build/* zmrepo@zmrepo.zoneminder.com:${targetfolder}/ 2>&1 - if [ $? -eq 0 ]; then - echo - echo "Files copied successfully." - echo - else - echo - echo "ERROR: Attempt to rsync to zmrepo.zoneminder.com failed!" - echo - exit 99 - fi - else - mkdir -p ./zmrepo - ssh_mntchk="$(sshfs zmrepo@zmrepo.zoneminder.com:./ ./zmrepo -o workaround=rename,reconnect 2>&1)" - - if [ -z "$ssh_mntchk" ]; then - echo - echo "Remote filesystem mounted successfully." - echo "Begin transfering files..." - echo - - # Don't keep packages older than 5 days - find ./zmrepo/$targetfolder/ -maxdepth 1 -type f,l -mtime +5 -delete - rsync -vzlh --ignore-errors build/* zmrepo/$targetfolder/ - fusermount -zu zmrepo - else - echo - echo "ERROR: Attempt to mount zmrepo.zoneminder.com failed!" - echo "sshfs gave the following error message:" - echo \"$ssh_mntchk\" - echo - exit 99 - fi + targetfolder="debian/release-${VERSION_PARTS[0]}.${VERSION_PARTS[1]}/mini-dinstall/incoming" fi + else + targetfolder="debian/master/mini-dinstall/incoming" + fi +else + targetfolder="travis" +fi + +echo +echo "Target subfolder set to $targetfolder" +echo + +echo "Running \$(rsync -v -e 'ssh -vvv' build/*.{rpm,deb,dsc,tar.xz,buildinfo,changes} zmrepo@zmrepo.zoneminder.com:${targetfolder}/ 2>&1)" +rsync -v --ignore-missing-args --exclude 'external-repo.noarch.rpm' -e 'ssh -vvv' build/*.{rpm,deb,dsc,tar.xz,buildinfo,changes} zmrepo@zmrepo.zoneminder.com:${targetfolder}/ 2>&1 +if [ "$?" -eq 0 ]; then + echo + echo "Files copied successfully." + echo +else + echo + echo "ERROR: Attempt to rsync to zmrepo.zoneminder.com failed!" + echo "See log output for details." + echo + exit 99 fi diff --git a/utils/packpack/startpackpack.sh b/utils/packpack/startpackpack.sh index 35a6aeab4..6a51e2995 100755 --- a/utils/packpack/startpackpack.sh +++ b/utils/packpack/startpackpack.sh @@ -9,7 +9,7 @@ # General sanity checks checksanity () { # Check to see if this script has access to all the commands it needs - for CMD in set echo curl git ln mkdir rmdir cat patch; do + for CMD in set echo curl git ln mkdir rmdir cat patch sed; do type $CMD 2>&1 > /dev/null if [ $? -ne 0 ]; then @@ -30,7 +30,7 @@ checksanity () { ARCH="x86_64" fi - if [[ "${ARCH}" != "x86_64" && "${ARCH}" != "i386" && "${ARCH}" != "armhf" ]]; then + if [[ "${ARCH}" != "x86_64" && "${ARCH}" != "i386" && "${ARCH}" != "armhf" && "${ARCH}" != "aarch64" ]]; then echo echo "ERROR: Unsupported architecture specified \"${ARCH}\"." echo @@ -150,7 +150,7 @@ install_deb () { exit 1 fi - # Install and test the zoneminder package (only) for Ubuntu Trusty + # Install and test the zoneminder package (only) for Ubuntu Xenial pkgname="build/zoneminder_${VERSION}-${RELEASE}_amd64.deb" if [ -e $pkgname ]; then @@ -275,6 +275,8 @@ checkdeploytarget () { echo "*** TRACEROUTE ***" echo traceroute -w 2 -m 15 ${DEPLOYTARGET} + + exit 97 fi } @@ -291,43 +293,43 @@ if [ "${TRAVIS}" == "true" ]; then fi checksanity -# We don't want to build packages for all supported distros after every commit -# Only build all packages when executed via cron -# See https://docs.travis-ci.com/user/cron-jobs/ - # Steps common to Redhat distros if [ "${OS}" == "el" ] || [ "${OS}" == "fedora" ]; then - if [ "${TRAVIS_EVENT_TYPE}" == "cron" ] || [ "${TRAVIS}" != "true" ]; then - commonprep - echo "Begin Redhat build..." + commonprep + echo "Begin Redhat build..." - setrpmpkgname + # Newer Redhat distros use dnf package manager rather than yum + if [ "${DIST}" -gt "7" ]; then + sed -i 's\yum\dnf\' utils/packpack/redhat_package.mk + fi - ln -sfT distros/redhat rpm + setrpmpkgname - # The rpm specfile requires the Crud submodule folder to be empty - rm -rf web/api/app/Plugin/Crud - mkdir web/api/app/Plugin/Crud + ln -sfT distros/redhat rpm - reporpm="rpmfusion-free-release" - dlurl="https://download1.rpmfusion.org/free/${OS}/${reporpm}-${DIST}.noarch.rpm" + # The rpm specfile requires the Crud submodule folder to be empty + rm -rf web/api/app/Plugin/Crud + mkdir web/api/app/Plugin/Crud - # Give our downloaded repo rpm a common name so redhat_package.mk can find it - if [ -n "$dlurl" ] && [ $? -eq 0 ]; then - echo "Retrieving ${reporpm} repo rpm..." - curl $dlurl > build/external-repo.noarch.rpm - else - echo "ERROR: Failed to retrieve ${reporpm} repo rpm..." - echo "Download url was: $dlurl" - exit 1 - fi + reporpm="rpmfusion-free-release" + dlurl="https://download1.rpmfusion.org/free/${OS}/${reporpm}-${DIST}.noarch.rpm" - setrpmchangelog + # Give our downloaded repo rpm a common name so redhat_package.mk can find it + if [ -n "$dlurl" ] && [ $? -eq 0 ]; then + echo "Retrieving ${reporpm} repo rpm..." + curl $dlurl > build/external-repo.noarch.rpm + else + echo "ERROR: Failed to retrieve ${reporpm} repo rpm..." + echo "Download url was: $dlurl" + exit 1 + fi - echo "Starting packpack..." - execpackpack - fi; - # Steps common to Debian based distros + setrpmchangelog + + echo "Starting packpack..." + execpackpack + +# Steps common to Debian based distros elif [ "${OS}" == "debian" ] || [ "${OS}" == "ubuntu" ] || [ "${OS}" == "raspbian" ]; then commonprep echo "Begin ${OS} ${DIST} build..." @@ -348,14 +350,27 @@ elif [ "${OS}" == "debian" ] || [ "${OS}" == "ubuntu" ] || [ "${OS}" == "raspbia echo "Starting packpack..." execpackpack - # We were not triggered via cron so just build and test trusty - if [ "${OS}" == "ubuntu" ] && [ "${DIST}" == "xenial" ] && [ "${ARCH}" == "x86_64" ]; then - # If we are running inside Travis then attempt to install the deb we just built - if [ "${TRAVIS}" == "true" ]; then + # Try to install and run the newly built zoneminder package + if [ "${OS}" == "ubuntu" ] && [ "${DIST}" == "xenial" ] && [ "${ARCH}" == "x86_64" ] && [ "${TRAVIS}" == "true" ]; then + echo "Begin Deb package installation..." install_deb - fi fi + +# Steps common to eslint checks +elif [ "${OS}" == "eslint" ] || [ "${DIST}" == "eslint" ]; then + + # Check we've got npm installed + type npm 2>&1 > /dev/null + + if [ $? -ne 0 ]; then + echo + echo "ERROR: The script cannot find the required command \"npm\"." + echo + exit 1 + fi + + npm install -g eslint@5.12.0 eslint-config-google@0.11.0 eslint-plugin-html@5.0.0 eslint-plugin-php-markup@0.2.5 + echo "Begin eslint checks..." + eslint --ext .php,.js . fi -exit 0 - diff --git a/version b/version index 63984dc0b..2aeaa11ee 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.33.15 +1.35.0 diff --git a/web/ajax/add_monitors.php b/web/ajax/add_monitors.php index b7e3c376c..aaa86790e 100644 --- a/web/ajax/add_monitors.php +++ b/web/ajax/add_monitors.php @@ -54,18 +54,18 @@ if ( 0 ) { SOL_SOCKET, // socket level SO_SNDTIMEO, // timeout option array( - "sec"=>0, // Timeout in seconds - "usec"=>500 // I assume timeout in microseconds + 'sec'=>0, // Timeout in seconds + 'usec'=>500 // I assume timeout in microseconds ) ); $new_stream = null; -Info("Testing connection to " . $url_bits['host'].':'.$port); + Info('Testing connection to '.$url_bits['host'].':'.$port); if ( socket_connect( $socket, $url_bits['host'], $port ) ) { $new_stream = $url_bits; // make a copy $new_stream['port'] = $port; } else { socket_close($socket); - ZM\Info("No connection to ".$url_bits['host'] . " on port $port"); + ZM\Info('No connection to '.$url_bits['host'].' on port '.$port); continue; } if ( $new_stream ) { @@ -100,10 +100,10 @@ Info("Testing connection to " . $url_bits['host'].':'.$port); } foreach ( $available_streams as &$stream ) { # check for existence in db. - $stream['url'] = unparse_url( $stream, array('path'=>'/','query'=>'action=stream') ); - $monitors = ZM\Monitor::find( array('Path'=>$stream['url']) ); + $stream['url'] = unparse_url($stream, array('path'=>'/','query'=>'action=stream')); + $monitors = ZM\Monitor::find(array('Path'=>$stream['url'])); if ( count($monitors) ) { - ZM\Info("Found monitors matching " . $stream['url'] ); + ZM\Info('Found monitors matching ' . $stream['url'] ); $stream['Monitor'] = $monitors[0]; if ( isset( $stream['Width'] ) and ( $stream['Monitor']->Width() != $stream['Width'] ) ) { $stream['Warning'] .= 'Monitor width ('.$stream['Monitor']->Width().') and stream width ('.$stream['Width'].") do not match!\n"; @@ -114,11 +114,11 @@ Info("Testing connection to " . $url_bits['host'].':'.$port); } else { $stream['Monitor'] = clone $defaultMonitor; if ( isset($stream['Width']) ) { - $stream['Monitor']->Width( $stream['Width'] ); - $stream['Monitor']->Height( $stream['Height'] ); + $stream['Monitor']->Width($stream['Width']); + $stream['Monitor']->Height($stream['Height']); } if ( isset($stream['Name']) ) { - $stream['Monitor']->Name( $stream['Name'] ); + $stream['Monitor']->Name($stream['Name']); } } // Monitor found or not } // end foreach Stream @@ -129,16 +129,16 @@ Info("Testing connection to " . $url_bits['host'].':'.$port); return $available_streams; } // end function probe -if ( canEdit( 'Monitors' ) ) { +if ( canEdit('Monitors') ) { switch ( $_REQUEST['action'] ) { case 'probe' : { $available_streams = array(); $url_bits = null; - if ( preg_match('/(\d+)\.(\d+)\.(\d+)\.(\d+)/', $_REQUEST['url'] ) ) { - $url_bits = array( 'host'=>$_REQUEST['url'] ); + if ( preg_match('/(\d+)\.(\d+)\.(\d+)\.(\d+)/', $_REQUEST['url']) ) { + $url_bits = array('host'=>$_REQUEST['url']); } else { - $url_bits = parse_url( $_REQUEST['url'] ); + $url_bits = parse_url($_REQUEST['url']); } if ( 0 ) { @@ -155,13 +155,13 @@ if ( 0 ) { } if ( ! $url_bits ) { - ajaxError("The given URL was too malformed to parse."); + ajaxError('The given URL was too malformed to parse.'); return; } - $available_streams = probe( $url_bits ); + $available_streams = probe($url_bits); - ajaxResponse( array('Streams'=>$available_streams) ); + ajaxResponse(array('Streams'=>$available_streams)); return; } // end case url_probe case 'import': @@ -169,16 +169,16 @@ if ( 0 ) { $file = $_FILES['import_file']; - if ($file["error"] > 0) { - ajaxError($file["error"]); + if ( $file['error'] > 0 ) { + ajaxError($file['error']); return; } else { - $filename = $file["name"]; + $filename = $file['name']; - $available_streams = array(); + $available_streams = array(); $row = 1; - if (($handle = fopen($file['tmp_name'], 'r')) !== FALSE) { - while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) { + if ( ($handle = fopen($file['tmp_name'], 'r')) !== FALSE ) { + while ( ($data = fgetcsv($handle, 1000, ',')) !== FALSE ) { $name = $data[0]; $url = $data[1]; $group = $data[2]; @@ -186,16 +186,16 @@ if ( 0 ) { $url_bits = null; if ( preg_match('/(\d+)\.(\d+)\.(\d+)\.(\d+)/', $url) ) { - $url_bits = array( 'host'=>$url, 'scheme'=>'http' ); + $url_bits = array('host'=>$url, 'scheme'=>'http'); } else { - $url_bits = parse_url( $url ); + $url_bits = parse_url($url); } if ( ! $url_bits ) { ZM\Info("Bad url, skipping line $name $url $group"); continue; } - $available_streams += probe( $url_bits ); + $available_streams += probe($url_bits); //$url_bits['url'] = unparse_url( $url_bits ); //$url_bits['Monitor'] = $defaultMonitor; @@ -205,23 +205,19 @@ if ( 0 ) { } // end while rows fclose($handle); - ajaxResponse( array('Streams'=>$available_streams) ); + ajaxResponse(array('Streams'=>$available_streams)); } else { - ajaxError("Uploaded file does not exist"); + ajaxError('Uploaded file does not exist'); return; } - } } // end case import default: - { - ZM\Warning("unknown action " . $_REQUEST['action'] ); - } // end ddcase default - } + ZM\Warning('unknown action '.$_REQUEST['action']); + } // end switch action } else { - ZM\Warning("Cannot edit monitors" ); + ZM\Warning('Cannot edit monitors'); } -ajaxError( 'Unrecognised action or insufficient permissions' ); - +ajaxError('Unrecognised action '.$_REQUEST['action'].' or insufficient permissions for user ' . $user['Username']); ?> diff --git a/web/ajax/console.php b/web/ajax/console.php index 1a5919b58..ae8a60b15 100644 --- a/web/ajax/console.php +++ b/web/ajax/console.php @@ -1,35 +1,33 @@ beginTransaction(); - $dbConn->exec('LOCK TABLES Monitors WRITE'); - for ( $i = 0; $i < count($monitor_ids); $i += 1 ) { - $monitor_id = $monitor_ids[$i]; - $monitor_id = preg_replace( '/^monitor_id-/', '', $monitor_id ); - if ( ( ! $monitor_id ) or ! ( is_integer( $monitor_id ) or ctype_digit( $monitor_id ) ) ) { - Warning("Got $monitor_id from " . $monitor_ids[$i]); - continue; - } - dbQuery('UPDATE Monitors SET Sequence=? WHERE Id=?', array($i, $monitor_id)); - } // end for each monitor_id - $dbConn->commit(); - $dbConn->exec('UNLOCK TABLES'); - - return; - } // end case sort - default: - { - ZM\Warning('unknown action ' . $_REQUEST['action']); - } // end ddcase default - } + switch ( $_REQUEST['action'] ) { + case 'sort' : + { + $monitor_ids = $_POST['monitor_ids']; + # Two concurrent sorts could generate odd sortings... so lock the table. + global $dbConn; + $dbConn->beginTransaction(); + $dbConn->exec('LOCK TABLES Monitors WRITE'); + for ( $i = 0; $i < count($monitor_ids); $i += 1 ) { + $monitor_id = $monitor_ids[$i]; + $monitor_id = preg_replace('/^monitor_id-/', '', $monitor_id); + if ( ( !$monitor_id ) or ! ( is_integer($monitor_id) or ctype_digit($monitor_id) ) ) { + Warning('Got '.$monitor_id.' from '.$monitor_ids[$i]); + continue; + } + dbQuery('UPDATE Monitors SET Sequence=? WHERE Id=?', array($i, $monitor_id)); + } // end for each monitor_id + $dbConn->commit(); + $dbConn->exec('UNLOCK TABLES'); + + return; + } // end case sort + default: + ZM\Warning('unknown action '.$_REQUEST['action']); + } } else { ZM\Warning('Cannot edit monitors'); } -ajaxError('Unrecognised action or insufficient permissions'); +ajaxError('Unrecognised action '.$_REQUEST['action'].' or insufficient permissions for user ' . $user['Username']); ?> diff --git a/web/ajax/event.php b/web/ajax/event.php index cb4d3d7ad..9c2e57c2c 100644 --- a/web/ajax/event.php +++ b/web/ajax/event.php @@ -155,5 +155,5 @@ if ( canEdit('Events') ) { } // end switch action } // end if canEdit('Events') -ajaxError('Unrecognised action or insufficient permissions'); +ajaxError('Unrecognised action '.$_REQUEST['action'].' or insufficient permissions for user ' . $user['Username']); ?> diff --git a/web/ajax/log.php b/web/ajax/log.php index ef2e450f2..36a29adc3 100644 --- a/web/ajax/log.php +++ b/web/ajax/log.php @@ -41,7 +41,7 @@ function buildLogQuery($action) { } foreach ( $filter as $field=>$value ) { - if ( ! in_array($field, $filterFields) ) { + if ( !in_array($field, $filterFields) ) { ZM\Error("'$field' is not in valid filter fields " . print_r($filterField,true)); continue; } @@ -58,7 +58,7 @@ function buildLogQuery($action) { $sql .= ' ORDER BY '.$sortField.' '.$sortOrder.' LIMIT '.$limit; return array('sql'=>$sql, 'values'=>$values); -} +} # function buildLogQuery($action) switch ( $_REQUEST['task'] ) { case 'create' : @@ -70,14 +70,16 @@ switch ( $_REQUEST['task'] ) { $string = $_POST['message']; $file = !empty($_POST['file']) ? preg_replace( '/\w+:\/\/[\w.:]+\//', '', $_POST['file'] ) : ''; - if ( !empty( $_POST['line'] ) ) + if ( !empty( $_POST['line'] ) ) { $line = validInt($_POST['line']); - else + } else { $line = NULL; + } $levels = array_flip(ZM\Logger::$codes); - if ( !isset($levels[$_POST['level']]) ) + if ( !isset($levels[$_POST['level']]) ) { ZM\Panic('Unexpected logger level '.$_POST['level']); + } $level = $levels[$_POST['level']]; ZM\Logger::fetch()->logPrint($level, $string, $file, $line); } @@ -141,6 +143,10 @@ switch ( $_REQUEST['task'] ) { $logs[] = $log; } + foreach ( $options as $field => $values ) { + asort($options[$field]); + } + $available = count($logs); ajaxResponse( array( 'updated' => preg_match('/%/', DATE_FMT_CONSOLE_LONG)?strftime(DATE_FMT_CONSOLE_LONG):date(DATE_FMT_CONSOLE_LONG), diff --git a/web/ajax/status.php b/web/ajax/status.php index 47fe6a446..cf6e4498a 100644 --- a/web/ajax/status.php +++ b/web/ajax/status.php @@ -1,8 +1,11 @@ array( 'sql' => '(SELECT min(Events.Id) FROM Events WHERE Events.MonitorId = Monitors.Id' ), 'MaxEventId' => array( 'sql' => '(SELECT max(Events.Id) FROM Events WHERE Events.MonitorId = Monitors.Id' ), 'TotalEvents' => array( 'sql' => '(SELECT count(Events.Id) FROM Events WHERE Events.MonitorId = Monitors.Id' ), - 'Status' => array( 'zmu' => '-m '.escapeshellarg($_REQUEST['id'][0]).' -s' ), - 'FrameRate' => array( 'zmu' => '-m '.escapeshellarg($_REQUEST['id'][0]).' -f' ), + 'Status' => (isset($_REQUEST['id'])?array( 'zmu' => '-m '.escapeshellarg($_REQUEST['id'][0]).' -s' ):null), + 'FrameRate' => (isset($_REQUEST['id'])?array( 'zmu' => '-m '.escapeshellarg($_REQUEST['id'][0]).' -f' ):null), ), ), 'events' => array( @@ -204,6 +207,7 @@ function collectData() { $fieldSql = array(); $joinSql = array(); $groupSql = array(); + $values = array(); $elements = &$entitySpec['elements']; $lc_elements = array_change_key_case( $elements ); @@ -258,7 +262,6 @@ function collectData() { if ( $id && !empty($entitySpec['selector']) ) { $index = 0; $where = array(); - $values = array(); foreach( $entitySpec['selector'] as $selIndex => $selector ) { $selectorParamName = ':selector' . $selIndex; if ( is_array( $selector ) ) { diff --git a/web/ajax/stream.php b/web/ajax/stream.php index 910b18cb5..297b744c6 100644 --- a/web/ajax/stream.php +++ b/web/ajax/stream.php @@ -86,10 +86,8 @@ if ( sem_acquire($semaphore,1) !== false ) { $numSockets = socket_select($rSockets, $wSockets, $eSockets, intval($timeout/1000), ($timeout%1000)*1000); if ( $numSockets === false ) { - ZM\Error('socket_select failed: ' . socket_strerror(socket_last_error())); ajaxError('socket_select failed: '.socket_strerror(socket_last_error())); } else if ( $numSockets < 0 ) { - ZM\Error("Socket closed $remSockFile"); ajaxError("Socket closed $remSockFile"); } else if ( $numSockets == 0 ) { ZM\Error("Timed out waiting for msg $remSockFile"); @@ -97,7 +95,6 @@ if ( sem_acquire($semaphore,1) !== false ) { #ajaxError("Timed out waiting for msg $remSockFile"); } else if ( $numSockets > 0 ) { if ( count($rSockets) != 1 ) { - ZM\Error('Bogus return from select, '.count($rSockets).' sockets available'); ajaxError('Bogus return from select, '.count($rSockets).' sockets available'); } } @@ -119,17 +116,17 @@ if ( sem_acquire($semaphore,1) !== false ) { switch ( $data['type'] ) { case MSG_DATA_WATCH : $data = unpack('ltype/imonitor/istate/dfps/ilevel/irate/ddelay/izoom/Cdelayed/Cpaused/Cenabled/Cforced', $msg); - ZM\Logger::Debug('FPS: ' . $data['fps']); $data['fps'] = round( $data['fps'], 2 ); - ZM\Logger::Debug('FPS: ' . $data['fps'] ); $data['rate'] /= RATE_BASE; $data['delay'] = round( $data['delay'], 2 ); $data['zoom'] = round( $data['zoom']/SCALE_BASE, 1 ); - if ( ZM_OPT_USE_AUTH && ZM_AUTH_RELAY == 'hashed' ) { - $time = time(); - // Regenerate auth hash after half the lifetime of the hash - if ( (!isset($_SESSION['AuthHashGeneratedAt'])) or ( $_SESSION['AuthHashGeneratedAt'] < $time - (ZM_AUTH_HASH_TTL * 1800) ) ) { - $data['auth'] = generateAuthHash(ZM_AUTH_HASH_IPS); + if ( ZM_OPT_USE_AUTH && (ZM_AUTH_RELAY == 'hashed') ) { + $auth_hash = generateAuthHash(ZM_AUTH_HASH_IPS); + if ( isset($_REQUEST['auth']) and ($_REQUEST['auth'] != $auth_hash) ) { + $data['auth'] = $auth_hash; + ZM\Logger::Debug("including nw auth hash " . $data['auth']); + } else { + ZM\Logger::Debug('Not including nw auth hash becase it hashn\'t changed '.$auth_hash); } } ajaxResponse(array('status'=>$data)); @@ -143,12 +140,11 @@ if ( sem_acquire($semaphore,1) !== false ) { $data = unpack('ltype/Qevent/iprogress/irate/izoom/Cpaused', $msg); } $data['rate'] /= RATE_BASE; - $data['zoom'] = round( $data['zoom']/SCALE_BASE, 1 ); - if ( ZM_OPT_USE_AUTH && ZM_AUTH_RELAY == 'hashed' ) { - $time = time(); - // Regenerate auth hash after half the lifetime of the hash - if ( (!isset($_SESSION['AuthHashGeneratedAt'])) or ( $_SESSION['AuthHashGeneratedAt'] < $time - (ZM_AUTH_HASH_TTL * 1800) ) ) { - $data['auth'] = generateAuthHash(ZM_AUTH_HASH_IPS); + $data['zoom'] = round($data['zoom']/SCALE_BASE, 1); + if ( ZM_OPT_USE_AUTH && (ZM_AUTH_RELAY == 'hashed') ) { + $auth_hash = generateAuthHash(ZM_AUTH_HASH_IPS); + if ( isset($_REQUEST['auth']) and ($_REQUEST['auth'] != $auth_hash) ) { + $data['auth'] = $auth_hash; } } ajaxResponse(array('status'=>$data)); diff --git a/web/api/app/Controller/EventsController.php b/web/api/app/Controller/EventsController.php index 2eb5f7280..d9ad29b0d 100644 --- a/web/api/app/Controller/EventsController.php +++ b/web/api/app/Controller/EventsController.php @@ -15,6 +15,10 @@ class EventsController extends AppController { */ public $components = array('RequestHandler', 'Scaler', 'Image', 'Paginator'); + public function beforeRender() { + $this->set($this->Event->enumValues()); + } + public function beforeFilter() { parent::beforeFilter(); global $user; diff --git a/web/api/app/Controller/GroupsController.php b/web/api/app/Controller/GroupsController.php index 6f0a88300..5d19b5d98 100644 --- a/web/api/app/Controller/GroupsController.php +++ b/web/api/app/Controller/GroupsController.php @@ -77,16 +77,24 @@ class GroupsController extends AppController { } $this->Group->create(); - if ( $this->Group->save($this->request->data) ) { + + if ( $this->request->data['Group']['MonitorIds'] and ! isset($this->request->data['Monitor']) ) { + $this->request->data['Monitor'] = explode(',', $this->request->data['Group']['MonitorIds']); + unset($this->request->data['Group']['MonitorIds']); + } + if ( $this->Group->saveAssociated($this->request->data, array('atomic'=>true)) ) { return $this->flash( __('The group has been saved.'), array('action' => 'index') ); - } - } - $monitors = $this->Group->Monitor->find('list'); + } else { + ZM\Error("Failed to save Group"); + debug($this->Group->invalidFields()); + } + } # end if post + $monitors = $this->Group->Monitor->find('list'); $this->set(compact('monitors')); - } + } # end add /** * edit method diff --git a/web/api/app/Controller/HostController.php b/web/api/app/Controller/HostController.php index 300352949..cf6e99b53 100644 --- a/web/api/app/Controller/HostController.php +++ b/web/api/app/Controller/HostController.php @@ -50,8 +50,10 @@ class HostController extends AppController { $cred_depr = []; if ( $username && $password ) { + ZM\Logger::Debug('Username and password provided, generating access and refresh tokens'); $cred = $this->_getCredentials(true, '', $username); // generate refresh } else { + ZM\Logger::Debug('Only generating access token'); $cred = $this->_getCredentials(false, $token); // don't generate refresh } @@ -69,6 +71,8 @@ class HostController extends AppController { $cred_depr = $this->_getCredentialsDeprecated(); $login_array['credentials'] = $cred_depr[0]; $login_array['append_password'] = $cred_depr[1]; + } else { + ZM\Logger::Debug('Legacy Auth is disabled, not generating auth= credentials'); } $login_array['version'] = $ver[0]; @@ -108,8 +112,11 @@ class HostController extends AppController { private function _getCredentials($generate_refresh_token=false, $token='', $username='') { - if ( !ZM_OPT_USE_AUTH ) + if ( !ZM_OPT_USE_AUTH ) { + ZM\Error('OPT_USE_AUTH is turned off. Tokens will be null'); return; + } + if ( !ZM_AUTH_HASH_SECRET ) throw new ForbiddenException(__('Please create a valid AUTH_HASH_SECRET in ZoneMinder')); @@ -136,7 +143,7 @@ class HostController extends AppController { }*/ $access_issued_at = time(); - $access_ttl = (ZM_AUTH_HASH_TTL || 2) * 3600; + $access_ttl = max(ZM_AUTH_HASH_TTL,1) * 3600; // by default access token will expire in 2 hrs // you can change it by changing the value of ZM_AUTH_HASH_TLL diff --git a/web/api/app/Controller/MonitorsController.php b/web/api/app/Controller/MonitorsController.php index ea7a666a1..92114a558 100644 --- a/web/api/app/Controller/MonitorsController.php +++ b/web/api/app/Controller/MonitorsController.php @@ -44,33 +44,26 @@ class MonitorsController extends AppController { } else { $conditions = array(); } - global $user; $allowedMonitors = $user ? preg_split('@,@', $user['MonitorIds'], NULL, PREG_SPLIT_NO_EMPTY) : null; if ( $allowedMonitors ) { $conditions['Monitor.Id' ] = $allowedMonitors; } - $find_array = array('conditions'=>$conditions,'contain'=>array('Group')); - if ( isset($conditions['GroupId']) ) { - $find_array['joins'] = array( + $find_array = array( + 'conditions' => &$conditions, + 'contain' => array('Group'), + 'joins' => array( array( 'table' => 'Groups_Monitors', - 'type' => 'inner', + 'type' => 'left', 'conditions' => array( - 'Groups_Monitors.MonitorId = Monitor.Id' + 'Groups_Monitors.MonitorId = Monitor.Id', ), ), - //array( - //'table' => 'Groups', - //'type' => 'inner', - //'conditions' => array( - //'Groups.Id = Groups_Monitors.GroupId', - //'Groups.Id' => $this->request->params['GroupId'], - //), - //) - ); - } + ), + 'group' => '`Monitor`.`Id`', + ); $monitors = $this->Monitor->find('all',$find_array); $this->set(array( 'monitors' => $monitors, @@ -249,16 +242,11 @@ class MonitorsController extends AppController { // where C=on|off|status public function alarm() { $id = $this->request->params['named']['id']; - $cmd = strtolower($this->request->params['named']['command']); if ( !$this->Monitor->exists($id) ) { throw new NotFoundException(__('Invalid monitor')); } - if ( $cmd != 'on' && $cmd != 'off' && $cmd != 'status' ) { - throw new BadRequestException(__('Invalid command')); - } - $zm_path_bin = Configure::read('ZM_PATH_BIN'); - $mToken = $this->request->query('token') ? $this->request->query('token') : null; + $cmd = strtolower($this->request->params['named']['command']); switch ($cmd) { case 'on': $q = '-a'; @@ -272,42 +260,43 @@ class MonitorsController extends AppController { $verbose = ''; // zmu has a bug - gives incorrect verbose output in this case $q = '-s'; break; + default : + throw new BadRequestException(__('Invalid command')); } // form auth key based on auth credentials - $this->loadModel('Config'); - $options = array('conditions' => array('Config.' . $this->Config->primaryKey => 'ZM_OPT_USE_AUTH')); - $config = $this->Config->find('first', $options); - $zmOptAuth = $config['Config']['Value']; - - $options = array('conditions' => array('Config.' . $this->Config->primaryKey => 'ZM_AUTH_RELAY')); - $config = $this->Config->find('first', $options); - $zmAuthRelay = $config['Config']['Value']; - $auth = ''; - if ( $zmOptAuth ) { - if ($mToken) { + if ( ZM_OPT_USE_AUTH ) { + global $user; + $mToken = $this->request->query('token') ? $this->request->query('token') : $this->request->data('token');; + if ( $mToken ) { $auth = ' -T '.$mToken; - } - elseif ( $zmAuthRelay == 'hashed' ) { - $options = array('conditions' => array('Config.' . $this->Config->primaryKey => 'ZM_AUTH_HASH_SECRET')); - $config = $this->Config->find('first', $options); - $zmAuthHashSecret = $config['Config']['Value']; + } else if ( ZM_AUTH_RELAY == 'hashed' ) { + $auth = ' -A '.generateAuthHash(ZM_AUTH_HASH_IPS); + } else if ( ZM_AUTH_RELAY == 'plain' ) { + # Plain requires the plain text password which must either be in request or stored in session + $password = $this->request->query('pass') ? $this->request->query('pass') : $this->request->data('pass');; + if ( !$password ) + $password = $this->request->query('password') ? $this->request->query('password') : $this->request->data('password'); - $time = localtime(); - $ak = $zmAuthHashSecret.$this->Session->Read('username').$this->Session->Read('passwordHash').$time[2].$time[3].$time[4].$time[5]; - $ak = md5($ak); - $auth = ' -A '.$ak; - } else if ( $zmAuthRelay == 'plain' ) { - $auth = ' -U ' .$this->Session->Read('username').' -P '.$this->Session->Read('password'); - - } else if ( $zmAuthRelay == 'none' ) { - $auth = ' -U ' .$this->Session->Read('username'); + if ( ! $password ) { + # during auth the session will have been populated with the plaintext password + $stateful = $this->request->query('stateful') ? $this->request->query('stateful') : $this->request->data('stateful'); + if ( $stateful ) { + $password = $_SESSION['password']; + } + } else if ( $_COOKIE['ZMSESSID'] ) { + $password = $_SESSION['password']; + } + + $auth = ' -U ' .$user['Username'].' -P '.$password; + } else if ( ZM_AUTH_RELAY == 'none' ) { + $auth = ' -U ' .$user['Username']; } } - $shellcmd = escapeshellcmd("$zm_path_bin/zmu $verbose -m$id $q $auth"); + $shellcmd = escapeshellcmd(ZM_PATH_BIN."/zmu $verbose -m$id $q $auth"); $status = exec ($shellcmd); $this->set(array( diff --git a/web/api/app/Controller/ServersController.php b/web/api/app/Controller/ServersController.php index c30de038f..c3ac6fad7 100644 --- a/web/api/app/Controller/ServersController.php +++ b/web/api/app/Controller/ServersController.php @@ -17,12 +17,17 @@ class ServersController extends AppController { public function beforeFilter() { parent::beforeFilter(); + /* + * A user needs the server data to calculate how to view a monitor, and there really isn't anything sensitive in this data. + * So it has been decided for now to just let everyone read it. + global $user; $canView = (!$user) || ($user['System'] != 'None'); if ( !$canView ) { throw new UnauthorizedException(__('Insufficient Privileges')); return; } + */ } /** @@ -34,7 +39,7 @@ class ServersController extends AppController { $this->Server->recursive = 0; $options = ''; - $servers = $this->Server->find('all',$options); + $servers = $this->Server->find('all', $options); $this->set(array( 'servers' => $servers, '_serialize' => array('servers') @@ -50,13 +55,13 @@ class ServersController extends AppController { */ public function view($id = null) { $this->Server->recursive = 0; - if (!$this->Server->exists($id)) { + if ( !$this->Server->exists($id) ) { throw new NotFoundException(__('Invalid server')); } $restricted = ''; $options = array('conditions' => array( - array('Server.' . $this->Server->primaryKey => $id), + array('Server.'.$this->Server->primaryKey => $id), $restricted ) ); diff --git a/web/api/app/Model/Group.php b/web/api/app/Model/Group.php index 108f9b9c7..04d783b14 100644 --- a/web/api/app/Model/Group.php +++ b/web/api/app/Model/Group.php @@ -59,7 +59,7 @@ class Group extends AppModel { * * @var array */ - public $hasMany = array( + public $hasAndBelongsToMany = array( 'Monitor' => array( 'className' => 'Monitor', 'joinTable' => 'Groups_Monitors', @@ -77,4 +77,5 @@ class Group extends AppModel { 'counterQuery' => '' ), ); + var $actsAs = array( 'Containable' ); } diff --git a/web/includes/Control.php b/web/includes/Control.php index 39c259092..2121061b1 100644 --- a/web/includes/Control.php +++ b/web/includes/Control.php @@ -128,12 +128,16 @@ class Control extends ZM_Object { $cmds['PresetHome'] = 'presetHome'; if ( $this->CanZoom() ) { - if ( $this->CanZoomCon() ) + if ( $this->CanZoomCon() ) { $cmds['ZoomRoot'] = 'zoomCon'; - elseif ( $this->CanZoomRel() ) + } else if ( $this->CanZoomRel() ) { $cmds['ZoomRoot'] = 'zoomRel'; - elseif ( $this->CanZoomAbs() ) + } else if ( $this->CanZoomAbs() ) { $cmds['ZoomRoot'] = 'zoomAbs'; + } else { + $cmds['ZoomRoot'] = ''; + Error('No zoom type selected. Please select Continuous, Relative, Absolute'); + } $cmds['ZoomTele'] = $cmds['ZoomRoot'].'Tele'; $cmds['ZoomWide'] = $cmds['ZoomRoot'].'Wide'; $cmds['ZoomStop'] = 'zoomStop'; @@ -142,12 +146,16 @@ class Control extends ZM_Object { } if ( $this->CanFocus() ) { - if ( $this->CanFocusCon() ) + if ( $this->CanFocusCon() ) { $cmds['FocusRoot'] = 'focusCon'; - elseif ( $this->CanFocusRel() ) + } else if ( $this->CanFocusRel() ) { $cmds['FocusRoot'] = 'focusRel'; - elseif ( $this->CanFocusAbs() ) + } else if ( $this->CanFocusAbs() ) { $cmds['FocusRoot'] = 'focusAbs'; + } else { + $cmds['FocusRoot'] = ''; + Error('No focus type selected. Please select Continuous, Relative, Absolute'); + } $cmds['FocusFar'] = $cmds['FocusRoot'].'Far'; $cmds['FocusNear'] = $cmds['FocusRoot'].'Near'; $cmds['FocusStop'] = 'focusStop'; @@ -156,12 +164,16 @@ class Control extends ZM_Object { } if ( $this->CanIris() ) { - if ( $this->CanIrisCon() ) + if ( $this->CanIrisCon() ) { $cmds['IrisRoot'] = 'irisCon'; - elseif ( $this->CanIrisRel() ) + } else if ( $this->CanIrisRel() ) { $cmds['IrisRoot'] = 'irisRel'; - elseif ( $this->CanIrisAbs() ) + } else if ( $this->CanIrisAbs() ) { $cmds['IrisRoot'] = 'irisAbs'; + } else { + $cmds['IrisRoot'] = ''; + Error('No iris type selected. Please select Continuous, Relative, Absolute'); + } $cmds['IrisOpen'] = $cmds['IrisRoot'].'Open'; $cmds['IrisClose'] = $cmds['IrisRoot'].'Close'; $cmds['IrisStop'] = 'irisStop'; @@ -170,12 +182,16 @@ class Control extends ZM_Object { } if ( $this->CanWhite() ) { - if ( $this->CanWhiteCon() ) + if ( $this->CanWhiteCon() ) { $cmds['WhiteRoot'] = 'whiteCon'; - elseif ( $this->CanWhiteRel() ) + } else if ( $this->CanWhiteRel() ) { $cmds['WhiteRoot'] = 'whiteRel'; - elseif ( $this->CanWhiteAbs() ) + } else if ( $this->CanWhiteAbs() ) { $cmds['WhiteRoot'] = 'whiteAbs'; + } else { + Error('No White type selected. Please select Continuous, Relative, Absolute'); + $cmds['WhiteRoot'] = ''; + } $cmds['WhiteIn'] = $cmds['WhiteRoot'].'In'; $cmds['WhiteOut'] = $cmds['WhiteRoot'].'Out'; $cmds['WhiteAuto'] = 'whiteAuto'; @@ -183,12 +199,16 @@ class Control extends ZM_Object { } if ( $this->CanGain() ) { - if ( $this->CanGainCon() ) + if ( $this->CanGainCon() ) { $cmds['GainRoot'] = 'gainCon'; - elseif ( $this->CanGainRel() ) + } else if ( $this->CanGainRel() ) { $cmds['GainRoot'] = 'gainRel'; - elseif ( $this->CanGainAbs() ) + } else if ( $this->CanGainAbs() ) { $cmds['GainRoot'] = 'gainAbs'; + } else { + Error('No Gain type selected'); + $cmds['GainRoot'] = ''; + } $cmds['GainUp'] = $cmds['GainRoot'].'Up'; $cmds['GainDown'] = $cmds['GainRoot'].'Down'; $cmds['GainAuto'] = 'gainAuto'; @@ -207,6 +227,7 @@ class Control extends ZM_Object { $cmds['Center'] = $cmds['PresetHome']; } else { $cmds['MoveRoot'] = ''; + Error('No move type selected. Please select Continuous, Relative, Absolute'); } $cmds['MoveUp'] = $cmds['MoveRoot'].'Up'; diff --git a/web/includes/Event.php b/web/includes/Event.php index 460d6eaa1..f1ba99626 100644 --- a/web/includes/Event.php +++ b/web/includes/Event.php @@ -47,14 +47,18 @@ class Event extends ZM_Object { return ZM_Object::_find_one(get_class(), $parameters, $options); } + public static function clear_cache() { + return ZM_Object::_clear_cache(get_class()); + } + public function Storage( $new = null ) { if ( $new ) { $this->{'Storage'} = $new; } - if ( ! ( array_key_exists('Storage', $this) and $this->{'Storage'} ) ) { + if ( ! ( property_exists($this, 'Storage') and $this->{'Storage'} ) ) { if ( isset($this->{'StorageId'}) and $this->{'StorageId'} ) $this->{'Storage'} = Storage::find_one(array('Id'=>$this->{'StorageId'})); - if ( ! ( array_key_exists('Storage', $this) and $this->{'Storage'} ) ) + if ( ! ( property_exists($this, 'Storage') and $this->{'Storage'} ) ) $this->{'Storage'} = new Storage(NULL); } return $this->{'Storage'}; @@ -64,10 +68,10 @@ class Event extends ZM_Object { if ( $new ) { $this->{'SecondaryStorage'} = $new; } - if ( ! ( array_key_exists('SecondaryStorage', $this) and $this->{'SecondaryStorage'} ) ) { + if ( ! ( property_exists($this, 'SecondaryStorage') and $this->{'SecondaryStorage'} ) ) { if ( isset($this->{'SecondaryStorageId'}) and $this->{'SecondaryStorageId'} ) $this->{'SecondaryStorage'} = Storage::find_one(array('Id'=>$this->{'SecondaryStorageId'})); - if ( ! ( array_key_exists('SecondaryStorage', $this) and $this->{'SecondaryStorage'} ) ) + if ( ! ( property_exists($this, 'SecondaryStorage') and $this->{'SecondaryStorage'} ) ) $this->{'SecondaryStorage'} = new Storage(NULL); } return $this->{'SecondaryStorage'}; @@ -262,7 +266,7 @@ class Event extends ZM_Object { if ( is_null($new) or ( $new != '' ) ) { $this->{'DiskSpace'} = $new; } - if ( (!array_key_exists('DiskSpace',$this)) or (null === $this->{'DiskSpace'}) ) { + if ( (!property_exists($this, 'DiskSpace')) or (null === $this->{'DiskSpace'}) ) { $this->{'DiskSpace'} = folder_size($this->Path()); dbQuery('UPDATE Events SET DiskSpace=? WHERE Id=?', array($this->{'DiskSpace'}, $this->{'Id'})); } @@ -298,7 +302,7 @@ class Event extends ZM_Object { } // end function createListThumbnail function ThumbnailWidth( ) { - if ( ! ( array_key_exists('ThumbnailWidth', $this) ) ) { + if ( ! ( property_exists($this, 'ThumbnailWidth') ) ) { if ( ZM_WEB_LIST_THUMB_WIDTH ) { $this->{'ThumbnailWidth'} = ZM_WEB_LIST_THUMB_WIDTH; $scale = (SCALE_BASE*ZM_WEB_LIST_THUMB_WIDTH)/$this->{'Width'}; @@ -315,7 +319,7 @@ class Event extends ZM_Object { } // end function ThumbnailWidth function ThumbnailHeight( ) { - if ( ! ( array_key_exists('ThumbnailHeight', $this) ) ) { + if ( ! ( property_exists($this, 'ThumbnailHeight') ) ) { if ( ZM_WEB_LIST_THUMB_WIDTH ) { $this->{'ThumbnailWidth'} = ZM_WEB_LIST_THUMB_WIDTH; $scale = (SCALE_BASE*ZM_WEB_LIST_THUMB_WIDTH)/$this->{'Width'}; @@ -413,31 +417,27 @@ class Event extends ZM_Object { } } // end if capture file exists } // end if analyze file exists - } + } // end if frame or snapshot $captPath = $eventPath.'/'.$captImage; if ( ! file_exists($captPath) ) { Error("Capture file does not exist at $captPath"); } - //echo "CI:$captImage, CP:$captPath, TCP:$captPath
"; - $analImage = sprintf('%0'.ZM_EVENT_IMAGE_DIGITS.'d-analyse.jpg', $frame['FrameId']); $analPath = $eventPath.'/'.$analImage; - //echo "AI:$analImage, AP:$analPath, TAP:$analPath
"; - $alarmFrame = $frame['Type']=='Alarm'; $hasAnalImage = $alarmFrame && file_exists($analPath) && filesize($analPath); $isAnalImage = $hasAnalImage && !$captureOnly; - if ( !ZM_WEB_SCALE_THUMBS || $scale >= SCALE_BASE || !function_exists('imagecreatefromjpeg') ) { + if ( !ZM_WEB_SCALE_THUMBS || ($scale >= SCALE_BASE) || !function_exists('imagecreatefromjpeg') ) { $imagePath = $thumbPath = $isAnalImage ? $analPath : $captPath; $imageFile = $imagePath; $thumbFile = $thumbPath; } else { - if ( version_compare( phpversion(), '4.3.10', '>=') ) + if ( version_compare(phpversion(), '4.3.10', '>=') ) $fraction = sprintf('%.3F', $scale/SCALE_BASE); else $fraction = sprintf('%.3f', $scale/SCALE_BASE); @@ -455,19 +455,19 @@ class Event extends ZM_Object { } $thumbFile = $thumbPath; - if ( $overwrite || ! file_exists( $thumbFile ) || ! filesize( $thumbFile ) ) { + if ( $overwrite || ! file_exists($thumbFile) || ! filesize($thumbFile) ) { // Get new dimensions - list( $imageWidth, $imageHeight ) = getimagesize( $imagePath ); + list( $imageWidth, $imageHeight ) = getimagesize($imagePath); $thumbWidth = $imageWidth * $fraction; $thumbHeight = $imageHeight * $fraction; // Resample - $thumbImage = imagecreatetruecolor( $thumbWidth, $thumbHeight ); - $image = imagecreatefromjpeg( $imagePath ); - imagecopyresampled( $thumbImage, $image, 0, 0, 0, 0, $thumbWidth, $thumbHeight, $imageWidth, $imageHeight ); + $thumbImage = imagecreatetruecolor($thumbWidth, $thumbHeight); + $image = imagecreatefromjpeg($imagePath); + imagecopyresampled($thumbImage, $image, 0, 0, 0, 0, $thumbWidth, $thumbHeight, $imageWidth, $imageHeight); - if ( !imagejpeg( $thumbImage, $thumbPath ) ) - Error( "Can't create thumbnail '$thumbPath'" ); + if ( !imagejpeg($thumbImage, $thumbPath) ) + Error("Can't create thumbnail '$thumbPath'"); } } # Create thumbnails @@ -507,11 +507,12 @@ class Event extends ZM_Object { if ( ZM_OPT_USE_AUTH ) { if ( ZM_AUTH_RELAY == 'hashed' ) { $url .= '?auth='.generateAuthHash( ZM_AUTH_HASH_IPS ); - } elseif ( ZM_AUTH_RELAY == 'plain' ) { - $url = '?user='.$_SESSION['username']; - $url = '?pass='.$_SESSION['password']; - } elseif ( ZM_AUTH_RELAY == 'none' ) { - $url = '?user='.$_SESSION['username']; + } else if ( ZM_AUTH_RELAY == 'plain' ) { + $url .= '?user='.$_SESSION['username']; + $url .= '?pass='.$_SESSION['password']; + } else { + Error('Multi-Server requires AUTH_RELAY be either HASH or PLAIN'); + return; } } Logger::Debug("sending command to $url"); @@ -550,15 +551,16 @@ class Event extends ZM_Object { $Server = $Storage->ServerId() ? $Storage->Server() : $this->Monitor()->Server(); if ( $Server->Id() != ZM_SERVER_ID ) { - $url = $Server->UrlToApi() . '/events/'.$this->{'Id'}.'.json'; + $url = $Server->UrlToApi().'/events/'.$this->{'Id'}.'.json'; if ( ZM_OPT_USE_AUTH ) { if ( ZM_AUTH_RELAY == 'hashed' ) { - $url .= '?auth='.generateAuthHash( ZM_AUTH_HASH_IPS ); + $url .= '?auth='.generateAuthHash(ZM_AUTH_HASH_IPS); } elseif ( ZM_AUTH_RELAY == 'plain' ) { - $url = '?user='.$_SESSION['username']; - $url = '?pass='.$_SESSION['password']; - } elseif ( ZM_AUTH_RELAY == 'none' ) { - $url = '?user='.$_SESSION['username']; + $url .= '?user='.$_SESSION['username']; + $url .= '?pass='.$_SESSION['password']; + } else { + Error('Multi-Server requires AUTH_RELAY be either HASH or PLAIN'); + return; } } Logger::Debug("sending command to $url"); diff --git a/web/includes/Filter.php b/web/includes/Filter.php index fa7c41bff..c15d8ba6a 100644 --- a/web/includes/Filter.php +++ b/web/includes/Filter.php @@ -11,6 +11,9 @@ class Filter extends ZM_Object { 'AutoExecute' => 0, 'AutoExecuteCmd' => 0, 'AutoEmail' => 0, + 'EmailTo' => '', + 'EmailSubject' => '', + 'EmailBody' => '', 'AutoDelete' => 0, 'AutoArchive' => 0, 'AutoVideo' => 0, @@ -39,8 +42,8 @@ class Filter extends ZM_Object { $this->{'Query'} = func_get_arg(0);; $this->{'Query_json'} = jsonEncode($this->{'Query'}); } - if ( !array_key_exists('Query', $this) ) { - if ( array_key_exists('Query_json', $this) and $this->{'Query_json'} ) { + if ( !property_exists($this, 'Query') ) { + if ( property_exists($this, 'Query_json') and $this->{'Query_json'} ) { $this->{'Query'} = jsonDecode($this->{'Query_json'}); } else { $this->{'Query'} = array(); @@ -115,13 +118,17 @@ class Filter extends ZM_Object { public function control($command, $server_id=null) { $Servers = $server_id ? Server::find(array('Id'=>$server_id)) : Server::find(array('Status'=>'Running')); - if ( !count($Servers) and !$server_id ) { - # This will be the non-multi-server case - $Servers = array(new Server()); + if ( !count($Servers) ) { + if ( !$server_id ) { + # This will be the non-multi-server case + $Servers = array(new Server()); + } else { + Warning("Server not found for id $server_id"); + } } foreach ( $Servers as $Server ) { - if ( !defined('ZM_SERVER_ID') or !$Server->Id() or ZM_SERVER_ID==$Server->Id() ) { + if ( (!defined('ZM_SERVER_ID')) or (!$Server->Id()) or (ZM_SERVER_ID==$Server->Id()) ) { # Local Logger::Debug("Controlling filter locally $command for server ".$Server->Id()); daemonControl($command, 'zmfilter.pl', '--filter_id='.$this->{'Id'}.' --daemon'); @@ -139,7 +146,7 @@ class Filter extends ZM_Object { $url = '?user='.$_SESSION['username']; } } - $url .= '&view=filter&action=control&command='.$command.'&Id='.$this->Id().'&ServerId='.$Server->Id(); + $url .= '&view=filter&object=filter&action=control&command='.$command.'&Id='.$this->Id().'&ServerId='.$Server->Id(); Logger::Debug("sending command to $url"); $data = array(); if ( defined('ZM_ENABLE_CSRF_MAGIC') ) { diff --git a/web/includes/Group.php b/web/includes/Group.php index b329946a3..3a34e0af4 100644 --- a/web/includes/Group.php +++ b/web/includes/Group.php @@ -18,7 +18,7 @@ class Group extends ZM_Object { } public function delete() { - if ( array_key_exists('Id', $this) ) { + if ( property_exists($this, 'Id') ) { dbQuery('DELETE FROM Groups_Monitors WHERE GroupId=?', array($this->{'Id'})); dbQuery('UPDATE Groups SET ParentId=NULL WHERE ParentId=?', array($this->{'Id'})); dbQuery('DELETE FROM Groups WHERE Id=?', array($this->{'Id'})); @@ -35,7 +35,7 @@ class Group extends ZM_Object { if ( isset($new) ) { $this->{'depth'} = $new; } - if ( !array_key_exists('depth', $this) or ($this->{'depth'} === null) ) { + if ( !property_exists($this, 'depth') or ($this->{'depth'} === null) ) { $this->{'depth'} = 0; if ( $this->{'ParentId'} != null ) { $Parent = Group::find_one(array('Id'=>$this->{'ParentId'})); @@ -46,7 +46,7 @@ class Group extends ZM_Object { } // end public function depth public function MonitorIds( ) { - if ( ! array_key_exists('MonitorIds', $this) ) { + if ( ! property_exists($this, 'MonitorIds') ) { $this->{'MonitorIds'} = dbFetchAll('SELECT MonitorId FROM Groups_Monitors WHERE GroupId=?', 'MonitorId', array($this->{'Id'})); } return $this->{'MonitorIds'}; diff --git a/web/includes/Monitor.php b/web/includes/Monitor.php index 83541065b..71bc8a658 100644 --- a/web/includes/Monitor.php +++ b/web/includes/Monitor.php @@ -9,129 +9,134 @@ require_once('Storage.php'); class Monitor extends ZM_Object { protected static $table = 'Monitors'; -protected $defaults = array( - 'Id' => null, - 'Name' => '', - 'ServerId' => 0, - 'StorageId' => 0, - 'Type' => 'Ffmpeg', - 'Function' => 'Mocord', - 'Enabled' => array('type'=>'boolean','default'=>1), - 'LinkedMonitors' => array('type'=>'set', 'default'=>null), - 'Triggers' => array('type'=>'set','default'=>''), - 'Device' => '', - 'Channel' => 0, - 'Format' => '0', - 'V4LMultiBuffer' => null, - 'V4LCapturesPerFrame' => 1, - 'Protocol' => null, - 'Method' => '', - 'Host' => null, - 'Port' => '', - 'SubPath' => '', - 'Path' => null, - 'Options' => null, - 'User' => null, - 'Pass' => null, - // These are NOT NULL default 0 in the db, but 0 is not a valid value. FIXME - 'Width' => null, - 'Height' => null, - 'Colours' => 4, - 'Palette' => '0', - 'Orientation' => null, - 'Deinterlacing' => 0, - 'DecoderHWAccelName' => null, - 'DecoderHWAccelDevice' => null, - 'SaveJPEGs' => 3, - 'VideoWriter' => '0', - 'OutputCodec' => null, - 'OutputContainer' => null, - 'EncoderParameters' => "# Lines beginning with # are a comment \n# For changing quality, use the crf option\n# 1 is best, 51 is worst quality\n#crf=23\n", - 'RecordAudio' => array('type'=>'boolean', 'default'=>0), - 'RTSPDescribe' => array('type'=>'boolean','default'=>0), - 'Brightness' => -1, - 'Contrast' => -1, - 'Hue' => -1, - 'Colour' => -1, - 'EventPrefix' => 'Event-', - 'LabelFormat' => '%N - %d/%m/%y %H:%M:%S', - 'LabelX' => 0, - 'LabelY' => 0, - 'LabelSize' => 1, - 'ImageBufferCount' => 100, - 'WarmupCount' => 0, - 'PreEventCount' => 0, - 'PostEventCount' => 0, - 'StreamReplayBuffer' => 0, - 'AlarmFrameCount' => 1, - 'SectionLength' => 600, - 'MinSectionLength' => 10, - 'FrameSkip' => 0, - 'MotionFrameSkip' => 0, - 'AnalysisFPSLimit' => null, - 'OutputCodec' => '0', - 'Encoder' => 'auto', - 'OutputContainer' => 'auto', - 'Triggers' => null, - 'AnalysisUpdateDelay' => 0, - 'MaxFPS' => null, - 'AlarmMaxFPS' => null, - 'FPSReportInterval' => 100, - 'RefBlendPerc' => 6, - 'AlarmRefBlendPerc' => 6, - 'Controllable' => array('type'=>'boolean','default'=>0), - 'ControlId' => null, - 'ControlDevice' => null, - 'ControlAddress' => null, - 'AutoStopTimeout' => null, - 'TrackMotion' => array('type'=>'boolean','default'=>0), - 'TrackDelay' => null, - 'ReturnLocation' => -1, - 'ReturnDelay' => null, - 'DefaultRate' => 100, - 'DefaultScale' => 100, - 'SignalCheckPoints' => 0, - 'SignalCheckColour' => '#0000BE', - 'WebColour' => 'red', - 'Exif' => array('type'=>'boolean','default'=>0), - 'Sequence' => null, - 'TotalEvents' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), - 'TotalEventDiskSpace' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), - 'HourEvents' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), - 'HourEventDiskSpace' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), - 'DayEvents' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), - 'DayEventDiskSpace' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), - 'WeekEvents' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), - 'WeekEventDiskSpace' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), - 'MonthEvents' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), - 'MonthEventDiskSpace' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), - 'ArchivedEvents' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), - 'ArchivedEventDiskSpace' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), - 'ZoneCount' => 0, - 'Refresh' => null, - 'DefaultCodec' => 'auto', - 'GroupIds' => array('default'=>array(), 'do_not_update'=>1), -); -private $status_fields = array( - 'Status' => null, - 'AnalysisFPS' => null, - 'CaptureFPS' => null, - 'CaptureBandwidth' => null, -); + protected $defaults = array( + 'Id' => null, + 'Name' => '', + 'Notes' => '', + 'ServerId' => 0, + 'StorageId' => 0, + 'Type' => 'Ffmpeg', + 'Function' => 'Mocord', + 'Enabled' => array('type'=>'boolean','default'=>1), + 'LinkedMonitors' => array('type'=>'set', 'default'=>null), + 'Triggers' => array('type'=>'set','default'=>''), + 'Device' => '', + 'Channel' => 0, + 'Format' => '0', + 'V4LMultiBuffer' => null, + 'V4LCapturesPerFrame' => 1, + 'Protocol' => null, + 'Method' => '', + 'Host' => null, + 'Port' => '', + 'SubPath' => '', + 'Path' => null, + 'Options' => null, + 'User' => null, + 'Pass' => null, + // These are NOT NULL default 0 in the db, but 0 is not a valid value. FIXME + 'Width' => null, + 'Height' => null, + 'Colours' => 4, + 'Palette' => '0', + 'Orientation' => null, + 'Deinterlacing' => 0, + 'DecoderHWAccelName' => null, + 'DecoderHWAccelDevice' => null, + 'SaveJPEGs' => 3, + 'VideoWriter' => '0', + 'OutputCodec' => null, + 'Encoder' => 'auto', + 'OutputContainer' => null, + 'EncoderParameters' => "# Lines beginning with # are a comment \n# For changing quality, use the crf option\n# 1 is best, 51 is worst quality\n#crf=23\n", + 'RecordAudio' => array('type'=>'boolean', 'default'=>0), + 'RTSPDescribe' => array('type'=>'boolean','default'=>0), + 'Brightness' => -1, + 'Contrast' => -1, + 'Hue' => -1, + 'Colour' => -1, + 'EventPrefix' => 'Event-', + 'LabelFormat' => '%N - %d/%m/%y %H:%M:%S', + 'LabelX' => 0, + 'LabelY' => 0, + 'LabelSize' => 1, + 'ImageBufferCount' => 20, + 'WarmupCount' => 0, + 'PreEventCount' => 5, + 'PostEventCount' => 5, + 'StreamReplayBuffer' => 0, + 'AlarmFrameCount' => 1, + 'SectionLength' => 600, + 'MinSectionLength' => 10, + 'FrameSkip' => 0, + 'MotionFrameSkip' => 0, + 'AnalysisFPSLimit' => null, + 'AnalysisUpdateDelay' => 0, + 'MaxFPS' => null, + 'AlarmMaxFPS' => null, + 'FPSReportInterval' => 100, + 'RefBlendPerc' => 6, + 'AlarmRefBlendPerc' => 6, + 'Controllable' => array('type'=>'boolean','default'=>0), + 'ControlId' => null, + 'ControlDevice' => null, + 'ControlAddress' => null, + 'AutoStopTimeout' => null, + 'TrackMotion' => array('type'=>'boolean','default'=>0), + 'TrackDelay' => null, + 'ReturnLocation' => -1, + 'ReturnDelay' => null, + 'DefaultRate' => 100, + 'DefaultScale' => 100, + 'SignalCheckPoints' => 0, + 'SignalCheckColour' => '#0000BE', + 'WebColour' => '#ff0000', + 'Exif' => array('type'=>'boolean','default'=>0), + 'Sequence' => null, + 'TotalEvents' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), + 'TotalEventDiskSpace' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), + 'HourEvents' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), + 'HourEventDiskSpace' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), + 'DayEvents' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), + 'DayEventDiskSpace' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), + 'WeekEvents' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), + 'WeekEventDiskSpace' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), + 'MonthEvents' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), + 'MonthEventDiskSpace' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), + 'ArchivedEvents' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), + 'ArchivedEventDiskSpace' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), + 'ZoneCount' => 0, + 'Refresh' => null, + 'DefaultCodec' => 'auto', + 'GroupIds' => array('default'=>array(), 'do_not_update'=>1), + ); + private $status_fields = array( + 'Status' => null, + 'AnalysisFPS' => null, + 'CaptureFPS' => null, + 'CaptureBandwidth' => null, + ); public function Control() { - if ( !array_key_exists('Control', $this) ) { + if ( !property_exists($this, 'Control') ) { if ( $this->ControlId() ) $this->{'Control'} = Control::find_one(array('Id'=>$this->{'ControlId'})); - if ( !(array_key_exists('Control', $this) and $this->{'Control'}) ) + if ( !(property_exists($this, 'Control') and $this->{'Control'}) ) $this->{'Control'} = new Control(); } return $this->{'Control'}; } public function Server() { - return new Server($this->{'ServerId'}); + if ( !property_exists($this, 'Server') ) { + if ( $this->ServerId() ) + $this->{'Server'} = Server::find_one(array('Id'=>$this->{'ServerId'})); + if ( !property_exists($this, 'Server') ) { + $this->{'Server'} = new Server(); + } + } + return $this->{'Server'}; } public function __call($fn, array $args){ @@ -142,7 +147,7 @@ private $status_fields = array( $this->{$fn} = $args[0]; } } - if ( array_key_exists($fn, $this) ) { + if ( property_exists($this, $fn) ) { return $this->{$fn}; } else if ( array_key_exists($fn, $this->defaults) ) { if ( is_array($this->defaults[$fn]) ) { @@ -215,9 +220,9 @@ private $status_fields = array( $this->{'Width'} = $new; $field = ( $this->Orientation() == 'ROTATE_90' or $this->Orientation() == 'ROTATE_270' ) ? 'Height' : 'Width'; - if ( array_key_exists($field, $this) ) + if ( property_exists($this, $field) ) return $this->{$field}; - return $this->defaults{$field}; + return $this->defaults[$field]; } // end function Width public function ViewHeight($new=null) { @@ -225,9 +230,9 @@ private $status_fields = array( $this->{'Height'} = $new; $field = ( $this->Orientation() == 'ROTATE_90' or $this->Orientation() == 'ROTATE_270' ) ? 'Width' : 'Height'; - if ( array_key_exists($field, $this) ) + if ( property_exists($this, $field) ) return $this->{$field}; - return $this->defaults{$field}; + return $this->defaults[$field]; } // end function Height public function SignalCheckColour($new=null) { @@ -238,10 +243,10 @@ private $status_fields = array( // Validate that it's a valid colour (we seem to allow color names, not just hex). // This also helps prevent XSS. - if (array_key_exists($field, $this) && preg_match('/^[#0-9a-zA-Z]+$/', $this->{$field})) { + if ( property_exists($this, $field) && preg_match('/^[#0-9a-zA-Z]+$/', $this->{$field})) { return $this->{$field}; } - return $this->defaults{$field}; + return $this->defaults[$field]; } // end function SignalCheckColour public static function find( $parameters = array(), $options = array() ) { @@ -257,7 +262,7 @@ private $status_fields = array( Warning('Attempt to control a monitor with no Id'); return; } - if ( (!defined('ZM_SERVER_ID')) or ( array_key_exists('ServerId', $this) and (ZM_SERVER_ID==$this->{'ServerId'}) ) ) { + if ( (!defined('ZM_SERVER_ID')) or ( property_exists($this, 'ServerId') and (ZM_SERVER_ID==$this->{'ServerId'}) ) ) { if ( $this->Type() == 'Local' ) { $zmcArgs = '-d '.$this->{'Device'}; } else { @@ -280,12 +285,13 @@ private $status_fields = array( $url = $Server->UrlToApi().'/monitors/daemonControl/'.$this->{'Id'}.'/'.$mode.'/zmc.json'; if ( ZM_OPT_USE_AUTH ) { if ( ZM_AUTH_RELAY == 'hashed' ) { - $url .= '?auth='.generateAuthHash( ZM_AUTH_HASH_IPS ); - } elseif ( ZM_AUTH_RELAY == 'plain' ) { - $url = '?user='.$_SESSION['username']; - $url = '?pass='.$_SESSION['password']; - } elseif ( ZM_AUTH_RELAY == 'none' ) { - $url = '?user='.$_SESSION['username']; + $url .= '?auth='.generateAuthHash(ZM_AUTH_HASH_IPS); + } else if ( ZM_AUTH_RELAY == 'plain' ) { + $url .= '?user='.$_SESSION['username']; + $url .= '?pass='.$_SESSION['password']; + } else { + Error('Multi-Server requires AUTH_RELAY be either HASH or PLAIN'); + return; } } Logger::Debug("sending command to $url"); @@ -310,7 +316,7 @@ private $status_fields = array( return; } - if ( (!defined('ZM_SERVER_ID')) or ( array_key_exists('ServerId', $this) and (ZM_SERVER_ID==$this->{'ServerId'}) ) ) { + if ( (!defined('ZM_SERVER_ID')) or ( property_exists($this, 'ServerId') and (ZM_SERVER_ID==$this->{'ServerId'}) ) ) { if ( $this->{'Function'} == 'None' || $this->{'Function'} == 'Monitor' || $mode == 'stop' ) { if ( ZM_OPT_CONTROL ) { daemonControl('stop', 'zmtrack.pl', '-m '.$this->{'Id'}); @@ -338,12 +344,13 @@ private $status_fields = array( $url = ZM_BASE_PROTOCOL . '://'.$Server->Hostname().'/zm/api/monitors/daemonControl/'.$this->{'Id'}.'/'.$mode.'/zma.json'; if ( ZM_OPT_USE_AUTH ) { if ( ZM_AUTH_RELAY == 'hashed' ) { - $url .= '?auth='.generateAuthHash( ZM_AUTH_HASH_IPS ); - } elseif ( ZM_AUTH_RELAY == 'plain' ) { - $url = '?user='.$_SESSION['username']; - $url = '?pass='.$_SESSION['password']; - } elseif ( ZM_AUTH_RELAY == 'none' ) { - $url = '?user='.$_SESSION['username']; + $url .= '?auth='.generateAuthHash(ZM_AUTH_HASH_IPS); + } else if ( ZM_AUTH_RELAY == 'plain' ) { + $url .= '?user='.$_SESSION['username']; + $url .= '?pass='.$_SESSION['password']; + } else { + Error('Multi-Server requires AUTH_RELAY be either HASH or PLAIN'); + return; } } Logger::Debug("sending command to $url"); @@ -371,8 +378,8 @@ private $status_fields = array( } } - if ( !array_key_exists('GroupIds', $this) ) { - if ( array_key_exists('Id', $this) and $this->{'Id'} ) { + if ( !property_exists($this, 'GroupIds') ) { + if ( property_exists($this, 'Id') and $this->{'Id'} ) { $this->{'GroupIds'} = dbFetchAll('SELECT `GroupId` FROM `Groups_Monitors` WHERE `MonitorId`=?', 'GroupId', array($this->{'Id'}) ); if ( ! $this->{'GroupIds'} ) $this->{'GroupIds'} = array(); @@ -421,7 +428,7 @@ private $status_fields = array( if ( $new ) { $this->{'Storage'} = $new; } - if ( ! ( array_key_exists('Storage', $this) and $this->{'Storage'} ) ) { + if ( ! ( property_exists($this, 'Storage') and $this->{'Storage'} ) ) { $this->{'Storage'} = isset($this->{'StorageId'}) ? Storage::find_one(array('Id'=>$this->{'StorageId'})) : new Storage(NULL); @@ -471,58 +478,58 @@ private $status_fields = array( return $source; } // end function Source - public function UrlToIndex() { - return $this->Server()->UrlToIndex(); + public function UrlToIndex($port=null) { + return $this->Server()->UrlToIndex($port); //ZM_MIN_STREAMING_PORT ? (ZM_MIN_STREAMING_PORT+$this->Id()) : null); } -public function sendControlCommand($command) { - // command is generally a command option list like --command=blah but might be just the word quit + public function sendControlCommand($command) { + // command is generally a command option list like --command=blah but might be just the word quit - $options = array(); - # Convert from a command line params to an option array - foreach ( explode(' ', $command) as $option ) { - if ( preg_match('/--([^=]+)(?:=(.+))?/', $option, $matches) ) { - $options[$matches[1]] = $matches[2]?$matches[2]:1; - } else if ( $option != '' and $option != 'quit' ) { - Warning("Ignored command for zmcontrol $option in $command"); - } - } - if ( !count($options) ) { - if ( $command == 'quit' ) { - $options['command'] = 'quit'; - } else { - Warning("No commands to send to zmcontrol from $command"); - return false; - } - } - - if ( (!defined('ZM_SERVER_ID')) or ( array_key_exists('ServerId', $this) and (ZM_SERVER_ID==$this->{'ServerId'}) ) ) { - # Local - Logger::Debug('Trying to send options ' . print_r($options, true)); - - $optionString = jsonEncode($options); - Logger::Debug("Trying to send options $optionString"); - // Either connects to running zmcontrol.pl or runs zmcontrol.pl to send the command. - $socket = socket_create(AF_UNIX, SOCK_STREAM, 0); - if ( $socket < 0 ) { - Error('socket_create() failed: '.socket_strerror($socket)); - return false; - } - $sockFile = ZM_PATH_SOCKS.'/zmcontrol-'.$this->{'Id'}.'.sock'; - if ( @socket_connect($socket, $sockFile) ) { - if ( !socket_write($socket, $optionString) ) { - Error('Can\'t write to control socket: '.socket_strerror(socket_last_error($socket))); - return false; + $options = array(); + # Convert from a command line params to an option array + foreach ( explode(' ', $command) as $option ) { + if ( preg_match('/--([^=]+)(?:=(.+))?/', $option, $matches) ) { + $options[$matches[1]] = $matches[2]?$matches[2]:1; + } else if ( $option != '' and $option != 'quit' ) { + Warning("Ignored command for zmcontrol $option in $command"); } - } else if ( $command != 'quit' ) { - $command = ZM_PATH_BIN.'/zmcontrol.pl '.$command.' --id='.$this->{'Id'}; - - // Can't connect so use script - $ctrlOutput = exec(escapeshellcmd($command)); } - socket_close($socket); - } else if ( $this->ServerId() ) { + if ( !count($options) ) { + if ( $command == 'quit' ) { + $options['command'] = 'quit'; + } else { + Warning("No commands to send to zmcontrol from $command"); + return false; + } + } + + if ( (!defined('ZM_SERVER_ID')) or ( property_exists($this, 'ServerId') and (ZM_SERVER_ID==$this->{'ServerId'}) ) ) { + # Local + Logger::Debug('Trying to send options ' . print_r($options, true)); + + $optionString = jsonEncode($options); + Logger::Debug("Trying to send options $optionString"); + // Either connects to running zmcontrol.pl or runs zmcontrol.pl to send the command. + $socket = socket_create(AF_UNIX, SOCK_STREAM, 0); + if ( $socket < 0 ) { + Error('socket_create() failed: '.socket_strerror($socket)); + return false; + } + $sockFile = ZM_PATH_SOCKS.'/zmcontrol-'.$this->{'Id'}.'.sock'; + if ( @socket_connect($socket, $sockFile) ) { + if ( !socket_write($socket, $optionString) ) { + Error('Can\'t write to control socket: '.socket_strerror(socket_last_error($socket))); + return false; + } + } else if ( $command != 'quit' ) { + $command = ZM_PATH_BIN.'/zmcontrol.pl '.$command.' --id='.$this->{'Id'}; + + // Can't connect so use script + $ctrlOutput = exec(escapeshellcmd($command)); + } + socket_close($socket); + } else if ( $this->ServerId() ) { $Server = $this->Server(); $url = ZM_BASE_PROTOCOL . '://'.$Server->Hostname().'/zm/api/monitors/daemonControl/'.$this->{'Id'}.'/'.$mode.'/zmcontrol.json'; @@ -547,7 +554,7 @@ public function sendControlCommand($command) { } } catch ( Exception $e ) { Error("Except $e thrown trying to restart zma"); - return false; + return false; } } else { Error('Server not assigned to Monitor in a multi-server setup. Please assign a server to the Monitor.'); diff --git a/web/includes/MontageLayout.php b/web/includes/MontageLayout.php index ca6b17ef7..f67f3ace5 100644 --- a/web/includes/MontageLayout.php +++ b/web/includes/MontageLayout.php @@ -1,133 +1,22 @@ null, 'Name' => '', 'Positions' => 0, ); - public function __construct( $IdOrRow = NULL ) { - if ( $IdOrRow ) { - $row = NULL; - if ( is_integer( $IdOrRow ) or is_numeric( $IdOrRow ) ) { - $row = dbFetchOne( 'SELECT * FROM MontageLayouts WHERE Id=?', NULL, array( $IdOrRow ) ); - if ( ! $row ) { - Error("Unable to load MontageLayout record for Id=" . $IdOrRow ); - } - } else if ( is_array( $IdOrRow ) ) { - $row = $IdOrRow; - } else { - Error("Unknown argument passed to MontageLayout Constructor ($IdOrRow)"); - return; - } - - if ( $row ) { - foreach ($row as $k => $v) { - $this->{$k} = $v; - } - } else { - Error('No row for MontageLayout ' . $IdOrRow ); - } - } # end if isset($IdOrRow) - } // end function __construct - - public function __call($fn, array $args){ - if ( count($args) ) { - $this->{$fn} = $args[0]; - } - if ( array_key_exists($fn, $this) ) { - return $this->{$fn}; - } else { - if ( array_key_exists( $fn, $this->defaults ) ) { - return $this->defaults{$fn}; - } else { - $backTrace = debug_backtrace(); - $file = $backTrace[1]['file']; - $line = $backTrace[1]['line']; - Warning( "Unknown function call MontageLayout->$fn from $file:$line" ); - } - } + public static function find( $parameters = array(), $options = array() ) { + return ZM_Object::_find(get_class(), $parameters, $options); } - public function set( $data ) { - foreach ($data as $k => $v) { - if ( is_array( $v ) ) { - # perhaps should turn into a comma-separated string - $this->{$k} = implode(',',$v); - } else if ( is_string( $v ) ) { - $this->{$k} = trim( $v ); - } else if ( is_integer( $v ) ) { - $this->{$k} = $v; - } else if ( is_bool( $v ) ) { - $this->{$k} = $v; - } else { - Error( "Unknown type $k => $v of var " . gettype( $v ) ); - $this->{$k} = $v; - } - } + public static function find_one( $parameters = array(), $options = array() ) { + return ZM_Object::_find_one(get_class(), $parameters, $options); } - public static function find( $parameters = null, $options = null ) { - $filters = array(); - $sql = 'SELECT * FROM MontageLayouts '; - $values = array(); - - if ( $parameters ) { - $fields = array(); - $sql .= 'WHERE '; - foreach ( $parameters as $field => $value ) { - if ( $value == null ) { - $fields[] = $field.' IS NULL'; - } else if ( is_array( $value ) ) { - $func = function(){return '?';}; - $fields[] = $field.' IN ('.implode(',', array_map( $func, $value ) ). ')'; - $values += $value; - - } else { - $fields[] = $field.'=?'; - $values[] = $value; - } - } - $sql .= implode(' AND ', $fields ); - } - if ( $options and isset($options['order']) ) { - $sql .= ' ORDER BY ' . $options['order']; - } - $result = dbQuery($sql, $values); - if ( $result ) { - $results = $result->fetchALL(); - foreach ( $results as $row ) { - $filters[] = new MontageLayout($row); - } - } - return $filters; - } - public function save( $new_values = null ) { - if ( $new_values ) { - foreach ( $new_values as $k=>$v ) { - $this->{$k} = $v; - } - } - - $fields = array_values( array_filter( array_keys($this->defaults), function($field){return $field != 'Id';} ) ); - $values = null; - if ( isset($this->{'Id'}) ) { - $sql = 'UPDATE MontageLayouts SET '.implode(', ', array_map( function($field) {return $field.'=?';}, $fields ) ) . ' WHERE Id=?'; - $values = array_map( function($field){return $this->{$field};}, $fields ); - $values[] = $this->{'Id'}; - dbQuery($sql, $values); - } else { - $sql = 'INSERT INTO MontageLayouts ('.implode( ',', $fields ).') VALUES ('.implode(',',array_map( function(){return '?';}, $fields ) ).')'; - $values = array_map( function($field){return $this->{$field};}, $fields ); - dbQuery($sql, $values); - global $dbConn; - $this->{'Id'} = $dbConn->lastInsertId(); - } - } // end function save } // end class MontageLayout ?> diff --git a/web/includes/Object.php b/web/includes/Object.php index fec0a3ff5..6f2e4d8bf 100644 --- a/web/includes/Object.php +++ b/web/includes/Object.php @@ -45,7 +45,7 @@ class ZM_Object { $this->{$fn} = $args[0]; } - if ( array_key_exists($fn, $this) ) { + if ( property_exists($this, $fn) ) { return $this->{$fn}; } else { if ( array_key_exists($fn, $this->defaults) ) { @@ -110,8 +110,9 @@ class ZM_Object { public static function _find_one($class, $parameters = array(), $options = array() ) { global $object_cache; - if ( ! isset($object_cache[$class]) ) + if ( ! isset($object_cache[$class]) ) { $object_cache[$class] = array(); + } $cache = &$object_cache[$class]; if ( ( count($parameters) == 1 ) and @@ -127,6 +128,11 @@ class ZM_Object { return $results[0]; } + public static function _clear_cache($class) { + global $object_cache; + $object_cache[$class] = array(); + } + public static function Objects_Indexed_By_Id($class) { $results = array(); foreach ( ZM_Object::_find($class, null, array('order'=>'lower(Name)')) as $Object ) { @@ -140,10 +146,10 @@ class ZM_Object { foreach ($this->defaults as $key => $value) { if ( is_callable(array($this, $key)) ) { $json[$key] = $this->$key(); - } else if ( array_key_exists($key, $this) ) { + } else if ( property_exists($this, $key) ) { $json[$key] = $this->{$key}; } else { - $json[$key] = $this->defaults{$key}; + $json[$key] = $this->defaults[$key]; } } return json_encode($json); @@ -158,14 +164,24 @@ class ZM_Object { # perhaps should turn into a comma-separated string $this->{$k} = implode(',', $v); } else if ( is_string($v) ) { - if ( $v == '' and array_key_exists($k, $this->defaults) ) { - if ( is_array($this->defaults[$k]) ) +if ( 0 ) { +# Remarking this out. We are setting a value, not asking for a default to be set. +# So don't do defaults here, do them somewhere else + if ( ($v == null) and array_key_exists($k, $this->defaults) ) { +Logger::Debug("$k => Have default for $v: "); + if ( is_array($this->defaults[$k]) ) { $this->{$k} = $this->defaults[$k]['default']; - else - $this->{$k} = $this->defaults[$k]; - } else { - $this->{$k} = trim($v); + } else { + $this->{$k} = $this->defaults[$k]; + Logger::Debug("$k => Have default for $v: " . $this->{$k}); + } + } else { + $this->{$k} = trim($v); } +} else { + $this->{$k} = trim($v); +} + } else if ( is_integer($v) ) { $this->{$k} = $v; } else if ( is_bool($v) ) { @@ -192,7 +208,7 @@ class ZM_Object { continue; } - if ( $this->defaults[$field] ) { + if ( isset($this->defaults[$field]) ) { if ( is_array($this->defaults[$field]) ) { $new_values[$field] = $this->defaults[$field]['default']; } else { @@ -215,7 +231,7 @@ class ZM_Object { } else if ( $this->$field() != $value ) { $changes[$field] = $value; } - } else if ( array_key_exists($field, $this) ) { + } else if ( property_exists($this, $field) ) { $type = (array_key_exists($field, $this->defaults) && is_array($this->defaults[$field])) ? $this->defaults[$field]['type'] : 'scalar'; Logger::Debug("Checking field $field => current ". (is_array($this->{$field}) ? implode(',',$this->{$field}) : $this->{$field}) . ' ?= ' . @@ -280,6 +296,18 @@ class ZM_Object { $this->set($new_values); } + # Set defaults. Note that we only replace "" with null, not other values + # because for example if we want to clear TimestampFormat, we clear it, but the default is a string value + foreach ( $this->defaults as $field => $default ) { + if ( (!property_exists($this, $field)) or ($this->{$field} == '') ) { + if ( is_array($default) ) { + $this->{$field} = $default['default']; + } else if ( $default == null ) { + $this->{$field} = $default; + } + } + } + $fields = array_filter( $this->defaults, function($v) { diff --git a/web/includes/Server.php b/web/includes/Server.php index 93a59bfed..c0fd27a38 100644 --- a/web/includes/Server.php +++ b/web/includes/Server.php @@ -80,7 +80,7 @@ class Server extends ZM_Object { public function PathToZMS( $new = null ) { if ( $new != null ) - $this{'PathToZMS'} = $new; + $this->{'PathToZMS'} = $new; if ( $this->Id() and $this->{'PathToZMS'} ) { return $this->{'PathToZMS'}; } else { diff --git a/web/includes/Storage.php b/web/includes/Storage.php index 01465de65..695da80c7 100644 --- a/web/includes/Storage.php +++ b/web/includes/Storage.php @@ -58,6 +58,13 @@ class Storage extends ZM_Object { return $this->{'Events'}; } + public function EventCount() { + if ( (! property_exists($this, 'EventCount')) or (!$this->{'EventCount'}) ) { + $this->{'EventCount'} = dbFetchOne('SELECT COUNT(*) AS EventCount FROM Events WHERE StorageId=?', 'EventCount', array($this->Id())); + } + return $this->{'EventCount'}; + } + public function disk_usage_percent() { $path = $this->Path(); if ( ! $path ) { @@ -80,7 +87,7 @@ class Storage extends ZM_Object { } public function disk_total_space() { - if ( !array_key_exists('disk_total_space', $this) ) { + if ( !property_exists($this, 'disk_total_space') ) { $path = $this->Path(); if ( file_exists($path) ) { $this->{'disk_total_space'} = disk_total_space($path); @@ -94,7 +101,7 @@ class Storage extends ZM_Object { public function disk_used_space() { # This isn't a function like this in php, so we have to add up the space used in each event. - if ( ( !array_key_exists('disk_used_space', $this)) or !$this->{'disk_used_space'} ) { + if ( ( !property_exists($this, 'disk_used_space')) or !$this->{'disk_used_space'} ) { if ( $this->{'Type'} == 's3fs' ) { $this->{'disk_used_space'} = $this->event_disk_space(); } else { @@ -112,17 +119,18 @@ class Storage extends ZM_Object { public function event_disk_space() { # This isn't a function like this in php, so we have to add up the space used in each event. - if ( (! array_key_exists('DiskSpace', $this)) or (!$this->{'DiskSpace'}) ) { + if ( (! property_exists($this, 'DiskSpace')) or (!$this->{'DiskSpace'}) ) { $used = dbFetchOne('SELECT SUM(DiskSpace) AS DiskSpace FROM Events WHERE StorageId=? AND DiskSpace IS NOT NULL', 'DiskSpace', array($this->Id())); do { - # Do in batches of 1000 so as to not useup all ram + # Do in batches of 1000 so as to not useup all ram, Event will do caching though... $events = Event::find(array('StorageId'=>$this->Id(), 'DiskSpace'=>null), array('limit'=>1000)); foreach ( $events as $Event ) { $Event->Storage($this); // Prevent further db hit # DiskSpace will update the event $used += $Event->DiskSpace(); } #end foreach + Event::clear_cache(); } while ( count($events) == 1000 ); $this->{'DiskSpace'} = $used; } @@ -130,8 +138,8 @@ class Storage extends ZM_Object { } // end function event_disk_space public function Server() { - if ( ! array_key_exists('Server',$this) ) { - if ( array_key_exists('ServerId', $this) ) { + if ( ! property_exists($this, 'Server') ) { + if ( property_exists($this, 'ServerId') ) { $this->{'Server'} = Server::find_one(array('Id'=>$this->{'ServerId'})); if ( !$this->{'Server'} ) { diff --git a/web/includes/Zone.php b/web/includes/Zone.php new file mode 100644 index 000000000..7a19df9d2 --- /dev/null +++ b/web/includes/Zone.php @@ -0,0 +1,41 @@ + null, + 'Name' => '', + 'Type' => 'Active', + 'Units' => 'Pixels', + 'CheckMethod' => 'Blobs', + 'MinPixelThreshold' => null, + 'MaxPixelThreshold' => null, + 'MinAlarmPixels' => null, + 'MaxAlarmPixels' => null, + 'FilterX' => null, + 'FilterY' => null, + 'MinFilterPixels' => null, + 'MaxFilterPixels' => null, + 'MinBlobPixels' => null, + 'MaxBlobPixels' => null, + 'MinBlobs' => null, + 'MaxBlobs' => null, + 'OverloadFrames' => 0, + 'ExtendAlarmFrames' => 0, + ); + + public static function find( $parameters = array(), $options = array() ) { + return ZM_Object::_find(get_class(), $parameters, $options); + } + + public static function find_one( $parameters = array(), $options = array() ) { + return ZM_Object::_find_one(get_class(), $parameters, $options); + } + +} # end class Zone +?> diff --git a/web/includes/actions/controlcap.php b/web/includes/actions/controlcap.php index eec3ffd8b..ad4985f11 100644 --- a/web/includes/actions/controlcap.php +++ b/web/includes/actions/controlcap.php @@ -28,6 +28,65 @@ if ( $action == 'controlcap' ) { require_once('includes/Control.php'); $Control = new ZM\Control( !empty($_REQUEST['cid']) ? $_REQUEST['cid'] : null ); + $field_defaults = array( + 'CanWake' => 0, + 'CanSleep' => 0, + 'CanReset' => 0, + 'CanReboot' => 0, + 'CanMove' => 0, + 'CanMoveDiag' => 0, + 'CanMoveMap' => 0, + 'CanMoveRel' => 0, + 'CanMoveAbs' => 0, + 'CanMoveCon' => 0, + 'CanPan' => 0, + 'HasPanSpeed' => 0, + 'HasTurboPan' => 0, + 'CanTilt' => 0, + 'HasTiltSpeed' => 0, + 'HasTurboTilt' => 0, + 'CanZoom' => 0, + 'CanZoomRel' => 0, + 'CanZoomAbs' => 0, + 'CanZoomCon' => 0, + 'HasZoomSpeed' => 0, + 'CanFocus' => 0, + 'CanAutoFocus' => 0, + 'CanFocusRel' => 0, + 'CanFocusAbs' => 0, + 'CanFocusCon' => 0, + 'HasFocusSpeed' => 0, + 'CanGain' => 0, + 'CanAutoGain' => 0, + 'CanGainRel' => 0, + 'CanGainAbs' => 0, + 'CanGainCon' => 0, + 'HasGainSpeed' => 0, + 'CanWhite' => 0, + 'CanAutoWhite' => 0, + 'CanWhiteRel' => 0, + 'CanWhiteAbs' => 0, + 'CanWhiteCon' => 0, + 'HasWhiteSpeed' => 0, + 'CanIris' => 0, + 'CanAutoIris' => 0, + 'CanIrisRel' => 0, + 'CanIrisAbs' => 0, + 'CanIrisCon' => 0, + 'HasIrisSpeed' => 0, + 'HasPresets' => 0, + 'HasHomePreset' => 0, + 'CanSetPresets' => 0, + ); + + # Checkboxes don't return an element in the POST data, so won't be present in newControl. + # So force a value for these fields + foreach ( $field_defaults as $field => $value ) { + if ( ! (isset($_REQUEST['newControl'][$field]) and $_REQUEST['newControl'][$field]) ) { + $_REQUEST['newControl'][$field] = $value; + } + } # end foreach type + //$changes = getFormChanges( $control, $_REQUEST['newControl'], $types, $columns ); $Control->save($_REQUEST['newControl']); $refreshParent = true; diff --git a/web/includes/actions/filter.php b/web/includes/actions/filter.php index ae6bf3ddc..ac2435302 100644 --- a/web/includes/actions/filter.php +++ b/web/includes/actions/filter.php @@ -64,35 +64,12 @@ if ( isset($_REQUEST['object']) and ( $_REQUEST['object'] == 'filter' ) ) { $_REQUEST['filter']['Background'] = empty($_REQUEST['filter']['Background']) ? 0 : 1; $_REQUEST['filter']['Concurrent'] = empty($_REQUEST['filter']['Concurrent']) ? 0 : 1; $changes = $filter->changes($_REQUEST['filter']); - ZM\Logger::Debug("Changes: " . print_r($changes,true)); - - if ( 0 ) { - $sql .= ', Query = '.dbEscape(jsonEncode($_REQUEST['filter']['Query'])); - $sql .= ', AutoArchive = '.(!empty($_REQUEST['filter']['AutoArchive']) ? 1 : 0); - $sql .= ', AutoVideo = '. ( !empty($_REQUEST['filter']['AutoVideo']) ? 1 : 0); - $sql .= ', AutoUpload = '. ( !empty($_REQUEST['filter']['AutoUpload']) ? 1 : 0); - $sql .= ', AutoEmail = '. ( !empty($_REQUEST['filter']['AutoEmail']) ? 1 : 0); - $sql .= ', AutoMessage = '. ( !empty($_REQUEST['filter']['AutoMessage']) ? 1 : 0); - $sql .= ', AutoExecute = '. ( !empty($_REQUEST['filter']['AutoExecute']) ? 1 : 0); - $sql .= ', AutoExecuteCmd = '.dbEscape($_REQUEST['filter']['AutoExecuteCmd']); - $sql .= ', AutoDelete = '. ( !empty($_REQUEST['filter']['AutoDelete']) ? 1 : 0); - if ( !empty($_REQUEST['filter']['AutoMove']) ? 1 : 0) { - $sql .= ', AutoMove = 1, AutoMoveTo='. validInt($_REQUEST['filter']['AutoMoveTo']); - } else { - $sql .= ', AutoMove = 0'; - } - $sql .= ', UpdateDiskSpace = '. ( !empty($_REQUEST['filter']['UpdateDiskSpace']) ? 1 : 0); - $sql .= ', Background = '. ( !empty($_REQUEST['filter']['Background']) ? 1 : 0); - $sql .= ', Concurrent = '. ( !empty($_REQUEST['filter']['Concurrent']) ? 1 : 0); - } + ZM\Logger::Debug('Changes: ' . print_r($changes,true)); if ( $_REQUEST['Id'] and ( $action == 'Save' ) ) { - if ( 0 ) { - dbQuery('UPDATE Filters SET '.$sql.' WHERE Id=?', array($_REQUEST['Id'])); - } - $filter->save($changes); if ( $filter->Background() ) $filter->control('stop'); + $filter->save($changes); } else { if ( $action == 'execute' ) { diff --git a/web/includes/actions/function.php b/web/includes/actions/function.php index ebdc416fc..00e4e21f7 100644 --- a/web/includes/actions/function.php +++ b/web/includes/actions/function.php @@ -39,7 +39,7 @@ if ( $action == 'function' ) { $oldFunction = $monitor['Function']; $oldEnabled = $monitor['Enabled']; if ( $newFunction != $oldFunction || $newEnabled != $oldEnabled ) { - dbQuery('UPDATE Monitors SET Function=?, Enabled=? WHERE Id=?', + dbQuery('UPDATE Monitors SET `Function`=?, `Enabled`=? WHERE `Id`=?', array($newFunction, $newEnabled, $mid)); $monitor['Function'] = $newFunction; diff --git a/web/includes/actions/login.php b/web/includes/actions/login.php index 6c8312b2f..68ca4e604 100644 --- a/web/includes/actions/login.php +++ b/web/includes/actions/login.php @@ -49,14 +49,23 @@ if ( ('login' == $action) && isset($_REQUEST['username']) && ( ZM_AUTH_TYPE == ' // as it produces the same error as when you don't answer a recaptcha if ( isset($responseData['error-codes']) && is_array($responseData['error-codes']) ) { if ( !in_array('invalid-input-secret', $responseData['error-codes']) ) { - Error('reCaptcha authentication failed'); + ZM\Error('reCaptcha authentication failed. response was: ' . print_r($responseData['error-codes'],true)); unset($user); // unset should be ok here because we aren't in a function return; } else { - Error('Invalid recaptcha secret detected'); + ZM\Error('Invalid recaptcha secret detected'); } } } // end if success==false + if ( ! (empty($_REQUEST['username']) or empty($_REQUEST['password'])) ) { + $ret = validateUser($_REQUEST['username'], $_REQUEST['password']); + if ( !$ret[0] ) { + ZM\Error($ret[1]); + unset($user); // unset should be ok here because we aren't in a function + } else { + $user = $ret[0]; + } + } # end if have username and password } // end if using reCaptcha // if captcha existed, it was passed diff --git a/web/includes/actions/monitor.php b/web/includes/actions/monitor.php index 823a06ff2..94e527e29 100644 --- a/web/includes/actions/monitor.php +++ b/web/includes/actions/monitor.php @@ -33,12 +33,11 @@ if ( $action == 'monitor' ) { if ( !$x10Monitor ) $x10Monitor = array(); } - if ( !canEdit('Monitors',$mid) ) { + if ( !canEdit('Monitors', $mid) ) { ZM\Warning('You do not have permission to edit this monitor'); return; } } else { - #$monitor = array(); if ( ZM_OPT_X10 ) { $x10Monitor = array(); } @@ -58,6 +57,7 @@ if ( $action == 'monitor' ) { 'Enabled' => 0, 'Exif' => 0, 'RTSPDescribe' => 0, + 'V4LMultiBuffer' => '', 'RecordAudio' => 0, 'Method' => 'raw', 'GroupIds' => array(), @@ -219,7 +219,6 @@ if ( $action == 'monitor' ) { } else { ZM\Error('Error saving new Monitor.'); - $error_message = dbError($sql); return; } } diff --git a/web/includes/actions/state.php b/web/includes/actions/state.php index 9799cdec3..0f7a9e9a5 100644 --- a/web/includes/actions/state.php +++ b/web/includes/actions/state.php @@ -31,19 +31,19 @@ if ( $action == 'state' ) { } } else if ( $action == 'save' ) { if ( !empty($_REQUEST['runState']) || !empty($_REQUEST['newState']) ) { - $sql = 'SELECT Id,Function,Enabled FROM Monitors ORDER BY Id'; + $sql = 'SELECT `Id`,`Function`,`Enabled` FROM Monitors ORDER BY Id'; $definitions = array(); - foreach( dbFetchAll($sql) as $monitor ) { + foreach ( dbFetchAll($sql) as $monitor ) { $definitions[] = $monitor['Id'].':'.$monitor['Function'].':'.$monitor['Enabled']; } $definition = join(',', $definitions); if ( $_REQUEST['newState'] ) $_REQUEST['runState'] = $_REQUEST['newState']; - dbQuery('REPLACE INTO States SET Name=?, Definition=?', array($_REQUEST['runState'],$definition)); + dbQuery('REPLACE INTO `States` SET `Name`=?, `Definition`=?', array($_REQUEST['runState'],$definition)); } } else if ( $action == 'delete' ) { if ( isset($_REQUEST['runState']) ) - dbQuery('DELETE FROM States WHERE Name=?', array($_REQUEST['runState'])); + dbQuery('DELETE FROM `States` WHERE `Name`=?', array($_REQUEST['runState'])); } $view = 'console'; ?> diff --git a/web/includes/actions/user.php b/web/includes/actions/user.php index bdbafd176..fde1c0d0f 100644 --- a/web/includes/actions/user.php +++ b/web/includes/actions/user.php @@ -45,8 +45,16 @@ if ( $action == 'user' ) { if ( !empty($_REQUEST['uid']) ) { dbQuery('UPDATE Users SET '.implode(', ', $changes).' WHERE Id = ?', array($_REQUEST['uid'])); # If we are updating the logged in user, then update our session user data. - if ( $user and ( $dbUser['Username'] == $user['Username'] ) ) - generateAuthHash(ZM_AUTH_HASH_IPS); + if ( $user and ( $dbUser['Username'] == $user['Username'] ) ) { + # We are the logged in user, need to update the $user object and generate a new auth_hash + $sql = 'SELECT * FROM Users WHERE Enabled=1 AND Id=?'; + $user = dbFetchOne($sql, NULL, array($_REQUEST['uid'])); + + # Have to update auth hash in session + zm_session_start(); + generateAuthHash(ZM_AUTH_HASH_IPS, true); + session_write_close(); + } } else { dbQuery('INSERT INTO Users SET '.implode(', ', $changes)); } @@ -61,8 +69,8 @@ if ( $action == 'user' ) { $types = array(); $changes = getFormChanges($dbUser, $_REQUEST['newUser'], $types); - if (function_exists ('password_hash')) { - $pass_hash = '"'.password_hash($pass, PASSWORD_BCRYPT).'"'; + if ( function_exists('password_hash') ) { + $pass_hash = '"'.password_hash($_REQUEST['newUser']['Password'], PASSWORD_BCRYPT).'"'; } else { $pass_hash = ' PASSWORD('.dbEscape($_REQUEST['newUser']['Password']).') '; ZM\Info ('Cannot use bcrypt as you are using PHP < 5.3'); @@ -75,8 +83,15 @@ if ( $action == 'user' ) { } if ( count($changes) ) { dbQuery('UPDATE Users SET '.implode(', ', $changes).' WHERE Id=?', array($uid)); + + # We are the logged in user, need to update the $user object and generate a new auth_hash + $sql = 'SELECT * FROM Users WHERE Enabled=1 AND Id=?'; + $user = dbFetchOne($sql, NULL, array($uid)); + + zm_session_start(); + generateAuthHash(ZM_AUTH_HASH_IPS, true); + session_write_close(); $refreshParent = true; - generateAuthHash(ZM_AUTH_HASH_IPS); } $view = 'none'; } diff --git a/web/includes/auth.php b/web/includes/auth.php index 4fc39d30e..c3a34405d 100644 --- a/web/includes/auth.php +++ b/web/includes/auth.php @@ -123,12 +123,16 @@ function validateToken($token, $allowed_token_type='access') { // convert from stdclass to array $jwt_payload = json_decode(json_encode($decoded_token), true); - - $type = $jwt_payload['type']; - if ( $type != $allowed_token_type ) { - ZM\Error("Token type mismatch. Expected $allowed_token_type but got $type"); - return array(false, 'Incorrect token type'); + if ($allowed_token_type != 'any') { + $type = $jwt_payload['type']; + if ( $type != $allowed_token_type ) { + ZM\Error("Token type mismatch. Expected $allowed_token_type but got $type"); + return array(false, 'Incorrect token type'); + } + } else { + ZM\Logger::Debug('Not comparing token types as [any] was passed'); } + $username = $jwt_payload['user']; $sql = 'SELECT * FROM Users WHERE Enabled=1 AND Username=?'; $saved_user_details = dbFetchOne($sql, NULL, array($username)); @@ -220,19 +224,19 @@ function generateAuthHash($useRemoteAddr, $force=false) { function visibleMonitor($mid) { global $user; - return ( empty($user['MonitorIds']) || in_array($mid, explode(',', $user['MonitorIds'])) ); + return ( $user && empty($user['MonitorIds']) || in_array($mid, explode(',', $user['MonitorIds'])) ); } function canView($area, $mid=false) { global $user; - return ( ($user[$area] == 'View' || $user[$area] == 'Edit') && ( !$mid || visibleMonitor($mid) ) ); + return ( $user && ($user[$area] == 'View' || $user[$area] == 'Edit') && ( !$mid || visibleMonitor($mid) ) ); } function canEdit($area, $mid=false) { global $user; - return ( $user[$area] == 'Edit' && ( !$mid || visibleMonitor($mid) )); + return ( $user && ($user[$area] == 'Edit') && ( !$mid || visibleMonitor($mid) )); } function userFromSession() { @@ -258,13 +262,14 @@ function userFromSession() { if ( ZM_OPT_USE_AUTH ) { if ( !empty($_REQUEST['token']) ) { - $ret = validateToken($_REQUEST['token'], 'access'); + // we only need to get the username here + // don't know the token type. That will + // be checked later + $ret = validateToken($_REQUEST['token'], 'any'); $user = $ret[0]; } else { // Non token based auth - $user = userFromSession(); - if ( ZM_AUTH_HASH_LOGINS && empty($user) && !empty($_REQUEST['auth']) ) { $user = getAuthUser($_REQUEST['auth']); } else if ( @@ -280,6 +285,12 @@ if ( ZM_OPT_USE_AUTH ) { return; } $user = $ret[0]; + } else if ( (ZM_AUTH_TYPE == 'remote') and !empty($_SERVER['REMOTE_USER']) ) { + $sql = 'SELECT * FROM Users WHERE Enabled=1 AND Username=?'; + // local user, shouldn't affect the global user + $user = dbFetchOne($sql, NULL, array($_SERVER['REMOTE_USER'])); + } else { + $user = userFromSession(); } if ( !empty($user) ) { diff --git a/web/includes/config.php.in b/web/includes/config.php.in index dd3439680..57064f22d 100644 --- a/web/includes/config.php.in +++ b/web/includes/config.php.in @@ -33,9 +33,9 @@ $configFile = ZM_CONFIG; $localConfigFile = basename($configFile); if ( file_exists( $localConfigFile ) && filesize( $localConfigFile ) > 0 ) { if ( php_sapi_name() == 'cli' && empty($_SERVER['REMOTE_ADDR']) ) - print( "Warning, overriding installed $localConfigFile file with local copy\n" ); + print("Warning, overriding installed $localConfigFile file with local copy\n"); else - error_log( "Warning, overriding installed $localConfigFile file with local copy" ); + error_log("Warning, overriding installed $localConfigFile file with local copy"); $configFile = $localConfigFile; } @@ -49,19 +49,19 @@ if ( is_dir($configSubFolder) ) { if ( is_readable($configSubFolder) ) { foreach ( glob("$configSubFolder/*.conf") as $filename ) { //error_log("processing $filename"); - $configvals = array_replace($configvals, process_configfile($filename) ); + $configvals = array_replace($configvals, process_configfile($filename)); } } else { - error_log( "WARNING: ZoneMinder configuration subfolder found but is not readable. Check folder permissions on $configSubFolder." ); + error_log("WARNING: ZoneMinder configuration subfolder found but is not readable. Check folder permissions on $configSubFolder."); } } else { - error_log( "WARNING: ZoneMinder configuration subfolder found but is not a directory. Check $configSubFolder." ); + error_log("WARNING: ZoneMinder configuration subfolder found but is not a directory. Check $configSubFolder."); } # Now that our array our finalized, define each key => value # pair in the array as a constant -foreach( $configvals as $key => $value) { - define( $key, $value ); +foreach ( $configvals as $key => $value ) { + define($key, $value); } // @@ -135,8 +135,8 @@ define( 'SCALE_BASE', 100 ); // The additional scalin define( 'STRF_FMT_DATETIME_DB', '%Y-%m-%d %H:%M:%S' ); // Strftime format for database queries, don't change define( 'MYSQL_FMT_DATETIME_SHORT', '%y/%m/%d %H:%i:%S' ); // MySQL date_format shorter format for dates with time -require_once( 'database.php' ); -require_once( 'logger.php' ); +require_once('database.php'); +require_once('logger.php'); loadConfig(); ZM\Logger::fetch()->initialise(); @@ -165,53 +165,57 @@ function loadConfig( $defineConsts=true ) { $result = $dbConn->query('SELECT Name,Value FROM Config'); if ( !$result ) echo mysql_error(); - while( $row = dbFetchNext( $result ) ) { + while( $row = dbFetchNext($result) ) { if ( $defineConsts ) - define( $row['Name'], $row['Value'] ); + define($row['Name'], $row['Value']); $config[$row['Name']] = $row; } } # end function loadConfig // For Human-readability, use ZM_SERVER_HOST or ZM_SERVER_NAME in zm.conf, and convert it here to a ZM_SERVER_ID if ( ! defined('ZM_SERVER_ID') ) { + require_once('Server.php'); if ( defined('ZM_SERVER_NAME') and ZM_SERVER_NAME ) { - $server_id = dbFetchOne('SELECT Id FROM Servers WHERE Name=?', 'Id', array(ZM_SERVER_NAME)); - if ( ! $server_id ) { - Error('Invalid Multi-Server configration detected. ZM_SERVER_NAME set to ' . ZM_SERVER_NAME . ' in zm.conf, but no corresponding entry found in Servers table.'); + # Use Server lookup so that it caches + $Server = ZM\Server::find_one(array('Name'=>ZM_SERVER_NAME)); + if ( !$Server ) { + ZM\Error('Invalid Multi-Server configration detected. ZM_SERVER_NAME set to ' . ZM_SERVER_NAME . ' in zm.conf, but no corresponding entry found in Servers table.'); } else { - define( 'ZM_SERVER_ID', $server_id ); + define('ZM_SERVER_ID', $Server->Id()); } } else if ( defined('ZM_SERVER_HOST') and ZM_SERVER_HOST ) { - $server_id = dbFetchOne('SELECT Id FROM Servers WHERE Name=?', 'Id', array(ZM_SERVER_HOST)); - if ( ! $server_id ) { - Error('Invalid Multi-Server configration detected. ZM_SERVER_HOST set to ' . ZM_SERVER_HOST . ' in zm.conf, but no corresponding entry found in Servers table.'); + $Server = ZM\Server::find_one(array('Name'=>ZM_SERVER_HOST)); + if ( ! $Server ) { + ZM\Error('Invalid Multi-Server configration detected. ZM_SERVER_HOST set to ' . ZM_SERVER_HOST . ' in zm.conf, but no corresponding entry found in Servers table.'); } else { - define( 'ZM_SERVER_ID', $server_id ); + define('ZM_SERVER_ID', $Server->Id()); } } } -ini_set('date.timezone', ZM_TIMEZONE); +if ( ZM_TIMEZONE ) + ini_set('date.timezone', ZM_TIMEZONE); function process_configfile($configFile) { if ( is_readable( $configFile ) ) { $configvals = array(); - $cfg = fopen( $configFile, 'r') or Error("Could not open config file: $configFile."); + $cfg = fopen($configFile, 'r') or ZM\Error("Could not open config file: $configFile."); while ( !feof($cfg) ) { - $str = fgets( $cfg, 256 ); - if ( preg_match( '/^\s*$/', $str )) + $str = fgets($cfg, 256); + if ( preg_match('/^\s*(#.*)?$/', $str) ) { continue; - elseif ( preg_match( '/^\s*#/', $str )) - continue; - elseif ( preg_match( '/^\s*([^=\s]+)\s*=\s*[\'"]*(.*?)[\'"]*\s*$/', $str, $matches )) + } else if ( preg_match( '/^\s*([^=\s]+)\s*=\s*[\'"]*(.*?)[\'"]*\s*$/', $str, $matches )) { $configvals[$matches[1]] = $matches[2]; + } else { + ZM\Error("Malformed line in config $configFile\n$str"); + } } - fclose( $cfg ); - return( $configvals ); + fclose($cfg); + return $configvals; } else { - error_log( "WARNING: ZoneMinder configuration file found but is not readable. Check file permissions on $configFile." ); - return( false ); + error_log("WARNING: ZoneMinder configuration file found but is not readable. Check file permissions on $configFile."); + return false; } } diff --git a/web/includes/database.php b/web/includes/database.php index eab70f47f..d0124656d 100644 --- a/web/includes/database.php +++ b/web/includes/database.php @@ -110,16 +110,10 @@ function dbError($sql) { function dbEscape( $string ) { global $dbConn; - if ( version_compare(phpversion(), '4.3.0', '<')) - if ( get_magic_quotes_gpc() ) - return $dbConn->quote(stripslashes($string)); - else - return $dbConn->quote($string); + if ( version_compare(phpversion(), '5.4', '<=') and get_magic_quotes_gpc() ) + return $dbConn->quote(stripslashes($string)); else - if ( get_magic_quotes_gpc() ) - return $dbConn->quote(stripslashes($string)); - else - return $dbConn->quote($string); + return $dbConn->quote($string); } function dbQuery($sql, $params=NULL) { @@ -212,6 +206,10 @@ function dbFetch($sql, $col=false) { } function dbFetchNext($result, $col=false) { + if ( !$result ) { + ZM\Error("dbFetchNext called on null result."); + return false; + } if ( $dbRow = $result->fetch(PDO::FETCH_ASSOC) ) return $col ? $dbRow[$col] : $dbRow; return false; diff --git a/web/includes/functions.php b/web/includes/functions.php index eaa8f8601..e1b1e058f 100644 --- a/web/includes/functions.php +++ b/web/includes/functions.php @@ -36,14 +36,18 @@ function noCacheHeaders() { } function CSPHeaders($view, $nonce) { - $additionalScriptSrc = ''; + global $Servers; + if ( ! $Servers ) + $Servers = ZM\Server::find(); + + $additionalScriptSrc = implode(' ', array_map(function($S){return $S->Url();}, $Servers)); switch ($view) { case 'login': { if (defined('ZM_OPT_USE_GOOG_RECAPTCHA') && defined('ZM_OPT_GOOG_RECAPTCHA_SITEKEY') && defined('ZM_OPT_GOOG_RECAPTCHA_SECRETKEY') && ZM_OPT_USE_GOOG_RECAPTCHA && ZM_OPT_GOOG_RECAPTCHA_SITEKEY && ZM_OPT_GOOG_RECAPTCHA_SECRETKEY) { - $additionalScriptSrc = "https://www.google.com"; + $additionalScriptSrc = ' https://www.google.com'; } // fall through } @@ -92,7 +96,9 @@ function CORSHeaders() { # The following is left for future reference/use. $valid = false; - $Servers = ZM\Server::find(); + global $Servers; + if ( ! $Servers ) + $Servers = ZM\Server::find(); if ( sizeof($Servers) < 1 ) { # Only need CORSHeaders in the event that there are multiple servers in use. # ICON: Might not be true. multi-port? @@ -409,6 +415,11 @@ ZM\Logger::Debug("Event type: " . gettype($event)); global $user; + if ( $event->Archived() ) { + ZM\Info('Cannot delete Archived event.'); + return; + } # end if Archived + if ( $user['Events'] == 'Edit' ) { $event->delete(); } # CAN EDIT @@ -431,9 +442,10 @@ function makeLink($url, $label, $condition=1, $options='') { */ function makePopupLink($url, $winName, $winSize, $label, $condition=1, $options='') { // Avoid double-encoding since some consumers incorrectly pass a pre-escaped URL. - $string = ''; } else { - $string .= ''; + $string .= '>'; } $string .= $label; $string .= ''; @@ -509,7 +521,8 @@ function htmlOptions($contents, $values) { $options_html .= ''; + '>'.htmlspecialchars($text, ENT_COMPAT | ENT_HTML401, ini_get('default_charset'), false).' +'; } return $options_html; } @@ -765,7 +778,7 @@ function canStreamIframe() { function canStreamNative() { // Old versions of Chrome can display the stream, but then it blocks everything else (Chrome bug 5876) - return( ZM_WEB_CAN_STREAM == 'yes' || ( ZM_WEB_CAN_STREAM == 'auto' && (!isInternetExplorer() && !isOldChrome()) ) ); + return ( ZM_WEB_CAN_STREAM == 'yes' || ( ZM_WEB_CAN_STREAM == 'auto' && (!isInternetExplorer() && !isOldChrome()) ) ); } function canStreamApplet() { @@ -897,11 +910,11 @@ function createListThumbnail($event, $overwrite=false) { if ( ZM_WEB_LIST_THUMB_WIDTH ) { $thumbWidth = ZM_WEB_LIST_THUMB_WIDTH; $scale = (SCALE_BASE*ZM_WEB_LIST_THUMB_WIDTH)/$event['Width']; - $thumbHeight = reScale( $event['Height'], $scale ); + $thumbHeight = reScale($event['Height'], $scale); } elseif ( ZM_WEB_LIST_THUMB_HEIGHT ) { $thumbHeight = ZM_WEB_LIST_THUMB_HEIGHT; $scale = (SCALE_BASE*ZM_WEB_LIST_THUMB_HEIGHT)/$event['Height']; - $thumbWidth = reScale( $event['Width'], $scale ); + $thumbWidth = reScale($event['Width'], $scale); } else { ZM\Fatal('No thumbnail width or height specified, please check in Options->Web'); } @@ -932,17 +945,17 @@ function createVideo($event, $format, $rate, $scale, $overwrite=false) { $command .= ' -o'; $command = escapeshellcmd($command); $result = exec($command, $output, $status); -Logger::Debug("generating Video $command: result($result outptu:(".implode("\n", $output )." status($status"); + ZM\Logger::Debug("generating Video $command: result($result outptu:(".implode("\n", $output )." status($status"); return $status ? '' : rtrim($result); } # This takes more than one scale amount, so it runs through each and alters dimension. # I can't imagine why you would want to do that. -function reScale( $dimension, $dummy ) { +function reScale($dimension, $dummy) { $new_dimension = $dimension; for ( $i = 1; $i < func_num_args(); $i++ ) { - $scale = func_get_arg( $i ); - if ( !empty($scale) && ($scale != 'auto') && ($scale != SCALE_BASE) ) + $scale = func_get_arg($i); + if ( !empty($scale) && ($scale != '0') && ($scale != SCALE_BASE) ) $new_dimension = (int)(($new_dimension*$scale)/SCALE_BASE); } return $new_dimension; @@ -1082,15 +1095,10 @@ function parseFilter(&$filter, $saveToSession=false, $querySep='&') { $validQueryConjunctionTypes = getFilterQueryConjunctionTypes(); $StorageArea = NULL; - $terms = isset($filter['Query']) ? $filter['Query']['terms'] : NULL; - if ( !isset($terms) ) { - $backTrace = debug_backtrace(); - $file = $backTrace[1]['file']; - $line = $backTrace[1]['line']; - ZM\Warning("No terms in filter from $file:$line"); - ZM\Warning(print_r($filter, true)); - } - if ( isset($terms) && count($terms) ) { + # It is not possible to pass an empty array in the url, so we have to deal with there not being a terms field. + $terms = (isset($filter['Query']) and isset($filter['Query']['terms']) and is_array($filter['Query']['terms'])) ? $filter['Query']['terms'] : array(); + + if ( count($terms) ) { for ( $i = 0; $i < count($terms); $i++ ) { $term = $terms[$i]; @@ -1109,6 +1117,9 @@ function parseFilter(&$filter, $saveToSession=false, $querySep='&') { $filter['query'] .= $querySep.urlencode("filter[Query][terms][$i][attr]").'='.urlencode($term['attr']); $filter['fields'] .= "\n"; switch ( $term['attr'] ) { + case 'AlarmedZoneId': + $term['op'] = 'EXISTS'; + break; case 'MonitorName': $filter['sql'] .= 'M.Name'; break; @@ -1226,11 +1237,15 @@ function parseFilter(&$filter, $saveToSession=false, $querySep='&') { $valueList = array(); foreach ( preg_split('/["\'\s]*?,["\'\s]*?/', preg_replace('/^["\']+?(.+)["\']+?$/', '$1', $term['val'])) as $value ) { switch ( $term['attr'] ) { + + case 'AlarmedZoneId': + $value = '(SELECT * FROM Stats WHERE EventId=E.Id AND ZoneId='.$value.')'; + break; case 'MonitorName': case 'Name': case 'Cause': case 'Notes': - if($term['op'] == 'LIKE' || $term['op'] == 'NOT LIKE') { + if ( $term['op'] == 'LIKE' || $term['op'] == 'NOT LIKE' ) { $value = '%'.$value.'%'; } $value = dbEscape($value); @@ -1256,13 +1271,16 @@ function parseFilter(&$filter, $saveToSession=false, $querySep='&') { case 'StartDateTime': case 'EndDateTime': if ( $value != 'NULL' ) - $value = '\''.strftime( STRF_FMT_DATETIME_DB, strtotime( $value ) ).'\''; + $value = '\''.strftime(STRF_FMT_DATETIME_DB, strtotime($value)).'\''; break; case 'Date': case 'StartDate': case 'EndDate': - if ( $value != 'NULL' ) - $value = 'to_days(\''.strftime(STRF_FMT_DATETIME_DB, strtotime($value)).'\')'; + if ( $value == 'CURDATE()' or $value == 'NOW()' ) { + $value = 'to_days('.$value.')'; + } else if ( $value != 'NULL' ) { + $value = 'to_days(\''.strftime(STRF_FMT_DATETIME_DB, strtotime($value)).'\')'; + } break; case 'Time': case 'StartTime': @@ -1297,10 +1315,13 @@ function parseFilter(&$filter, $saveToSession=false, $querySep='&') { break; case '=[]' : case 'IN' : - $filter['sql'] .= ' in ('.join(',', $valueList).')'; + $filter['sql'] .= ' IN ('.join(',', $valueList).')'; break; case '![]' : $filter['sql'] .= ' not in ('.join(',', $valueList).')'; + break; + case 'EXISTS' : + $filter['sql'] .= ' EXISTS ' .$value; break; case 'IS' : if ( $value == 'Odd' ) { @@ -1324,10 +1345,10 @@ function parseFilter(&$filter, $saveToSession=false, $querySep='&') { $filter['query'] .= $querySep.urlencode("filter[Query][terms][$i][val]").'='.urlencode($term['val']); $filter['fields'] .= "\n"; } - } // end if ( isset($term['attr']) ) + } // end if isset($term['attr']) if ( isset($term['cbr']) && (string)(int)$term['cbr'] == $term['cbr'] ) { $filter['query'] .= $querySep.urlencode("filter[Query][terms][$i][cbr]").'='.urlencode($term['cbr']); - $filter['sql'] .= ' '.str_repeat(')', $term['cbr']).' '; + $filter['sql'] .= ' '.str_repeat(')', $term['cbr']); $filter['fields'] .= "\n"; } } // end foreach term @@ -1336,6 +1357,9 @@ function parseFilter(&$filter, $saveToSession=false, $querySep='&') { if ( $saveToSession ) { $_SESSION['filter'] = $filter; } + } else { + $filter['query'] = $querySep; + #.urlencode('filter[Query][terms]=[]'); } // end if terms #if ( 0 ) { @@ -1348,7 +1372,7 @@ function parseFilter(&$filter, $saveToSession=false, $querySep='&') { #$filter['sql'] .= ' LIMIT ' . validInt($filter['Query']['limit']); #} #} -} +} // end function parseFilter(&$filter, $saveToSession=false, $querySep='&') // Please note that the filter is passed in by copy, so you need to use the return value from this function. // @@ -1449,7 +1473,7 @@ function sortHeader($field, $querySep='&') { global $view; return implode($querySep, array( '?view='.$view, - 'page=1'.$_REQUEST['filter']['query'], + 'page=1'.(isset($_REQUEST['filter'])?$_REQUEST['filter']['query']:''), 'sort_field='.$field, 'sort_asc='.($_REQUEST['sort_field'] == $field ? !$_REQUEST['sort_asc'] : 0), 'limit='.validInt($_REQUEST['limit']), @@ -2169,7 +2193,8 @@ function ajaxError($message, $code=HTTP_STATUS_OK) { ajaxCleanup(); if ( $code == HTTP_STATUS_OK ) { $response = array('result'=>'Error', 'message'=>$message); - header('Content-type: text/plain'); + header('Content-type: application/json'); + #header('Content-type: text/plain'); exit(jsonEncode($response)); } header("HTTP/1.0 $code $message"); @@ -2185,7 +2210,8 @@ function ajaxResponse($result=false) { } else if ( !empty($result) ) { $response['message'] = $result; } - header('Content-type: text/plain'); + header('Content-type: application/json'); + #header('Content-type: text/plain'); exit(jsonEncode($response)); } @@ -2279,9 +2305,14 @@ function validHtmlStr($input) { function getStreamHTML($monitor, $options = array()) { - if ( isset($options['scale']) and $options['scale'] and ($options['scale'] != 100) ) { - $options['width'] = reScale($monitor->ViewWidth(), $options['scale']).'px'; - $options['height'] = reScale($monitor->ViewHeight(), $options['scale']).'px'; + if ( isset($options['scale']) ) { + if ( $options['scale'] and ( $options['scale'] != 'auto' ) ) { + $options['width'] = reScale($monitor->ViewWidth(), $options['scale']).'px'; + $options['height'] = reScale($monitor->ViewHeight(), $options['scale']).'px'; + } else { + $options['width'] = '100%'; + $options['height'] = 'auto'; + } } else { # scale is empty or 100 # There may be a fixed width applied though, in which case we need to leave the height empty @@ -2569,4 +2600,47 @@ function array_recursive_diff($aArray1, $aArray2) { return $aReturn; } +function html_radio($name, $values, $selected=null, $options=array(), $attrs=array()) { + + $html = ''; + if ( isset($options['default']) and ( $selected == null ) ) { + $selected = $options['default']; + } # end if + + foreach ( $values as $value => $label ) { + if ( isset($options['container']) ) { + $html .= $options['container'][0]; + } + $attributes = array_map( + function($attr, $value){return $attr.'="'.$value.'"';}, + array_keys($attrs), + array_values($attrs) + ); + $attributes_string = implode(' ', $attributes); + + $html .= sprintf(' +
+
+ ', $name, $value, $label, ($value==$selected?' checked="checked"':''), + $attributes_string, + (isset($options['id']) ? $options['id'] : ''), + ( ( (!isset($options['inline'])) or $options['inline'] ) ? '-inline' : '') + ); + if ( isset($options['container']) ) { + $html .= $options['container'][1]; + } + } # end foreach value + return $html; +} # end sub html_radio + + +function random_colour() { + return '#'. + str_pad( dechex( mt_rand( 0, 255 ) ), 2, '0', STR_PAD_LEFT). + str_pad( dechex( mt_rand( 0, 255 ) ), 2, '0', STR_PAD_LEFT). + str_pad( dechex( mt_rand( 0, 255 ) ), 2, '0', STR_PAD_LEFT); +} + ?> diff --git a/web/includes/lang.php b/web/includes/lang.php index 38f1179d8..a567f7273 100644 --- a/web/includes/lang.php +++ b/web/includes/lang.php @@ -18,40 +18,52 @@ // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // -function translate( $name ) { +function translate($name) { global $SLANG; - if ( array_key_exists($name, $SLANG) ) + // The isset is more performant + if ( isset($SLANG[$name]) || array_key_exists($name, $SLANG) ) return $SLANG[$name]; else return $name; } -function loadLanguage( $prefix='' ) { +function loadLanguage($prefix='') { global $user; if ( $prefix ) $prefix = $prefix.'/'; - $fallbackLangFile = $prefix.'lang/en_gb.php'; - $systemLangFile = $prefix.'lang/'.ZM_LANG_DEFAULT.'.php'; - if ( isset($user['Language']) ) + if ( isset($user['Language']) ) { $userLangFile = $prefix.'lang/'.$user['Language'].'.php'; - if ( isset($userLangFile) && file_exists($userLangFile) ) - return $userLangFile; - elseif ( file_exists($systemLangFile) ) + if ( file_exists($userLangFile) ) { + return $userLangFile; + } else { + ZM\Warning("User language file $userLangFile does not exist."); + } + } + + $systemLangFile = $prefix.'lang/'.ZM_LANG_DEFAULT.'.php'; + if ( file_exists($systemLangFile) ) { return $systemLangFile; - elseif ( file_exists($fallbackLangFile) ) + } else { + ZM\Warning("System language file $systemLangFile does not exist."); + } + + $fallbackLangFile = $prefix.'lang/en_gb.php'; + if ( file_exists($fallbackLangFile) ) { return $fallbackLangFile; - else - return false; + } else { + ZM\Error("Default language file $fallbackLangFile does not exist."); + } + return false; } if ( $langFile = loadLanguage() ) { require_once($langFile); require_once('lang/default.php'); foreach ($DLANG as $key => $value) { - if ( ! array_key_exists($key, $SLANG) ) + if ( ! (isset($SLANG[$key]) || array_key_exists($key, $SLANG)) ) $SLANG[$key] = $DLANG[$key]; } } diff --git a/web/includes/logger.php b/web/includes/logger.php index 3aa8f891b..2b5463dfb 100644 --- a/web/includes/logger.php +++ b/web/includes/logger.php @@ -274,7 +274,7 @@ class Logger { } } } - return( $this->databaseLevel ); + return $this->databaseLevel; } public function fileLevel( $fileLevel ) { @@ -288,7 +288,7 @@ class Logger { $this->openFile(); } } - return( $this->fileLevel ); + return $this->fileLevel; } public function weblogLevel( $weblogLevel ) { @@ -303,7 +303,7 @@ class Logger { $this->weblogLevel = $weblogLevel; } } - return( $this->weblogLevel ); + return $this->weblogLevel; } public function syslogLevel( $syslogLevel ) { @@ -317,30 +317,31 @@ class Logger { $this->openSyslog(); } } - return( $this->syslogLevel ); + return $this->syslogLevel; } private function openSyslog() { - openlog( $this->id, LOG_PID|LOG_NDELAY, LOG_LOCAL1 ); + openlog($this->id, LOG_PID|LOG_NDELAY, LOG_LOCAL1); } private function closeSyslog() { closelog(); } - private function logFile( $logFile ) { - if ( preg_match( '/^(.+)\+$/', $logFile, $matches ) ) + private function logFile($logFile) { + if ( preg_match('/^(.+)\+$/', $logFile, $matches) ) { $this->logFile = $matches[1].'.'.getmypid(); - else + } else { $this->logFile = $logFile; + } } private function openFile() { if ( !$this->useErrorLog ) { - if ( $this->logFd = fopen( $this->logFile, 'a+' ) ) { - if ( strnatcmp( phpversion(), '5.2.0' ) >= 0 ) { + if ( $this->logFd = fopen($this->logFile, 'a+') ) { + if ( strnatcmp(phpversion(), '5.2.0') >= 0 ) { $error = error_get_last(); - trigger_error( "Can't open log file '$logFile': ".$error['message'].' @ '.$error['file'].'/'.$error['line'], E_USER_ERROR ); + trigger_error("Can't open log file '$logFile': ".$error['message'].' @ '.$error['file'].'/'.$error['line'], E_USER_ERROR); } $this->fileLevel = self::NOLOG; } @@ -349,73 +350,83 @@ class Logger { private function closeFile() { if ( $this->logFd ) - fclose( $this->logFd ); + fclose($this->logFd); } public function logPrint( $level, $string, $file=NULL, $line=NULL ) { - if ( $level <= $this->effectiveLevel ) { - $string = preg_replace( '/[\r\n]+$/', '', $string ); - $code = self::$codes[$level]; + if ( $level > $this->effectiveLevel ) { + return; + } - $time = gettimeofday(); - $message = sprintf( '%s.%06d %s[%d].%s [%s]', strftime( '%x %H:%M:%S', $time['sec'] ), $time['usec'], $this->id, getmypid(), $code, $string ); + $string = preg_replace('/[\r\n]+$/', '', $string); + $code = self::$codes[$level]; - if ( is_null($file) ) { - if ( $this->useErrorLog || $this->databaseLevel > self::NOLOG ) { - $backTrace = debug_backtrace(); - $file = $backTrace[1]['file']; - $line = $backTrace[1]['line']; - if ( $this->hasTerm ) - $rootPath = getcwd(); - else - $rootPath = $_SERVER['DOCUMENT_ROOT']; - $file = preg_replace( '/^'.addcslashes($rootPath,'/').'\/?/', '', $file ); - } - } + $time = gettimeofday(); + $message = sprintf('%s.%06d %s[%d].%s [%s] [%s]', + strftime('%x %H:%M:%S', $time['sec']), $time['usec'], + $this->id, getmypid(), $code, $_SERVER['REMOTE_ADDR'], $string); - if ( $this->useErrorLog ) - $message .= ' at '.$file.' line '.$line; - else - $message = $message; - - if ( $level <= $this->termLevel ) + if ( is_null($file) ) { + if ( $this->useErrorLog || ($this->databaseLevel > self::NOLOG) ) { + $backTrace = debug_backtrace(); + $file = $backTrace[1]['file']; + $line = $backTrace[1]['line']; if ( $this->hasTerm ) - print( $message."\n" ); + $rootPath = getcwd(); else - print( preg_replace( "/\n/", '
', htmlspecialchars($message) ).'
' ); - - if ( $level <= $this->fileLevel ) - if ( $this->useErrorLog ) { - if ( !error_log( $message."\n", 3, $this->logFile ) ) { - if ( strnatcmp( phpversion(), '5.2.0' ) >= 0 ) { - $error = error_get_last(); - trigger_error( "Can't write to log file '".$this->logFile."': ".$error['message'].' @ '.$error['file'].'/'.$error['line'], E_USER_ERROR ); - } - } - } elseif ( $this->logFd ) { - fprintf( $this->logFd, $message."\n" ); - } - - $message = $code.' ['.$string.']'; - if ( $level <= $this->syslogLevel ) - syslog( self::$syslogPriorities[$level], $message ); - if ( $level <= $this->databaseLevel ) { - try { - global $dbConn; - $sql = 'INSERT INTO Logs ( TimeKey, Component, Pid, Level, Code, Message, File, Line ) values ( ?, ?, ?, ?, ?, ?, ?, ? )'; - $stmt = $dbConn->prepare( $sql ); - $result = $stmt->execute( array( sprintf( '%d.%06d', $time['sec'], $time['usec'] ), $this->id, getmypid(), $level, $code, $string, $file, $line ) ); - } catch(PDOException $ex) { - $this->databaseLevel = self::NOLOG; - Error("Can't write log entry '$sql': ". $ex->getMessage()); - } + $rootPath = $_SERVER['DOCUMENT_ROOT']; + $file = preg_replace('/^'.addcslashes($rootPath,'/').'\/?/', '', $file); } - // This has to be last as trigger_error can be fatal - if ( $level <= $this->weblogLevel ) { - if ( $this->useErrorLog ) - error_log( $message, 0 ); - else - trigger_error( $message, self::$phpErrorLevels[$level] ); + } + + if ( $this->useErrorLog ) { + $message .= ' at '.$file.' line '.$line; + } else { + $message = $message; + } + + if ( $level <= $this->termLevel ) { + if ( $this->hasTerm ) + print($message."\n"); + else + print(preg_replace("/\n/", '
', htmlspecialchars($message)).'
'); + } + + if ( $level <= $this->fileLevel ) { + if ( $this->useErrorLog ) { + if ( !error_log($message."\n", 3, $this->logFile) ) { + if ( strnatcmp(phpversion(), '5.2.0') >= 0 ) { + $error = error_get_last(); + trigger_error("Can't write to log file '".$this->logFile."': ".$error['message'].' @ '.$error['file'].'/'.$error['line'], E_USER_ERROR); + } + } + } else if ( $this->logFd ) { + fprintf($this->logFd, $message."\n"); + } + } + + $message = $code.' ['.$string.']'; + if ( $level <= $this->syslogLevel ) + syslog( self::$syslogPriorities[$level], $message ); + + if ( $level <= $this->databaseLevel ) { + try { + global $dbConn; + $sql = 'INSERT INTO `Logs` ( `TimeKey`, `Component`, `ServerId`, `Pid`, `Level`, `Code`, `Message`, `File`, `Line` ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ? )'; + $stmt = $dbConn->prepare($sql); + $result = $stmt->execute(array(sprintf('%d.%06d', $time['sec'], $time['usec']), $this->id, + (defined('ZM_SERVER_ID') ? ZM_SERVER_ID : null), getmypid(), $level, $code, $string, $file, $line)); + } catch(PDOException $ex) { + $this->databaseLevel = self::NOLOG; + Error("Can't write log entry '$sql': ". $ex->getMessage()); + } + } + // This has to be last as trigger_error can be fatal + if ( $level <= $this->weblogLevel ) { + if ( $this->useErrorLog ) { + error_log($message, 0); + } else { + trigger_error($message, self::$phpErrorLevels[$level]); } } } diff --git a/web/index.php b/web/index.php index a3413b625..3625c9e35 100644 --- a/web/index.php +++ b/web/index.php @@ -52,6 +52,8 @@ require_once('includes/Event.php'); require_once('includes/Group.php'); require_once('includes/Monitor.php'); +$Servers = ZM\Server::find(); + if ( (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') or @@ -71,7 +73,7 @@ define('ZM_BASE_URL', ''); require_once('includes/functions.php'); if ( $_SERVER['REQUEST_METHOD'] == 'OPTIONS' ) { - ZM\Logger::Debug("OPTIONS Method, only doing CORS"); + ZM\Logger::Debug('OPTIONS Method, only doing CORS'); # Add Cross domain access headers CORSHeaders(); return; @@ -152,10 +154,7 @@ if ( setcookie('zmCSS', $css, time()+3600*24*30*12*10); } -# Only one request can open the session file at a time, so let's close the session here to improve concurrency. -# Any file/page that sets session variables must re-open it. -require_once('includes/lang.php'); # Running is global but only do the daemonCheck if it is actually needed $running = null; @@ -186,8 +185,14 @@ if ( isset($_REQUEST['request']) ) $request = detaintPath($_REQUEST['request']); require_once('includes/auth.php'); + +# Only one request can open the session file at a time, so let's close the session here to improve concurrency. +# Any file/page that sets session variables must re-open it. session_write_close(); +// lang references $user[Language] so must come after auth +require_once('includes/lang.php'); + foreach ( getSkinIncludes('skin.php') as $includeFile ) { require_once $includeFile; } @@ -202,7 +207,8 @@ isset($action) || $action = NULL; if ( (!$view and !$request) or ($view == 'console') ) { // Verify the system, php, and mysql timezones all match - date_default_timezone_set(ZM_TIMEZONE); + #if ( ZM_TIMEZONE ) + #date_default_timezone_set(ZM_TIMEZONE); check_timezone(); } @@ -242,6 +248,11 @@ if ( ZM_OPT_USE_AUTH and (!isset($user)) and ($view != 'login') and ($view != 'n ZM\Logger::Debug('Redirecting to login'); $view = 'none'; $redirect = ZM_BASE_URL.$_SERVER['PHP_SELF'].'?view=login'; + if ( ! $request ) { + zm_session_start(); + $_SESSION['postLoginQuery'] = $_SERVER['QUERY_STRING']; + session_write_close(); + } $request = null; } else if ( ZM_SHOW_PRIVACY && ($view != 'privacy') && ($view != 'options') && (!$request) && canEdit('System') ) { $view = 'none'; diff --git a/web/lang/en_gb.php b/web/lang/en_gb.php index a6f2fa488..0570005d3 100644 --- a/web/lang/en_gb.php +++ b/web/lang/en_gb.php @@ -116,6 +116,7 @@ $SLANG = array( 'Area' => 'Area', 'AreaUnits' => 'Area (px/%)', 'AttrAlarmFrames' => 'Alarm Frames', + 'AttrAlarmedZone' => 'Alarmed Zone', 'AttrArchiveStatus' => 'Archive Status', 'AttrAvgScore' => 'Avg. Score', 'AttrCause' => 'Cause', @@ -361,6 +362,9 @@ $SLANG = array( 'FilterCopyEvents' => 'Copy all matches', 'FilterMoveEvents' => 'Move all matches', 'FilterEmailEvents' => 'Email details of all matches', + 'FilterEmailTo' => 'Email To', + 'FilterEmailSubject' => 'Email Subject', + 'FilterEmailBody' => 'Email Body', 'FilterExecuteEvents' => 'Execute command on all matches', 'FilterLog' => 'Filter log', 'FilterMessageEvents' => 'Message details of all matches', @@ -771,6 +775,7 @@ $SLANG = array( 'TurboPanSpeed' => 'Turbo Pan Speed', 'TurboTiltSpeed' => 'Turbo Tilt Speed', 'Type' => 'Type', + 'TZUnset' => 'Unset - use value in php.ini', 'Unarchive' => 'Unarchive', 'Undefined' => 'Undefined', 'Units' => 'Units', @@ -981,6 +986,14 @@ $OLANG = array( "loglevel=debug" Set verbosity of FFmpeg (quiet, panic, fatal, error, warning, info, verbose, debug) ' ), + 'OPTIONS_DECODERHWACCELNAME' => array( + 'Help' => ' + This is equivalent to the ffmpeg -hwaccel command line option. With intel graphics support, use "vaapi". For NVIDIA cuda support use "cuda". To check for support, run ffmpeg -hwaccels on the command line.' + ), + 'OPTIONS_DECODERHWACCELDEVICE' => array( + 'Help' => ' + This is equivalent to the ffmpeg -hwaccel_device command line option. You should only have to specify this if you have multiple GPUs. A typical value for Intel VAAPI would be /dev/dri/renderD128.' + ), 'OPTIONS_RTSPTrans' => array( 'Help' => ' This sets the RTSP Transport Protocol for FFmpeg.~~ diff --git a/web/skins/classic/css/base/skin.css b/web/skins/classic/css/base/skin.css index cd0eaebc3..a963dc0e7 100644 --- a/web/skins/classic/css/base/skin.css +++ b/web/skins/classic/css/base/skin.css @@ -701,3 +701,6 @@ li.search-choice { margin-bottom:5px; } +.form-check-inline { + display: inline-block; +} diff --git a/web/skins/classic/css/base/views/console.css b/web/skins/classic/css/base/views/console.css index 4a5470b6b..1b0e77b4f 100644 --- a/web/skins/classic/css/base/views/console.css +++ b/web/skins/classic/css/base/views/console.css @@ -75,7 +75,7 @@ .consoleTable .colMark { width: 52px; - text-align: center; + text-align: left; } .consoleTable .colEvents { diff --git a/web/skins/classic/css/base/views/control.css b/web/skins/classic/css/base/views/control.css index f0a26e763..473802a90 100644 --- a/web/skins/classic/css/base/views/control.css +++ b/web/skins/classic/css/base/views/control.css @@ -15,7 +15,7 @@ .ptzControls input.ptzTextBtn { margin-top: 2px; } -.ptzControls button { +.ptzControls .pantiltPanel button { border: none; } @@ -24,7 +24,7 @@ } .ptzControls input[type=image] { - border: 0px; + border: none; } .ptzControls .controlsPanel .arrowControl { @@ -140,10 +140,7 @@ background: url("../skins/classic/graphics/arrow-dr.png") no-repeat 0 0; } -.ptzControls .controlsPanel .powerControls { - margin: 5px auto; -} - +.ptzControls .controlsPanel .powerControls, .ptzControls .presetControls div { margin: 5px 200px 5px 180px; } @@ -153,8 +150,5 @@ } .ptzControls .presetControls button.ptzNumBtn { - padding: 1px 2px; width: 45px; - color: #ffffff; - text-align: center; } diff --git a/web/skins/classic/css/base/views/controlcap.css b/web/skins/classic/css/base/views/controlcap.css new file mode 100644 index 000000000..ed9a664e7 --- /dev/null +++ b/web/skins/classic/css/base/views/controlcap.css @@ -0,0 +1,4 @@ + +input[type="number"] { + width: 70px; +} diff --git a/web/skins/classic/css/base/views/controlcaps.css b/web/skins/classic/css/base/views/controlcaps.css index 1a2783fd7..d860d88ae 100644 --- a/web/skins/classic/css/base/views/controlcaps.css +++ b/web/skins/classic/css/base/views/controlcaps.css @@ -1,3 +1,9 @@ -#content table.major .colCanMove, #content table.major .colCanZoom, #content table.major .colCanFocus, #content table.major .colCanIris, #content table.major .colCanWhiteBal, #content table.major .colHasPresets { +#content table.major .colCanMove, +#content table.major .colCanZoom, +#content table.major .colCanFocus, +#content table.major .colCanIris, +#content table.major .colCanWhiteBal, +#content table.major .colHasPresets { text-align: center; } + diff --git a/web/skins/classic/css/base/views/export.css b/web/skins/classic/css/base/views/export.css index e22851ca0..2b090a77a 100644 --- a/web/skins/classic/css/base/views/export.css +++ b/web/skins/classic/css/base/views/export.css @@ -1,4 +1,4 @@ -#contentTable + input { +input { margin-top: 6px; } @@ -13,3 +13,7 @@ #downloadLink { margin-top: 8px; } + +.form-group { + text-align: right; +} diff --git a/web/skins/classic/css/base/views/filter.css b/web/skins/classic/css/base/views/filter.css index 638b85c9b..88854988e 100644 --- a/web/skins/classic/css/base/views/filter.css +++ b/web/skins/classic/css/base/views/filter.css @@ -56,9 +56,14 @@ select { width: 300px; text-align: right; } +input[name="filter[EmailSubject]"], +input[name="filter[EmailTo]"], +textarea[name="filter[EmailBody]"] { +width: 500px; +} select#Id { -min-width: 400px; +min-width: 500px; } .Name input { -min-width: 400px; +min-width: 500px; } diff --git a/web/skins/classic/css/base/views/monitor.css b/web/skins/classic/css/base/views/monitor.css index 2b41e7d06..c0f4a36d9 100644 --- a/web/skins/classic/css/base/views/monitor.css +++ b/web/skins/classic/css/base/views/monitor.css @@ -9,6 +9,12 @@ width: 100%; } +textarea, +input[name="newMonitor[Name]"], +input[name="newMonitor[ControlDevice]"], +input[name="newMonitor[ControlAddress]"] { + width: 100%; +} input[name="newMonitor[Width]"], input[name="newMonitor[Height]"] { width: 80px; diff --git a/web/skins/classic/css/base/views/options.css b/web/skins/classic/css/base/views/options.css index f7123086c..a60d98001 100644 --- a/web/skins/classic/css/base/views/options.css +++ b/web/skins/classic/css/base/views/options.css @@ -21,3 +21,8 @@ input.large { #contentTable.userTable .colMonitor, #contentTable.userTable .colUsername { text-align: left; } + +input[name="newConfig[ZM_OPT_GOOG_RECAPTCHA_SITEKEY]"], +input[name="newConfig[ZM_OPT_GOOG_RECAPTCHA_SECRETKEY]"] { + width: 100%; +} diff --git a/web/skins/classic/css/classic/views/control.css b/web/skins/classic/css/classic/views/control.css index b9c59ae74..1234e0b12 100644 --- a/web/skins/classic/css/classic/views/control.css +++ b/web/skins/classic/css/classic/views/control.css @@ -134,10 +134,7 @@ background: url("../skins/classic/graphics/arrow-dr.png") no-repeat 0 0; } -.ptzControls .controlsPanel .powerControls { - margin: 5px auto; -} - +.ptzControls .controlsPanel .powerControls, .ptzControls .presetControls { margin: 5px 200px 5px 180px; } @@ -146,10 +143,9 @@ margin: 1px; } -.ptzControls .presetControls input.ptzNumBtn { - padding: 1px 2px; - width: 24px; - color: #ffffff; - text-align: center; +.ptzControls .presetControls button.ptzNumBtn { + /* background-color: #016A9D; + */ + border: 1px solid #7f7fb2; } diff --git a/web/skins/classic/includes/config.php b/web/skins/classic/includes/config.php index 2eded5690..091b72c85 100644 --- a/web/skins/classic/includes/config.php +++ b/web/skins/classic/includes/config.php @@ -31,7 +31,7 @@ $rates = array( ); $scales = array( - 'auto' => translate('Scale to Fit'), + '0' => translate('Scale to Fit'), '' => translate('Fixed Width/Height'), '400' => '4x', '300' => '3x', @@ -45,7 +45,7 @@ $scales = array( '12.5' => '1/8x', ); -if (isset($_REQUEST['view']) && ($_REQUEST['view'] == 'montage')) { +if ( isset($_REQUEST['view']) && ($_REQUEST['view'] == 'montage') ) { unset($scales['auto']); //Remove auto on montage, use everywhere else } else { unset($scales['']); //Remove fixed on everything but montage diff --git a/web/skins/classic/includes/control_functions.php b/web/skins/classic/includes/control_functions.php index 7554486af..80cfe4aff 100644 --- a/web/skins/classic/includes/control_functions.php +++ b/web/skins/classic/includes/control_functions.php @@ -125,7 +125,7 @@ function controlPanTilt($monitor, $cmds) { - + diff --git a/web/skins/classic/includes/export_functions.php b/web/skins/classic/includes/export_functions.php index 02d0d86dd..d43c697e3 100644 --- a/web/skins/classic/includes/export_functions.php +++ b/web/skins/classic/includes/export_functions.php @@ -113,17 +113,17 @@ function exportEventDetail($event, $exportFrames, $exportImages) { - + - + - +
Id() ?>
Name()) ?>
MonitorName()) ?> (MonitorId() ?>)
Monitor()->Name()) ?> (MonitorId() ?>)
Cause()) ?>
Notes()) ?>
StartTime()) ) ?>
StartTime())) ?>
Length() ?>
Frames() ?>
AlarmFrames() ?>
TotScore() ?>
AvgScore() ?>
MaxScore() ?>
Archived()?translate('Yes'):translate('No') ?>
Archived()?'Yes':'No') ?>
@@ -226,13 +226,13 @@ function exportEventImages($event, $exportDetail, $exportFrames, $myfilelist) { ?> @@ -247,7 +247,7 @@ function exportEventImages($event, $exportDetail, $exportFrames, $myfilelist) { if ( $Monitor->VideoWriter() == '2' ) { # Passthrough $Rotation = $event->Orientation(); - if ( in_array($event->Orientation(),array('90','270')) ) + if ( in_array($event->Orientation(),array('ROTATE_90','ROTATE_270')) ) $Zoom = $event->Height()/$event->Width(); } ?> @@ -280,7 +280,7 @@ function exportEventImages($event, $exportDetail, $exportFrames, $myfilelist) {
 
 
+ onmousedown="slide(event,'horizontal', Width()-20)?>, 1, , ,0, 'imageslider_display_id');"> 
@@ -618,10 +618,10 @@ function exportEventImagesMaster($eids) {

Master

$eids)); + + foreach ($events as $event) { //get monitor id and event id - $event = new ZM\Event($eid); $eventMonitorId[$eid] = $event->MonitorId(); $eventPath[$eid] = $event->Relative_Path(); } @@ -653,20 +653,18 @@ function exportEventImagesMaster($eids) {

All

"; echo '

Monitor: ' . $monitorNames[$monitor_id] . '

'; - foreach ( $eids as $eid ) { - $Event = new ZM\Event($eid); - if ( $Event->MonitorId() == $monitor_id ) { - eventlist_html($Event); + foreach ( $events as $event ) { + if ( $event->MonitorId() == $monitor_id ) { + eventlist_html($event); } # end if its the right monitor } # end foreach event echo ''; @@ -780,7 +778,7 @@ function exportFileList($event, $exportDetail, $exportFrames, $exportImages, $ex } fwrite($fp, exportEventDetail($event, $exportFrames, $exportImages)); fclose($fp); - $exportFileList[$file] = $event->Id().'/'.$file; + $exportFileList[$file] = $file; } if ( $exportFrames ) { $file = 'zmEventFrames.html'; @@ -789,7 +787,7 @@ function exportFileList($event, $exportDetail, $exportFrames, $exportImages, $ex } fwrite($fp, exportEventFrames($event, $exportDetail, $exportImages)); fclose($fp); - $exportFileList[$file] = $event->Id().'/'.$file; + $exportFileList[$file] = $file; } if ( $exportImages ) { @@ -797,7 +795,7 @@ function exportFileList($event, $exportDetail, $exportFrames, $exportImages, $ex $myfilelist = array(); foreach ( $files as $file ) { if ( preg_match('/-(?:capture|analyse).jpg$/', $file ) ) { - $myfilelist[$file] = $exportFileList[$file] = $event->Id().'/'.$file; + $myfilelist[$file] = $exportFileList[$file] = $file; } else { $filesLeft[$file] = $file; } @@ -806,12 +804,12 @@ function exportFileList($event, $exportDetail, $exportFrames, $exportImages, $ex // create an image slider if ( !empty($myfilelist) ) { - $file = $event->Id().'/zmEventImages.html'; + $file = 'zmEventImages.html'; if ( !($fp = fopen($file, 'w')) ) ZM\Fatal("Can't open event images export file '$file'"); fwrite($fp, exportEventImages($event, $exportDetail, $exportFrames, $myfilelist)); fclose($fp); - $exportFileList[$file] = $event->Id().'/'.$file; + $exportFileList[$file] = $file; } } # end if exportImages @@ -819,7 +817,7 @@ function exportFileList($event, $exportDetail, $exportFrames, $exportImages, $ex $filesLeft = array(); foreach ( $files as $file ) { if ( preg_match('/\.(?:mpg|mpeg|mov|swf|mp4|mkv|avi|asf|3gp)$/', $file) ) { - $exportFileList[$file] = $event->Id().'/'.$file; + $exportFileList[$file] = $file; } else { $filesLeft[$file] = $file; } @@ -829,7 +827,7 @@ function exportFileList($event, $exportDetail, $exportFrames, $exportImages, $ex if ( $exportMisc ) { foreach ( $files as $file ) { - $exportFileList[$file] = $event->Id().'/'.$file; + $exportFileList[$file] = $file; } $files = array(); } @@ -850,10 +848,10 @@ function exportEvents( ) { if ( !canView('Events') ) { - ZM\Error("You do not have permission to view events."); + ZM\Error('You do not have permission to view events.'); return false; } else if ( empty($eids) ) { - ZM\Error("Attempt to export an empty list of events."); + ZM\Error('Attempt to export an empty list of events.'); return false; } @@ -863,23 +861,22 @@ function exportEvents( } # Ensure that we are going to be able to do this. - if ( ! file_exists(ZM_DIR_EXPORTS) ) { - if ( ! mkdir(ZM_DIR_EXPORTS) ) { - ZM\Fatal("Can't create exports dir at '".ZM_DIR_EXPORTS."'"); - } + if ( ! ( mkdir(ZM_DIR_EXPORTS) or file_exists(ZM_DIR_EXPORTS) ) ) { + ZM\Fatal('Can\'t create exports dir at \''.ZM_DIR_EXPORTS.'\''); } + chmod(ZM_DIR_EXPORTS, 0700); $export_dir = ZM_DIR_EXPORTS.'/zmExport_'.$connkey; # Ensure that we are going to be able to do this. - if ( ! file_exists($export_dir) ) { - if ( ! mkdir($export_dir) ) { - ZM\Fatal("Can't create exports dir at '$export_dir'"); - } else { - ZM\Logger::Debug("Successfully created dir '$export_dir'"); - } + if ( ! ( mkdir($export_dir) or file_exists($export_dir) ) ) { + ZM\Fatal("Can't create exports dir at '$export_dir'"); + } else { + ZM\Logger::Debug("Successfully created dir '$export_dir'"); } - if ( !chdir($export_dir) ) + chmod($export_dir, 0700); + if ( !chdir($export_dir) ) { ZM\Fatal("Can't chdir to $export_dir"); + } $export_root = 'zmExport'; $export_listFile = 'zmFileList.txt'; @@ -889,27 +886,30 @@ function exportEvents( if ( !is_array($eids) ) { $eids = array($eids); } - ZM\Logger::Debug("Eids: " . print_r($eids,true)); + ZM\Logger::Debug('Eids: ' . print_r($eids,true)); foreach ( $eids as $eid ) { $event = new ZM\Event($eid); $event_dir = $export_dir.'/'.$event->Id(); - if ( !mkdir($event_dir) ) + if ( !(mkdir($event_dir) or file_exists($event_dir) ) ) { ZM\Error("Can't mkdir $event_dir"); + } $event_exportFileList = exportFileList($event, $exportDetail, $exportFrames, $exportImages, $exportVideo, $exportMisc); ZM\Logger::Debug("File list for event $eid " . print_r($event_exportFileList, true)); $exportFileList = array_merge($exportFileList,$event_exportFileList); foreach ( $event_exportFileList as $file ) { - if ( preg_match('/\.html$/', $file ) ) + if ( preg_match('/\.html$/', $file) ) continue; - ZM\Logger::Debug('cp -as '.$event->Path().'/../'.$file.' '.$export_dir.'/'.$file); - exec('cp -as '.$event->Path().'/../'.$file.' '.$export_dir.'/'.$file); + #exec('cp -as '.$event->Path().'/../'.$file.' '.$export_dir.'/'.$file, $output, $return); + $cmd = 'cp -as '.$event->Path().'/'.$file.' '.$export_dir.'/'.$event->Id().'/'.$file. ' 2>&1'; + exec($cmd, $output, $return); + ZM\Logger::Debug($cmd.' return code: '.$return.' output: '.print_r($output,true)); } - } + } # end foreach event // create an master image if ( $exportImages ) { if ( !symlink(ZM_PATH_WEB.'/'.ZM_SKIN_PATH.'/js/jquery.js', $export_dir.'/jquery.js') ) - ZM\Error("Failed linking jquery.js"); + ZM\Error('Failed linking jquery.js'); //if ( !symlink(ZM_PATH_WEB.'/'.ZM_SKIN_PATH.'/js/video.js', $export_dir.'/video.js') ) //Error("Failed linking video.js"); @@ -940,6 +940,8 @@ function exportEvents( $archive = ''; if ( $exportFormat == 'tar' ) { $archive = ZM_DIR_EXPORTS.'/'.$export_root.($connkey?'_'.$connkey:'').'.tar'; + $version = shell_exec('tar -v'); + $command = 'tar --create --dereference'; if ( $exportCompressed ) { $archive .= '.gz'; @@ -947,8 +949,11 @@ function exportEvents( $exportFormat .= '.gz'; } if ( $exportStructure == 'flat' ) { - //strip file paths if we - $command .= " --xform='s#^.+/##x'"; + if (preg_match("/BSD/i", $version)) { + $command .= " -s '#^.*/##'"; + } else { + $command .= " --xform='s#^.+/##x'"; + } } $command .= ' --file='.escapeshellarg($archive); } elseif ( $exportFormat == 'zip' ) { @@ -960,12 +965,12 @@ function exportEvents( @unlink($archive); $command .= ' zmExport_' . $connkey.'/'; - ZM\Logger::Debug("Command is $command"); exec($command, $output, $status); if ( $status ) { ZM\Error("Command '$command' returned with status $status"); - if ( isset($output[0]) ) - ZM\Error("First line of output is '".$output[0]."'"); + if ( isset($output[0]) ) { + ZM\Error('First line of output is \''.$output[0].'\''); + } return false; } diff --git a/web/skins/classic/includes/functions.php b/web/skins/classic/includes/functions.php index 06603c7b9..3042a5488 100644 --- a/web/skins/classic/includes/functions.php +++ b/web/skins/classic/includes/functions.php @@ -30,7 +30,6 @@ function xhtmlHeaders($file, $title) { $skinCssPhpFile = getSkinFile('css/'.$css.'/skin.css.php'); - $skinJsFile = getSkinFile('js/skin.js'); $skinJsPhpFile = getSkinFile('js/skin.js.php'); $cssJsFile = getSkinFile('js/'.$css.'.js'); @@ -84,7 +83,6 @@ echo output_link_if_exists( array( 'css/base/views/'.$basename.'.css', 'js/dateTimePicker/jquery-ui-timepicker-addon.css', 'js/jquery-ui-1.12.1/jquery-ui.structure.min.css', - #'js/jquery-ui-1.12.1/jquery-ui.theme.min.css', ) ); if ( $css != 'base' ) @@ -95,15 +93,15 @@ if ( $css != 'base' ) ) ); ?> + - +