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 b43680b0b..cca9fe82e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -138,9 +138,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") @@ -347,19 +347,50 @@ else(JPEG_FOUND) "ZoneMinder requires jpeg but it was not found on your system") endif(JPEG_FOUND) +# LIBJWT +find_package(LibJWT) +if(LIBJWT_FOUND) + set(HAVE_LIBJWT 1) + set(optlibsfound "${optlibsfound} LIBJWT") + list(APPEND ZM_BIN_LIBS "${LIBJWT_LIBRARY}") +else(LIBJWT_FOUND) + set(optlibsnotfound "${optlibsnotfound} LIBJWT") +endif(LIBJWT_FOUND) + +# gnutls (using find_library and find_path) +if(HAVE_LIBJWT) + find_library(GNUTLS_LIBRARIES gnutls) + if(GNUTLS_LIBRARIES) + set(HAVE_LIBGNUTLS 1) + list(APPEND ZM_BIN_LIBS "${GNUTLS_LIBRARIES}") + find_path(GNUTLS_INCLUDE_DIR gnutls/gnutls.h) + if(GNUTLS_INCLUDE_DIR) + include_directories("${GNUTLS_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${GNUTLS_INCLUDE_DIR}") + endif(GNUTLS_INCLUDE_DIR) + mark_as_advanced(FORCE GNUTLS_LIBRARIES GNUTLS_INCLUDE_DIR) + check_include_file("gnutls/gnutls.h" HAVE_GNUTLS_GNUTLS_H) + set(optlibsfound "${optlibsfound} GnuTLS") + else(GNUTLS_LIBRARIES) + set(optlibsnotfound "${optlibsnotfound} GnuTLS") + endif(GNUTLS_LIBRARIES) +endif(HAVE_LIBJWT) + # OpenSSL -find_package(OpenSSL) -if(OPENSSL_FOUND) - set(HAVE_LIBOPENSSL 1) - set(HAVE_LIBCRYPTO 1) - list(APPEND ZM_BIN_LIBS "${OPENSSL_LIBRARIES}") - include_directories("${OPENSSL_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${OPENSSL_INCLUDE_DIR}") - check_include_file("openssl/md5.h" HAVE_OPENSSL_MD5_H) - set(optlibsfound "${optlibsfound} OpenSSL") -else(OPENSSL_FOUND) - set(optlibsnotfound "${optlibsnotfound} OpenSSL") -endif(OPENSSL_FOUND) +if(NOT HAVE_LIBGNUTLS OR NOT HAVE_LIBJWT) + find_package(OpenSSL) + if(OPENSSL_FOUND) + set(HAVE_LIBOPENSSL 1) + set(HAVE_LIBCRYPTO 1) + list(APPEND ZM_BIN_LIBS "${OPENSSL_LIBRARIES}") + include_directories("${OPENSSL_INCLUDE_DIR}") + set(CMAKE_REQUIRED_INCLUDES "${OPENSSL_INCLUDE_DIR}") + check_include_file("openssl/md5.h" HAVE_OPENSSL_MD5_H) + set(optlibsfound "${optlibsfound} OpenSSL") + else(OPENSSL_FOUND) + set(optlibsnotfound "${optlibsnotfound} OpenSSL") + endif(OPENSSL_FOUND) +endif(NOT HAVE_LIBGNUTLS OR NOT HAVE_LIBJWT) # pthread (using find_library and find_path) find_library(PTHREAD_LIBRARIES pthread) @@ -416,28 +447,6 @@ else(GCRYPT_LIBRARIES) set(optlibsnotfound "${optlibsnotfound} GCrypt") endif(GCRYPT_LIBRARIES) -# gnutls (using find_library and find_path) -find_library(GNUTLS_LIBRARIES gnutls-openssl) -if(NOT GNUTLS_LIBRARIES) - find_library(GNUTLS_LIBRARIES gnutls) -endif(NOT GNUTLS_LIBRARIES) - -if(GNUTLS_LIBRARIES) - set(HAVE_LIBGNUTLS 1) - list(APPEND ZM_BIN_LIBS "${GNUTLS_LIBRARIES}") - find_path(GNUTLS_INCLUDE_DIR gnutls/gnutls.h) - if(GNUTLS_INCLUDE_DIR) - include_directories("${GNUTLS_INCLUDE_DIR}") - set(CMAKE_REQUIRED_INCLUDES "${GNUTLS_INCLUDE_DIR}") - endif(GNUTLS_INCLUDE_DIR) - mark_as_advanced(FORCE GNUTLS_LIBRARIES GNUTLS_INCLUDE_DIR) - check_include_file("gnutls/openssl.h" HAVE_GNUTLS_OPENSSL_H) - check_include_file("gnutls/gnutls.h" HAVE_GNUTLS_GNUTLS_H) - set(optlibsfound "${optlibsfound} GnuTLS") -else(GNUTLS_LIBRARIES) - set(optlibsnotfound "${optlibsnotfound} GnuTLS") -endif(GNUTLS_LIBRARIES) - # mysqlclient (using find_library and find_path) find_library(MYSQLCLIENT_LIBRARIES mysqlclient PATH_SUFFIXES mysql) if(MYSQLCLIENT_LIBRARIES) @@ -731,14 +740,7 @@ if(HAVE_OPENSSL_MD5_H) "unsigned char *MD5(const unsigned char *d, size_t n, unsigned char *md)" "NULL" "openssl/md5.h" HAVE_MD5_OPENSSL) endif(HAVE_OPENSSL_MD5_H) -if(HAVE_GNUTLS_OPENSSL_H) - set(CMAKE_REQUIRED_LIBRARIES "${GNUTLS_LIBRARIES}") - set(CMAKE_REQUIRED_INCLUDES "${GNUTLS_INCLUDE_DIR}") - check_prototype_definition( - MD5 - "unsigned char *MD5 (const unsigned char *buf, unsigned long len, unsigned char *md)" "NULL" "gnutls/openssl.h" - HAVE_MD5_GNUTLS) -endif(HAVE_GNUTLS_OPENSSL_H) + if(HAVE_GNUTLS_GNUTLS_H) set(CMAKE_REQUIRED_LIBRARIES "${GNUTLS_LIBRARIES}") set(CMAKE_REQUIRED_INCLUDES "${GNUTLS_INCLUDE_DIR}") @@ -747,13 +749,17 @@ if(HAVE_GNUTLS_GNUTLS_H) "int gnutls_fingerprint (gnutls_digest_algorithm_t algo, const gnutls_datum_t * data, void *result, size_t * result_size)" "0" "stdlib.h;gnutls/gnutls.h" HAVE_DECL_GNUTLS_FINGERPRINT) endif(HAVE_GNUTLS_GNUTLS_H) -if(HAVE_MD5_OPENSSL OR HAVE_MD5_GNUTLS) + +if(HAVE_MD5_OPENSSL) set(HAVE_DECL_MD5 1) -else(HAVE_MD5_OPENSSL OR HAVE_MD5_GNUTLS) +endif(HAVE_MD5_OPENSSL) + +if((NOT HAVE_MD5_OPENSSL) AND (NOT HAVE_DECL_GNUTLS_FINGERPRINT)) message(AUTHOR_WARNING - "ZoneMinder requires a working MD5 function for hashed authenication but - none were found - hashed authenication will not be available") -endif(HAVE_MD5_OPENSSL OR HAVE_MD5_GNUTLS) + "ZoneMinder requires a working MD5 function for hashed authentication but + none were found - hashed authentication will not be available") +endif((NOT HAVE_MD5_OPENSSL) AND (NOT HAVE_DECL_GNUTLS_FINGERPRINT)) + # Dirty fix for zm_user only using openssl's md5 if gnutls and gcrypt are not available. # This needs to be fixed in zm_user.[h,cpp] but such fix will also require changes to configure.ac if(HAVE_LIBCRYPTO AND HAVE_OPENSSL_MD5_H AND HAVE_MD5_OPENSSL) @@ -903,7 +909,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..e1ed37087 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 @@ -21,12 +22,6 @@ https://github.com/ZoneMinder/zmdockerfiles ## Installation Methods -### Building from Source is Discouraged - -Historically, installing ZoneMinder onto your system required building from source code by issuing the traditional configure, make, make install commands. To get ZoneMinder to build, all of its dependencies had to be determined and installed beforehand. Init and logrotate scripts had to be manually copied into place following the build. Optional packages such as jscalendar and Cambozola had to be manually installed. Uninstalls could leave stale files around, which could cause problems during an upgrade. Speaking of upgrades, when it comes time to upgrade all these manual steps must be repeated again. - -Better methods exist today that do much of this for you. The current development team, along with other volunteers, have taken great strides in providing the resources necessary to avoid building from source. - ### Install from a Package Repository This is the recommended method to install ZoneMinder onto your system. ZoneMinder packages are maintained for the following distros: @@ -42,6 +37,13 @@ This is the recommended method to install ZoneMinder onto your system. ZoneMinde If a repository that hosts ZoneMinder packages is not available for your distro, then you are encouraged to build your own package, rather than build from source. While each distro is different in ways that set it apart from all the others, they are often similar enough to allow you to adapt another distro's package building instructions to your own. +### Building from Source is Discouraged + +Historically, installing ZoneMinder onto your system required building from source code by issuing the traditional configure, make, make install commands. To get ZoneMinder to build, all of its dependencies had to be determined and installed beforehand. Init and logrotate scripts had to be manually copied into place following the build. Optional packages such as jscalendar and Cambozola had to be manually installed. Uninstalls could leave stale files around, which could cause problems during an upgrade. Speaking of upgrades, when it comes time to upgrade all these manual steps must be repeated again. + +Better methods exist today that do much of this for you. The current development team, along with other volunteers, have taken great strides in providing the resources necessary to avoid building from source. + + ### Building a ZoneMinder Package ### Building ZoneMinder into a package is not any harder than building from source. As a matter of fact, if you have successfully built ZoneMinder from source in the past, then you may find these steps to be easier. diff --git a/cmake/Modules/FindLibJWT.cmake b/cmake/Modules/FindLibJWT.cmake new file mode 100644 index 000000000..e0c834609 --- /dev/null +++ b/cmake/Modules/FindLibJWT.cmake @@ -0,0 +1,28 @@ +include(FindPackageHandleStandardArgs) + +find_package(PkgConfig QUIET) +pkg_check_modules(PC_LIBJWT QUIET libjwt) + +find_path(LIBJWT_INCLUDE_DIR + NAMES jwt.h + HINTS ${PC_LIBJWT_INCLUDEDIR} ${PC_LIBJWT_INCLUDE_DIRS} + ) + +find_library(LIBJWT_LIBRARY + NAMES jwt-gnutls libjwt-gnutls liblibjwt-gnutls + HINTS ${PC_LIBJWT_LIBDIR} ${PC_LIBJWT_LIBRARY_DIR} + ) + +find_package_handle_standard_args(LibJWT + REQUIRED_VARS LIBJWT_INCLUDE_DIR LIBJWT_LIBRARY + ) + +if(LIBJWT_FOUND) + add_library(libjwt STATIC IMPORTED GLOBAL) + set_target_properties(libjwt PROPERTIES + IMPORTED_LOCATION "${LIBJWT_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${LIBJWT_INCLUDE_DIR}" + ) +endif() + +mark_as_advanced(LIBJWT_INCLUDE_DIR LIBJWT_LIBRARY) \ No newline at end of file diff --git a/db/zm_create.sql.in b/db/zm_create.sql.in index 44eb1a048..6b561a116 100644 --- a/db/zm_create.sql.in +++ b/db/zm_create.sql.in @@ -434,6 +434,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', @@ -807,6 +808,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); @@ -841,6 +843,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.34.6.sql b/db/zm_update-1.34.6.sql new file mode 100644 index 000000000..1a58bee1f --- /dev/null +++ b/db/zm_update-1.34.6.sql @@ -0,0 +1,5 @@ +-- +-- This updates a 1.34.5 database to 1.34.6 +-- +-- No changes required +-- diff --git a/db/zm_update-1.34.7.sql b/db/zm_update-1.34.7.sql new file mode 100644 index 000000000..ba86b1202 --- /dev/null +++ b/db/zm_update-1.34.7.sql @@ -0,0 +1,5 @@ +-- +-- This updates a 1.34.6 database to 1.34.7 +-- +-- No changes required +-- 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..f7eeeb016 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.34.10 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/installationguide/ubuntu.rst b/docs/installationguide/ubuntu.rst index 491ee1eb8..7b714e51a 100644 --- a/docs/installationguide/ubuntu.rst +++ b/docs/installationguide/ubuntu.rst @@ -42,7 +42,7 @@ guide you with a quick search. :: - add-apt-repository ppa:iconnor/zoneminder-1.32 + add-apt-repository ppa:iconnor/zoneminder-1.34 Update repo and upgrade. 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/docs/userguide/options/options_system.rst b/docs/userguide/options/options_system.rst index bd871a1e8..33c59cf1c 100644 --- a/docs/userguide/options/options_system.rst +++ b/docs/userguide/options/options_system.rst @@ -14,13 +14,13 @@ LANG_DEFAULT - ZoneMinder allows the web interface to use languages other than E OPT_USE_AUTH - ZoneMinder can run in two modes. The simplest is an entirely unauthenticated mode where anyone can access ZoneMinder and perform all tasks. This is most suitable for installations where the web server access is limited in other ways. The other mode enables user accounts with varying sets of permissions. Users must login or authenticate to access ZoneMinder and are limited by their defined permissions. Authenticated mode alone should not be relied up for securing Internet connected ZoneMinder. -AUTH_TYPE - ZoneMinder can use two methods to authenticate users when running in authenticated mode. The first is a builtin method where ZoneMinder provides facilities for users to log in and maintains track of their identity. The second method allows interworking with other methods such as http basic authentication which passes an independently authentication 'remote' user via http. In this case ZoneMinder would use the supplied user without additional authentication provided such a user is configured ion ZoneMinder. +AUTH_TYPE - ZoneMinder can use two methods to authenticate users when running in authenticated mode. The first is a builtin method where ZoneMinder provides facilities for users to log in and maintains track of their identity. The second method allows interworking with other methods such as http basic authentication which passes an independently authenticated 'remote' user via http. In this case ZoneMinder would use the supplied user without additional authentication provided such a user is configured in ZoneMinder. AUTH_RELAY - When ZoneMinder is running in authenticated mode it can pass user details between the web pages and the back end processes. There are two methods for doing this. This first is to use a time limited hashed string which contains no direct username or password details, the second method is to pass the username and passwords around in plaintext. This method is not recommend except where you do not have the md5 libraries available on your system or you have a completely isolated system with no external access. You can also switch off authentication relaying if your system is isolated in other ways. -AUTH_HASH_SECRET - When ZoneMinder is running in hashed authenticated mode it is necessary to generate hashed strings containing encrypted sensitive information such as usernames and password. Although these string are reasonably secure the addition of a random secret increases security substantially. Note that if you are using the new token based APIs, then this field is mandatory with ZM 1.34 and above +AUTH_HASH_SECRET - When ZoneMinder is running in hashed authenticated mode it is necessary to generate hashed strings containing encrypted sensitive information such as usernames and passwords. Although these strings are reasonably secure the addition of a random secret increases security substantially. Note that if you are using the new token based APIs, then this field is mandatory with ZM 1.34 and above. -AUTH_HASH_IPS - When ZoneMinder is running in hashed authenticated mode it can optionally include the requesting IP address in the resultant hash. This adds an extra level of security as only requests from that address may use that authentication key. However in some circumstances, such as access over mobile networks, the requesting address can change for each request which will cause most requests to fail. This option allows you to control whether IP addresses are included in the authentication hash on your system. If you experience intermitent problems with authentication, switching this option off may help. It is recommended you keep this off if you use mobile apps like zmNinja over mobile carrier networks - several APNs change the IP very frequently which may result in authentication failure. +AUTH_HASH_IPS - When ZoneMinder is running in hashed authenticated mode it can optionally include the requesting IP address in the resultant hash. This adds an extra level of security as only requests from that address may use that authentication key. However in some circumstances, such as access over mobile networks, the requesting address can change for each request which will cause most requests to fail. This option allows you to control whether IP addresses are included in the authentication hash on your system. If you experience intermitent problems with authentication, switching this option off may help. It is recommended you keep this off if you use mobile apps like zmNinja over mobile carrier networks - several APNs change the IP very frequently which may result in authentication failures. AUTH_HASH_TTL - Time before ZM auth will expire (does not apply to API tokens). The default has traditionally been 2 hours. A new hash will automatically be regenerated at half this value. @@ -34,11 +34,11 @@ OPT_USE_API - A global setting to enable/disable ZoneMinder APIs. If you are usi OPT_USE_LEGACY_AUTH - Starting version 1.34.0, ZoneMinder uses a more secure Authentication mechanism using JWT tokens. Older versions used a less secure MD5 based auth hash. It is recommended you turn this off after you are sure you don't need it. If you are using a 3rd party app that relies on the older API auth mechanisms, you will have to update that app if you turn this off. Note that zmNinja 1.3.057 onwards supports the new token system. -OPT_USE_EVENT_NOTIFICATION - zmeventnotification is a 3rd party event notification server that is used to get notifications for alarms detected by ZoneMinder in real time. zmNinja requires this server for push notifications to mobile phones. This option only enables the server if its already installed. Please visit the `Event Notification Server project site `__ for installation instructions. +OPT_USE_EVENT_NOTIFICATION - zmeventnotification is a 3rd party event notification server that is used to get notifications for alarms detected by ZoneMinder in real time. zmNinja requires this server for push notifications to mobile phones. This option only enables the server if it is already installed. Please visit the `Event Notification Server project site `__ for installation instructions. -OPT_USE_GOOG_RECAPTCHA - This option allows you to include a google reCaptcha validation at login. This means in addition to providing a valid usernane and password, you will also have to pass the reCaptcha test. Please note that enabling this option results in the zoneminder login page reach out to google servers for captcha validation. Also please note that enabling this option may break 3rd party clients if they rely on web based logins (Note that zmNinja now uses the API based token method and will not be affected if reCAPTCHA is enabled). If you enable this, you also need to specify your site and secret key (please refer to context help in the ZoneMinder system screen) +OPT_USE_GOOG_RECAPTCHA - This option allows you to include a google reCaptcha validation at login. This means in addition to providing a valid username and password, you will also have to pass the reCaptcha test. Please note that enabling this option results in the zoneminder login page reaching out to google servers for captcha validation. Also please note that enabling this option may break 3rd party clients if they rely on web based logins (Note that zmNinja now uses the API based token method and will not be affected if reCAPTCHA is enabled). If you enable this, you also need to specify your site and secret key (please refer to context help in the ZoneMinder system screen). -SYSTEM_SHUTDOWN - this option decides if it is allowed to shutdown the full system via the ZM UI. The system will need to have sudo installed and the following added to /etc/sudoers: +SYSTEM_SHUTDOWN - this option puts a poweroff icon in the header of the ZM UI for users with System privilege accessi. This icon will allow the user to shutdown the full system via the ZM UI. The system will need to have sudo installed and the following added to /etc/sudoers: :: @@ -46,9 +46,9 @@ SYSTEM_SHUTDOWN - this option decides if it is allowed to shutdown the full syst to perform the shutdown or reboot -OPT_FAST_DELETE - Normally an event created as the result of an alarm consists of entries in one or more database tables plus the various files associated with it. When deleting events in the browser it can take a long time to remove all of this if your are trying to do a lot of events at once. **NOTE**: It is recommended that you keep this option OFF, unless you are running on an old or low-powered system. +OPT_FAST_DELETE - Normally an event created as the result of an alarm consists of entries in one or more database tables plus the various files associated with it. When deleting events in the browser it can take a long time to remove all of this if youxr are trying to do a lot of events at once. **NOTE**: It is recommended that you keep this option OFF, unless you are running on an old or low-powered system. -FILTER_RELOAD_DELAY - ZoneMinder allows you to save filters to the database which allow events that match certain criteria to be emailed, deleted or uploaded to a remote machine etc. The zmfilter daemon loads these and does the actual operation. This option determines how often in seconds the filters are reloaded from the database to get the latest versions or new filters. If you don't change filters very often this value can be set to a large value. +FILTER_RELOAD_DELAY - ZoneMinder allows you to save filters to the database which allow events that match certain criteria to be emailed, deleted or uploaded to a remote machine etc. The zmfilter daemon loads these and does the actual operation. This option determines how often in seconds the filters are reloaded from the database to get the latest versions or new filters. If you don't change filters very often this value can be set to a large value. As of 1.34.0 filters should be automatically reloaded when saving a filter so this setting should have little effect. FILTER_EXECUTE_INTERVAL - ZoneMinder allows you to save filters to the database which allow events that match certain criteria to be emailed, deleted or uploaded to a remote machine etc. The zmfilter daemon loads these and does the actual operation. This option determines how often the filters are executed on the saved event in the database. If you want a rapid response to new events this should be a smaller value, however this may increase the overall load on the system and affect performance of other elements. @@ -58,9 +58,9 @@ STATUS_UPDATE_INTERVAL - The zmstats daemon performs various db queries related WATCH_CHECK_INTERVAL - The zmwatch daemon checks the image capture performance of the capture daemons to ensure that they have not locked up (rarely a sync error may occur which blocks indefinitely). This option determines how often the daemons are checked. -WATCH_MAX_DELAY - The zmwatch daemon checks the image capture performance of the capture daemons to ensure that they have not locked up (rarely a sync error may occur which blocks indefinitely). This option determines the maximum delay to allow since the last captured frame. The daemon will be restarted if it has not captured any images after this period though the actual restart may take slightly longer in conjunction with the check interval value above. +WATCH_MAX_DELAY - The zmwatch daemon checks the image capture performance of the capture daemons to ensure that they have not locked up (rarely a sync error may occur which blocks indefinitely). This option determines the maximum delay to allow since the last captured frame. The daemon will be restarted if it has not captured any images after this period though the actual restart may take slightly longer in conjunction with the check interval value above. Please note that some cameras can take up to 30 seconds to get a valid image, so this setting should be larger than that. -RUN_AUDIT - The zmaudit daemon exists to check that the saved information in the database and on the filesystem match and are consistent with each other. If an error occurs or if you are using 'fast deletes' it may be that database records are deleted but files remain. In this case, and similar, zmaudit will remove redundant information to synchronise the two data stores. This option controls whether zmaudit is run in the background and performs these checks and fixes continuously. It is recommended you keep this **OFF** in most systems. +RUN_AUDIT - The zmaudit daemon exists to check that the saved information in the database and on the filesystem match and are consistent with each other. If an error occurs or if you are using 'fast deletes' it may be that database records are deleted but files remain. In this case, and similar, zmaudit will remove redundant information to synchronise the two data stores. This option controls whether zmaudit is run in the background and performs these checks and fixes continuously. It is recommended you keep this **OFF** in most systems and run it manually if needed after a system crash. AUDIT_CHECK_INTERVAL - The zmaudit daemon exists to check that the saved information in the database and on the filesystem match and are consistent with each other. If an error occurs or if you are using 'fast deletes' it may be that database records are deleted but files remain. In this case, and similar, zmaudit will remove redundant information to synchronise the two data stores. The default check interval of 900 seconds (15 minutes) is fine for most systems however if you have a very large number of events the process of scanning the database and filesystem may take a long time and impact performance. In this case you may prefer to make this interval much larger to reduce the impact on your system. This option determines how often these checks are performed. @@ -70,11 +70,11 @@ OPT_CONTROL - ZoneMinder includes limited support for controllable cameras. A nu OPT_TRIGGERS - ZoneMinder can interact with external systems which prompt or cancel alarms. This is done via the zmtrigger.pl script. This option indicates whether you want to use these external triggers. Most people will say no here. -CHECK_FOR_UPDATES - From ZoneMinder version 1.17.0 onwards new versions are expected to be more frequent. To save checking manually for each new version ZoneMinder can check with the zoneminder.com website to determine the most recent release. These checks are infrequent, about once per week, and no personal or system information is transmitted other than your current version number. If you do not wish these checks to take place or your ZoneMinder system has no internet access you can switch these check off with this configuration variable +CHECK_FOR_UPDATES - To save checking manually for each new version ZoneMinder can check with the zoneminder.com website to determine the most recent release. These checks are infrequent, about once per week, and no personal or system information is transmitted other than your current version number. If you do not wish these checks to take place or your ZoneMinder system has no internet access you can switch these check off with this configuration variable. TELEMETRY_DATA - Enable collection of usage information of the local system and send it to the ZoneMinder development team. This data will be used to determine things like who and where our customers are, how big their systems are, the underlying hardware and operating system, etc. This is being done for the sole purpose of creating a better product for our target audience. This script is intended to be completely transparent to the end user, and can be disabled from the web console under Options. For more details on what information we collect, please refer to Zoneminder's privacy statement (available in the contextual help of TELEMETRY_DATA on your installation). -UPDATE_CHECK_PROXY - If you use a proxy to access the internet then ZoneMinder needs to know so it can access zoneminder.com to check for updates. If you do use a proxy enter the full proxy url here in the form of ``http://:/`` +UPDATE_CHECK_PROXY - If you use a proxy to access the internet then ZoneMinder needs to know so it can access zoneminder.com to check for updates. If you do use a proxy enter the full proxy url here in the form of ``http://:/``. SHM_KEY - ZoneMinder uses shared memory to speed up communication between modules. To identify the right area to use shared memory keys are used. This option controls what the base key is, each monitor will have it's Id or'ed with this to get the actual key used. You will not normally need to change this value unless it clashes with another instance of ZoneMinder on the same machine. Only the first four hex digits are used, the lower four will be masked out and ignored. 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/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in index 31fafc1bc..de4eb6984 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 @@ -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/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..4f14e787a --- /dev/null +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/DCS5020L.pm @@ -0,0 +1,310 @@ +# =========================================================================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 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 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__ + +=head1 NAME + +ZoneMinder::Control::DCS5020L - 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 + +Copyright (C) 2018 ZoneMinder LLC + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +=cut 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/Memory.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/Memory.pm.in index 53da1659b..13cae4d4b 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Memory.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/Memory.pm.in @@ -301,7 +301,7 @@ sub zmMemVerify { } return !undef; -} +} # end sub zmMemVerify sub zmMemRead { my $monitor = shift; @@ -375,10 +375,8 @@ sub zmMemInvalidate { my $mem_key = zmMemKey($monitor); if ( $mem_key ) { zmMemDetach($monitor); - } else { - Warning('no memkey in zmMemInvalidate'); } -} +} # end sub zmMemInvalidate sub zmMemTidy { zmMemClean(); @@ -504,10 +502,10 @@ sub zmHasAlarmed { my $last_event_id = shift; my ( $state, $last_event ) = zmMemRead($monitor, - ['shared_data:state' ,'shared_data:last_event'] + ['shared_data:state', 'shared_data:last_event'] ); - if ( $state == STATE_ALARM || $state == STATE_ALERT ) { + if ( $state == STATE_ALARM or $state == STATE_ALERT ) { return $last_event; } elsif( $last_event != $last_event_id ) { return $last_event; diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Memory/Mapped.pm b/scripts/ZoneMinder/lib/ZoneMinder/Memory/Mapped.pm index b9a8b6a1c..cb7920913 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Memory/Mapped.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Memory/Mapped.pm @@ -51,7 +51,7 @@ our %EXPORT_TAGS = ( ); push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; -our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); +our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } ); our @EXPORT = @EXPORT_OK; @@ -77,17 +77,17 @@ sub zmMemAttach { my ( $monitor, $size ) = @_; if ( !$size ) { - Error("No size passed to zmMemAttach for monitor $$monitor{Id}"); + Error('No size passed to zmMemAttach for monitor '.$$monitor{Id}); return undef; } if ( defined($monitor->{MMapAddr}) ) { - Debug("zmMemAttach already attached at $monitor->{MMapAddr}"); + Debug("zmMemAttach already attached at $monitor->{MMapAddr} for $$monitor{Id}"); return !undef; } my $mmap_file = $Config{ZM_PATH_MAP}.'/zm.mmap.'.$monitor->{Id}; if ( ! -e $mmap_file ) { - Error("Memory map file '$mmap_file' does not exist. zmc might not be running."); + Error("Memory map file '$mmap_file' does not exist in zmMemAttach. zmc might not be running."); return undef; } my $mmap_file_size = -s $mmap_file; @@ -119,18 +119,24 @@ sub zmMemDetach { if ( $monitor->{MMap} ) { if ( ! munmap(${$monitor->{MMap}}) ) { - Warn( "Unable to munmap for monitor $$monitor{Id}\n"); + Warn("Unable to munmap for monitor $$monitor{Id}"); } delete $monitor->{MMap}; + } else { + Warn("No MMap for $$monitor{Id}"); } if ( $monitor->{MMapAddr} ) { delete $monitor->{MMapAddr}; + } else { + Warn("No MMapAddr in $$monitor{Id}"); } if ( $monitor->{MMapHandle} ) { close($monitor->{MMapHandle}); delete $monitor->{MMapHandle}; + } else { + Warn("No MMapHandle in $$monitor{Id}"); } -} +} # end sub zmMemDetach sub zmMemGet { my $monitor = shift; @@ -162,7 +168,7 @@ sub zmMemPut { } sub zmMemClean { - Debug("Removing memory map files"); + Debug('Removing memory map files'); my $mapPath = $Config{ZM_PATH_MAP}.'/zm.mmap.*'; foreach my $mapFile( glob( $mapPath ) ) { ( $mapFile ) = $mapFile =~ /^(.+)$/; 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..f4dedb3b9 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/ONVIF.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/ONVIF.pm.in @@ -255,15 +255,15 @@ 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 +272,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 +330,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 9204ef3a1..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 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..77dfd1e08 100644 --- a/scripts/zmfilter.pl.in +++ b/scripts/zmfilter.pl.in @@ -860,7 +860,7 @@ sub sendEmail { From => $Config{ZM_FROM_EMAIL}, To => $Config{ZM_EMAIL_ADDRESS}, Subject => $subject, - Type => (($body=~//)?'text/html':'text/plain'), + Type => (($body=~/ $body ); @@ -962,7 +962,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/zmtelemetry.pl.in b/scripts/zmtelemetry.pl.in index efd5e735e..a3debabdb 100644 --- a/scripts/zmtelemetry.pl.in +++ b/scripts/zmtelemetry.pl.in @@ -23,6 +23,7 @@ use strict; use bytes; +use utf8; @EXTRA_PERL_LIB@ use ZoneMinder; @@ -34,6 +35,7 @@ use Sys::MemInfo qw(totalmem); use Sys::CPU qw(cpu_count); use POSIX qw(strftime uname); use JSON::MaybeXS; +use Encode; $ENV{PATH} = '/bin:/usr/bin:/usr/local/bin'; $ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL}; @@ -43,6 +45,7 @@ delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; my $help = 0; my $force = 0; +my $show = 0; # Interval between version checks my $interval; my $version; @@ -50,6 +53,7 @@ my $version; GetOptions( force => \$force, help => \$help, + show => \$show, interval => \$interval, version => \$version ); @@ -57,6 +61,14 @@ if ( $version ) { print( ZoneMinder::Base::ZM_VERSION . "\n"); exit(0); } +if ($show) { + my %telemetry; + my $dbh = zmDbConnect(); + collectData($dbh, \%telemetry); + my $result = jsonEncode(\%telemetry); + print ($result); + exit(0); +} if ( $help ) { pod2usage(-exitstatus => -1); } @@ -87,21 +99,9 @@ while( 1 ) { my $dbh = zmDbConnect(); # Build the telemetry hash # We should keep *BSD systems in mind when calling system commands + my %telemetry; - $telemetry{uuid} = getUUID($dbh); - @telemetry{qw(city region country latitude longitude)} = getGeo(); - $telemetry{timestamp} = strftime('%Y-%m-%dT%H:%M:%S%z', localtime()); - $telemetry{monitor_count} = countQuery($dbh,'Monitors'); - $telemetry{event_count} = countQuery($dbh,'Events'); - $telemetry{architecture} = runSysCmd('uname -p'); - ($telemetry{kernel}, $telemetry{distro}, $telemetry{version}) = getDistro(); - $telemetry{zm_version} = ZoneMinder::Base::ZM_VERSION; - $telemetry{system_memory} = totalmem(); - $telemetry{processor_count} = cpu_count(); - $telemetry{monitors} = getMonitorRef($dbh); - - Info('Sending data to ZoneMinder Telemetry server.'); - + collectData($dbh,\%telemetry); my $result = jsonEncode(\%telemetry); if ( sendData($result) ) { @@ -124,6 +124,24 @@ print 'ZoneMinder Telemetry Agent exiting at '.strftime('%y/%m/%d %H:%M:%S', loc # SUBROUTINES # ############### +# collect data to send +sub collectData { + my $dbh = shift; + my $telemetry = shift; + $telemetry->{uuid} = getUUID($dbh); + ($telemetry->{city},$telemetry->{region},$telemetry->{country},$telemetry->{latitude},$telemetry->{longitude})=getGeo(); + $telemetry->{timestamp} = strftime('%Y-%m-%dT%H:%M:%S%z', localtime()); + $telemetry->{monitor_count} = countQuery($dbh,'Monitors'); + $telemetry->{event_count} = countQuery($dbh,'Events'); + $telemetry->{architecture} = runSysCmd('uname -p'); + ($telemetry->{kernel}, $telemetry->{distro}, $telemetry->{version}) = getDistro(); + $telemetry->{zm_version} = ZoneMinder::Base::ZM_VERSION; + $telemetry->{system_memory} = totalmem(); + $telemetry->{processor_count} = cpu_count(); + $telemetry->{use_event_server} = $Config{ZM_OPT_USE_EVENTNOTIFICATION}; + $telemetry->{monitors} = getMonitorRef($dbh); +} + # Find, verify, then run the supplied system command sub runSysCmd { my $msg = shift; @@ -166,7 +184,7 @@ sub sendData { $req->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 +214,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 +250,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 +263,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({}); @@ -363,7 +381,7 @@ zmtelemetry.pl - Send usage information to the ZoneMinder development team =head1 SYNOPSIS - zmtelemetry.pl [--force] [--help] [--interval=seconds] [--version] + zmtelemetry.pl [--force] [--help] [--show] [--interval=seconds] [--version] =head1 DESCRIPTION @@ -380,6 +398,7 @@ console under Options. --force Force the script to upload it's data instead of waiting for the defined interval since last upload. --help Display usage information + --show Displays telemetry data that is sent to zoneminder --interval Override the default configured interval since last upload. The value should be given in seconds, but can be an expression such as 24*60*60. diff --git a/scripts/zmtrigger.pl.in b/scripts/zmtrigger.pl.in index b1743aeae..1ee392c1e 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() ); @@ -357,11 +356,16 @@ sub handleMessage { my $connection = shift; my $message = shift; + # CUA - Axis camera send the message quoted with" + # CUA - Also Axis camera cannot save the plus sign which + $message =~ s/^\"//g; + $message =~ s/\"$//g; + my ( $id, $action, $score, $cause, $text, $showtext ) = split( /\|/, $message ); - $score = 0 if ( !defined($score) ); - $cause = '' if ( !defined($cause) ); - $text = '' if ( !defined($text) ); + $score = 0 if !defined($score); + $cause = '' if !defined($cause); + $text = '' if !defined($text); my $monitor = $monitors{$id}; if ( !$monitor ) { @@ -373,7 +377,7 @@ sub handleMessage { next if !zmMemVerify($monitor); Debug("Handling action '$action'"); - if ( $action =~ /^(enable|disable)(?:\+(\d+))?$/ ) { + if ( $action =~ /^(enable|disable)(?:[\+ ](\d+))?$/ ) { my $state = $1; my $delay = $2; if ( $state eq 'enable' ) { diff --git a/scripts/zmupdate.pl.in b/scripts/zmupdate.pl.in index b0b63757a..3662b6655 100644 --- a/scripts/zmupdate.pl.in +++ b/scripts/zmupdate.pl.in @@ -847,9 +847,9 @@ if ( $version ) { } $cascade = !undef; } - if ( $cascade || $version eq "1.24.4" ) { + if ( $cascade || $version eq '1.24.4' ) { # Patch the database - patchDB( $dbh, "1.24.4" ); + patchDB($dbh, '1.24.4'); # Copy the FTP specific values to the new general config my $fetchSql = "select * from Config where Name like 'ZM_UPLOAD_FTP_%'"; @@ -863,12 +863,12 @@ if ( $version ) { } $cascade = !undef; } - if ( $cascade || $version lt "1.26.0" ) { - my $sth = $dbh->prepare_cached( 'select * from Monitors LIMIT 0,1' ); + if ( $cascade || $version lt '1.26.0' ) { + my $sth = $dbh->prepare_cached('SELECT * FROM Monitors LIMIT 0,1'); die "Error: " . $dbh->errstr . "\n" unless ($sth); die "Error: " . $sth->errstr . "\n" unless ($sth->execute); - my $columns = $sth->{'NAME'}; + my $columns = $sth->{NAME}; if ( ! grep(/^Colours$/, @$columns ) ) { $dbh->do(q{alter table Monitors add column `Colours` tinyint(3) unsigned NOT NULL default '1' after `Height`;}); } # end if @@ -898,28 +898,31 @@ if ( $version ) { die "Should have found upgrade scripts at $updateDir\n"; } # end if + my $sql = "UPDATE `Config` SET `Value` = ? WHERE `Name` = 'ZM_DYN_DB_VERSION'"; + my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); + foreach my $patch ( @files ) { my ( $v ) = $patch =~ /^zm_update\-([\d\.]+)\.sql$/; #PP make sure we use version compare - if ( version->parse('v' . $v) > version->parse('v' . $version) ) { - print( "Upgrading DB to $v from $version\n" ); - patchDB( $dbh, $v ); - my $sql = "update Config set Value = ? where Name = 'ZM_DYN_DB_VERSION'"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $version ) or die( "Can't execute: ".$sth->errstr() ); - $sth->finish(); + if ( version->parse('v'.$v) > version->parse('v'.$version) ) { + print("Upgrading DB to $v from $version\n"); + if ( patchDB($dbh, $v) ) { + my $res = $sth->execute($version) or die( "Can't execute: ".$sth->errstr() ); + } #patchDB_using_do( $dbh, $version, $updateDir.'/'.$patch ); } # end if newer version } # end foreach patchfile + + $sth->finish(); $cascade = !undef; } # end if if ( $cascade ) { - my $installed_version = ZM_VERSION; - my $sql = 'update Config set Value = ? where Name = ?'; + # This is basically here so that we don't need zm-update-blah.sql files for versions without db changes + my $sql = 'UPDATE `Config` SET `Value` = ? WHERE `Name` = ?'; my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( "$installed_version", 'ZM_DYN_DB_VERSION' ) or die( "Can't execute: ".$sth->errstr() ); - $res = $sth->execute( "$installed_version", 'ZM_DYN_CURR_VERSION' ) or die( "Can't execute: ".$sth->errstr() ); + $sth->execute(ZM_VERSION, 'ZM_DYN_DB_VERSION') or die( "Can't execute: ".$sth->errstr() ); + $sth->execute(ZM_VERSION, 'ZM_DYN_CURR_VERSION') or die( "Can't execute: ".$sth->errstr() ); $sth->finish(); } else { zmDbDisconnect(); @@ -930,41 +933,42 @@ if ( $version ) { #my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); #my $res = $sth->execute( ) or die( "Can't execute: ".$sth->errstr() ); #$sth->finish(); - print( "\nDatabase upgrade to version ".ZM_VERSION." successful.\n\n" ); -} + print("\nDatabase upgrade to version ".ZM_VERSION." successful.\n\n"); +} # end if version + zmDbDisconnect(); -exit( 0 ); +exit(0); sub patchDB_using_do { my ( $dbh, $version, $file ) = @_; - open( my $fh, '<', $file ) or die "Unable to open $file $!"; + open(my $fh, '<', $file) or die "Unable to open $file $!"; $/ = undef; my $sql = <$fh>; close $fh; if ( $sql ) { - $dbh->{'AutoCommit'} = 0; + $dbh->{AutoCommit} = 0; $dbh->do($sql); if ( $dbh->errstr() ) { $dbh->rollback(); - die "Error: " . $dbh->errstr(). ". Rolled back.\n"; + die 'Error: '.$dbh->errstr().". Rolled back.\n"; } # end if error - my $sql = "update Config set Value = ? where Name = 'ZM_DYN_DB_VERSION'"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $version ) or die( "Can't execute: ".$sth->errstr() ); + my $sql = 'UPDATE `Config` SET `Value` = ? WHERE `Name` = \'ZM_DYN_DB_VERSION\''; + my $sth = $dbh->prepare_cached($sql) or die "Can't prepare '$sql': ".$dbh->errstr(); + my $res = $sth->execute($version) or die 'Can\'t execute: '.$sth->errstr(); $sth->finish(); - $dbh->{'AutoCommit'} = 1; + $dbh->{AutoCommit} = 1; } else { Warning("Empty db update file at $file"); } -} +} # end sub patchDB_using_do + sub patchDB { my $dbh = shift; my $version = shift; - my ( $host, $portOrSocket ) = ( $Config{ZM_DB_HOST} =~ /^([^:]+)(?::(.+))?$/ ); my $command = 'mysql'; if ( defined($portOrSocket) ) { @@ -988,39 +992,38 @@ sub patchDB { } $command .= '/zm_update-'.$version.'.sql'; - print( "Executing '$command'\n" ) if ( logDebugging() ); + print("Executing '$command'\n") if logDebugging(); my $output = qx($command); my $status = $? >> 8; if ( $status || logDebugging() ) { - chomp( $output ); - print( "Output: $output\n" ); + chomp($output); + print("Output: $output\n"); } if ( $status ) { - die( "Command '$command' exited with status: $status\n" ); + die("Command '$command' exited with status: $status\n"); } - print( "\nDatabase successfully upgraded to version $version.\n" ); - -} + print("\nDatabase successfully upgraded to version $version.\n"); +} # end sub patchDB sub migratePasswords { - print ("Migratings passwords, if any...\n"); - my $sql = "select * from Users"; - 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() ); - while( my $user = $sth->fetchrow_hashref() ) { - my $scheme = substr($user->{Password}, 0, 1); - if ($scheme eq "*") { - print ("-->".$user->{Username}. " password will be migrated\n"); - my $salt = Crypt::Eksblowfish::Bcrypt::en_base64(rand_bits(16*8)); - my $settings = '$2a$10$'.$salt; - my $pass_hash = Crypt::Eksblowfish::Bcrypt::bcrypt($user->{Password},$settings); - my $new_pass_hash = "-ZM-".$pass_hash; - $sql = "UPDATE Users SET PASSWORD=? WHERE Username=?"; - my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute($new_pass_hash, $user->{Username}) or die( "Can't execute: ".$sth->errstr() ); - } + print ("Migratings passwords, if any...\n"); + my $sql = 'SELECT * FROM `Users`'; + 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()); + while( my $user = $sth->fetchrow_hashref() ) { + my $scheme = substr($user->{Password}, 0, 1); + if ($scheme eq '*') { + print ('-->'.$user->{Username}." password will be migrated\n"); + my $salt = Crypt::Eksblowfish::Bcrypt::en_base64(rand_bits(16*8)); + my $settings = '$2a$10$'.$salt; + my $pass_hash = Crypt::Eksblowfish::Bcrypt::bcrypt($user->{Password},$settings); + my $new_pass_hash = '-ZM-'.$pass_hash; + $sql = 'UPDATE Users SET `Password`=? WHERE `Username`=?'; + my $sth = $dbh->prepare_cached($sql) or die("Can't prepare '$sql': ".$dbh->errstr()); + my $res = $sth->execute($new_pass_hash, $user->{Username}) or die("Can't execute: ".$sth->errstr()); } -} + } +} # end sub migratePasswords sub migratePaths { 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/jwt-cpp/include/jwt-cpp/jwt.h b/src/jwt-cpp/include/jwt-cpp/jwt_cpp.h similarity index 100% rename from src/jwt-cpp/include/jwt-cpp/jwt.h rename to src/jwt-cpp/include/jwt-cpp/jwt_cpp.h diff --git a/src/zm_crypt.cpp b/src/zm_crypt.cpp index 6b78e169b..8e42b3b3c 100644 --- a/src/zm_crypt.cpp +++ b/src/zm_crypt.cpp @@ -1,12 +1,81 @@ #include "zm.h" #include "zm_crypt.h" #include "BCrypt.hpp" -#include "jwt.h" +#if HAVE_LIBJWT +#include +#else +#include "jwt_cpp.h" +#endif #include +#if HAVE_LIBCRYPTO #include +#elif HAVE_GNUTLS_GNUTLS_H +#include +#include +#endif #include // returns username if valid, "" if not +#if HAVE_LIBJWT +std::pair verifyToken(std::string jwt_token_str, std::string key) { + std::string username = ""; + unsigned int token_issued_at = 0; + int err = 0; + jwt_t *jwt = nullptr; + + err = jwt_new(&jwt); + if( err ) { + Error("Unable to Allocate JWT object"); + return std::make_pair("", 0); + } + + err = jwt_set_alg(jwt, JWT_ALG_HS256, (const unsigned char*)key.c_str(), key.length()); + if( err ) { + jwt_free(jwt); + Error("Error setting Algorithm for JWT decode"); + return std::make_pair("", 0); + } + + err = jwt_decode(&jwt, jwt_token_str.c_str(), nullptr, 0); + if( err ) { + jwt_free(jwt); + Error("Could not decode JWT"); + return std::make_pair("", 0); + } + + const char *c_type = jwt_get_grant(jwt, (const char*)"type"); + if ( !c_type ) { + jwt_free(jwt); + Error("Missing token type. This should not happen"); + return std::make_pair("", 0); + } else if ( std::string(c_type) != "access" ) { + jwt_free(jwt); + Error("Only access tokens are allowed. Please do not use refresh tokens"); + return std::make_pair("", 0); + } + + const char *c_username = jwt_get_grant(jwt, (const char*)"user"); + if( !c_username ) { + jwt_free(jwt); + Error("User not found in claim"); + return std::make_pair("", 0); + } + + username = std::string(c_username); + Debug(1, "Got %s as user claim from token", username.c_str()); + + token_issued_at = (unsigned int)jwt_get_grant_int(jwt, "iat"); + if ( errno == ENOENT ) { + jwt_free(jwt); + Error("IAT not found in claim. This should not happen"); + return std::make_pair("", 0); + } + + Debug(1, "Got IAT token=%u", token_issued_at); + jwt_free(jwt); + return std::make_pair(username, token_issued_at); +} +#else // HAVE_LIBJWT std::pair verifyToken(std::string jwt_token_str, std::string key) { std::string username = ""; unsigned int token_issued_at = 0; @@ -58,6 +127,7 @@ std::pair verifyToken(std::string jwt_token_str, std } return std::make_pair(username, token_issued_at); } +#endif // HAVE_LIBJWT bool verifyPassword(const char *username, const char *input_password, const char *db_password_hash) { bool password_correct = false; @@ -70,10 +140,16 @@ bool verifyPassword(const char *username, const char *input_password, const char // MYSQL PASSWORD Debug(1, "%s is using an MD5 encoded password", username); - SHA_CTX ctx1, ctx2; + #ifndef SHA_DIGEST_LENGTH + #define SHA_DIGEST_LENGTH 20 + #endif + unsigned char digest_interim[SHA_DIGEST_LENGTH]; unsigned char digest_final[SHA_DIGEST_LENGTH]; - + +#if HAVE_LIBCRYPTO + SHA_CTX ctx1, ctx2; + //get first iteration SHA1_Init(&ctx1); SHA1_Update(&ctx1, input_password, strlen(input_password)); @@ -83,6 +159,15 @@ bool verifyPassword(const char *username, const char *input_password, const char SHA1_Init(&ctx2); SHA1_Update(&ctx2, digest_interim,SHA_DIGEST_LENGTH); SHA1_Final (digest_final, &ctx2); +#elif HAVE_GNUTLS_GNUTLS_H + //get first iteration + gnutls_hash_fast(GNUTLS_DIG_SHA1, input_password, strlen(input_password), digest_interim); + //2nd iteration + gnutls_hash_fast(GNUTLS_DIG_SHA1, digest_interim, SHA_DIGEST_LENGTH, digest_final); +#else + Error("Authentication Error. ZoneMinder not built with GnuTLS or Openssl"); + return false; +#endif char final_hash[SHA_DIGEST_LENGTH * 2 +2]; final_hash[0] = '*'; diff --git a/src/zm_db.cpp b/src/zm_db.cpp index 77c149f03..8e6258b0b 100644 --- a/src/zm_db.cpp +++ b/src/zm_db.cpp @@ -40,7 +40,7 @@ bool zmDbConnect() { Error("Can't initialise database connection: %s", mysql_error(&dbconn)); return false; } - my_bool reconnect = 1; + bool reconnect = 1; if ( mysql_options(&dbconn, MYSQL_OPT_RECONNECT, &reconnect) ) Error("Can't set database auto reconnect option: %s", mysql_error(&dbconn)); if ( !staticConfig.DB_SSL_CA_CERT.empty() ) diff --git a/src/zm_eventstream.cpp b/src/zm_eventstream.cpp index f9a4b5d9c..b775107e8 100644 --- a/src/zm_eventstream.cpp +++ b/src/zm_eventstream.cpp @@ -76,7 +76,7 @@ bool EventStream::loadInitialEventData(int monitor_id, time_t event_time) { curr_frame_id = 1; // curr_frame_id is 1-based if ( event_time >= event_data->start_time ) { Debug(2, "event time is after event start"); - for (unsigned int i = 0; i < event_data->frame_count; i++ ) { + for ( unsigned int i = 0; i < event_data->frame_count; i++ ) { //Info( "eft %d > et %d", event_data->frames[i].timestamp, event_time ); if ( event_data->frames[i].timestamp >= event_time ) { curr_frame_id = i+1; @@ -100,6 +100,7 @@ bool EventStream::loadInitialEventData(uint64_t init_event_id, unsigned int init if ( init_frame_id >= event_data->frame_count ) { Error("Invalid frame id specified. %d > %d", init_frame_id, event_data->frame_count); curr_stream_time = event_data->start_time; + curr_frame_id = 1; } else { curr_stream_time = event_data->frames[init_frame_id-1].timestamp; curr_frame_id = init_frame_id; @@ -117,7 +118,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,9 +161,25 @@ 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); + if ( !monitor ) { + monitor = Monitor::Load(event_data->monitor_id, false, Monitor::QUERY); + } else if ( monitor->Id() != event_data->monitor_id ) { + delete monitor; + monitor = Monitor::Load(event_data->monitor_id, false, Monitor::QUERY); + } + if ( !monitor ) { + Fatal("Unable to load monitor id %d for streaming", event_data->monitor_id); + } + + if ( !storage ) { + storage = new Storage(event_data->storage_id); + } else if ( storage->Id() != event_data->storage_id ) { + delete storage; + storage = new Storage(event_data->storage_id); + } const char *storage_path = storage->Path(); if ( event_data->scheme == Storage::DEEP ) { @@ -204,7 +221,6 @@ bool EventStream::loadEventData(uint64_t event_id) { staticConfig.PATH_WEB.c_str(), storage_path, event_data->monitor_id, event_data->event_id); } - delete storage; storage = NULL; updateFrameRate((double)event_data->frame_count/event_data->duration); Debug(3, "fps set by frame_count(%d)/duration(%f)", @@ -275,7 +291,10 @@ bool EventStream::loadEventData(uint64_t event_id) { mysql_free_result(result); - if ( event_data->video_file[0] ) { + if ( event_data->video_file[0] || (monitor->GetOptVideoWriter() > 0) ) { + if ( !event_data->video_file[0] ) { + snprintf(event_data->video_file, sizeof(event_data->video_file), "%" PRIu64 "-%s", event_data->event_id, "video.mp4"); + } std::string filepath = std::string(event_data->path) + "/" + std::string(event_data->video_file); //char filepath[PATH_MAX]; //snprintf(filepath, sizeof(filepath), "%s/%s", event_data->path, event_data->video_file); @@ -376,12 +395,16 @@ void EventStream::processCommand(const CmdMsg *msg) { paused = true; replay_rate = ZM_RATE_BASE; step = 1; + if ( (unsigned int)curr_frame_id < event_data->frame_count ) + curr_frame_id += 1; break; case CMD_SLOWREV : Debug(1, "Got SLOW REV command"); paused = true; replay_rate = ZM_RATE_BASE; step = -1; + curr_frame_id -= 1; + if ( curr_frame_id < 1 ) curr_frame_id = 1; break; case CMD_FASTREV : Debug(1, "Got FAST REV command"); @@ -697,9 +720,10 @@ Debug(1, "Loading image"); } else if ( ffmpeg_input ) { // Get the frame from the mp4 input Debug(1,"Getting frame from ffmpeg"); - AVFrame *frame; FrameData *frame_data = &event_data->frames[curr_frame_id-1]; - frame = ffmpeg_input->get_frame( ffmpeg_input->get_video_stream_id(), frame_data->offset ); + AVFrame *frame = ffmpeg_input->get_frame( + ffmpeg_input->get_video_stream_id(), + frame_data->offset); if ( frame ) { image = new Image(frame); //av_frame_free(&frame); @@ -707,6 +731,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; @@ -736,6 +788,10 @@ Debug(1, "Loading image"); Fatal("Unexpected frame type %d", type); break; } + if ( send_image != image ) { + delete send_image; + send_image = NULL; + } delete image; image = NULL; } // end if send_raw or not @@ -824,17 +880,13 @@ void EventStream::runStream() { // commands may set send_frame to true while ( checkCommandQueue() && !zm_terminate ) { // The idea is to loop here processing all commands before proceeding. - Debug(1, "Have command queue"); } - Debug(2, "Done command queue"); // Update modified time of the socket .lock file so that we can tell which ones are stale. if ( now.tv_sec - last_comm_update.tv_sec > 3600 ) { touch(sock_path_lock); last_comm_update = now; } - } else { - Debug(2, "Not checking command queue"); } // Get current frame data @@ -851,7 +903,7 @@ void EventStream::runStream() { send_frame = true; } } else if ( step != 0 ) { - Debug(2, "Paused with step"); + Debug(2, "Paused with step %d", step); // We are paused and are just stepping forward or backward one frame step = 0; send_frame = true; @@ -990,30 +1042,30 @@ void EventStream::runStream() { //if ( step != 0 )// Adding 0 is cheaper than an if 0 // curr_frame_id starts at 1 though, so we might skip the first frame? curr_frame_id += step; - } // end if !paused - // Detects when we hit end of event and will load the next event or previous event - if ( checkEventLoaded() ) { - // Have change of event + // Detects when we hit end of event and will load the next event or previous event + if ( checkEventLoaded() ) { + // Have change of event - // This next bit is to determine if we are in the current event time wise - // and whether to show an image saying how long until the next event. - if ( replay_rate > 0 ) { - // This doesn't make sense unless we have hit the end of the event. - time_to_event = event_data->frames[0].timestamp - curr_stream_time; - Debug(1, "replay rate(%d) time_to_event(%f)=frame timestamp:%f - curr_stream_time(%f)", - replay_rate, time_to_event, - event_data->frames[0].timestamp, - curr_stream_time); + // This next bit is to determine if we are in the current event time wise + // and whether to show an image saying how long until the next event. + if ( replay_rate > 0 ) { + // This doesn't make sense unless we have hit the end of the event. + time_to_event = event_data->frames[0].timestamp - curr_stream_time; + Debug(1, "replay rate(%d) time_to_event(%f)=frame timestamp:%f - curr_stream_time(%f)", + replay_rate, time_to_event, + event_data->frames[0].timestamp, + curr_stream_time); - } else if ( replay_rate < 0 ) { - time_to_event = curr_stream_time - event_data->frames[event_data->frame_count-1].timestamp; - Debug(1, "replay rate(%d) time_to_event(%f)=curr_stream_time(%f)-frame timestamp:%f", - replay_rate, time_to_event, curr_stream_time, event_data->frames[event_data->frame_count-1].timestamp); - } + } else if ( replay_rate < 0 ) { + time_to_event = curr_stream_time - event_data->frames[event_data->frame_count-1].timestamp; + Debug(1, "replay rate(%d) time_to_event(%f)=curr_stream_time(%f)-frame timestamp:%f", + replay_rate, time_to_event, curr_stream_time, event_data->frames[event_data->frame_count-1].timestamp); + } // end if forward or reverse - } - } // end while ! zm_terminate + } // end if checkEventLoaded + } // end if !paused + } // end while ! zm_terminate #if HAVE_LIBAVCODEC if ( type == STREAM_MPEG ) delete vid_stream; @@ -1022,18 +1074,12 @@ void EventStream::runStream() { closeComms(); } // void EventStream::runStream() -void EventStream::setStreamStart( uint64_t init_event_id, unsigned int init_frame_id=0 ) { +void EventStream::setStreamStart( + uint64_t init_event_id, + unsigned int init_frame_id=0) { loadInitialEventData(init_event_id, init_frame_id); - if ( !(monitor = Monitor::Load(event_data->monitor_id, false, Monitor::QUERY)) ) { - Fatal("Unable to load monitor id %d for streaming", event_data->monitor_id); - return; - } -} +} // end void EventStream::setStreamStart(init_event_id,init_frame_id=0) void EventStream::setStreamStart(int monitor_id, time_t event_time) { loadInitialEventData(monitor_id, event_time); - if ( !(monitor = Monitor::Load(event_data->monitor_id, false, Monitor::QUERY)) ) { - Fatal("Unable to load monitor id %d for streaming", monitor_id); - return; - } } diff --git a/src/zm_eventstream.h b/src/zm_eventstream.h index 980f2f6e1..02e7335e0 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: @@ -82,6 +83,7 @@ class EventStream : public StreamBase { struct timeval start; // clock time when started the event EventData *event_data; + Storage *storage; FFmpeg_Input *ffmpeg_input; protected: @@ -96,7 +98,7 @@ class EventStream : public StreamBase { public: EventStream() { mode = DEFAULT_MODE; - replay_rate = DEFAULT_RATE; + replay_rate = DEFAULT_RATE; forceEventChange = false; @@ -104,13 +106,32 @@ class EventStream : public StreamBase { curr_stream_time = 0.0; send_frame = false; - event_data = 0; + event_data = NULL; // Used when loading frames from an mp4 input_codec_context = 0; input_codec = 0; ffmpeg_input = NULL; + storage = NULL; + } + ~EventStream() { + if ( event_data ) { + delete event_data; + event_data = NULL; + } + if ( monitor ) { + delete monitor; + monitor = NULL; + } + if ( storage ) { + delete storage; + storage = NULL; + } + if ( ffmpeg_input ) { + delete ffmpeg_input; + ffmpeg_input = NULL; + } } void setStreamStart( uint64_t init_event_id, unsigned int init_frame_id ); void setStreamStart( int monitor_id, time_t event_time ); diff --git a/src/zm_ffmpeg.cpp b/src/zm_ffmpeg.cpp index 6b6036db5..f0b3dc76e 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 ef4dfc057..ea13f9382 100644 --- a/src/zm_ffmpeg_camera.cpp +++ b/src/zm_ffmpeg_camera.cpp @@ -517,19 +517,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"); @@ -537,7 +537,7 @@ int FfmpegCamera::OpenFfmpeg() { #else Warning("HWAccel support not compiled in."); #endif - } // end if hwacel_name + } // end if hwaccel_name // Open the codec #if !LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 8, 0) @@ -603,6 +603,8 @@ int FfmpegCamera::OpenFfmpeg() { Error("Unable to allocate frame for %s", mPath.c_str()); return -1; } + mFrame->width = width; + mFrame->height = height; #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) int pSize = av_image_get_buffer_size(imagePixFormat, width, height, 1); @@ -630,20 +632,6 @@ int FfmpegCamera::OpenFfmpeg() { return -1; } -# if 0 - // Must get a frame first to find out the actual format returned by decoding - mConvertContext = sws_getContext( - mVideoCodecContext->width, - mVideoCodecContext->height, - mVideoCodecContext->pix_fmt, - width, height, - imagePixFormat, SWS_BICUBIC, NULL, - NULL, NULL); - if ( mConvertContext == NULL ) { - Error("Unable to create conversion context for %s", mPath.c_str()); - return -1; - } -#endif #else // HAVE_LIBSWSCALE Fatal("You must compile ffmpeg with the --enable-swscale " "option to use ffmpeg cameras"); @@ -1087,9 +1075,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()); @@ -1128,19 +1119,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_input.cpp b/src/zm_ffmpeg_input.cpp index ea21d82be..28ecd4ac7 100644 --- a/src/zm_ffmpeg_input.cpp +++ b/src/zm_ffmpeg_input.cpp @@ -10,14 +10,31 @@ FFmpeg_Input::FFmpeg_Input() { FFMPEGInit(); streams = NULL; frame = NULL; + last_seek_request = -1; } FFmpeg_Input::~FFmpeg_Input() { if ( streams ) { + for ( unsigned int i = 0; i < input_format_context->nb_streams; i += 1 ) { + avcodec_close(streams[i].context); + streams[i].context = NULL; + } delete streams; streams = NULL; } -} + if ( frame ) { + av_frame_free(&frame); + frame = NULL; + } + if ( input_format_context ) { +#if !LIBAVFORMAT_VERSION_CHECK(53, 17, 0, 25, 0) + av_close_input_file(input_format_context); +#else + avformat_close_input(&input_format_context); +#endif + input_format_context = NULL; + } +} // end ~FFmpeg_Input() int FFmpeg_Input::Open(const char *filepath) { @@ -89,6 +106,7 @@ int FFmpeg_Input::Open(const char *filepath) { avcodec_free_context(&streams[i].context); #endif avformat_close_input(&input_format_context); + input_format_context = NULL; return error; } } // end foreach stream @@ -102,8 +120,6 @@ int FFmpeg_Input::Open(const char *filepath) { } // end int FFmpeg_Input::Open( const char * filepath ) AVFrame *FFmpeg_Input::get_frame(int stream_id) { - Debug(4, "Getting frame from stream %d", stream_id); - int frameComplete = false; AVPacket packet; av_init_packet(&packet); @@ -144,6 +160,12 @@ AVFrame *FFmpeg_Input::get_frame(int stream_id) { zm_av_packet_unref(&packet); av_frame_free(&frame); continue; + } else { + if ( is_video_stream(input_format_context->streams[packet.stream_index]) ) { + zm_dump_video_frame(frame, "resulting video frame"); + } else { + zm_dump_frame(frame, "resulting frame"); + } } frameComplete = 1; @@ -175,11 +197,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); - zm_dump_frame(frame, "Got first frame, returning it"); - return frame; - } // 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, @@ -193,6 +224,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"); @@ -207,5 +240,4 @@ AVFrame *FFmpeg_Input::get_frame(int stream_id, double at) { } return get_frame(stream_id); - -} // end AVFrame *FFmpeg_Input::get_frame( int stream_id, struct timeval at) +} // end AVFrame *FFmpeg_Input::get_frame( int stream_id, struct timeval at) 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 97c2132c6..6cc7fe000 100644 --- a/src/zm_image.cpp +++ b/src/zm_image.cpp @@ -48,6 +48,8 @@ static short *g_v_table; static short *g_u_table; static short *b_u_table; +struct SwsContext *sws_convert_context = NULL; + jpeg_compress_struct *Image::writejpg_ccinfo[101] = { 0 }; jpeg_compress_struct *Image::encodejpg_ccinfo[101] = { 0 }; jpeg_decompress_struct *Image::readjpg_dcinfo = 0; @@ -160,7 +162,7 @@ Image::Image( int p_width, int p_height, int p_colours, int p_subpixelorder, uin update_function_pointers(); } -Image::Image( const AVFrame *frame ) { +Image::Image(const AVFrame *frame) { AVFrame *dest_frame = zm_av_frame_alloc(); text[0] = '\0'; @@ -183,26 +185,28 @@ Image::Image( const AVFrame *frame ) { #endif #if HAVE_LIBSWSCALE - struct SwsContext *mConvertContext = sws_getContext( + sws_convert_context = sws_getCachedContext( + sws_convert_context, width, height, (AVPixelFormat)frame->format, width, height, AV_PIX_FMT_RGBA, SWS_BICUBIC, NULL, NULL, NULL); - if ( mConvertContext == NULL ) - Fatal( "Unable to create conversion context" ); + if ( sws_convert_context == NULL ) + Fatal("Unable to create conversion context"); - if ( sws_scale(mConvertContext, frame->data, frame->linesize, 0, frame->height, dest_frame->data, dest_frame->linesize) < 0 ) + if ( sws_scale(sws_convert_context, frame->data, frame->linesize, 0, frame->height, + dest_frame->data, dest_frame->linesize) < 0 ) Fatal("Unable to convert raw format %u to target format %u", frame->format, AV_PIX_FMT_RGBA); #else // HAVE_LIBSWSCALE Fatal("You must compile ffmpeg with the --enable-swscale option to use ffmpeg cameras"); #endif // HAVE_LIBSWSCALE - av_frame_free( &dest_frame ); + av_frame_free(&dest_frame); update_function_pointers(); -} +} // end Image::Image(const AVFrame *frame) -Image::Image( const Image &p_image ) { +Image::Image(const Image &p_image) { if ( !initialised ) Initialise(); width = p_image.width; @@ -215,7 +219,7 @@ Image::Image( const Image &p_image ) { holdbuffer = 0; AllocImgBuffer(size); (*fptr_imgbufcpy)(buffer, p_image.buffer, size); - strncpy( text, p_image.text, sizeof(text) ); + strncpy(text, p_image.text, sizeof(text)); update_function_pointers(); } @@ -225,35 +229,39 @@ Image::~Image() { /* Should be called as part of program shutdown to free everything */ void Image::Deinitialise() { - if ( initialised ) { - /* - delete[] y_table; - delete[] uv_table; - delete[] r_v_table; - delete[] g_v_table; - delete[] g_u_table; - delete[] b_u_table; + if ( !initialised ) return; + /* + delete[] y_table; + delete[] uv_table; + delete[] r_v_table; + delete[] g_v_table; + delete[] g_u_table; + delete[] b_u_table; */ - initialised = false; - if ( readjpg_dcinfo ) { - jpeg_destroy_decompress( readjpg_dcinfo ); - delete readjpg_dcinfo; - readjpg_dcinfo = 0; - } - if ( decodejpg_dcinfo ) { - jpeg_destroy_decompress( decodejpg_dcinfo ); - delete decodejpg_dcinfo; - decodejpg_dcinfo = 0; - } - for ( unsigned int quality=0; quality <= 100; quality += 1 ) { - if ( writejpg_ccinfo[quality] ) { - jpeg_destroy_compress( writejpg_ccinfo[quality] ); - delete writejpg_ccinfo[quality]; - writejpg_ccinfo[quality] = NULL; - } - } // end foreach quality + initialised = false; + if ( readjpg_dcinfo ) { + jpeg_destroy_decompress( readjpg_dcinfo ); + delete readjpg_dcinfo; + readjpg_dcinfo = 0; } -} + if ( decodejpg_dcinfo ) { + jpeg_destroy_decompress( decodejpg_dcinfo ); + delete decodejpg_dcinfo; + decodejpg_dcinfo = 0; + } + for ( unsigned int quality=0; quality <= 100; quality += 1 ) { + if ( writejpg_ccinfo[quality] ) { + jpeg_destroy_compress( writejpg_ccinfo[quality] ); + delete writejpg_ccinfo[quality]; + writejpg_ccinfo[quality] = NULL; + } + } // end foreach quality + + if ( sws_convert_context ) { + sws_freeContext(sws_convert_context); + sws_convert_context = NULL; + } +} // end void Image::Deinitialise() void Image::Initialise() { /* Assign the blend pointer to function */ @@ -655,15 +663,16 @@ void Image::Assign( const Image &image ) { return; } - if ( !buffer || image.width != width || image.height != height || image.colours != colours || image.subpixelorder != subpixelorder) { + if ( !buffer || image.width != width || image.height != height + || image.colours != colours || image.subpixelorder != subpixelorder) { - if (holdbuffer && buffer) { - if (new_size > allocation) { + if ( holdbuffer && buffer ) { + if ( new_size > allocation ) { Error("Held buffer is undersized for assigned buffer"); return; } } else { - if(new_size > allocation || !buffer) { + if ( new_size > allocation || !buffer) { // DumpImgBuffer(); This is also done in AllocImgBuffer AllocImgBuffer(new_size); } diff --git a/src/zm_image.h b/src/zm_image.h index 6b2448c67..6be574dfb 100644 --- a/src/zm_image.h +++ b/src/zm_image.h @@ -121,7 +121,7 @@ protected: }; inline void DumpImgBuffer() { - DumpBuffer(buffer,buffertype); + DumpBuffer(buffer, buffertype); buffer = NULL; allocation = 0; } diff --git a/src/zm_logger.cpp b/src/zm_logger.cpp index 1b8afc517..011a0166e 100644 --- a/src/zm_logger.cpp +++ b/src/zm_logger.cpp @@ -156,6 +156,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 @@ -359,7 +360,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; } @@ -535,8 +536,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 ) diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 2f0e4826b..35f878020 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -413,14 +413,14 @@ Monitor::Monitor( auto_resume_time = 0; - if ( strcmp( config.event_close_mode, "time" ) == 0 ) + if ( strcmp(config.event_close_mode, "time") == 0 ) event_close_mode = CLOSE_TIME; - else if ( strcmp( config.event_close_mode, "alarm" ) == 0 ) + else if ( strcmp(config.event_close_mode, "alarm") == 0 ) event_close_mode = CLOSE_ALARM; else event_close_mode = CLOSE_IDLE; - Debug( 1, "monitor purpose=%d", purpose ); + Debug(1, "monitor purpose=%d", purpose); mem_size = sizeof(SharedData) + sizeof(TriggerData) @@ -440,17 +440,15 @@ Monitor::Monitor( storage = new Storage(storage_id); Debug(1, "Storage path: %s", storage->Path()); // Should maybe store this for later use - char monitor_dir[PATH_MAX] = ""; + char monitor_dir[PATH_MAX]; snprintf(monitor_dir, sizeof(monitor_dir), "%s/%d", storage->Path(), id); if ( purpose == CAPTURE ) { - if ( mkdir(monitor_dir, 0755) ) { - if ( errno != EEXIST ) { - Error("Can't mkdir %s: %s", monitor_dir, strerror(errno)); - } + if ( mkdir(monitor_dir, 0755) && ( errno != EEXIST ) ) { + Error("Can't mkdir %s: %s", monitor_dir, strerror(errno)); } - if ( ! this->connect() ) { + if ( !this->connect() ) { Error("unable to connect, but doing capture"); exit(-1); } @@ -486,20 +484,16 @@ Monitor::Monitor( video_store_data->size = sizeof(VideoStoreData); //video_store_data->frameNumber = 0; } else if ( purpose == ANALYSIS ) { - if ( ! ( this->connect() && mem_ptr ) ) exit(-1); + if ( ! (this->connect() && mem_ptr && shared_data->valid) ) { + Error("Shared data not initialised by capture daemon for monitor %s", name); + exit(-1); + } shared_data->state = IDLE; shared_data->last_read_time = 0; shared_data->alarm_x = -1; shared_data->alarm_y = -1; } - if ( ( ! mem_ptr ) || ! shared_data->valid ) { - if ( purpose != QUERY ) { - Error("Shared data not initialised by capture daemon for monitor %s", name); - exit(-1); - } - } - start_time = last_fps_time = time( 0 ); event = 0; @@ -545,9 +539,8 @@ Monitor::Monitor( FifoStream::fifo_create_if_missing(diag_path_d.c_str()); } } - - } // end if purpose == ANALYSIS -} // Monitor::Monitor + } // end if purpose == ANALYSIS +} // Monitor::Monitor bool Monitor::connect() { Debug(3, "Connecting to monitor. Purpose is %d", purpose ); @@ -1815,7 +1808,7 @@ void Monitor::Reload() { "`AlarmFrameCount`, `SectionLength`, `MinSectionLength`, `FrameSkip`, " "`MotionFrameSkip`, `AnalysisFPSLimit`, `AnalysisUpdateDelay`, `MaxFPS`, `AlarmMaxFPS`, " "`FPSReportInterval`, `RefBlendPerc`, `AlarmRefBlendPerc`, `TrackMotion`, " - "`SignalCheckColour` FROM `Monitors` WHERE `Id` = '%d'", id); + "`SignalCheckPoints`, `SignalCheckColour` FROM `Monitors` WHERE `Id` = '%d'", id); zmDbRow *row = zmDbFetchOne(sql); if ( !row ) { @@ -1861,13 +1854,8 @@ void Monitor::Reload() { alarm_ref_blend_perc = atoi(dbrow[index++]); track_motion = atoi(dbrow[index++]); - signal_check_points = dbrow[index]?atoi(dbrow[index]):0;index++; - - if ( dbrow[index][0] == '#' ) - signal_check_colour = strtol(dbrow[index]+1,0,16); - else - signal_check_colour = strtol(dbrow[index],0,16); - index++; + signal_check_points = dbrow[index]?atoi(dbrow[index]):0; index++; + signal_check_colour = strtol(dbrow[index][0]=='#'?dbrow[index]+1:dbrow[index], 0, 16); index++; shared_data->state = state = IDLE; shared_data->alarm_x = shared_data->alarm_y = -1; @@ -1880,7 +1868,7 @@ void Monitor::Reload() { } // end if row ReloadZones(); -} // end void Monitor::Reload() +} // end void Monitor::Reload() void Monitor::ReloadZones() { Debug(1, "Reloading zones for monitor %s", name); @@ -2059,20 +2047,21 @@ int Monitor::LoadFfmpegMonitors(const char *file, Monitor **&monitors, Purpose p } // end int Monitor::LoadFfmpegMonitors #endif // HAVE_LIBAVFORMAT -/* - std::string load_monitor_sql = - "SELECT Id, Name, ServerId, StorageId, Type, Function+0, Enabled, LinkedMonitors, " - "AnalysisFPSLimit, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS," - "Device, Channel, Format, V4LMultiBuffer, V4LCapturesPerFrame, " // V4L Settings - "Protocol, Method, Options, User, Pass, Host, Port, Path, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, RTSPDescribe, " - "SaveJPEGs, VideoWriter, EncoderParameters, -"OutputCodec, Encoder, OutputContainer," -" RecordAudio, " - "Brightness, Contrast, Hue, Colour, " - "EventPrefix, LabelFormat, LabelX, LabelY, LabelSize," - "ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, " - "SectionLength, MinSectionLength, FrameSkip, MotionFrameSkip, " - "FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif, SignalCheckColour FROM Monitors"; +/* For reference +std::string load_monitor_sql = +"SELECT `Id`, `Name`, `ServerId`, `StorageId`, `Type`, `Function`+0, `Enabled`, `LinkedMonitors`, " +"`AnalysisFPSLimit`, `AnalysisUpdateDelay`, `MaxFPS`, `AlarmMaxFPS`," +"`Device`, `Channel`, `Format`, `V4LMultiBuffer`, `V4LCapturesPerFrame`, " // V4L Settings +"`Protocol`, `Method`, `Options`, `User`, `Pass`, `Host`, `Port`, `Path`, `Width`, `Height`, `Colours`, `Palette`, `Orientation`+0, `Deinterlacing`, " +"`DecoderHWAccelName`, `DecoderHWAccelDevice`, `RTSPDescribe`, " +"`SaveJPEGs`, `VideoWriter`, `EncoderParameters`, " +//" OutputCodec, Encoder, OutputContainer, " +"`RecordAudio`, " +"`Brightness`, `Contrast`, `Hue`, `Colour`, " +"`EventPrefix`, `LabelFormat`, `LabelX`, `LabelY`, `LabelSize`," +"`ImageBufferCount`, `WarmupCount`, `PreEventCount`, `PostEventCount`, `StreamReplayBuffer`, `AlarmFrameCount`, " +"`SectionLength`, `MinSectionLength`, `FrameSkip`, `MotionFrameSkip`, " +"`FPSReportInterval`, `RefBlendPerc`, `AlarmRefBlendPerc`, `TrackMotion`, `Exif`, `SignalCheckPoints`, `SignalCheckColour` FROM `Monitors`"; */ Monitor *Monitor::Load(MYSQL_ROW dbrow, bool load_zones, Purpose purpose) { @@ -2092,8 +2081,6 @@ Monitor *Monitor::Load(MYSQL_ROW dbrow, bool load_zones, Purpose purpose) { double capture_max_fps = dbrow[col] ? atof(dbrow[col]) : 0.0; col++; double capture_delay = ( capture_max_fps > 0.0 ) ? int(DT_PREC_3/capture_max_fps) : 0; - - Debug(1,"Capture Delay!? %.3f", capture_delay); unsigned int alarm_capture_delay = (dbrow[col]&&atof(dbrow[col])>0.0)?int(DT_PREC_3/atof(dbrow[col])):0; col++; const char *device = dbrow[col]; col++; @@ -2166,9 +2153,9 @@ Monitor *Monitor::Load(MYSQL_ROW dbrow, bool load_zones, Purpose purpose) { int ref_blend_perc = atoi(dbrow[col]); col++; int alarm_ref_blend_perc = atoi(dbrow[col]); col++; int track_motion = atoi(dbrow[col]); col++; + bool embed_exif = (*dbrow[col] != '0'); col++; int signal_check_points = dbrow[col] ? atoi(dbrow[col]) : 0;col++; int signal_check_color = strtol(dbrow[col][0] == '#' ? dbrow[col]+1 : dbrow[col], 0, 16); col++; - bool embed_exif = (*dbrow[col] != '0'); col++; Camera *camera = 0; if ( type == "Local" ) { diff --git a/src/zm_monitorstream.cpp b/src/zm_monitorstream.cpp index 0c71b5c61..49d617f7d 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, @@ -327,7 +327,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 ) { @@ -383,7 +383,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 : @@ -412,7 +412,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)); } @@ -430,7 +430,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 ) @@ -455,7 +455,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); @@ -474,22 +474,21 @@ 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); } else { swap_path = staticConfig.PATH_SWAP; - Debug( 3, "Checking swap path folder: %s", swap_path.c_str() ); + Debug(3, "Checking swap path folder: %s", swap_path.c_str()); if ( checkSwapPath(swap_path.c_str(), true) ) { swap_path += stringtf("/zmswap-m%d", monitor->Id()); @@ -509,8 +508,8 @@ void MonitorStream::runStream() { } else { Debug(2, "Assigning temporary buffer"); temp_image_buffer = new SwapImage[temp_image_buffer_count]; - memset( temp_image_buffer, 0, sizeof(*temp_image_buffer)*temp_image_buffer_count ); - Debug( 2, "Assigned temporary buffer" ); + memset(temp_image_buffer, 0, sizeof(*temp_image_buffer)*temp_image_buffer_count); + Debug(2, "Assigned temporary buffer"); } } } else { @@ -525,7 +524,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", @@ -562,7 +561,7 @@ void MonitorStream::runStream() { touch(sock_path_lock); last_comm_update = now; } - } // end if connkey + } // end if connkey if ( paused ) { if ( !was_paused ) { @@ -589,7 +588,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 ) { @@ -597,51 +596,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 @@ -652,16 +661,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 + int index = monitor->shared_data->last_write_index % monitor->image_buffer_count; // % shouldn't be neccessary 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 Monitor::Snapshot *snap = &monitor->image_buffer[index]; @@ -670,9 +679,13 @@ void MonitorStream::runStream() { Debug(2, "sendFrame failed, quiting."); zm_terminate = true; } - // Perhaps we should use NOW instead. - memcpy(&last_frame_timestamp, snap->timestamp, sizeof(last_frame_timestamp)); - //frame_sent = true; + // Perhaps we should use NOW instead. + memcpy( + &last_frame_timestamp, + snap->timestamp, + sizeof(last_frame_timestamp) + ); + // frame_sent = true; temp_read_index = temp_write_index; } else { @@ -697,7 +710,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 @@ -718,27 +731,27 @@ void MonitorStream::runStream() { temp_index); temp_image_buffer[temp_index].valid = true; } - memcpy( &(temp_image_buffer[temp_index].timestamp), monitor->image_buffer[index].timestamp, sizeof(temp_image_buffer[0].timestamp) ); - monitor->image_buffer[index].image->WriteJpeg( temp_image_buffer[temp_index].file_name, config.jpeg_file_quality ); - temp_write_index = MOD_ADD( temp_write_index, 1, temp_image_buffer_count ); + memcpy(&(temp_image_buffer[temp_index].timestamp), monitor->image_buffer[index].timestamp, sizeof(temp_image_buffer[0].timestamp)); + monitor->image_buffer[index].image->WriteJpeg(temp_image_buffer[temp_index].file_name, config.jpeg_file_quality); + 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); @@ -755,10 +768,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) @@ -799,9 +812,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 @@ -809,7 +822,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; @@ -817,42 +830,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; Monitor::Snapshot *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; @@ -861,17 +877,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_remote_camera_http.cpp b/src/zm_remote_camera_http.cpp index 96c3707ff..f725d17b9 100644 --- a/src/zm_remote_camera_http.cpp +++ b/src/zm_remote_camera_http.cpp @@ -1060,7 +1060,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_rtsp_auth.cpp b/src/zm_rtsp_auth.cpp index 81aa13c27..2f8b2c3ce 100644 --- a/src/zm_rtsp_auth.cpp +++ b/src/zm_rtsp_auth.cpp @@ -144,7 +144,7 @@ std::string Authenticator::computeDigestResponse(std::string &method, std::strin #if HAVE_DECL_MD5 MD5((unsigned char*)ha1Data.c_str(), ha1Data.length(), md5buf); #elif HAVE_DECL_GNUTLS_FINGERPRINT - gnutls_datum_t md5dataha1 = { (unsigned char*)ha1Data.c_str(), ha1Data.length() }; + gnutls_datum_t md5dataha1 = { (unsigned char*)ha1Data.c_str(), (unsigned int)ha1Data.length() }; gnutls_fingerprint( GNUTLS_DIG_MD5, &md5dataha1, md5buf, &md5len ); #endif for ( unsigned int j = 0; j < md5len; j++ ) { @@ -159,7 +159,7 @@ std::string Authenticator::computeDigestResponse(std::string &method, std::strin #if HAVE_DECL_MD5 MD5((unsigned char*)ha2Data.c_str(), ha2Data.length(), md5buf ); #elif HAVE_DECL_GNUTLS_FINGERPRINT - gnutls_datum_t md5dataha2 = { (unsigned char*)ha2Data.c_str(), ha2Data.length() }; + gnutls_datum_t md5dataha2 = { (unsigned char*)ha2Data.c_str(), (unsigned int)ha2Data.length() }; gnutls_fingerprint( GNUTLS_DIG_MD5, &md5dataha2, md5buf, &md5len ); #endif for ( unsigned int j = 0; j < md5len; j++ ) { @@ -180,7 +180,7 @@ std::string Authenticator::computeDigestResponse(std::string &method, std::strin #if HAVE_DECL_MD5 MD5((unsigned char*)digestData.c_str(), digestData.length(), md5buf); #elif HAVE_DECL_GNUTLS_FINGERPRINT - gnutls_datum_t md5datadigest = { (unsigned char*)digestData.c_str(), digestData.length() }; + gnutls_datum_t md5datadigest = { (unsigned char*)digestData.c_str(), (unsigned int)digestData.length() }; gnutls_fingerprint( GNUTLS_DIG_MD5, &md5datadigest, md5buf, &md5len ); #endif for ( unsigned int j = 0; j < md5len; j++ ) { diff --git a/src/zm_rtsp_auth.h b/src/zm_rtsp_auth.h index 34056eee6..8e65746de 100644 --- a/src/zm_rtsp_auth.h +++ b/src/zm_rtsp_auth.h @@ -19,9 +19,6 @@ #ifndef ZM_RTSP_AUTH_H #define ZM_RTSP_AUTH_H -#if HAVE_GNUTLS_OPENSSL_H -#include -#endif #if HAVE_GNUTLS_GNUTLS_H #include #endif diff --git a/src/zm_stream.cpp b/src/zm_stream.cpp index ee874cf64..09eaec624 100644 --- a/src/zm_stream.cpp +++ b/src/zm_stream.cpp @@ -44,7 +44,12 @@ bool StreamBase::loadMonitor(int monitor_id) { Error("Unable to load monitor id %d for streaming", monitor_id); return false; } - if ( ! monitor->connect() ) { + if ( monitor->GetFunction() == Monitor::NONE ) { + Error("Monitor %d has function NONE. Will not be able to connect to it.", monitor_id); + return false; + } + + if ( !monitor->connect() ) { Error("Unable to connect to monitor id %d for streaming", monitor_id); return false; } @@ -109,7 +114,7 @@ bool StreamBase::checkCommandQueue() { return false; } -Image *StreamBase::prepareImage( Image *image ) { +Image *StreamBase::prepareImage(Image *image) { // Do not bother to scale zoomed in images, just crop them and let the browser scale // Works in FF2 but breaks FF3 which doesn't like image sizes changing in mid stream. @@ -119,50 +124,51 @@ Image *StreamBase::prepareImage( Image *image ) { int mag = (scale * zoom) / ZM_SCALE_BASE; int act_mag = optimisedScaling?(mag > ZM_SCALE_BASE?ZM_SCALE_BASE:mag):mag; - Debug( 3, "Scaling by %d, zooming by %d = magnifying by %d(%d)", scale, zoom, mag, act_mag ); int last_mag = (last_scale * last_zoom) / ZM_SCALE_BASE; int last_act_mag = last_mag > ZM_SCALE_BASE?ZM_SCALE_BASE:last_mag; - Debug( 3, "Last scaling by %d, zooming by %d = magnifying by %d(%d)", last_scale, last_zoom, last_mag, last_act_mag ); - int base_image_width = image->Width(), base_image_height = image->Height(); - Debug( 3, "Base image width = %d, height = %d", base_image_width, base_image_height ); - int virt_image_width = (base_image_width * mag) / ZM_SCALE_BASE, virt_image_height = (base_image_height * mag) / ZM_SCALE_BASE; - Debug( 3, "Virtual image width = %d, height = %d", virt_image_width, virt_image_height ); - int last_virt_image_width = (base_image_width * last_mag) / ZM_SCALE_BASE, last_virt_image_height = (base_image_height * last_mag) / ZM_SCALE_BASE; - Debug( 3, "Last virtual image width = %d, height = %d", last_virt_image_width, last_virt_image_height ); - int act_image_width = (base_image_width * act_mag ) / ZM_SCALE_BASE, act_image_height = (base_image_height * act_mag ) / ZM_SCALE_BASE; - Debug( 3, "Actual image width = %d, height = %d", act_image_width, act_image_height ); - int last_act_image_width = (base_image_width * last_act_mag ) / ZM_SCALE_BASE, last_act_image_height = (base_image_height * last_act_mag ) / ZM_SCALE_BASE; - Debug( 3, "Last actual image width = %d, height = %d", last_act_image_width, last_act_image_height ); - int disp_image_width = (image->Width() * scale) / ZM_SCALE_BASE, disp_image_height = (image->Height() * scale) / ZM_SCALE_BASE; - Debug( 3, "Display image width = %d, height = %d", disp_image_width, disp_image_height ); - int last_disp_image_width = (image->Width() * last_scale) / ZM_SCALE_BASE, last_disp_image_height = (image->Height() * last_scale) / ZM_SCALE_BASE; - Debug( 3, "Last display image width = %d, height = %d", last_disp_image_width, last_disp_image_height ); - int send_image_width = (disp_image_width * act_mag ) / mag, send_image_height = (disp_image_height * act_mag ) / mag; - Debug( 3, "Send image width = %d, height = %d", send_image_width, send_image_height ); - int last_send_image_width = (last_disp_image_width * last_act_mag ) / last_mag, last_send_image_height = (last_disp_image_height * last_act_mag ) / last_mag; - Debug( 3, "Last send image width = %d, height = %d", last_send_image_width, last_send_image_height ); - if ( mag != ZM_SCALE_BASE ) { - if ( act_mag != ZM_SCALE_BASE ) { - Debug(3, "Magnifying by %d", mag); - if ( !image_copied ) { - static Image copy_image; - copy_image.Assign(*image); - image = ©_image; - image_copied = true; - } - image->Scale(mag); - } + Debug(3, + "Scaling by %d, zooming by %d = magnifying by %d(%d)\n" + "Last scaling by %d, zooming by %d = magnifying by %d(%d)\n" + "Base image width = %d, height = %d\n" + "Virtual image width = %d, height = %d\n" + "Last virtual image width = %d, height = %d\n" + "Actual image width = %d, height = %d\n" + "Last actual image width = %d, height = %d\n" + "Display image width = %d, height = %d\n" + "Last display image width = %d, height = %d\n" + "Send image width = %d, height = %d\n" + "Last send image width = %d, height = %d\n", + scale, zoom, mag, act_mag, + last_scale, last_zoom, last_mag, last_act_mag, + base_image_width, base_image_height, + virt_image_width, virt_image_height, + last_virt_image_width, last_virt_image_height, + act_image_width, act_image_height, + last_act_image_width, last_act_image_height, + disp_image_width, disp_image_height, + last_disp_image_width, last_disp_image_height, + send_image_width, send_image_height, + last_send_image_width, last_send_image_height + ); + + if ( ( mag != ZM_SCALE_BASE ) && (act_mag != ZM_SCALE_BASE) ) { + Debug(3, "Magnifying by %d", mag); + static Image copy_image; + copy_image.Assign(*image); + image = ©_image; + image_copied = true; + image->Scale(mag); } Debug(3, "Real image width = %d, height = %d", image->Width(), image->Height()); @@ -171,26 +177,22 @@ Image *StreamBase::prepareImage( Image *image ) { static Box last_crop; if ( mag != last_mag || x != last_x || y != last_y ) { - Debug( 3, "Got click at %d,%d x %d", x, y, mag ); - - //if ( !last_mag ) - //last_mag = mag; + Debug(3, "Got click at %d,%d x %d", x, y, mag); if ( !(last_disp_image_width < last_virt_image_width || last_disp_image_height < last_virt_image_height) ) last_crop = Box(); - Debug( 3, "Recalculating crop" ); // Recalculate crop parameters, as %ges int click_x = (last_crop.LoX() * 100 ) / last_act_image_width; // Initial crop offset from last image click_x += ( x * 100 ) / last_virt_image_width; int click_y = (last_crop.LoY() * 100 ) / last_act_image_height; // Initial crop offset from last image click_y += ( y * 100 ) / last_virt_image_height; - Debug( 3, "Got adjusted click at %d%%,%d%%", click_x, click_y ); + Debug(3, "Got adjusted click at %d%%,%d%%", click_x, click_y); // Convert the click locations to the current image pixels click_x = ( click_x * act_image_width ) / 100; click_y = ( click_y * act_image_height ) / 100; - Debug( 3, "Got readjusted click at %d,%d", click_x, click_y ); + Debug(3, "Got readjusted click at %d,%d", click_x, click_y); int lo_x = click_x - (send_image_width/2); if ( lo_x < 0 ) @@ -209,23 +211,25 @@ Image *StreamBase::prepareImage( Image *image ) { lo_y = hi_y - (send_image_height - 1); } last_crop = Box( lo_x, lo_y, hi_x, hi_y ); - } - Debug( 3, "Cropping to %d,%d -> %d,%d", last_crop.LoX(), last_crop.LoY(), last_crop.HiX(), last_crop.HiY() ); + } // end if ( mag != last_mag || x != last_x || y != last_y ) + + Debug(3, "Cropping to %d,%d -> %d,%d", last_crop.LoX(), last_crop.LoY(), last_crop.HiX(), last_crop.HiY()); if ( !image_copied ) { static Image copy_image; - copy_image.Assign( *image ); + copy_image.Assign(*image); image = ©_image; image_copied = true; } - image->Crop( last_crop ); - } + image->Crop(last_crop); + } // end if difference in image vs displayed dimensions + last_scale = scale; last_zoom = zoom; last_x = x; last_y = y; return image; -} +} // end Image *StreamBase::prepareImage(Image *image) bool StreamBase::sendTextFrame(const char *frame_text) { Debug(2, "Sending %dx%d * %d text frame '%s'", diff --git a/src/zm_user.cpp b/src/zm_user.cpp index 1ebd3f1ff..bca796dd1 100644 --- a/src/zm_user.cpp +++ b/src/zm_user.cpp @@ -27,9 +27,6 @@ #include #include -#if HAVE_GNUTLS_OPENSSL_H -#include -#endif #if HAVE_GNUTLS_GNUTLS_H #include #endif @@ -112,12 +109,13 @@ User *zmLoadUser(const char *username, const char *password) { snprintf(sql, sizeof(sql), "SELECT `Id`, `Username`, `Password`, `Enabled`, `Stream`+0, `Events`+0, `Control`+0, `Monitors`+0, `System`+0, `MonitorIds`" " FROM `Users` WHERE `Username` = '%s' AND `Enabled` = 1", safer_username); + delete safer_username; + safer_username = NULL; if ( mysql_query(&dbconn, sql) ) { Error("Can't run query: %s", mysql_error(&dbconn)); exit(mysql_errno(&dbconn)); } - delete safer_username; MYSQL_RES *result = mysql_store_result(&dbconn); if ( !result ) { @@ -125,35 +123,30 @@ User *zmLoadUser(const char *username, const char *password) { exit(mysql_errno(&dbconn)); } - if ( mysql_num_rows(result) != 1 ) { + if ( mysql_num_rows(result) == 1 ) { + MYSQL_ROW dbrow = mysql_fetch_row(result); + User *user = new User(dbrow); mysql_free_result(result); - Warning("Unable to authenticate user %s", username); - return NULL; - } - MYSQL_ROW dbrow = mysql_fetch_row(result); - User *user = new User(dbrow); + if ( + (! password ) // relay type must be none + || + verifyPassword(username, password, user->getPassword()) ) { + Info("Authenticated user '%s'", user->getUsername()); + return user; + } + } // end if 1 result from db mysql_free_result(result); - if ( !password ) { - // relay type must be none - return user; - } - - if ( verifyPassword(username, password, user->getPassword()) ) { - Info("Authenticated user '%s'", user->getUsername()); - return user; - } - Warning("Unable to authenticate user %s", username); return NULL; -} +} // end User *zmLoadUser(const char *username, const char *password) -User *zmLoadTokenUser (std::string jwt_token_str, bool use_remote_addr ) { +User *zmLoadTokenUser(std::string jwt_token_str, bool use_remote_addr) { std::string key = config.auth_hash_secret; std::string remote_addr = ""; - if (use_remote_addr) { + if ( use_remote_addr ) { remote_addr = std::string(getenv( "REMOTE_ADDR" )); if ( remote_addr == "" ) { Warning( "Can't determine remote address, using null" ); @@ -162,125 +155,123 @@ User *zmLoadTokenUser (std::string jwt_token_str, bool use_remote_addr ) { key += remote_addr; } - Debug (1,"Inside zmLoadTokenUser, formed key=%s", key.c_str()); + Debug(1, "Inside zmLoadTokenUser, formed key=%s", key.c_str()); std::pair ans = verifyToken(jwt_token_str, key); std::string username = ans.first; unsigned int iat = ans.second; - Debug (1,"retrieved user '%s' from token", username.c_str()); + Debug(1, "retrieved user '%s' from token", username.c_str()); - if (username != "") { - char sql[ZM_SQL_MED_BUFSIZ] = ""; - snprintf(sql, sizeof(sql), - "SELECT `Id`, `Username`, `Password`, `Enabled`, `Stream`+0, `Events`+0, `Control`+0, `Monitors`+0, `System`+0, `MonitorIds`, `TokenMinExpiry`" - " FROM `Users` WHERE `Username` = '%s' AND `Enabled` = 1", username.c_str() ); - - if ( mysql_query(&dbconn, sql) ) { - Error("Can't run query: %s", mysql_error(&dbconn)); - exit(mysql_errno(&dbconn)); - } - - MYSQL_RES *result = mysql_store_result(&dbconn); - if ( !result ) { - Error("Can't use query result: %s", mysql_error(&dbconn)); - exit(mysql_errno(&dbconn)); - } - int n_users = mysql_num_rows(result); - - if ( n_users != 1 ) { - mysql_free_result(result); - Error("Unable to authenticate user '%s'", username.c_str()); - return NULL; - } - - MYSQL_ROW dbrow = mysql_fetch_row(result); - User *user = new User(dbrow); - unsigned int stored_iat = strtoul(dbrow[10], NULL,0 ); - - if (stored_iat > iat ) { // admin revoked tokens - mysql_free_result(result); - Error("Token was revoked for '%s'", username.c_str()); - return NULL; - } - - Debug (1,"Got stored expiry time of %u",stored_iat); - Debug (1,"Authenticated user '%s' via token", username.c_str()); - mysql_free_result(result); - return user; - - } - else { + if ( username == "" ) { return NULL; } -} + char sql[ZM_SQL_MED_BUFSIZ] = ""; + snprintf(sql, sizeof(sql), + "SELECT `Id`, `Username`, `Password`, `Enabled`, `Stream`+0, `Events`+0," + " `Control`+0, `Monitors`+0, `System`+0, `MonitorIds`, `TokenMinExpiry`" + " FROM `Users` WHERE `Username` = '%s' AND `Enabled` = 1", username.c_str()); + + if ( mysql_query(&dbconn, sql) ) { + Error("Can't run query: %s", mysql_error(&dbconn)); + return NULL; + } + + MYSQL_RES *result = mysql_store_result(&dbconn); + if ( !result ) { + Error("Can't use query result: %s", mysql_error(&dbconn)); + return NULL; + } + + int n_users = mysql_num_rows(result); + if ( n_users != 1 ) { + mysql_free_result(result); + Error("Unable to authenticate user '%s'", username.c_str()); + return NULL; + } + + MYSQL_ROW dbrow = mysql_fetch_row(result); + User *user = new User(dbrow); + unsigned int stored_iat = strtoul(dbrow[10], NULL, 0); + + if ( stored_iat > iat ) { // admin revoked tokens + mysql_free_result(result); + Error("Token was revoked for '%s'", username.c_str()); + return NULL; + } + + 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; +} // User *zmLoadTokenUser(std::string jwt_token_str, bool use_remote_addr) // Function to validate an authentication string User *zmLoadAuthUser( const char *auth, bool use_remote_addr ) { #if HAVE_DECL_MD5 || HAVE_DECL_GNUTLS_FINGERPRINT #ifdef HAVE_GCRYPT_H // Special initialisation for libgcrypt - if ( !gcry_check_version( GCRYPT_VERSION ) ) { + if ( !gcry_check_version(GCRYPT_VERSION) ) { Fatal( "Unable to initialise libgcrypt" ); } - gcry_control( GCRYCTL_DISABLE_SECMEM, 0 ); - gcry_control( GCRYCTL_INITIALIZATION_FINISHED, 0 ); + gcry_control(GCRYCTL_DISABLE_SECMEM, 0); + gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); #endif // HAVE_GCRYPT_H const char *remote_addr = ""; if ( use_remote_addr ) { - remote_addr = getenv( "REMOTE_ADDR" ); + remote_addr = getenv("REMOTE_ADDR"); if ( !remote_addr ) { - Warning( "Can't determine remote address, using null" ); + Warning("Can't determine remote address, using null"); remote_addr = ""; } } - Debug( 1, "Attempting to authenticate user from auth string '%s'", auth ); + Debug(1, "Attempting to authenticate user from auth string '%s'", auth); char sql[ZM_SQL_SML_BUFSIZ] = ""; - snprintf( sql, sizeof(sql), "SELECT `Id`, `Username`, `Password`, `Enabled`, `Stream`+0, `Events`+0, `Control`+0, `Monitors`+0, `System`+0, `MonitorIds` FROM `Users` WHERE `Enabled` = 1" ); + snprintf(sql, sizeof(sql), + "SELECT `Id`, `Username`, `Password`, `Enabled`, `Stream`+0, `Events`+0," + " `Control`+0, `Monitors`+0, `System`+0, `MonitorIds`" + " FROM `Users` WHERE `Enabled` = 1"); - if ( mysql_query( &dbconn, sql ) ) { - Error( "Can't run query: %s", mysql_error( &dbconn ) ); - exit( mysql_errno( &dbconn ) ); + if ( mysql_query(&dbconn, sql) ) { + Error("Can't run query: %s", mysql_error(&dbconn)); + return NULL; } MYSQL_RES *result = mysql_store_result( &dbconn ); if ( !result ) { - Error( "Can't use query result: %s", mysql_error( &dbconn ) ); - exit( mysql_errno( &dbconn ) ); + Error("Can't use query result: %s", mysql_error(&dbconn)); + return NULL; } - int n_users = mysql_num_rows( result ); - + int n_users = mysql_num_rows(result); if ( n_users < 1 ) { - mysql_free_result( result ); - Warning( "Unable to authenticate user" ); - return( 0 ); + mysql_free_result(result); + Warning("Unable to authenticate user"); + return NULL; } - while( MYSQL_ROW dbrow = mysql_fetch_row( result ) ) { + time_t now = time(0); + unsigned int hours = config.auth_hash_ttl; + if ( ! hours ) { + Warning("No value set for ZM_AUTH_HASH_TTL. Defaulting to 2."); + hours = 2; + } else { + Debug( 1, "AUTH_HASH_TTL is %d", hours ); + } + char auth_key[512] = ""; + char auth_md5[32+1] = ""; + size_t md5len = 16; + unsigned char md5sum[md5len]; + + while ( MYSQL_ROW dbrow = mysql_fetch_row(result) ) { const char *user = dbrow[1]; const char *pass = dbrow[2]; - char auth_key[512] = ""; - char auth_md5[32+1] = ""; - size_t md5len = 16; - unsigned char md5sum[md5len]; - - time_t now = time( 0 ); - unsigned int hours = config.auth_hash_ttl; - - if ( ! hours ) { - Warning("No value set for ZM_AUTH_HASH_TTL. Defaulting to 2."); - hours = 2; - } else { - Debug( 1, "AUTH_HASH_TTL is %d", hours ); - } - for ( unsigned int i = 0; i < hours; i++, now -= 3600 ) { - struct tm *now_tm = localtime( &now ); + struct tm *now_tm = localtime(&now); - snprintf( auth_key, sizeof(auth_key), "%s%s%s%s%d%d%d%d", + snprintf(auth_key, sizeof(auth_key), "%s%s%s%s%d%d%d%d", config.auth_hash_secret, user, pass, @@ -292,47 +283,48 @@ User *zmLoadAuthUser( const char *auth, bool use_remote_addr ) { ); #if HAVE_DECL_MD5 - MD5( (unsigned char *)auth_key, strlen(auth_key), md5sum ); + MD5((unsigned char *)auth_key, strlen(auth_key), md5sum); #elif HAVE_DECL_GNUTLS_FINGERPRINT gnutls_datum_t md5data = { (unsigned char *)auth_key, strlen(auth_key) }; - gnutls_fingerprint( GNUTLS_DIG_MD5, &md5data, md5sum, &md5len ); + gnutls_fingerprint(GNUTLS_DIG_MD5, &md5data, md5sum, &md5len); #endif auth_md5[0] = '\0'; for ( unsigned int j = 0; j < md5len; j++ ) { - sprintf( &auth_md5[2*j], "%02x", md5sum[j] ); + sprintf(&auth_md5[2*j], "%02x", md5sum[j]); } - Debug( 1, "Checking auth_key '%s' -> auth_md5 '%s' == '%s'", auth_key, auth_md5, auth ); + Debug(1, "Checking auth_key '%s' -> auth_md5 '%s' == '%s'", auth_key, auth_md5, auth); if ( !strcmp( auth, auth_md5 ) ) { // We have a match User *user = new User( dbrow ); Debug(1, "Authenticated user '%s'", user->getUsername() ); - mysql_free_result( result ); - return( user ); + mysql_free_result(result); + return user; } else { - Debug(1, "No match for %s", auth ); + Debug(1, "No match for %s", auth); } } } - mysql_free_result( result ); + mysql_free_result(result); #else // HAVE_DECL_MD5 - Error( "You need to build with gnutls or openssl installed to use hash based authentication" ); + Error("You need to build with gnutls or openssl installed to use hash based authentication"); #endif // HAVE_DECL_MD5 - Debug(1, "No user found for auth_key %s", auth ); - return 0; -} + Debug(1, "No user found for auth_key %s", auth); + return NULL; +} // end User *zmLoadAuthUser(const char *auth, bool use_remote_addr) //Function to check Username length -bool checkUser ( const char *username) { - if ( ! username ) +bool checkUser(const char *username) { + if ( !username ) return false; if ( strlen(username) > 32 ) return false; return true; } + //Function to check password length -bool checkPass (const char *password) { +bool checkPass(const char *password) { if ( !password ) return false; if ( strlen(password) > 64 ) diff --git a/src/zm_utils.cpp b/src/zm_utils.cpp index d4bb48b73..ad1bfd001 100644 --- a/src/zm_utils.cpp +++ b/src/zm_utils.cpp @@ -393,12 +393,12 @@ void timespec_diff(struct timespec *start, struct timespec *end, struct timespec char *timeval_to_string( struct timeval tv ) { time_t nowtime; struct tm *nowtm; - static char tmbuf[64], buf[64]; + static char tmbuf[20], buf[28]; nowtime = tv.tv_sec; nowtm = localtime(&nowtime); strftime(tmbuf, sizeof tmbuf, "%Y-%m-%d %H:%M:%S", nowtm); - snprintf(buf, sizeof buf, "%s.%06ld", tmbuf, tv.tv_usec); + snprintf(buf, sizeof buf-1, "%s.%06ld", tmbuf, tv.tv_usec); return buf; } diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index ad04124fa..6acee0cfd 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -409,7 +409,7 @@ bool VideoStore::open() { AVDictionary *opts = NULL; // av_dict_set(&opts, "movflags", "frag_custom+dash+delay_moov", 0); // Shiboleth reports that this may break seeking in mp4 before it downloads - //av_dict_set(&opts, "movflags", "frag_keyframe+empty_moov", 0); + av_dict_set(&opts, "movflags", "frag_keyframe+empty_moov", 0); // av_dict_set(&opts, "movflags", // "frag_keyframe+empty_moov+default_base_moof", 0); if ( (ret = avformat_write_header(oc, &opts)) < 0 ) { diff --git a/src/zm_zone.cpp b/src/zm_zone.cpp index 1e0a16cda..e9ee1af47 100644 --- a/src/zm_zone.cpp +++ b/src/zm_zone.cpp @@ -53,7 +53,7 @@ void Zone::Setup( id = p_id; label = new char[strlen(p_label)+1]; - strcpy( label, p_label ); + strcpy(label, p_label); type = p_type; polygon = p_polygon; alarm_rgb = p_alarm_rgb; @@ -89,10 +89,10 @@ void Zone::Setup( overload_count = 0; extend_alarm_count = 0; - pg_image = new Image( monitor->Width(), monitor->Height(), 1, ZM_SUBPIX_ORDER_NONE ); + pg_image = new Image(monitor->Width(), monitor->Height(), 1, ZM_SUBPIX_ORDER_NONE); pg_image->Clear(); - pg_image->Fill( 0xff, polygon ); - pg_image->Outline( 0xff, polygon ); + pg_image->Fill(0xff, polygon); + pg_image->Outline(0xff, polygon); ranges = new Range[monitor->Height()]; for ( unsigned int y = 0; y < monitor->Height(); y++ ) { @@ -113,8 +113,10 @@ void Zone::Setup( } if ( config.record_diag_images ) { - snprintf(diag_path, sizeof(diag_path), config.record_diag_images_fifo ? "%s/diagpipe-%d-poly.jpg" : "%s/diag-%d-poly.jpg", monitor->getStorage()->Path(), id); - if (config.record_diag_images_fifo) + snprintf(diag_path, sizeof(diag_path), + config.record_diag_images_fifo ? "%s/diagpipe-%d-poly.jpg" : "%s/diag-%d-poly.jpg", + monitor->getStorage()->Path(), id); + if ( config.record_diag_images_fifo ) FifoStream::fifo_create_if_missing(diag_path); pg_image->WriteJpeg(diag_path, config.record_diag_images_fifo); } else { diff --git a/src/zmc.cpp b/src/zmc.cpp index ad9dea406..511e019ab 100644 --- a/src/zmc.cpp +++ b/src/zmc.cpp @@ -350,7 +350,7 @@ int main(int argc, char *argv[]) { } if ( result < 0 ) { // Failure, try reconnecting - sleep(1); + sleep(5); break; } } // end while ! zm_terminate diff --git a/src/zms.cpp b/src/zms.cpp index 544aa9936..ad7076e7a 100644 --- a/src/zms.cpp +++ b/src/zms.cpp @@ -95,7 +95,7 @@ int main(int argc, const char *argv[]) { Debug(1, "Query: %s", query); char temp_query[1024]; - strncpy(temp_query, query, sizeof(temp_query)); + strncpy(temp_query, query, sizeof(temp_query)-1); char *q_ptr = temp_query; char *parms[16]; // Shouldn't be more than this int parm_no = 0; @@ -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()); @@ -184,7 +184,7 @@ int main(int argc, const char *argv[]) { logInit(log_id_string); if ( config.opt_use_auth ) { - User *user = 0; + User *user = NULL; if ( jwt_token_str != "" ) { // user = zmLoadTokenUser(jwt_token_str, config.auth_hash_ips); @@ -195,19 +195,11 @@ int main(int argc, const char *argv[]) { } else { Error("Bad username"); } - } else { - // if ( strcmp( config.auth_relay, "hashed" ) == 0 ) - { - if ( *auth ) { - user = zmLoadAuthUser(auth, config.auth_hash_ips); - } - } - // else if ( strcmp( config.auth_relay, "plain" ) == 0 ) - { - if ( username.length() && password.length() ) { - user = zmLoadUser(username.c_str(), password.c_str()); - } + if ( *auth ) { + user = zmLoadAuthUser(auth, config.auth_hash_ips); + } else if ( username.length() && password.length() ) { + user = zmLoadUser(username.c_str(), password.c_str()); } } if ( !user ) { @@ -218,11 +210,15 @@ int main(int argc, const char *argv[]) { return 0; } if ( !ValidateAccess(user, monitor_id) ) { + delete user; + user = NULL; fputs("HTTP/1.0 403 Forbidden\r\n\r\n", stdout); logTerm(); zmDbClose(); return 0; } + delete user; + user = NULL; } // end if config.opt_use_auth hwcaps_detect(); @@ -237,11 +233,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 +277,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; @@ -332,5 +332,5 @@ int main(int argc, const char *argv[]) { logTerm(); zmDbClose(); - return(0); + return 0; } diff --git a/src/zmu.cpp b/src/zmu.cpp index bd70f8337..33eb730e5 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); } @@ -477,14 +476,12 @@ int main(int argc, char *argv[]) { } // end if auth if ( mon_id > 0 ) { - //fprintf(stderr,"Monitor %d\n", mon_id); Monitor *monitor = Monitor::Load(mon_id, function&(ZMU_QUERY|ZMU_ZONES), Monitor::QUERY); if ( monitor ) { - //fprintf(stderr,"Monitor %d(%s)\n", monitor->Id(), monitor->Name()); if ( verbose ) { printf("Monitor %d(%s)\n", monitor->Id(), monitor->Name()); } - if ( ! monitor->connect() ) { + if ( !monitor->connect() ) { Error("Can't connect to capture daemon: %d %s", monitor->Id(), monitor->Name()); exit_zmu(-1); } @@ -496,13 +493,13 @@ int main(int argc, char *argv[]) { if ( verbose ) { printf("Current state: %s\n", state==Monitor::ALARM?"Alarm":(state==Monitor::ALERT?"Alert":"Idle")); } else { - if ( have_output ) printf("%c", separator); + if ( have_output ) fputc(separator, stdout); printf("%d", state); have_output = true; } } if ( function & ZMU_TIME ) { - struct timeval timestamp = monitor->GetTimestamp( image_idx ); + struct timeval timestamp = monitor->GetTimestamp(image_idx); if ( verbose ) { char timestamp_str[64] = "None"; if ( timestamp.tv_sec ) @@ -512,7 +509,7 @@ int main(int argc, char *argv[]) { else printf("Time of image %d capture: %s.%02ld\n", image_idx, timestamp_str, timestamp.tv_usec/10000); } else { - if ( have_output ) printf("%c", separator); + if ( have_output ) fputc(separator, stdout); printf("%ld.%02ld", timestamp.tv_sec, timestamp.tv_usec/10000); have_output = true; } @@ -521,7 +518,7 @@ int main(int argc, char *argv[]) { if ( verbose ) printf("Last read index: %d\n", monitor->GetLastReadIndex()); else { - if ( have_output ) printf("%c", separator); + if ( have_output ) fputc(separator, stdout); printf("%d", monitor->GetLastReadIndex()); have_output = true; } @@ -530,7 +527,7 @@ int main(int argc, char *argv[]) { if ( verbose ) { printf("Last write index: %d\n", monitor->GetLastWriteIndex()); } else { - if ( have_output ) printf("%c", separator); + if ( have_output ) fputc(separator, stdout); printf("%d", monitor->GetLastWriteIndex()); have_output = true; } @@ -539,16 +536,16 @@ int main(int argc, char *argv[]) { if ( verbose ) { printf("Last event id: %" PRIu64 "\n", monitor->GetLastEventId()); } else { - if ( have_output ) printf("%c", separator); + if ( have_output ) fputc(separator, stdout); printf("%" PRIu64, monitor->GetLastEventId()); have_output = true; } } if ( function & ZMU_FPS ) { - if ( verbose ) + if ( verbose ) { printf("Current capture rate: %.2f frames per second\n", monitor->GetFPS()); - else { - if ( have_output ) printf("%c", separator); + } else { + if ( have_output ) fputc(separator, stdout); printf("%.2f", monitor->GetFPS()); have_output = true; } @@ -574,10 +571,16 @@ int main(int argc, char *argv[]) { if ( monitor->GetFunction() == Monitor::Function::MONITOR ) { printf("A Monitor in monitor mode cannot handle alarms. Please use NoDect\n"); } else { - if ( verbose ) - printf("Forcing alarm on\n"); + Monitor::State state = monitor->GetState(); + + if ( verbose ) { + printf("Forcing alarm on current state: %s, event %" PRIu64 "\n", + state==Monitor::ALARM?"Alarm":(state==Monitor::ALERT?"Alert":"Idle"), + monitor->GetLastEventId() + ); + } monitor->ForceAlarmOn(config.forced_alarm_score, "Forced Web"); - while ( (monitor->GetState() != Monitor::ALARM) && !zm_terminate ) { + while ( ((state = monitor->GetState()) != Monitor::ALARM) && !zm_terminate ) { // Wait for monitor to notice. usleep(1000); } @@ -631,7 +634,7 @@ int main(int argc, char *argv[]) { else printf("Current brightness: %d\n", monitor->actionBrightness()); } else { - if ( have_output ) printf("%c", separator); + if ( have_output ) fputc(separator, stdout); if ( brightness >= 0 ) printf("%d", monitor->actionBrightness(brightness)); else @@ -646,7 +649,7 @@ int main(int argc, char *argv[]) { else printf("Current contrast: %d\n", monitor->actionContrast()); } else { - if ( have_output ) printf("%c", separator); + if ( have_output ) fputc(separator, stdout); if ( contrast >= 0 ) printf("%d", monitor->actionContrast(contrast)); else @@ -661,7 +664,7 @@ int main(int argc, char *argv[]) { else printf("Current hue: %d\n", monitor->actionHue()); } else { - if ( have_output ) printf("%c", separator); + if ( have_output ) fputc(separator, stdout); if ( hue >= 0 ) printf("%d", monitor->actionHue(hue)); else @@ -676,7 +679,7 @@ int main(int argc, char *argv[]) { else printf("Current colour: %d\n", monitor->actionColour()); } else { - if ( have_output ) printf("%c", separator); + if ( have_output ) fputc(separator, stdout); if ( colour >= 0 ) printf("%d", monitor->actionColour(colour)); else diff --git a/utils/do_debian_package.sh b/utils/do_debian_package.sh index 3e20b303a..af9921294 100755 --- a/utils/do_debian_package.sh +++ b/utils/do_debian_package.sh @@ -80,7 +80,7 @@ fi; if [ "$DISTROS" == "" ]; then if [ "$RELEASE" != "" ]; then - DISTROS="xenial,bionic,disco,eoan,trusty" + DISTROS="xenial,bionic,disco,eoan,focal,trusty" else DISTROS=`lsb_release -a 2>/dev/null | grep Codename | awk '{print $2}'`; fi; @@ -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..0ceb7f6f3 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.33.15 +1.34.10 diff --git a/web/ajax/add_monitors.php b/web/ajax/add_monitors.php index de24cdfac..e7048bb4a 100644 --- a/web/ajax/add_monitors.php +++ b/web/ajax/add_monitors.php @@ -46,18 +46,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 ) { @@ -92,10 +92,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"; @@ -106,11 +106,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 @@ -121,16 +121,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 ) { @@ -147,13 +147,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': @@ -161,16 +161,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]; @@ -178,16 +178,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; @@ -197,23 +197,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..fb9000cc6 100644 --- a/web/ajax/event.php +++ b/web/ajax/event.php @@ -1,5 +1,5 @@ diff --git a/web/ajax/log.php b/web/ajax/log.php index ef2e450f2..d5483b7ca 100644 --- a/web/ajax/log.php +++ b/web/ajax/log.php @@ -1,14 +1,15 @@ $value ) { - if ( ! in_array($field, $filterFields) ) { - ZM\Error("'$field' is not in valid filter fields " . print_r($filterField,true)); + if ( !in_array($field, $filterFields) ) { + ZM\Error("'$field' is not in valid filter fields " . print_r($filterField, true)); continue; } - if ( $field == 'Level' ){ + if ( $field == 'Level' ) { $where[] = $field.' <= ?'; $values[] = $value; } else { @@ -58,7 +59,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' : @@ -69,17 +70,21 @@ switch ( $_REQUEST['task'] ) { $string = $_POST['message']; - $file = !empty($_POST['file']) ? preg_replace( '/\w+:\/\/[\w.:]+\//', '', $_POST['file'] ) : ''; - if ( !empty( $_POST['line'] ) ) + $file = !empty($_POST['file']) ? preg_replace('/\w+:\/\/[\w.:]+\//', '', $_POST['file']) : ''; + 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); + } else { + ZM\Error('Invalid log create: '.print_r($_POST, true)); } ajaxResponse(); break; @@ -91,12 +96,7 @@ switch ( $_REQUEST['task'] ) { $query = buildLogQuery('DELETE'); $result = dbQuery($query['sql'], $query['values']); - ajaxResponse( array( - 'result'=>'Ok', - 'deleted'=>$result->rowCount(), - ) ); - - + ajaxResponse(array('result'=>'Ok', 'deleted'=>$result->rowCount())); } case 'query' : { @@ -105,10 +105,12 @@ switch ( $_REQUEST['task'] ) { $total = dbFetchOne('SELECT count(*) AS Total FROM Logs', 'Total'); $query = buildLogQuery('SELECT *'); - $servers = ZM\Server::find(); + global $Servers; + if ( !$Servers ) + $Servers = ZM\Server::find(); $servers_by_Id = array(); # There is probably a better way to do this. - foreach ( $servers as $server ) { + foreach ( $Servers as $server ) { $servers_by_Id[$server->Id()] = $server; } @@ -118,11 +120,9 @@ switch ( $_REQUEST['task'] ) { foreach ( dbFetchAll($query['sql'], NULL, $query['values']) as $log ) { $log['DateTime'] = strftime('%Y-%m-%d %H:%M:%S', intval($log['TimeKey'])); - #Warning("TimeKey: " . $log['TimeKey'] . 'Intval:'.intval($log['TimeKey']).' DateTime:'.$log['DateTime']); - #$log['DateTime'] = preg_replace('/^\d+/', strftime('%Y-%m-%d %H:%M:%S', intval($log['TimeKey'])), $log['TimeKey']); $log['Server'] = ( $log['ServerId'] and isset($servers_by_Id[$log['ServerId']]) ) ? $servers_by_Id[$log['ServerId']]->Name() : ''; $log['Message'] = preg_replace('/[\x00-\x1F\x7F-\xFF]/', '', $log['Message']); - foreach( $filterFields as $field ) { + foreach ( $filterFields as $field ) { if ( !isset($options[$field]) ) $options[$field] = array(); $value = $log[$field]; @@ -139,17 +139,21 @@ switch ( $_REQUEST['task'] ) { } } $logs[] = $log; + } # end foreach log db row + + 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), + ajaxResponse(array( + 'updated' => preg_match('/%/', DATE_FMT_CONSOLE_LONG) ? strftime(DATE_FMT_CONSOLE_LONG) : date(DATE_FMT_CONSOLE_LONG), 'total' => $total, 'available' => isset($available) ? $available : $total, 'logs' => $logs, 'state' => logState(), 'options' => $options, - ) ); + )); break; } case 'export' : @@ -157,9 +161,9 @@ switch ( $_REQUEST['task'] ) { if ( !canView('System') ) ajaxError('Insufficient permissions to export logs'); - $minTime = isset($_POST['minTime'])?$_POST['minTime']:NULL; - $maxTime = isset($_POST['maxTime'])?$_POST['maxTime']:NULL; - if ( !is_null($minTime) && !is_null($maxTime) && $minTime > $maxTime ) { + $minTime = isset($_POST['minTime']) ? $_POST['minTime'] : NULL; + $maxTime = isset($_POST['maxTime']) ? $_POST['maxTime'] : NULL; + if ( !is_null($minTime) && !is_null($maxTime) && ($minTime > $maxTime) ) { $tempTime = $minTime; $minTime = $maxTime; $maxTime = $tempTime; @@ -168,15 +172,17 @@ switch ( $_REQUEST['task'] ) { $filter = isset($_POST['filter'])?$_POST['filter']:array(); $sortField = 'TimeKey'; if ( isset($_POST['sortField']) ) { - if ( ! in_array( $_POST['sortField'], $filterFields ) and ( $_POST['sortField'] != 'TimeKey' ) ) { - ZM\Error("Invalid sort field " . $_POST['sortField'] ); + if ( !in_array($_POST['sortField'], $filterFields) and ($_POST['sortField'] != 'TimeKey') ) { + ZM\Error('Invalid sort field '.$_POST['sortField']); } else { $sortField = $_POST['sortField']; } } - $sortOrder = (isset($_POST['sortOrder']) and $_POST['sortOrder']) == 'asc' ? 'asc':'desc'; + $sortOrder = (isset($_POST['sortOrder']) and $_POST['sortOrder']) == 'asc' ? 'asc' : 'desc'; - $servers = ZM\Server::find(); + global $Servers; + if ( !$Servers ) + $Servers = ZM\Server::find(); $servers_by_Id = array(); # There is probably a better way to do this. foreach ( $servers as $server ) { @@ -187,11 +193,9 @@ switch ( $_REQUEST['task'] ) { $where = array(); $values = array(); if ( $minTime ) { - ZM\Logger::Debug("MinTime: $minTime"); if ( preg_match('/(.+)(\.\d+)/', $minTime, $matches) ) { # This handles sub second precision $minTime = strtotime($matches[1]).$matches[2]; - ZM\Logger::Debug("MinTime: $minTime"); } else { $minTime = strtotime($minTime); } @@ -219,11 +223,11 @@ switch ( $_REQUEST['task'] ) { } } if ( count($where) ) - $sql.= ' WHERE '.join( ' AND ', $where ); + $sql.= ' WHERE '.join(' AND ', $where); $sql .= ' ORDER BY '.$sortField.' '.$sortOrder; //$sql .= " limit ".dbEscape($limit); - $format = isset($_POST['format'])?$_POST['format']:'text'; - switch( $format ) { + $format = isset($_POST['format']) ? $_POST['format'] : 'text'; + switch ( $format ) { case 'text' : $exportExt = 'txt'; break; @@ -239,43 +243,40 @@ switch ( $_REQUEST['task'] ) { default : ZM\Fatal("Unrecognised log export format '$format'"); } - $exportKey = substr(md5(rand()),0,8); - $exportFile = "zm-log.$exportExt"; - if ( ! file_exists(ZM_DIR_EXPORTS) ) { - ZM\Logger::Debug('Creating ' . ZM_DIR_EXPORTS); - if ( ! mkdir(ZM_DIR_EXPORTS) ) { - ZM\Fatal("Can't create exports dir at '".ZM_DIR_EXPORTS."'"); - } + $exportKey = substr(md5(rand()), 0, 8); + $exportFile = 'zm-log.'.$exportExt; + if ( ! ( mkdir(ZM_DIR_EXPORTS) || file_exists(ZM_DIR_EXPORTS) ) ) { + ZM\Fatal('Can\'t create exports dir at \''.ZM_DIR_EXPORTS.'\''); } - $exportPath = ZM_DIR_EXPORTS."/zm-log-$exportKey.$exportExt"; + $exportPath = ZM_DIR_EXPORTS.'/zm-log-'.$exportKey.$exportExt; ZM\Logger::Debug("Exporting to $exportPath"); if ( !($exportFP = fopen($exportPath, 'w')) ) ZM\Fatal("Unable to open log export file $exportPath"); $logs = array(); foreach ( dbFetchAll($sql, NULL, $values) as $log ) { - $log['DateTime'] = preg_replace('/^\d+/', strftime( "%Y-%m-%d %H:%M:%S", intval($log['TimeKey']) ), $log['TimeKey']); + $log['DateTime'] = preg_replace('/^\d+/', strftime('%Y-%m-%d %H:%M:%S', intval($log['TimeKey'])), $log['TimeKey']); $log['Server'] = ( $log['ServerId'] and isset($servers_by_Id[$log['ServerId']]) ) ? $servers_by_Id[$log['ServerId']]->Name() : ''; $logs[] = $log; } - ZM\Logger::Debug(count($logs)." lines being exported by $sql " . implode(',',$values)); + ZM\Logger::Debug(count($logs).' lines being exported by '.$sql.implode(',', $values)); switch( $format ) { case 'text' : { foreach ( $logs as $log ) { if ( $log['Line'] ) - fprintf( $exportFP, "%s %s[%d].%s-%s/%d [%s]\n", - $log['DateTime'], $log['Component'], $log['Pid'], $log['Code'], $log['File'], $log['Line'], $log['Message'] ); + fprintf($exportFP, "%s %s[%d].%s-%s/%d [%s]\n", + $log['DateTime'], $log['Component'], $log['Pid'], $log['Code'], $log['File'], $log['Line'], $log['Message']); else - fprintf( $exportFP, "%s %s[%d].%s-%s [%s]\n", - $log['DateTime'], $log['Component'], $log['Pid'], $log['Code'], $log['File'], $log['Message'] ); + fprintf($exportFP, "%s %s[%d].%s-%s [%s]\n", + $log['DateTime'], $log['Component'], $log['Pid'], $log['Code'], $log['File'], $log['Message']); } break; } case 'tsv' : { # This line doesn't need fprintf, it could use fwrite - fprintf( $exportFP, join( "\t", + fprintf($exportFP, join("\t", translate('DateTime'), translate('Component'), translate('Server'), @@ -284,17 +285,17 @@ switch ( $_REQUEST['task'] ) { translate('Message'), translate('File'), translate('Line') - )."\n" ); + )."\n"); foreach ( $logs as $log ) { - fprintf( $exportFP, "%s\t%s\t%s\t%d\t%s\t%s\t%s\t%s\n", $log['DateTime'], $log['Component'], $log['Server'], $log['Pid'], $log['Code'], $log['Message'], $log['File'], $log['Line'] ); + fprintf($exportFP, "%s\t%s\t%s\t%d\t%s\t%s\t%s\t%s\n", + $log['DateTime'], $log['Component'], $log['Server'], $log['Pid'], $log['Code'], $log['Message'], $log['File'], $log['Line']); } break; } case 'html' : { - fwrite( $exportFP, - ' - + fwrite($exportFP, +' '.translate('ZoneMinderLog').' @@ -333,34 +334,36 @@ switch ( $_REQUEST['task'] ) {

'.translate('ZoneMinderLog').'

-

'.htmlspecialchars(preg_match( '/%/', DATE_FMT_CONSOLE_LONG )?strftime( DATE_FMT_CONSOLE_LONG ):date( DATE_FMT_CONSOLE_LONG )).'

+

'.htmlspecialchars(preg_match('/%/', DATE_FMT_CONSOLE_LONG) ? strftime(DATE_FMT_CONSOLE_LONG) : date(DATE_FMT_CONSOLE_LONG)).'

'.count($logs).' '.translate('Logs').'

- ' ); + '); foreach ( $logs as $log ) { $classLevel = $log['Level']; - if ( $classLevel < ZM\Logger::FATAL ) + if ( $classLevel < ZM\Logger::FATAL ) { $classLevel = ZM\Logger::FATAL; - elseif ( $classLevel > ZM\Logger::DEBUG ) + } else if ( $classLevel > ZM\Logger::DEBUG ) { $classLevel = ZM\Logger::DEBUG; + } $logClass = 'log-'.strtolower(ZM\Logger::$codes[$classLevel]); - fprintf( $exportFP, " \n", $logClass, $log['DateTime'], $log['Component'], $log['Server'], $log['Pid'], $log['Code'], $log['Message'], $log['File'], $log['Line'] ); + fprintf($exportFP, ' + ', $logClass, $log['DateTime'], $log['Component'], $log['Server'], $log['Pid'], $log['Code'], $log['Message'], $log['File'], $log['Line']); } - fwrite( $exportFP, + fwrite($exportFP, '
'.translate('DateTime').''.translate('Component').''.translate('Server').''.translate('Pid').''.translate('Level').''.translate('Message').''.translate('File').''.translate('Line').'
%s%s%s%d%s%s%s%s
%s%s%s%d%s%s%s%s
- ' ); + '); break; } case 'xml' : { - fwrite( $exportFP, + fwrite($exportFP, ' - - '.$_POST['selector'].'' ); + + '.$_POST['selector'].''); foreach ( $filter as $field=>$value ) if ( $value != '' ) fwrite( $exportFP, @@ -375,7 +378,7 @@ switch ( $_REQUEST['task'] ) { ' ); foreach ( $logs as $log ) { fprintf( $exportFP, - " + ' %s %s %s @@ -384,7 +387,8 @@ switch ( $_REQUEST['task'] ) { %s %d - \n", $log['DateTime'], $log['Component'], $log['Server'], $log['Pid'], $log['Code'], utf8_decode( $log['Message'] ), $log['File'], $log['Line'] ); + +', $log['DateTime'], $log['Component'], $log['Server'], $log['Pid'], $log['Code'], utf8_decode( $log['Message'] ), $log['File'], $log['Line'] ); } fwrite( $exportFP, ' @@ -413,7 +417,7 @@ switch ( $_REQUEST['task'] ) { ZM\Fatal('No log export format given'); $format = $_REQUEST['format']; - switch( $format ) { + switch ( $format ) { case 'text' : $exportExt = 'txt'; break; @@ -430,15 +434,15 @@ switch ( $_REQUEST['task'] ) { ZM\Fatal("Unrecognised log export format '$format'"); } - $exportFile = "zm-log.$exportExt"; - $exportPath = ZM_DIR_EXPORTS."/zm-log-$exportKey.$exportExt"; + $exportFile = 'zm-log.'.$exportExt; + $exportPath = ZM_DIR_EXPORTS.'/zm-log-'.$exportKey.$exportExt; header('Pragma: public'); header('Expires: 0'); header('Cache-Control: must-revalidate, post-check=0, pre-check=0'); - header('Cache-Control: private', false ); // required by certain browsers + header('Cache-Control: private', false); // required by certain browsers header('Content-Description: File Transfer'); - header('Content-Disposition: attachment; filename="'.$exportFile.'"' ); + header('Content-Disposition: attachment; filename="'.$exportFile.'"'); header('Content-Transfer-Encoding: binary'); header('Content-Type: application/force-download'); header('Content-Length: '.filesize($exportPath)); @@ -446,6 +450,6 @@ switch ( $_REQUEST['task'] ) { exit(0); break; } -} +} // end switch ( $_REQUEST['task'] ) ajaxError('Unrecognised action or insufficient permissions'); ?> diff --git a/web/ajax/status.php b/web/ajax/status.php index a553eaa04..fea5943ca 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( @@ -93,6 +96,7 @@ $statusData = array( 'selector' => 'Events.MonitorId', 'elements' => array( 'Id' => true, + 'MonitorId' => true, 'Name' => true, 'Cause' => true, 'Notes' => true, @@ -204,6 +208,7 @@ function collectData() { $fieldSql = array(); $joinSql = array(); $groupSql = array(); + $values = array(); $elements = &$entitySpec['elements']; $lc_elements = array_change_key_case( $elements ); @@ -258,7 +263,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 d9ad29b0d..5188c18b4 100644 --- a/web/api/app/Controller/EventsController.php +++ b/web/api/app/Controller/EventsController.php @@ -149,6 +149,10 @@ class EventsController extends AppController { )); $event['Event']['NextOfMonitor'] = $event_monitor_neighbors['next']['Event']['Id']; $event['Event']['PrevOfMonitor'] = $event_monitor_neighbors['prev']['Event']['Id']; + + $this->loadModel('Frame'); + $event['Event']['MaxScoreFrameId'] = $this->Frame->findByEventid($id,'FrameId',array('Score'=>'desc','FrameId'=>'asc'))['Frame']['FrameId']; + $event['Event']['AlarmFrameId'] = $this->Frame->findByEventidAndType($id,'Alarm')['Frame']['FrameId']; $this->set(array( 'event' => $event, 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 3be9166b8..3818f69d3 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, @@ -386,6 +379,8 @@ class MonitorsController extends AppController { $args = ''; if ( $daemon == 'zmc' and $monitor['Type'] == 'Local' ) { $args = '-d ' . $monitor['Device']; + } else if ( $daemon == 'zmcontrol.pl' ) { + $args = '--id '.$id; } else { $args = '-m ' . $id; } 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..8bccf3860 100644 --- a/web/includes/Filter.php +++ b/web/includes/Filter.php @@ -39,8 +39,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 +115,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 +143,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 a9be54405..c788b5fb8 100644 --- a/web/includes/Monitor.php +++ b/web/includes/Monitor.php @@ -9,125 +9,133 @@ 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, - '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' => array('type'=>'text','filter_regexp'=>'/[^\w\-\.\(\)\:\/ ]/'), + '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, + '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){ @@ -138,7 +146,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]) ) { @@ -211,9 +219,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) { @@ -221,9 +229,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) { @@ -234,10 +242,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() ) { @@ -253,7 +261,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 { @@ -276,12 +284,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"); @@ -306,7 +315,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'}); @@ -334,12 +343,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"); @@ -367,8 +377,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(); @@ -417,7 +427,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); @@ -467,61 +477,65 @@ 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 if ( $command == 'start' ) { + $options['command'] = 'start'; + } else if ( $command == 'stop' ) { + $options['command'] = 'stop'; + } 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'; + $url = ZM_BASE_PROTOCOL . '://'.$Server->Hostname().'/zm/api/monitors/daemonControl/'.$this->{'Id'}.'/'.$command.'/zmcontrol.pl.json'; if ( ZM_OPT_USE_AUTH ) { if ( ZM_AUTH_RELAY == 'hashed' ) { $url .= '?auth='.generateAuthHash( ZM_AUTH_HASH_IPS ); @@ -537,13 +551,13 @@ public function sendControlCommand($command) { $context = stream_context_create(); try { $result = file_get_contents($url, false, $context); - if ($result === FALSE) { /* Handle error */ - Error("Error restarting zma using $url"); + if ( $result === FALSE ) { /* Handle error */ + Error("Error sending command using $url"); return false; } } catch ( Exception $e ) { - Error("Except $e thrown trying to restart zma"); - return false; + Error("Exception $e thrown trying to send command to $url"); + 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 d51cf082d..1668a7767 100644 --- a/web/includes/Object.php +++ b/web/includes/Object.php @@ -39,13 +39,16 @@ class ZM_Object { public function __call($fn, array $args){ $type = (array_key_exists($fn, $this->defaults) && is_array($this->defaults[$fn])) ? $this->defaults[$fn]['type'] : 'scalar'; if ( count($args) ) { - if ( $type == 'set' and is_array($args[0]) ) + if ( $type == 'set' and is_array($args[0]) ) { $this->{$fn} = implode(',', $args[0]); - else + } else if ( array_key_exists($fn, $this->defaults) && is_array($this->defaults[$fn]) && isset($this->defaults[$fn]['filter_regexp']) ) { + $this->{$fn} = preg_replace($this->defaults[$fn]['filter_regexp'], '', $args[0]); + } else { $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) ) { @@ -63,7 +66,7 @@ class ZM_Object { public static function _find($class, $parameters = null, $options = null ) { $table = $class::$table; $filters = array(); - $sql = "SELECT * FROM `$table` "; + $sql = 'SELECT * FROM `'.$table.'` '; $values = array(); if ( $parameters ) { @@ -110,8 +113,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 +131,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 +149,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,13 +167,10 @@ 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]) ) - $this->{$k} = $this->defaults[$k]['default']; - else - $this->{$k} = $this->defaults[$k]; + if ( array_key_exists($k, $this->defaults) && is_array($this->defaults[$k]) && isset($this->defaults[$k]['filter_regexp']) ) { + $this->{$k} = preg_replace($this->defaults[$k]['filter_regexp'], '', trim($v)); } else { - $this->{$k} = trim($v); + $this->{$k} = trim($v); } } else if ( is_integer($v) ) { $this->{$k} = $v; @@ -200,7 +206,8 @@ class ZM_Object { } } } # end foreach default - } + } # end if defaults + foreach ( $new_values as $field => $value ) { if ( method_exists($this, $field) ) { @@ -215,7 +222,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}) . ' ?= ' . @@ -234,6 +241,9 @@ class ZM_Object { # Input might be a command separated string, or an array } else { + if ( array_key_exists($field, $this->defaults) && is_array($this->defaults[$field]) && isset($this->defaults[$field]['filter_regexp']) ) { + $value = preg_replace($this->defaults[$field]['filter_regexp'], '', trim($value)); + } if ( $this->{$field} != $value ) { $changes[$field] = $value; } @@ -254,17 +264,6 @@ class ZM_Object { $changes[$field] = $value; } } - - #if ( (!array_key_exists($field, $this)) or ( $this->{$field} != $new_values[$field] ) ) { - #Logger::Debug("Checking default $field => $default_value changes becaause" . $new_values[$field].' != '.$new_values[$field]); - #$changes[$field] = $new_values[$field]; - ##} else if { - #Logger::Debug("Checking default $field => $default_value changes becaause " . $new_values[$field].' != '.$new_values[$field]); - ##array_push( $changes, [$field=>$defaults[$field]] ); - #} - #} else { - #Logger::Debug("Checking default $field => $default_value not in new_values"); - #} } # end foreach newvalue @@ -280,6 +279,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/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/group.php b/web/includes/actions/group.php index 9aa988540..b23659d18 100644 --- a/web/includes/actions/group.php +++ b/web/includes/actions/group.php @@ -21,42 +21,31 @@ // Group edit actions # Should probably verify that each monitor id is a valid monitor, that we have access to. # However at the moment, you have to have System permissions to do this -if ( ! canEdit('Groups') ) { +if ( !canEdit('Groups') ) { ZM\Warning('Need group edit permissions to edit groups'); return; } if ( $action == 'Save' ) { - $monitors = empty($_POST['newGroup']['MonitorIds']) ? '' : implode(',', $_POST['newGroup']['MonitorIds']); $group_id = null; - if ( !empty($_POST['gid']) ) { + if ( !empty($_POST['gid']) ) $group_id = $_POST['gid']; - dbQuery( - 'UPDATE Groups SET Name=?, ParentId=? WHERE Id=?', + $group = new ZM\Group($group_id); + $group->save( array( - $_POST['newGroup']['Name'], - ( $_POST['newGroup']['ParentId'] == '' ? null : $_POST['newGroup']['ParentId'] ), - $group_id, + 'Name'=> $_POST['newGroup']['Name'], + 'ParentId'=>( $_POST['newGroup']['ParentId'] == '' ? null : $_POST['newGroup']['ParentId'] ), ) ); - dbQuery('DELETE FROM Groups_Monitors WHERE GroupId=?', array($group_id)); - } else { - dbQuery( - 'INSERT INTO Groups (Name,ParentId) VALUES (?,?)', - array( - $_POST['newGroup']['Name'], - ( $_POST['newGroup']['ParentId'] == '' ? null : $_POST['newGroup']['ParentId'] ), - ) - ); - $group_id = dbInsertId(); - } + dbQuery('DELETE FROM `Groups_Monitors` WHERE `GroupId`=?', array($group_id)); + $group_id = $group->Id(); if ( $group_id ) { foreach ( $_POST['newGroup']['MonitorIds'] as $mid ) { - dbQuery('INSERT INTO Groups_Monitors (GroupId,MonitorId) VALUES (?,?)', array($group_id, $mid)); + dbQuery('INSERT INTO `Groups_Monitors` (`GroupId`,`MonitorId`) VALUES (?,?)', array($group_id, $mid)); } } $view = 'none'; $refreshParent = true; $closePopup = true; -} +} ?> diff --git a/web/includes/actions/login.php b/web/includes/actions/login.php index 6c8312b2f..da1b60ed2 100644 --- a/web/includes/actions/login.php +++ b/web/includes/actions/login.php @@ -29,8 +29,13 @@ if ( ('login' == $action) && isset($_REQUEST['username']) && ( ZM_AUTH_TYPE == ' && defined('ZM_OPT_GOOG_RECAPTCHA_SITEKEY') && ZM_OPT_USE_GOOG_RECAPTCHA && ZM_OPT_GOOG_RECAPTCHA_SECRETKEY - && ZM_OPT_GOOG_RECAPTCHA_SITEKEY ) - { + && ZM_OPT_GOOG_RECAPTCHA_SITEKEY + ) { + if ( !isset($_REQUEST['g-recaptcha-response']) ) { + ZM\Error('reCaptcha authentication failed. No g-recpatcha-response in REQUEST: '); + unset($user); // unset should be ok here because we aren't in a function + return; + } $url = 'https://www.google.com/recaptcha/api/siteverify'; $fields = array ( 'secret' => ZM_OPT_GOOG_RECAPTCHA_SECRETKEY, @@ -49,14 +54,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..78b64e13c 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(), @@ -92,6 +92,9 @@ if ( $action == 'monitor' ) { if ( $monitor->Type() != 'WebSite' ) { $monitor->zmaControl('stop'); $monitor->zmcControl('stop'); + if ( $monitor->Controllable() ) { + $monitor->sendControlCommand('stop'); + } } # These are used in updating zones @@ -219,7 +222,6 @@ if ( $action == 'monitor' ) { } else { ZM\Error('Error saving new Monitor.'); - $error_message = dbError($sql); return; } } @@ -265,8 +267,7 @@ if ( $action == 'monitor' ) { $monitor->zmaControl('start'); if ( $monitor->Controllable() ) { - require_once('includes/control_functions.php'); - $monitor->sendControlCommand('quit'); + $monitor->sendControlCommand('start'); } } // really should thump zmwatch and maybe zmtrigger too. diff --git a/web/includes/actions/montage.php b/web/includes/actions/montage.php index 207c9a0f0..cd9a41aaf 100644 --- a/web/includes/actions/montage.php +++ b/web/includes/actions/montage.php @@ -36,7 +36,7 @@ if ( isset($_REQUEST['object']) ) { } $Layout->Positions($_REQUEST['Positions']); $Layout->save(); - session_start(); + zm_session_start(); $_SESSION['zmMontageLayout'] = $Layout->Id(); setcookie('zmMontageLayout', $Layout->Id(), 1); session_write_close(); 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/actions/version.php b/web/includes/actions/version.php index fde85427f..a203e1dd3 100644 --- a/web/includes/actions/version.php +++ b/web/includes/actions/version.php @@ -42,6 +42,8 @@ if ( $action == 'version' && isset($_REQUEST['option']) ) { $nextReminder += 24*60*60; } elseif ( $option == 'week' ) { $nextReminder += 7*24*60*60; + } elseif ( $option == 'month' ) { + $nextReminder += 30*24*60*60; } dbQuery("UPDATE Config SET Value = '".$nextReminder."' WHERE Name = 'ZM_DYN_NEXT_REMINDER'"); break; 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..7052a061a 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 ( defined('ZM_TIMEZONE') and 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 1710b0ab4..e616e2c8f 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 @@ -932,7 +943,7 @@ 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); } @@ -1052,7 +1063,7 @@ function parseSort($saveToSession=false, $querySep='&') { $sortColumn = 'E.StartTime'; break; } - if ( !$_REQUEST['sort_asc'] ) + if ( !isset($_REQUEST['sort_asc']) ) $_REQUEST['sort_asc'] = 0; $sortOrder = $_REQUEST['sort_asc'] ? 'asc' : 'desc'; $sortQuery = $querySep.'sort_field='.validHtmlStr($_REQUEST['sort_field']).$querySep.'sort_asc='.validHtmlStr($_REQUEST['sort_asc']); @@ -1082,15 +1093,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]; @@ -1224,6 +1230,7 @@ function parseFilter(&$filter, $saveToSession=false, $querySep='&') { break; } $valueList = array(); + if ( !isset($term['val']) ) $term['val'] = ''; foreach ( preg_split('/["\'\s]*?,["\'\s]*?/', preg_replace('/^["\']+?(.+)["\']+?$/', '$1', $term['val'])) as $value ) { switch ( $term['attr'] ) { case 'MonitorName': @@ -1256,7 +1263,7 @@ 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': @@ -1324,10 +1331,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 +1343,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 +1358,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. // @@ -2169,7 +2179,7 @@ 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'); exit(jsonEncode($response)); } header("HTTP/1.0 $code $message"); @@ -2185,7 +2195,7 @@ function ajaxResponse($result=false) { } else if ( !empty($result) ) { $response['message'] = $result; } - header('Content-type: text/plain'); + header('Content-type: application/json'); exit(jsonEncode($response)); } @@ -2279,9 +2289,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'] != '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 @@ -2580,21 +2595,23 @@ function html_radio($name, $values, $selected=null, $options=array(), $attrs=arr 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"':''), - implode(' ', array_map( - function($attr, $value){return $attr.'="'.$value.'"';}, - array_keys($attrs), - array_values($attrs) - ), - ), - ( isset($options['id']) ? $options['id'] : ''), - ( ( (!isset($options['inline'])) or $options['inline'] ) ? '-inline' : ''), - ); + $attributes_string, + (isset($options['id']) ? $options['id'] : ''), + ( ( (!isset($options['inline'])) or $options['inline'] ) ? '-inline' : '') + ); if ( isset($options['container']) ) { $html .= $options['container'][1]; } @@ -2602,4 +2619,27 @@ function html_radio($name, $values, $selected=null, $options=array(), $attrs=arr 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); +} + +function zm_random_bytes($length = 32){ + if ( !isset($length) || intval($length) <= 8 ) { + $length = 32; + } + if ( function_exists('random_bytes') ) { + return random_bytes($length); + } + if ( function_exists('mcrypt_create_iv') ) { + return mcrypt_create_iv($length, MCRYPT_DEV_URANDOM); + } + if ( function_exists('openssl_random_pseudo_bytes') ) { + return openssl_random_pseudo_bytes($length); + } + ZM\Error('No random_bytes function found.'); +} ?> diff --git a/web/includes/lang.php b/web/includes/lang.php index 38f1179d8..78a64a0e1 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']) and $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 83be992e8..18da56e63 100644 --- a/web/index.php +++ b/web/index.php @@ -152,10 +152,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; @@ -179,15 +176,21 @@ if ( isset($_REQUEST['view']) ) # Add CSP Headers -$cspNonce = bin2hex(openssl_random_pseudo_bytes(16)); +$cspNonce = bin2hex(zm_random_bytes(16)); $request = null; 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 +205,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 +246,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/js/logger.js b/web/js/logger.js index 0a1195043..bf5444268 100644 --- a/web/js/logger.js +++ b/web/js/logger.js @@ -53,12 +53,13 @@ function logReport( level, message, file, line ) { /* eslint-enable no-caller */ if ( !debugReq ) { + debugParms = "view=request&request=log&task=create"; if ( Browser ) { - debugParms = "view=request&request=log&task=create&browser[name]="+Browser.name+"&browser[version]="+Browser.version+"&browser[platform]="+(Browser.Platform?Browser.Platform.name:'unknown'); + debugParms += "&browser[name]="+Browser.name+"&browser[version]="+Browser.version+"&browser[platform]="+(Browser.Platform?Browser.Platform.name:'unknown'); } else { - debugParms = "view=request&request=log&task=create&browser[name]=unknown&browser[version]=unknown&browser[platform]=unknown"; + debugParms += "&browser[name]=unknown&browser[version]=unknown&browser[platform]=unknown"; } - debugReq = new Request.JSON( {url: thisUrl, method: 'post', timeout: AJAX_TIMEOUT, link: 'chain'} ); + debugReq = new Request.JSON({url: thisUrl, method: 'post', timeout: AJAX_TIMEOUT, link: 'chain'}); } var requestParms = debugParms; requestParms += "&level="+level+"&message="+encodeURIComponent(message); @@ -71,57 +72,57 @@ function logReport( level, message, file, line ) { if ( line ) { requestParms += "&line="+line; } - debugReq.send( requestParms ); + debugReq.send(requestParms); } -function Panic( message ) { - console.error( message ); - logReport( "PNC", message ); - alert( "PANIC: "+message ); +function Panic(message) { + console.error(message); + logReport("PNC", message); + alert("PANIC: "+message); } -function Fatal( message ) { - console.error( message ); +function Fatal(message) { + console.error(message); logReport( "FAT", message ); alert( "FATAL: "+message ); } -function Error( message ) { - console.error( message ); - logReport( "ERR", message ); +function Error(message) { + console.error(message); + logReport("ERR", message); } -function Warning( message ) { - console.warn( message ); - logReport( "WAR", message ); +function Warning(message) { + console.warn(message); + logReport("WAR", message); } -function Info( message ) { - console.info( message ); - logReport( "INF", message ); +function Info(message) { + console.info(message); + logReport("INF", message); } -function Debug( message ) { - console.debug( message ); - //logReport( "DBG", message ); +function Debug(message) { + console.debug(message); + //logReport("DBG", message); } -function Dump( value, label ) { +function Dump(value, label) { if ( label ) { - console.debug( label+" => " ); + console.debug(label+" => "); } - console.debug( value ); + console.debug(value); } window.onerror = function( message, url, line ) { - logReport( "ERR", message, url, line ); + logReport("ERR", message, url, line); }; window.addEventListener("securitypolicyviolation", function logCSP(evt) { var level = evt.disposition == "enforce" ? "ERR" : "DBG"; var message = evt.blockedURI + " violated CSP " + evt.violatedDirective; - if (evt.sample) { + if ( evt.sample ) { message += " (Sample: " + evt.sample + ")"; } logReport(level, message, evt.sourceFile, evt.lineNumber); diff --git a/web/js/overlay.js b/web/js/overlay.js index 6517d2d7d..84547c53f 100644 --- a/web/js/overlay.js +++ b/web/js/overlay.js @@ -51,8 +51,8 @@ var Overlay = new Class({ }, show: function() { this.mask.show(); - window.addEventListener( 'resize', this.update.bind(this) ); - window.addEventListener( 'scroll', this.update.bind(this) ); + window.addEventListener( 'resize', this.update.bind(this), {passive: true} ); + window.addEventListener( 'scroll', this.update.bind(this), {passive: true} ); this.element.tween( 'opacity', [0, 1.0] ); this.element.show(); this.element.position(); @@ -80,8 +80,8 @@ var Overlay = new Class({ } updateOverlayLoading(); this.loading.setStyle( 'display', 'block' ); - window.addEventListener( 'resize', this.update.bind(this) ); - window.addEventListener( 'scroll', this.update.bind(this) ); + window.addEventListener( 'resize', this.update.bind(this), {passive: true} ); + window.addEventListener( 'scroll', this.update.bind(this), {passive: true} ); }, hideAnimation: function() { if ( this.loading ) { diff --git a/web/lang/cn_zh.php b/web/lang/cn_zh.php index 0a751023e..9cdd9c4f1 100644 --- a/web/lang/cn_zh.php +++ b/web/lang/cn_zh.php @@ -74,12 +74,14 @@ $SLANG = array( '24BitColour' => '24 位彩色', '32BitColour' => '32 位彩色', // Added - 2011-06-15 '8BitGrey' => '8 位灰度', + 'API' => 'API', // Added - 2020-04-09 + 'APIEnabled' => 'API已启用', // Added - 2020-04-09 'Action' => '活动动作', 'Actual' => '实际', 'AddNewControl' => '新建控制', 'AddNewMonitor' => '新建监视器', - 'AddNewServer' => 'Add New Server', // Added - 2018-08-30 - 'AddNewStorage' => 'Add New Storage', // Added - 2018-08-30 + 'AddNewServer' => '新建服务器', // Added - 2018-08-30 + 'AddNewStorage' => '新建存储', // Added - 2018-08-30 'AddNewUser' => '新建用户', 'AddNewZone' => '新建区域', 'Alarm' => '报警', @@ -90,11 +92,12 @@ $SLANG = array( 'AlarmMaximumFPS' => '报警最大帧率FPS', 'AlarmPx' => '报警像素', 'AlarmRGBUnset' => '你必须设置一个报警颜色(RGB)', - 'AlarmRefImageBlendPct'=> 'Alarm Reference Image Blend %ge', // Added - 2015-04-18 + 'AlarmRefImageBlendPct'=> '报警参考影像混合 %ge', // Added - 2015-04-18 'Alert' => '警报', 'All' => '全部', - 'AnalysisFPS' => 'Analysis FPS', // Added - 2015-07-22 - 'AnalysisUpdateDelay' => 'Analysis Update Delay', // Added - 2015-07-23 + 'AllTokensRevoked' => '已撤销所有tokens', // Added - 2020-04-09 + 'AnalysisFPS' => '分析帧率 FPS', // Added - 2015-07-22 + 'AnalysisUpdateDelay' => '分析更新延迟', // Added - 2015-07-23 'Apply' => '应用', 'ApplyingStateChange' => '状态改变生效', 'ArchArchived' => '仅限于存档', @@ -107,30 +110,31 @@ $SLANG = array( 'AttrArchiveStatus' => '存档状态', 'AttrAvgScore' => '平均分数', 'AttrCause' => '原因', - 'AttrDiskBlocks' => '磁碟区块', - 'AttrDiskPercent' => '磁碟百分比', - 'AttrDiskSpace' => 'Disk Space', // Added - 2018-08-30 + 'AttrDiskBlocks' => '磁盘区块', + 'AttrDiskPercent' => '磁盘百分比', + 'AttrDiskSpace' => '磁盘空间', // Added - 2018-08-30 'AttrDuration' => '过程', - 'AttrEndDate' => 'End Date', // Added - 2018-08-30 - 'AttrEndDateTime' => 'End Date/Time', // Added - 2018-08-30 - 'AttrEndTime' => 'End Time', // Added - 2018-08-30 - 'AttrEndWeekday' => 'End Weekday', // Added - 2018-08-30 - 'AttrFilterServer' => 'Server Filter is Running On', // Added - 2018-08-30 + 'AttrEndDate' => '结束日期', // Added - 2018-08-30 + 'AttrEndDateTime' => '结束日期/时间', // Added - 2018-08-30 + 'AttrEndTime' => '结束时间', // Added - 2018-08-30 + 'AttrEndWeekday' => '结束星期', // Added - 2018-08-30 + 'AttrFilterServer' => '过滤服务正运行在', // Added - 2018-08-30 'AttrFrames' => '帧', 'AttrId' => 'Id', 'AttrMaxScore' => '最大分数', 'AttrMonitorId' => '监视器 Id', 'AttrMonitorName' => '监视器名称', - 'AttrMonitorServer' => 'Server Monitor is Running On', // Added - 2018-08-30 + 'AttrMonitorServer' => '监控服务正运行在', // Added - 2018-08-30 'AttrName' => '名称', 'AttrNotes' => '备注', - 'AttrStartDate' => 'Start Date', // Added - 2018-08-30 - 'AttrStartDateTime' => 'Start Date/Time', // Added - 2018-08-30 - 'AttrStartTime' => 'Start Time', // Added - 2018-08-30 - 'AttrStartWeekday' => 'Start Weekday', // Added - 2018-08-30 - 'AttrStateId' => 'Run State', // Added - 2018-08-30 - 'AttrStorageArea' => 'Storage Area', // Added - 2018-08-30 - 'AttrStorageServer' => 'Server Hosting Storage', // Added - 2018-08-30 + 'AttrSecondaryStorageArea'=> '第二存储区域', // Added - 2020-04-09 + 'AttrStartDate' => '开始日期', // Added - 2018-08-30 + 'AttrStartDateTime' => '开始日期/时间', // Added - 2018-08-30 + 'AttrStartTime' => '开始时间', // Added - 2018-08-30 + 'AttrStartWeekday' => '开始星期', // Added - 2018-08-30 + 'AttrStateId' => '运行状态', // Added - 2018-08-30 + 'AttrStorageArea' => '存储区域', // Added - 2018-08-30 + 'AttrStorageServer' => '存储服务器', // Added - 2018-08-30 'AttrSystemLoad' => '系统负载', 'AttrTotalScore' => '总分数', 'Auto' => '自动', @@ -141,10 +145,10 @@ $SLANG = array( 'BackgroundFilter' => '在后台运行筛选器', 'BadAlarmFrameCount' => '报警帧数必须设为大于1的整数', 'BadAlarmMaxFPS' => '报警最大帧率必须是正整数或正浮点数', - 'BadAnalysisFPS' => 'Analysis FPS must be a positive integer or floating point value', // Added - 2015-07-22 - 'BadAnalysisUpdateDelay'=> 'Analysis update delay must be set to an integer of zero or more', // Added - 2015-07-23 + 'BadAnalysisFPS' => '分析帧率 FPS 必须是正整数或正浮点数', // Added - 2015-07-22 + 'BadAnalysisUpdateDelay'=> '分析更新延迟必须设为大于零的整数', // Added - 2015-07-23 'BadChannel' => '通道必须设为大于零的整数', - 'BadColours' => 'Target colour must be set to a valid value', // Added - 2011-06-15 + 'BadColours' => '颜色必须设置为有效值', // Added - 2011-06-15 'BadDevice' => '必须为器件设置有效值', 'BadFPSReportInterval' => 'FPS帧数报告间隔缓冲数必须是0以上整数', 'BadFormat' => '格式必须设为大于零的整数', @@ -155,9 +159,10 @@ $SLANG = array( 'BadLabelX' => '标签 X 坐标必须设为大于零的整数', 'BadLabelY' => '标签 Y 坐标必须设为大于零的整数', 'BadMaxFPS' => '最大帧数FPS必须设为正整数或着浮点数', - 'BadMotionFrameSkip' => 'Motion Frame skip count must be an integer of zero or more', + 'BadMotionFrameSkip' => '运动跳帧数必须设为大于零的整数', 'BadNameChars' => '名称只可以包含字母,数字,波折号和下划线', - 'BadPalette' => 'Palette must be set to a valid value', // Added - 2009-03-31 + 'BadNoSaveJPEGsOrVideoWriter'=> '保存为JPEGs和保存为视频同时禁用后。不会有任何记录 ', // Added - 2020-04-09 + 'BadPalette' => '调色板必须设为有效值', // Added - 2009-03-31 'BadPath' => '路径必须设为有效值', 'BadPort' => '端口必须设为有效数字', 'BadPostEventCount' => '之后事件影像数目必须设为大于零的整数', @@ -165,11 +170,11 @@ $SLANG = array( 'BadRefBlendPerc' => '参考混合百分比必须设为一个正整数', 'BadSectionLength' => '节长度必须设为30的整数倍', 'BadSignalCheckColour' => '信号检查颜色必须设为有效的RGB颜色字符', - 'BadSourceType' => 'Source Type \"Web Site\" requires the Function to be set to \"Monitor\"', // Added - 2018-08-30 + 'BadSourceType' => '源类型 \"网站\" 要求 功能 设置为 \"监视\"', // Added - 2018-08-30 'BadStreamReplayBuffer' => '流重放缓冲必须为零或更多整数', 'BadWarmupCount' => '预热帪必须设为零或更多整数', 'BadWebColour' => 'Web颜色必须设为有效Web颜色字符', - 'BadWebSitePath' => 'Please enter a complete website url, including the http:// or https:// prefix.', // Added - 2018-08-30 + 'BadWebSitePath' => '请输入一个完整的网站链接,包括http://或https://前缀。', // Added - 2018-08-30 'BadWidth' => '宽度必须设为有效值', 'Bandwidth' => '带宽', 'BandwidthHead' => 'Bandwidth', // This is the end of the bandwidth status on the top of the console, different in many language due to phrasing @@ -177,9 +182,9 @@ $SLANG = array( 'BlobSizes' => 'Blob大小', 'Blobs' => 'Blobs', 'Brightness' => '亮度', - 'Buffer' => 'Buffer', // Added - 2015-04-18 + 'Buffer' => '缓冲', // Added - 2015-04-18 'Buffers' => '缓冲器', - 'CSSDescription' => 'Change the default css for this computer', // Added - 2015-04-18 + 'CSSDescription' => '改变本机默认css', // Added - 2015-04-18 'CanAutoFocus' => '可以自动对焦', 'CanAutoGain' => '可以自动增益控制', 'CanAutoIris' => '可以自动光圈', @@ -204,8 +209,8 @@ $SLANG = array( 'CanMoveMap' => '可以映射网格移动', 'CanMoveRel' => '可以相对移动', 'CanPan' => '可以平移' , + 'CanReboot' => '可以重启', 'CanReset' => '可以复位', - 'CanReboot' => 'Can Reboot', 'CanSetPresets' => '可以进行预设', 'CanSleep' => '可以休眠', 'CanTilt' => '可以倾斜', @@ -224,22 +229,23 @@ $SLANG = array( 'CaptureHeight' => '捕获高度', 'CaptureMethod' => '捕获方式', 'CapturePalette' => '捕获调色板', - 'CaptureResolution' => 'Capture Resolution', // Added - 2015-04-18 + 'CaptureResolution' => '捕获分辨率', // Added - 2015-04-18 'CaptureWidth' => '捕获宽度', 'Cause' => '原因', 'CheckMethod' => '报警检查方式', - 'ChooseDetectedCamera' => 'Choose Detected Camera', // Added - 2009-03-31 + 'ChooseDetectedCamera' => '选择检测到的摄像头', // Added - 2009-03-31 + 'ChooseDetectedProfile'=> '选择检测到的流媒体', // Added - 2020-04-09 'ChooseFilter' => '选择筛选器', - 'ChooseLogFormat' => 'Choose a log format', // Added - 2011-06-17 + 'ChooseLogFormat' => '选择日志格式', // Added - 2011-06-17 'ChooseLogSelection' => 'Choose a log selection', // Added - 2011-06-17 'ChoosePreset' => '选择预置', - 'Clear' => 'Clear', // Added - 2011-06-16 - 'CloneMonitor' => 'Clone', // Added - 2018-08-30 + 'Clear' => '清除', // Added - 2011-06-16 + 'CloneMonitor' => '克隆', // Added - 2018-08-30 'Close' => '关闭', 'Colour' => '彩色', 'Command' => '命令', - 'Component' => 'Component', // Added - 2011-06-16 - 'ConcurrentFilter' => 'Run filter concurrently', // Added - 2018-08-30 + 'Component' => '组件', // Added - 2011-06-16 + 'ConcurrentFilter' => '同时应用筛选器', // Added - 2018-08-30 'Config' => '配置', 'ConfiguredFor' => '配置标的', 'ConfirmDeleteEvents' => '确认希望删除所选事件?', @@ -257,24 +263,25 @@ $SLANG = array( 'ControlDevice' => '控制设备', 'ControlType' => '控制类型', 'Controllable' => '可控', - 'Current' => 'Current', // Added - 2015-04-18 + 'Current' => '现在', // Added - 2015-04-18 'Cycle' => '循环', 'CycleWatch' => '循环监视', - 'DateTime' => 'Date/Time', // Added - 2011-06-16 + 'DateTime' => '日期', // Added - 2011-06-16 'Day' => '日', 'Debug' => '调试', + 'DefaultCodec' => '默认即时观看方法', // Added - 2020-04-09 'DefaultRate' => '缺省速率', 'DefaultScale' => '缺省缩放', 'DefaultView' => '缺省视角', - 'Deinterlacing' => 'Deinterlacing', // Added - 2015-04-18 - 'Delay' => 'Delay', // Added - 2015-04-18 + 'Deinterlacing' => '去隔行', // Added - 2015-04-18 + 'Delay' => '延迟', // Added - 2015-04-18 'Delete' => '删除', 'DeleteAndNext' => '删除并下一个', 'DeleteAndPrev' => '删除并前一个', 'DeleteSavedFilter' => '删除存储过滤器', 'Description' => '描述', - 'DetectedCameras' => 'Detected Cameras', // Added - 2009-03-31 - 'DetectedProfiles' => 'Detected Profiles', // Added - 2015-04-18 + 'DetectedCameras' => '检测到的摄像头', // Added - 2009-03-31 + 'DetectedProfiles' => '检测到的流媒体', // Added - 2015-04-18 'Device' => '设备', 'DeviceChannel' => '设备通道', 'DeviceFormat' => '设备格式', @@ -283,10 +290,10 @@ $SLANG = array( 'Devices' => '设备', 'Dimensions' => '维度', 'DisableAlarms' => '关闭警报', - 'Disk' => '磁碟', - 'Display' => 'Display', // Added - 2011-01-30 - 'Displaying' => 'Displaying', // Added - 2011-06-16 - 'DoNativeMotionDetection'=> 'Do Native Motion Detection', + 'Disk' => '磁盘', + 'Display' => '显示', // Added - 2011-01-30 + 'Displaying' => '正在显示', // Added - 2011-06-16 + 'DoNativeMotionDetection'=> '在本机进行运动检测', 'Donate' => '请捐款', 'DonateAlready' => '不,我已经捐赠过了', 'DonateEnticement' => '迄今,您已经运行ZoneMinder有一阵子了,希望它能够有助于增强您家或者办公区域的安全。尽管ZoneMinder是,并将保持免费和开源,该项目依然在研发和支持中投入了资金和精力。如果您愿意支持今后的开发和新功能,那么请考虑为该项目捐款。捐款不是必须的,任何数量的捐赠,我们都很感谢。

如果您愿意捐款,请选择下列选项,或者访问 https://zoneminder.com/donate/ 捐赠主页。

感谢您使用ZoneMinder,并且不要忘记访问访问ZoneMinder.com的论坛以获得支持或建议,这可以提升您的ZoneMinder的体验。', @@ -297,11 +304,11 @@ $SLANG = array( 'DonateRemindWeek' => '现在不,1星期内再次提醒我', 'DonateYes' => '好,我现在就捐款', 'Download' => '下载', - 'DownloadVideo' => 'Download Video', // Added - 2018-08-30 + 'DownloadVideo' => '下载视频', // Added - 2018-08-30 'DuplicateMonitorName' => 'Duplicate Monitor Name', // Added - 2009-03-31 'Duration' => 'Duration', 'Edit' => '编辑', - 'EditLayout' => 'Edit Layout', // Added - 2018-08-30 + 'EditLayout' => '编辑布局', // Added - 2018-08-30 'Email' => 'Email', 'EnableAlarms' => '启动报警', 'Enabled' => '已启动', @@ -318,8 +325,9 @@ $SLANG = array( 'Events' => '事件', 'Exclude' => '排除', 'Execute' => '执行', - 'Exif' => 'Embed EXIF data into image', // Added - 2018-08-30 + 'Exif' => '嵌入EXIF信息到图片', // Added - 2018-08-30 'Export' => '导出', + 'ExportCompress' => '使用压缩', // Added - 2020-04-09 'ExportDetails' => '导出时间详情', 'ExportFailed' => '导出失败', 'ExportFormat' => '导出文件格式', @@ -327,7 +335,8 @@ $SLANG = array( 'ExportFormatZip' => 'Zip', 'ExportFrames' => '导出帧详情', 'ExportImageFiles' => '导出影像文件', - 'ExportLog' => 'Export Log', // Added - 2011-06-17 + 'ExportLog' => '导出日志', // Added - 2011-06-17 + 'ExportMatches' => '导出匹配项', // Added - 2020-04-09 'ExportMiscFiles' => '导出其他文件 (如果存在)', 'ExportOptions' => '导出选项', 'ExportSucceeded' => '导出成功', @@ -341,29 +350,30 @@ $SLANG = array( 'Feed' => '转送源', 'Ffmpeg' => 'Ffmpeg', 'File' => '文件', - 'Filter' => 'Filter', // Added - 2015-04-18 - 'FilterArchiveEvents' => '将全部匹配项存档', - 'FilterDeleteEvents' => '将全部匹配项删除', - 'FilterEmailEvents' => '将全部匹配项详情电邮出去', + 'Filter' => '过滤器', // Added - 2015-04-18 + 'FilterArchiveEvents' => '存档全部匹配项', + 'FilterCopyEvents' => '复制全部匹配项', // Added - 2020-04-09 + 'FilterDeleteEvents' => '删除全部匹配项', + 'FilterEmailEvents' => '邮件发送全部匹配项详情', 'FilterExecuteEvents' => '执行全部匹配项命令', - 'FilterLog' => 'Filter log', // Added - 2015-04-18 + 'FilterLog' => '过滤日志', // Added - 2015-04-18 'FilterMessageEvents' => '全部匹配项的信息详情', - 'FilterMoveEvents' => 'Move all matches', // Added - 2018-08-30 + 'FilterMoveEvents' => '移除全部匹配项', // Added - 2018-08-30 'FilterPx' => '过滤器像素', 'FilterUnset' => '您必须指定过滤器宽度和高度', - 'FilterUpdateDiskSpace'=> 'Update used disk space', // Added - 2018-08-30 + 'FilterUpdateDiskSpace'=> '刷新磁盘空间', // Added - 2018-08-30 'FilterUploadEvents' => '上传全部匹配项', 'FilterVideoEvents' => '为全部匹配项创建视频', 'Filters' => '过滤器', 'First' => '首先', 'FlippedHori' => '水平翻转', 'FlippedVert' => '垂直翻转', - 'FnMocord' => 'Mocord', // Added 2013.08.16. - 'FnModect' => 'Modect', // Added 2013.08.16. - 'FnMonitor' => 'Monitor', // Added 2013.08.16. + 'FnMocord' => '运动侦测并录制', // Added 2013.08.16. + 'FnModect' => '运动侦测', // Added 2013.08.16. + 'FnMonitor' => '监视', // Added 2013.08.16. 'FnNodect' => 'Nodect', // Added 2013.08.16. - 'FnNone' => 'None', // Added 2013.08.16. - 'FnRecord' => 'Record', // Added 2013.08.16. + 'FnNone' => '无', // Added 2013.08.16. + 'FnRecord' => '录制', // Added 2013.08.16. 'Focus' => '聚焦', 'ForceAlarm' => '强制报警', 'Format' => '格式', @@ -376,7 +386,7 @@ $SLANG = array( 'Function' => '功能', 'Gain' => '增益', 'General' => '一般', - 'GenerateDownload' => 'Generate Download', // Added - 2018-08-30 + 'GenerateDownload' => '创建下载项', // Added - 2018-08-30 'GenerateVideo' => '创建视频', 'GeneratingVideo' => '正在创建视频', 'GoToZoneMinder' => '访问 ZoneMinder.com', @@ -397,7 +407,7 @@ $SLANG = array( 'High' => '高', 'HighBW' => '高 B/W', 'Home' => '主页', - 'Hostname' => 'Hostname', // Added - 2018-08-30 + 'Hostname' => '主机名', // Added - 2018-08-30 'Hour' => '小时', 'Hue' => '色调', 'Id' => 'Id', @@ -408,6 +418,7 @@ $SLANG = array( 'Images' => '影像', 'In' => '在', 'Include' => '包含', + 'InvalidateTokens' => '使所有创建的tokens无效', // Added - 2020-04-09 'Inverted' => '反向', 'Iris' => '光圈', 'KeyString' => '密钥字符', @@ -415,19 +426,19 @@ $SLANG = array( 'Language' => '语言', 'Last' => '最后', 'Layout' => '布局', - 'Level' => 'Level', // Added - 2011-06-16 + 'Level' => '级别', // Added - 2011-06-16 'Libvlc' => 'Libvlc', 'LimitResultsPost' => '个结果', // This is used at the end of the phrase 'Limit to first N results only' 'LimitResultsPre' => '仅限于开始', // This is used at the beginning of the phrase 'Limit to first N results only' - 'Line' => 'Line', // Added - 2011-06-16 + 'Line' => '行', // Added - 2011-06-16 'LinkedMonitors' => '管理监视器', 'List' => '列表', - 'ListMatches' => 'List Matches', // Added - 2018-08-30 + 'ListMatches' => '列出匹配项', // Added - 2018-08-30 'Load' => '加载', 'Local' => '本地', - 'Log' => 'Log', // Added - 2011-06-16 + 'Log' => '日志', // Added - 2011-06-16 'LoggedInAs' => '登录为', - 'Logging' => 'Logging', // Added - 2011-06-16 + 'Logging' => '日志', // Added - 2011-06-16 'LoggingIn' => '登录', 'Login' => '登入', 'Logout' => '登出', @@ -465,7 +476,7 @@ $SLANG = array( 'MaximumFPS' => '最大帧率 FPS', 'Medium' => '中等', 'MediumBW' => '中等 B/W', - 'Message' => 'Message', // Added - 2011-06-16 + 'Message' => '消息', // Added - 2011-06-16 'MinAlarmAreaLtMax' => '最小报警区域应该小于最大区域', 'MinAlarmAreaUnset' => '您必须指定最小报警像素数量', 'MinBlobAreaLtMax' => '最小blob区必须小数最大区域', @@ -500,25 +511,25 @@ $SLANG = array( 'MinZoomSpeed' => '最小缩放速度', 'MinZoomStep' => '最小缩放步进', 'Misc' => '杂项', - 'Mode' => 'Mode', // Added - 2015-04-18 + 'Mode' => '模式', // Added - 2015-04-18 'Monitor' => '监视器', 'MonitorIds' => '监视器 Ids', 'MonitorPreset' => '监视器预设值', 'MonitorPresetIntro' => '从以下列表中选择一个合适的预设值.

请注意该方式可能覆盖您为该监视器配置的数值.

', - 'MonitorProbe' => 'Monitor Probe', // Added - 2009-03-31 - 'MonitorProbeIntro' => 'The list below shows detected analog and network cameras and whether they are already being used or available for selection.

Select the desired entry from the list below.

Please note that not all cameras may be detected and that choosing a camera here may overwrite any values you already have configured for the current monitor.

', // Added - 2009-03-31 + 'MonitorProbe' => '监视器探测', // Added - 2009-03-31 + 'MonitorProbeIntro' => '以下列表显示了检测到的模拟和网络摄像头,以及其可用状态

请从列表中选择你想要的项

请注意可能有些摄像头并没有检测到,而且选择一个摄像头可能覆盖一些你已经设置的配置。

', // Added - 2009-03-31 'Monitors' => '监视器', 'Montage' => '镜头组接', 'MontageReview' => 'Montage Review', // Added - 2018-08-30 'Month' => '月', - 'More' => 'More', // Added - 2011-06-16 - 'MotionFrameSkip' => 'Motion Frame Skip', + 'More' => '更多', // Added - 2011-06-16 + 'MotionFrameSkip' => '运动侦测跳帧', 'Move' => '移动', 'Mtg2widgrd' => '2-wide grid', // Added 2013.08.15. 'Mtg3widgrd' => '3-wide grid', // Added 2013.08.15. 'Mtg3widgrx' => '3-wide grid, scaled, enlarge on alarm', // Added 2013.08.15. 'Mtg4widgrd' => '4-wide grid', // Added 2013.08.15. - 'MtgDefault' => 'Default', // Added 2013.08.15. + 'MtgDefault' => '默认', // Added 2013.08.15. 'MustBeGe' => '必须大于等于', 'MustBeLe' => '必须小于等于', 'MustConfirmPassword' => '您必须确认密码', @@ -534,9 +545,10 @@ $SLANG = array( 'NewState' => '新状态', 'NewUser' => '新用户', 'Next' => '下一个', + 'NextMonitor' => '下一个监视器', // Added - 2020-04-09 'No' => '不', - 'NoDetectedCameras' => 'No Detected Cameras', // Added - 2009-03-31 - 'NoDetectedProfiles' => 'No Detected Profiles', // Added - 2018-08-30 + 'NoDetectedCameras' => '没有检测到摄像头', // Added - 2009-03-31 + 'NoDetectedProfiles' => '没有检测到流媒体', // Added - 2018-08-30 'NoFramesRecorded' => '该事件没有相关帧的记录', 'NoGroup' => '无组', 'NoSavedFilters' => '没有保存过滤器', @@ -548,25 +560,27 @@ $SLANG = array( 'NumPresets' => '数值预置', 'Off' => '关', 'On' => '开', - 'OnvifCredentialsIntro'=> 'Please supply user name and password for the selected camera.
If no user has been created for the camera then the user given here will be created with the given password.

', // Added - 2015-04-18 + 'OnvifCredentialsIntro'=> '请为所选摄像头提供用户名和密码。
如果还没有为这台摄像头创建过用户,那么将会使用提供的用户名和密码创建用户

', // Added - 2015-04-18 'OnvifProbe' => 'ONVIF', // Added - 2015-04-18 - 'OnvifProbeIntro' => 'The list below shows detected ONVIF cameras and whether they are already being used or available for selection.

Select the desired entry from the list below.

Please note that not all cameras may be detected and that choosing a camera here may overwrite any values you already have configured for the current monitor.

', // Added - 2015-04-18 + 'OnvifProbeIntro' => '以下列表显示了检测到的ONVIF摄像头和其可用状态。

选择一个你想要的项

请注意可能有些摄像头并没有检测到,而且选择一个摄像头可能覆盖一些你已经设置的配置。

', // Added - 2015-04-18 'OpEq' => '等于', 'OpGt' => '大于', 'OpGtEq' => '大于等于', 'OpIn' => '在集', - 'OpIs' => 'is', // Added - 2018-08-30 - 'OpIsNot' => 'is not', // Added - 2018-08-30 + 'OpIs' => '是', // Added - 2018-08-30 + 'OpIsNot' => '不是', // Added - 2018-08-30 + 'OpLike' => '包含', // Added - 2020-04-09 'OpLt' => '小于', 'OpLtEq' => '小于等于', 'OpMatches' => '匹配', 'OpNe' => '不等于', 'OpNotIn' => '未在集', + 'OpNotLike' => '未包含', // Added - 2020-04-09 'OpNotMatches' => '不匹配', 'Open' => '打开', 'OptionHelp' => '选项帮助', 'OptionRestartWarning' => '这些改动在系统运行时可以不会完全生效.\n 当你设置完毕改动后\n请确认\n您重新启动 ZoneMinder.', - 'OptionalEncoderParam' => 'Optional Encoder Parameters', // Added - 2018-08-30 + 'OptionalEncoderParam' => '编码参数(可选)', // Added - 2018-08-30 'Options' => '选项', 'OrEnterNewName' => '或输入新名词', 'Order' => '次序', @@ -579,10 +593,15 @@ $SLANG = array( 'PanRight' => '向右平移', 'PanTilt' => '平移/倾斜', 'Parameter' => '参数', + 'ParentGroup' => '父组', // Added - 2020-04-09 'Password' => '密码', 'PasswordsDifferent' => '新建密码和确认密码不一致', + 'PathToApi' => 'Api路径', // Added - 2020-04-09 + 'PathToIndex' => 'Index路径', // Added - 2020-04-09 + 'PathToZMS' => 'ZMS路径', // Added - 2020-04-09 'Paths' => '路径', 'Pause' => '暂停', + 'PauseCycle' => 'Pause Cycle', // Added - 2020-04-09 'Phone' => '电话', 'PhoneBW' => '电话 B/W', 'Pid' => 'PID', // Added - 2011-06-16 @@ -590,8 +609,9 @@ $SLANG = array( 'Pixels' => '像素', 'Play' => '播放', 'PlayAll' => '播放全部', + 'PlayCycle' => 'Play Cycle', // Added - 2020-04-09 'PleaseWait' => '请等待', - 'Plugins' => 'Plugins', + 'Plugins' => '插件', 'Point' => '点', 'PostEventImageBuffer' => '事件之后影像数', 'PreEventImageBuffer' => '时间之前影像数', @@ -599,18 +619,31 @@ $SLANG = array( 'Preset' => '预置', 'Presets' => '预置', 'Prev' => '前', - 'Probe' => 'Probe', // Added - 2009-03-31 - 'ProfileProbe' => 'Stream Probe', // Added - 2015-04-18 - 'ProfileProbeIntro' => 'The list below shows the existing stream profiles of the selected camera .

Select the desired entry from the list below.

Please note that ZoneMinder cannot configure additional profiles and that choosing a camera here may overwrite any values you already have configured for the current monitor.

', // Added - 2015-04-18 + 'PreviousMonitor' => '前一个监视器', // Added - 2020-04-09 + 'Privacy' => 'Privacy', // Added - 2020-04-09 + 'PrivacyAbout' => '关于', // Added - 2020-04-09 + 'PrivacyAboutText' => 'Since 2002, ZoneMinder has been the premier free and open-source Video Management System (VMS) solution for Linux platforms. ZoneMinder is supported by the community and is managed by those who choose to volunteer their spare time to the project. The best way to improve ZoneMinder is to get involved.', // Added - 2020-04-09 + 'PrivacyConclusionText'=> 'We are NOT collecting any image specific data from your cameras. We don’t know what your cameras are watching. This data will not be sold or used for any purpose not stated herein. By clicking accept, you agree to send us this data to help make ZoneMinder a better product. By clicking decline, you can still freely use ZoneMinder and all its features.', // Added - 2020-04-09 + 'PrivacyContact' => '联系', // Added - 2020-04-09 + 'PrivacyContactText' => 'Please contact us here for any questions regarding our privacy policy or to have your information removed.

For support, there are three primary ways to engage with the community:

Our Github forum is only for bug reporting. Please use our user forum or slack channel for all other questions or comments.

', // Added - 2020-04-09 + 'PrivacyCookies' => 'Cookies', // Added - 2020-04-09 + 'PrivacyCookiesText' => 'Whether you use a web browser or a mobile app to communicate with the ZoneMinder server, a ZMSESSID cookie is created on the client to uniquely identify a session with the ZoneMinder server. ZmCSS and zmSkin cookies are created to remember your style and skin choices.', // Added - 2020-04-09 + 'PrivacyMonitorList' => 'The following configuration parameters from each monitor are collected:
  • Id
  • Name
  • Type
  • Function
  • Width
  • Height
  • Colours
  • MaxFPS
  • AlarmMaxFPS
', // Added - 2020-04-09 + 'PrivacyTelemetry' => 'Telemetry', // Added - 2020-04-09 + 'PrivacyTelemetryList' => 'The ZoneMinder Telemetry daemon collects the following data about your system:
  • A unique identifier (UUID)
  • City based location is gathered by querying ipinfo.io. City, region, country, latitude, and longitude parameters are saved. The latitude and longitude coordinates are accurate down to the city or town level only!
  • Current time
  • Total number of monitors
  • Total number of events
  • System architecture
  • Operating system kernel, distro, and distro version
  • Version of ZoneMinder
  • Total amount of memory
  • Number of cpu cores
', // Added - 2020-04-09 + 'PrivacyTelemetryText' => 'Because ZoneMinder is open-source, anyone can install it without registering. This makes it difficult to answer questions such as: how many systems are out there, what is the largest system out there, what kind of systems are out there, or where are these systems located? Knowing the answers to these questions, helps users who ask us these questions, and it helps us set priorities based on the majority user base.', // Added - 2020-04-09 + 'Probe' => '探测', // Added - 2009-03-31 + 'ProfileProbe' => '流媒体探测', // Added - 2015-04-18 + 'ProfileProbeIntro' => '以下列表显示了所选摄像头可用的流媒体。

从列表中选择一个你想要的项

请注意ZoneMinder不能设置额外的配置并且选择摄像头可能会覆盖一些你已设置的配置。

', // Added - 2015-04-18 'Progress' => 'Progress', // Added - 2015-04-18 'Protocol' => '协议', - 'RTSPDescribe' => 'Use RTSP Response Media URL', // Added - 2018-08-30 - 'RTSPTransport' => 'RTSP Transport Protocol', // Added - 2018-08-30 + 'RTSPDescribe' => '使用 RTSP Response 媒体链接', // Added - 2018-08-30 + 'RTSPTransport' => 'RTSP传输协议', // Added - 2018-08-30 'Rate' => '速率', 'Real' => '实际', - 'RecaptchaWarning' => 'Your reCaptcha secret key is invalid. Please correct it, or reCaptcha will not work', // Added - 2018-08-30 + 'RecaptchaWarning' => '你的reCaptcha秘匙无效。 请更正,否则reCaptcha无法工作', // Added - 2018-08-30 'Record' => '记录', - 'RecordAudio' => 'Whether to store the audio stream when saving an event.', // Added - 2018-08-30 + 'RecordAudio' => '记录事件时保存音频.', // Added - 2018-08-30 'RefImageBlendPct' => '参考影像混合 %ge', 'Refresh' => '刷新', 'Remote' => '远程', @@ -626,7 +659,7 @@ $SLANG = array( 'ReplayAll' => '全部事件', 'ReplayGapless' => '无间隙事件', 'ReplaySingle' => '单一事件', - 'ReportEventAudit' => 'Audit Events Report', // Added - 2018-08-30 + 'ReportEventAudit' => '事件报表', // Added - 2018-08-30 'Reset' => '重置', 'ResetEventCounts' => '重置事件数', 'Restart' => '重启动', @@ -635,24 +668,29 @@ $SLANG = array( 'RestrictedMonitors' => '受限监视器', 'ReturnDelay' => '返回延时', 'ReturnLocation' => '返回位置', + 'RevokeAllTokens' => '撤销所有Tokens', // Added - 2020-04-09 'Rewind' => '重绕', 'RotateLeft' => '向左旋转', 'RotateRight' => '向右旋转', - 'RunLocalUpdate' => 'Please run zmupdate.pl to update', // Added - 2011-05-25 + 'RunAudit' => '审计', // Added - 2020-04-09 + 'RunEventNotification' => '事件提醒', // Added - 2020-04-09 + 'RunLocalUpdate' => '请运行zmupdate.pl来更新', // Added - 2011-05-25 'RunMode' => '运行模式', 'RunState' => '运行状态', + 'RunStats' => '状态检测', // Added - 2020-04-09 + 'RunTrigger' => '触发', // Added - 2020-04-09 'Running' => '运行', 'Save' => '保存', 'SaveAs' => '另存为', 'SaveFilter' => '存储过滤器', - 'SaveJPEGs' => 'Save JPEGs', // Added - 2018-08-30 + 'SaveJPEGs' => '保存为JPEGs', // Added - 2018-08-30 'Scale' => '比例', 'Score' => '分数', 'Secs' => '秒', 'Sectionlength' => '段长度', 'Select' => '选择', - 'SelectFormat' => 'Select Format', // Added - 2011-06-17 - 'SelectLog' => 'Select Log', // Added - 2011-06-17 + 'SelectFormat' => '选择格式', // Added - 2011-06-17 + 'SelectLog' => '选择日志', // Added - 2011-06-17 'SelectMonitors' => '选择监视器', 'SelfIntersecting' => '多边形边线不得交叉', 'Set' => '设置', @@ -661,10 +699,11 @@ $SLANG = array( 'Settings' => '设置', 'ShowFilterWindow' => '显示过滤器视窗', 'ShowTimeline' => '显示时间轴', - 'SignalCheckColour' => '型号检查颜色', - 'SignalCheckPoints' => 'Signal Check Points', // Added - 2018-08-30 + 'Shutdown' => '关机', // Added - 2020-04-09 + 'SignalCheckColour' => '信号检查颜色', + 'SignalCheckPoints' => '信号检测点数目', // Added - 2018-08-30 'Size' => '大小', - 'SkinDescription' => 'Change the default skin for this computer', // Added - 2011-01-30 + 'SkinDescription' => '改变本机默认皮肤', // Added - 2011-01-30 'Sleep' => '睡眠', 'SortAsc' => '升序', 'SortBy' => '排序', @@ -682,10 +721,10 @@ $SLANG = array( 'State' => '状态', 'Stats' => '统计', 'Status' => '状况', - 'StatusConnected' => 'Capturing', // Added - 2018-08-30 - 'StatusNotRunning' => 'Not Running', // Added - 2018-08-30 - 'StatusRunning' => 'Not Capturing', // Added - 2018-08-30 - 'StatusUnknown' => 'Unknown', // Added - 2018-08-30 + 'StatusConnected' => '正在捕获', // Added - 2018-08-30 + 'StatusNotRunning' => '未运行', // Added - 2018-08-30 + 'StatusRunning' => '未捕获', // Added - 2018-08-30 + 'StatusUnknown' => '未知', // Added - 2018-08-30 'Step' => '步进', 'StepBack' => '单步后退', 'StepForward' => '单步前进', @@ -696,14 +735,16 @@ $SLANG = array( 'Stills' => '静止', 'Stop' => '停止', 'Stopped' => '已停止', - 'StorageArea' => 'Storage Area', // Added - 2018-08-30 - 'StorageScheme' => 'Scheme', // Added - 2018-08-30 + 'StorageArea' => '存储区域', // Added - 2018-08-30 + 'StorageDoDelete' => '开始删除', // Added - 2020-04-09 + 'StorageScheme' => '存储方案', // Added - 2018-08-30 'Stream' => '流', 'StreamReplayBuffer' => '流重放影像缓冲', 'Submit' => '发送', 'System' => '系统', - 'SystemLog' => 'System Log', // Added - 2011-06-16 - 'TargetColorspace' => 'Target colorspace', // Added - 2015-04-18 + 'SystemLog' => '系统日志', // Added - 2011-06-16 + 'TZUnset' => '未设置 - 使用php.ini的配置', // Added - 2020-04-09 + 'TargetColorspace' => '色彩空间', // Added - 2015-04-18 'Tele' => 'Tele', 'Thumbnail' => '缩略图', 'Tilt' => '倾斜', @@ -711,18 +752,18 @@ $SLANG = array( 'TimeDelta' => '相对时间', 'TimeStamp' => '时间戳', 'Timeline' => '时间轴', - 'TimelineTip1' => 'Pass your mouse over the graph to view a snapshot image and event details.', // Added 2013.08.15. - 'TimelineTip2' => 'Click on the coloured sections of the graph, or the image, to view the event.', // Added 2013.08.15. + 'TimelineTip1' => '移动你的鼠标到图表上来查看快照图片和事件详情。', // Added 2013.08.15. + 'TimelineTip2' => '单击图形或图像的彩色部分来查看事件。', // Added 2013.08.15. 'TimelineTip3' => 'Click on the background to zoom in to a smaller time period based around your click.', // Added 2013.08.15. 'TimelineTip4' => 'Use the controls below to zoom out or navigate back and forward through the time range.', // Added 2013.08.15. 'Timestamp' => '时间戳', 'TimestampLabelFormat' => '时间戳标签格式', - 'TimestampLabelSize' => 'Font Size', // Added - 2018-08-30 + 'TimestampLabelSize' => '字体大小', // Added - 2018-08-30 'TimestampLabelX' => '时间戳标签 X', 'TimestampLabelY' => '时间戳标签 Y', 'Today' => '今天', 'Tools' => '工具', - 'Total' => 'Total', // Added - 2011-06-16 + 'Total' => '总', // Added - 2011-06-16 'TotalBrScore' => '总
分数', 'TrackDelay' => '轨迹延时', 'TrackMotion' => '轨迹运动', @@ -737,23 +778,24 @@ $SLANG = array( 'Update' => '更新', 'UpdateAvailable' => '有新版本的ZoneMinder.', 'UpdateNotNecessary' => '无须更新', - 'Updated' => 'Updated', // Added - 2011-06-16 - 'Upload' => 'Upload', // Added - 2011-08-23 + 'Updated' => '已更新', // Added - 2011-06-16 + 'Upload' => '上传', // Added - 2011-08-23 'UseFilter' => '使用筛选器', 'UseFilterExprsPost' => ' 筛选器 表达式', // This is used at the end of the phrase 'use N filter expressions' 'UseFilterExprsPre' => '使用 ', // This is used at the beginning of the phrase 'use N filter expressions' - 'UsedPlugins' => 'Used Plugins', + 'UsedPlugins' => '已使用的插件', 'User' => '用户', 'Username' => '用户名', 'Users' => '用户', 'V4L' => 'V4L', // Added - 2015-04-18 - 'V4LCapturesPerFrame' => 'Captures Per Frame', // Added - 2015-04-18 - 'V4LMultiBuffer' => 'Multi Buffering', // Added - 2015-04-18 + 'V4LCapturesPerFrame' => '每帧捕获', // Added - 2015-04-18 + 'V4LMultiBuffer' => '多缓冲', // Added - 2015-04-18 'Value' => '数值', 'Version' => '版本', 'VersionIgnore' => '忽略该版本', 'VersionRemindDay' => '一天内再次提醒', 'VersionRemindHour' => '一小时内再次提醒', + 'VersionRemindMonth' => '一个月内再次提醒', // Added - 2020-04-09 'VersionRemindNever' => '不再提醒新版本', 'VersionRemindWeek' => '一周内再次提醒', 'Video' => '视频', @@ -764,17 +806,18 @@ $SLANG = array( 'VideoGenParms' => '视频产生参数', 'VideoGenSucceeded' => '视频产生成功!', 'VideoSize' => '视频尺寸', - 'VideoWriter' => 'Video Writer', // Added - 2018-08-30 + 'VideoWriter' => '保存为视频', // Added - 2018-08-30 'View' => '查看', 'ViewAll' => '查看全部', 'ViewEvent' => '查看事件', + 'ViewMatches' => '查看匹配项', // Added - 2020-04-09 'ViewPaged' => '查看分页', 'Wake' => '唤醒', 'WarmupFrames' => '预热帪', 'Watch' => '观察', 'Web' => 'Web', 'WebColour' => 'Web颜色', - 'WebSiteUrl' => 'Website URL', // Added - 2018-08-30 + 'WebSiteUrl' => '网站链接', // Added - 2018-08-30 'Week' => '周', 'White' => '白', 'WhiteBalance' => '白平衡', @@ -797,7 +840,7 @@ $SLANG = array( 'ZoneMinMaxBlobs' => '最小/最大污渍区数 Blobs', 'ZoneMinMaxFiltArea' => '最小/最大过滤区域', 'ZoneMinMaxPixelThres' => '最小/最大像素阈值(0-255)', - 'ZoneMinderLog' => 'ZoneMinder Log', // Added - 2011-06-17 + 'ZoneMinderLog' => 'ZoneMinder日志', // Added - 2011-06-17 'ZoneOverloadFrames' => '忽略过载帪数', 'Zones' => '区域', 'Zoom' => '缩放', @@ -814,7 +857,7 @@ $CLANG = array( 'MonitorCount' => '%1$s %2$s', // For example '4 Monitors' (from Vlang below) 'MonitorFunction' => '监视器 %1$s 功能', 'RunningRecentVer' => '您运行的是最新版的 ZoneMinder, v%s.', - 'VersionMismatch' => 'Version mismatch, system is version %1$s, database is %2$s.', // Added - 2011-05-25 + 'VersionMismatch' => '版本不匹配, 系统版本是 %1$s, 数据库版本是 %2$s.', // Added - 2011-05-25 ); // The next section allows you to describe a series of word ending and counts used to diff --git a/web/lang/en_gb.php b/web/lang/en_gb.php index a6f2fa488..6b60c0099 100644 --- a/web/lang/en_gb.php +++ b/web/lang/en_gb.php @@ -771,6 +771,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', @@ -793,6 +794,7 @@ $SLANG = array( 'VersionRemindHour' => 'Remind again in 1 hour', 'VersionRemindNever' => 'Don\'t remind about new versions', 'VersionRemindWeek' => 'Remind again in 1 week', + 'VersionRemindMonth' => 'Remind again in 1 month', 'Version' => 'Version', 'ViewMatches' => 'View Matches', 'VideoFormat' => 'Video Format', @@ -981,6 +983,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/export.css b/web/skins/classic/css/base/export.css index 757b49057..8cccfddad 100644 --- a/web/skins/classic/css/base/export.css +++ b/web/skins/classic/css/base/export.css @@ -52,3 +52,42 @@ td.monoRow { background-color: #ffffff; } +ul.tabs { + margin: 0; + margin-bottom: -1px; + padding: 0; + float: left; + list-style: none; + height: 32px; + border-bottom: 1px solid #7f7fb2; + border-left: 1px solid #7f7fb2; + width: 100%; +} +ul.tabs li { + float: left; + margin: 0; + padding: 0; + height: 31px; + line-height: 31px; + border: 1px solid #7f7fb2; + border-left: none; + margin-bottom: -1px; + background: #fff; + overflow: hidden; + position: relative; +} +ul.tabs li a { + text-decoration: none; + color: #000; + display: block; + font-size: 1.2em; + padding: 0 20px; + outline: none; +} +ul.tabs li a:hover { + background: #ccc; +} +html ul.tabs li.active, html ul.tabs li.active a:hover { + background: #dddddd; + border-bottom: 1px solid #e0e0e0; +} diff --git a/web/skins/classic/css/base/skin.css b/web/skins/classic/css/base/skin.css index a963dc0e7..cc58073e1 100644 --- a/web/skins/classic/css/base/skin.css +++ b/web/skins/classic/css/base/skin.css @@ -445,14 +445,14 @@ th.table-th-sort span.table-th-sort-span { float: right; width: 12px; height: 12px; - background: url("/skins/classic/graphics/arrow-s-u.png") no-repeat 0 0; + background: url("../skins/classic/graphics/arrow-s-u.png") no-repeat 0 0; } th.table-th-sort-rev span.table-th-sort-span { float: right; width: 12px; height: 12px; - background: url("/skins/classic/graphics/arrow-s-d.png") no-repeat 0 0; + background: url("../skins/classic/graphics/arrow-s-d.png") no-repeat 0 0; } .table-tr-odd { 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/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/export.css b/web/skins/classic/css/classic/export.css index 757b49057..e69de29bb 100644 --- a/web/skins/classic/css/classic/export.css +++ b/web/skins/classic/css/classic/export.css @@ -1,54 +0,0 @@ -body { - font-family: Verdana, Arial, Helvetica, sans-serif; - font-size:10px; - font-weight: normal; - color: #333333; -} - -table { - border-collapse: collapse; -} - -th, td { - border: 1px solid #7f7fb2; - text-align: center; - padding: 2px 4px; -} - -a:link { - color: #7f7fb2; - text-decoration: none -} - -a:visited { - color: #7f7fb2; - text-decoration: none -} - -a:hover { - color: #666699; - text-decoration: underline -} - -img.thumb { - width: 40px; -} - -td.monoRow { - line-height: 200%; - text-align: center; - vertical-align: middle; -} - -#eventFrames tr.alarm { - background-color: #fa8072; -} - -#eventFrames tr.bulk { - background-color: #cccccc; -} - -#eventFrames tr.normal { - background-color: #ffffff; -} - 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/css/dark/export.css b/web/skins/classic/css/dark/export.css index 757b49057..e69de29bb 100644 --- a/web/skins/classic/css/dark/export.css +++ b/web/skins/classic/css/dark/export.css @@ -1,54 +0,0 @@ -body { - font-family: Verdana, Arial, Helvetica, sans-serif; - font-size:10px; - font-weight: normal; - color: #333333; -} - -table { - border-collapse: collapse; -} - -th, td { - border: 1px solid #7f7fb2; - text-align: center; - padding: 2px 4px; -} - -a:link { - color: #7f7fb2; - text-decoration: none -} - -a:visited { - color: #7f7fb2; - text-decoration: none -} - -a:hover { - color: #666699; - text-decoration: underline -} - -img.thumb { - width: 40px; -} - -td.monoRow { - line-height: 200%; - text-align: center; - vertical-align: middle; -} - -#eventFrames tr.alarm { - background-color: #fa8072; -} - -#eventFrames tr.bulk { - background-color: #cccccc; -} - -#eventFrames tr.normal { - background-color: #ffffff; -} - 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..6f1399786 100644 --- a/web/skins/classic/includes/control_functions.php +++ b/web/skins/classic/includes/control_functions.php @@ -25,15 +25,15 @@ function controlFocus($monitor, $cmds) { ?>
- - - + + +
CanAutoFocus() ) { ?> - - + + @@ -48,15 +48,15 @@ function controlZoom($monitor, $cmds) { ?>
- - - + + +
CanAutoZoom() ) { ?> - - + + @@ -70,15 +70,15 @@ function controlIris($monitor, $cmds) { ?>
- - - + + +
CanAutoIris() ) { ?> - - + + @@ -93,15 +93,15 @@ function controlWhite($monitor, $cmds) { ?>
- - - + + +
CanAutoWhite() ) { ?> - - + + @@ -122,19 +122,19 @@ function controlPanTilt($monitor, $cmds) { $hasTilt = $control->CanTilt(); $hasDiag = $hasPan && $hasTilt && $control->CanMoveDiag(); ?> - - - - + + + + - + - - - - + + + +
Id() ) ) as $row ) { + foreach ( dbFetchAll($sql, NULL, array($monitor->Id())) as $row ) { $labels[$row['Preset']] = $row['Label']; } @@ -162,7 +162,7 @@ function controlPresets($monitor, $cmds) { NumPresets(); $i++ ) { ?> - + @@ -171,7 +171,7 @@ function controlPresets($monitor, $cmds) { HasHomePreset() ) { ?> - + CanSetPresets() ) { @@ -196,22 +196,22 @@ function controlPower($monitor, $cmds) { CanWake() ) { ?> - + CanSleep() ) { ?> - + CanReset() ) { ?> - + CanReboot() ) { ?> - + diff --git a/web/skins/classic/includes/export_functions.php b/web/skins/classic/includes/export_functions.php index 02d0d86dd..5ec45b022 100644 --- a/web/skins/classic/includes/export_functions.php +++ b/web/skins/classic/includes/export_functions.php @@ -20,52 +20,18 @@ function exportHeader($title) { ?> + <?php echo $title ?> - @@ -95,35 +61,35 @@ html ul.tabs li.active, html ul.tabs li.active a:hover { Id()); $otherlinks = ''; - if( $exportFrames ) $otherlinks .= ''.translate('Frames').','; - if( $exportImages ) $otherlinks .= ''.translate('Images').','; - $otherlinks = substr($otherlinks,0,-1); + if ( $exportFrames ) $otherlinks .= ' '.translate('Frames').','; + if ( $exportImages ) $otherlinks .= ' '.translate('Images').','; + $otherlinks = substr($otherlinks, 0, -1); ?>
-

: Name()) ?> ()

+

Name()).( (!empty($otherlinks)) ? ' ('.$otherlinks.') ' : '' ) ?>

- + - + - +
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') ?>
@@ -134,21 +100,21 @@ function exportEventDetail($event, $exportFrames, $exportImages) { } function exportEventFrames($event, $exportDetail, $exportImages) { - $sql = 'SELECT *, unix_timestamp( TimeStamp ) AS UnixTimeStamp FROM Frames WHERE EventID = ? ORDER BY FrameId'; + $sql = 'SELECT *, unix_timestamp(TimeStamp) AS UnixTimeStamp FROM Frames WHERE EventID = ? ORDER BY FrameId'; $frames = dbFetchAll($sql, NULL, array($event->Id())); ob_start(); exportHeader(translate('Frames').' '.$event->Id()); $otherlinks = ''; - if ( $exportDetail ) $otherlinks .= ''.translate('Event').','; - if ( $exportImages ) $otherlinks .= ''.translate('Images').','; - $otherlinks = substr($otherlinks,0,-1); + if ( $exportDetail ) $otherlinks .= ' '.translate('Event').','; + if ( $exportImages ) $otherlinks .= ' '.translate('Images').','; + $otherlinks = substr($otherlinks, 0, -1); ?>
-

: Name()) ?> ()

+

Name()).( (!empty($otherlinks)) ? ' ('.$otherlinks.')':'') ?>

@@ -208,35 +174,54 @@ function exportEventFrames($event, $exportDetail, $exportImages) { Id()); $otherlinks = ''; - if( $exportDetail ) $otherlinks .= ''.translate('Event').','; - if( $exportFrames ) $otherlinks .= ''.translate('Frames').','; - $otherlinks = substr($otherlinks,0,-1); + if ( $exportDetail ) $otherlinks .= ' '.translate('Event').','; + if ( $exportFrames ) $otherlinks .= ' '.translate('Frames').','; + $otherlinks = substr($otherlinks, 0, -1); $filelist = array_keys($myfilelist); - sort($filelist,SORT_NUMERIC); + sort($filelist, SORT_NUMERIC); $slides = '"'.implode('","',$filelist).'"'; $listcount = count($filelist); ?> -

: Name()) ?> ()

+

Name()).( (!empty($otherlinks)) ? ' ('.$otherlinks.') ' : '' ) ?>

DefaultVideo() ) { @@ -247,12 +232,12 @@ 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(); - } + } # end if passthrough ?>
-
@@ -582,48 +567,57 @@ else if (document.layers) window.onload=start_slider; -
- +'; if ( $Event->SaveJPEGs() ) { -?> - - -<?php echo $Event->Id()?> -Id(); } ?> - -Id().'/zmEventImages.html\');return false;"> +'; + if ( ZM_WEB_LIST_THUMBS ) { + $html .= ''.$Event->Id().' +'; + } else { + $html .= $Event->Id(); + } + $html .= '
+'; } # end if has jpegs if ( $Event->DefaultVideo() ) { if ( ZM_WEB_LIST_THUMBS ) { -?> - - <?php echo $Event->Id()?> - -Id().'/'.$Event->DefaultVideo() .'">'; + $html .= ''.$Event->Id().''; + $html .= '
+ '; } } -?> -
-Id().'/zmEventDetail.html\');return false;">Detail +'; + } + if ( $exportFrames ) { + $html .= 'Frames +'; + } + $html .= ' +'; + return $html; +} // end function eventlist_html -function exportEventImagesMaster($eids) { +function exportEventImagesMaster($eids, $exportDetail, $exportFrames) { ob_start(); exportHeader(translate('Images').' Master'); ?>

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(); + $eventMonitorId[$event->Id()] = $event->MonitorId(); + $eventPath[$event->Id()] = $event->Relative_Path(); } $monitors = array_values(array_flip(array_flip($eventMonitorId))); //unique monitors and reindex the array @@ -631,11 +625,10 @@ function exportEventImagesMaster($eids) { //* if ( !empty($monitors) ) { - $tmp = dbFetchAll('SELECT Id,Name FROM Monitors WHERE Id IN ('.implode(',', $monitors).') '); + $tmp = dbFetchAll('SELECT Id, Name FROM Monitors WHERE Id IN ('.implode(',', $monitors).') '); foreach ( $tmp as $row ) { $monitorNames[$row['Id']] = $row['Name']; } } //*/ - //trigger_error(print_r($monitorNames,1)); ?>
    @@ -647,33 +640,32 @@ 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 ( $monitors as $monitor_id ) { + echo '
'; + echo '

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

'; + foreach ( $events as $event ) { + if ( $event->MonitorId() == $monitor_id ) { + echo eventlist_html($event, $exportDetail, $exportFrames); } # end if its the right monitor } # end foreach event echo '
'; } # end foreach monitor ?> -
- @@ -683,7 +675,7 @@ function exportEventImagesMaster($eids) { @@ -753,7 +763,14 @@ function loadintoIframe(iframeid, url) { return ob_get_clean(); } -function exportFileList($event, $exportDetail, $exportFrames, $exportImages, $exportVideo, $exportMisc) { +function exportFileList( + $event, + $exportDetail, + $exportFrames, + $exportImages, + $exportVideo, + $exportMisc +) { if ( !canView('Events') or !$event ) { return; @@ -775,29 +792,32 @@ function exportFileList($event, $exportDetail, $exportFrames, $exportImages, $ex if ( $exportDetail ) { $file = 'zmEventDetail.html'; - if ( !($fp = fopen($eventPath.'/'.$file, 'w')) ) { - ZM\Fatal("Can't open event detail export file '$file'"); + if ( $fp = fopen($eventPath.'/'.$file, 'w') ) { + fwrite($fp, exportEventDetail($event, $exportFrames, $exportImages)); + fclose($fp); + $exportFileList[$file] = $file; + } else { + ZM\Error("Can't open event detail export file '$file'"); } - fwrite($fp, exportEventDetail($event, $exportFrames, $exportImages)); - fclose($fp); - $exportFileList[$file] = $event->Id().'/'.$file; } + if ( $exportFrames ) { $file = 'zmEventFrames.html'; - if ( !($fp = fopen($eventPath.'/'.$file, 'w')) ) { - ZM\Fatal("Can't open event frames export file '$file'"); + if ( $fp = fopen($eventPath.'/'.$file, 'w') ) { + fwrite($fp, exportEventFrames($event, $exportDetail, $exportImages)); + fclose($fp); + $exportFileList[$file] = $file; + } else { + ZM\Error("Can't open event frames export file '$file'"); } - fwrite($fp, exportEventFrames($event, $exportDetail, $exportImages)); - fclose($fp); - $exportFileList[$file] = $event->Id().'/'.$file; } if ( $exportImages ) { $filesLeft = array(); $myfilelist = array(); foreach ( $files as $file ) { - if ( preg_match('/-(?:capture|analyse).jpg$/', $file ) ) { - $myfilelist[$file] = $exportFileList[$file] = $event->Id().'/'.$file; + if ( preg_match('/-(?:capture|analyse).jpg$/', $file) ) { + $myfilelist[$file] = $exportFileList[$file] = $file; } else { $filesLeft[$file] = $file; } @@ -806,12 +826,14 @@ function exportFileList($event, $exportDetail, $exportFrames, $exportImages, $ex // create an image slider if ( !empty($myfilelist) ) { - $file = $event->Id().'/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; + $file = 'zmEventImages.html'; + if ( $fp = fopen($eventPath.'/'.$file, 'w') ) { + fwrite($fp, exportEventImages($event, $exportDetail, $exportFrames, $myfilelist)); + fclose($fp); + $exportFileList[$file] = $file; + } else { + ZM\Error("Can't open event images export file '$file'"); + } } } # end if exportImages @@ -819,17 +841,17 @@ 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; } } $files = $filesLeft; - } # end if exportVideo + } # end if exportVideo if ( $exportMisc ) { foreach ( $files as $file ) { - $exportFileList[$file] = $event->Id().'/'.$file; + $exportFileList[$file] = $file; } $files = array(); } @@ -850,10 +872,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 +885,23 @@ 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\Error("Can't create exports dir at '$export_dir'"); + return false; + } + ZM\Logger::Debug("Successfully created dir '$export_dir'"); + chmod($export_dir, 0700); + if ( !chdir($export_dir) ) { + ZM\Error("Can't chdir to $export_dir"); + return; } - if ( !chdir($export_dir) ) - ZM\Fatal("Can't chdir to $export_dir"); $export_root = 'zmExport'; $export_listFile = 'zmFileList.txt'; @@ -889,40 +911,37 @@ function exportEvents( if ( !is_array($eids) ) { $eids = array($eids); } - 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); + $exportFileList = array_merge($exportFileList, $event_exportFileList); foreach ( $event_exportFileList as $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); - } - } + #if ( preg_match('/\.html$/', $file) ) + #continue; + $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_exportFile + } # 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"); - //if ( !symlink(ZM_PATH_WEB.'/'.ZM_SKIN_PATH.'/js/video.js', $export_dir.'/video.js') ) - //Error("Failed linking video.js"); + if ( !symlink(ZM_PATH_WEB.'/'.ZM_SKIN_PATH.'/js/jquery.js', $export_dir.'/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"); - $html_eventMaster_file = 'zmEventImagesMaster_'.date('Ymd_His'). '.html'; - $html_eventMaster_path = $export_dir.'/'.$html_eventMaster_file; + $html_eventMaster_file = 'zmEventImagesMaster.html'; + $html_eventMaster_path = $export_dir.'/'.$html_eventMaster_file; - if ( ($fp = fopen($html_eventMaster_path, 'w')) ) { - fwrite($fp, exportEventImagesMaster($eids)); - fclose($fp); - $exportFileList[] = $html_eventMaster_file; - } else { - ZM\Error("Can't open event images export file '$html_eventMaster_path'"); - } + if ( ($fp = fopen($html_eventMaster_path, 'w')) ) { + fwrite($fp, exportEventImagesMaster($eids, $exportDetail, $exportFrames)); + fclose($fp); + $exportFileList[] = $html_eventMaster_file; + } else { + ZM\Error("Can't open event images export file '$html_eventMaster_path'"); } $listFile = $export_dir.'/'.$export_listFile; @@ -940,6 +959,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 +968,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,19 +984,19 @@ 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; } - //clean up temporary files + // clean up temporary files if ( !empty($html_eventMaster) ) { unlink($monitorPath.'/'.$html_eventMaster); } return '?view=archive%26type='.$exportFormat.'%26connkey='.$connkey; -} +} // end function exportEvents diff --git a/web/skins/classic/includes/functions.php b/web/skins/classic/includes/functions.php index 5abd519cf..e0d919325 100644 --- a/web/skins/classic/includes/functions.php +++ b/web/skins/classic/includes/functions.php @@ -1,6 +1,6 @@ + - +