Merge branch 'release-1.34' into fix_zms

This commit is contained in:
Isaac Connor 2020-04-26 18:19:30 -04:00
commit ae9a5766f5
184 changed files with 5710 additions and 4549 deletions

12
.github/FUNDING.yml vendored Normal file
View File

@ -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']

View File

@ -13,7 +13,7 @@ addons:
ssh_known_hosts: zmrepo.zoneminder.com ssh_known_hosts: zmrepo.zoneminder.com
apt: apt:
sources: sources:
- sourceline: ppa:iconnor/zoneminder - sourceline: ppa:iconnor/zoneminder-master
- key_url: http://keyserver.ubuntu.com:11371/pks/lookup?op=get&search=0x4D0BF748776FFB04 - key_url: http://keyserver.ubuntu.com:11371/pks/lookup?op=get&search=0x4D0BF748776FFB04
packages: packages:
- gdebi - gdebi
@ -33,24 +33,23 @@ install:
env: env:
- SMPFLAGS=-j4 OS=el DIST=7 - SMPFLAGS=-j4 OS=el DIST=7
- SMPFLAGS=-j4 OS=el DIST=8 - SMPFLAGS=-j4 OS=el DIST=8 DOCKER_REPO=knnniggett/packpack
- SMPFLAGS=-j4 OS=fedora DIST=29 DOCKER_REPO=knnniggett/packpack
- SMPFLAGS=-j4 OS=fedora DIST=30 - SMPFLAGS=-j4 OS=fedora DIST=30
- SMPFLAGS=-j4 OS=ubuntu DIST=trusty DOCKER_REPO=iconzm/packpack USE_SFTP=yes - SMPFLAGS=-j4 OS=fedora DIST=31
- SMPFLAGS=-j4 OS=ubuntu DIST=xenial DOCKER_REPO=iconzm/packpack USE_SFTP=yes - SMPFLAGS=-j4 OS=ubuntu DIST=trusty DOCKER_REPO=iconzm/packpack
- SMPFLAGS=-j4 OS=ubuntu DIST=bionic DOCKER_REPO=iconzm/packpack USE_SFTP=yes - SMPFLAGS=-j4 OS=ubuntu DIST=xenial DOCKER_REPO=iconzm/packpack
- SMPFLAGS=-j4 OS=ubuntu DIST=disco DOCKER_REPO=iconzm/packpack USE_SFTP=yes - SMPFLAGS=-j4 OS=ubuntu DIST=bionic DOCKER_REPO=iconzm/packpack
- SMPFLAGS=-j4 OS=ubuntu DIST=eoan DOCKER_REPO=iconzm/packpack USE_SFTP=yes - SMPFLAGS=-j4 OS=ubuntu DIST=disco DOCKER_REPO=iconzm/packpack
- SMPFLAGS=-j4 OS=debian DIST=jessie DOCKER_REPO=iconzm/packpack USE_SFTP=yes - SMPFLAGS=-j4 OS=ubuntu DIST=eoan DOCKER_REPO=iconzm/packpack
- SMPFLAGS=-j4 OS=debian DIST=stretch DOCKER_REPO=iconzm/packpack USE_SFTP=yes - SMPFLAGS=-j4 OS=debian DIST=jessie DOCKER_REPO=iconzm/packpack
- SMPFLAGS=-j4 OS=debian DIST=buster DOCKER_REPO=iconzm/packpack USE_SFTP=yes - 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=trusty ARCH=i386
- SMPFLAGS=-j4 OS=ubuntu DIST=xenial 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=ubuntu DIST=disco ARCH=i386
- SMPFLAGS=-j4 OS=debian DIST=buster ARCH=i386 - SMPFLAGS=-j4 OS=debian DIST=buster ARCH=i386
- SMPFLAGS=-j4 OS=debian DIST=stretch 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: compiler:
- gcc - gcc
@ -58,12 +57,6 @@ services:
- mysql - mysql
- docker - 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: script:
- utils/packpack/startpackpack.sh - utils/packpack/startpackpack.sh

View File

@ -138,9 +138,9 @@ set(ZM_TMPDIR "/var/tmp/zm" CACHE PATH
"Location of temporary files, default: /tmp/zm") "Location of temporary files, default: /tmp/zm")
set(ZM_LOGDIR "/var/log/zm" CACHE PATH set(ZM_LOGDIR "/var/log/zm" CACHE PATH
"Location of generated log files, default: /var/log/zm") "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: <prefix>/${CMAKE_INSTALL_DATADIR}/zoneminder/www") "Location of the web files, default: <prefix>/${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: <prefix>/${CMAKE_INSTALL_LIBEXECDIR}/zoneminder/cgi-bin") "Location of the cgi-bin files, default: <prefix>/${CMAKE_INSTALL_LIBEXECDIR}/zoneminder/cgi-bin")
set(ZM_CACHEDIR "/var/cache/zoneminder" CACHE PATH set(ZM_CACHEDIR "/var/cache/zoneminder" CACHE PATH
"Location of the web server cache busting files, default: /var/cache/zoneminder") "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") "ZoneMinder requires jpeg but it was not found on your system")
endif(JPEG_FOUND) 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 # OpenSSL
find_package(OpenSSL) if(NOT HAVE_LIBGNUTLS OR NOT HAVE_LIBJWT)
if(OPENSSL_FOUND) find_package(OpenSSL)
set(HAVE_LIBOPENSSL 1) if(OPENSSL_FOUND)
set(HAVE_LIBCRYPTO 1) set(HAVE_LIBOPENSSL 1)
list(APPEND ZM_BIN_LIBS "${OPENSSL_LIBRARIES}") set(HAVE_LIBCRYPTO 1)
include_directories("${OPENSSL_INCLUDE_DIR}") list(APPEND ZM_BIN_LIBS "${OPENSSL_LIBRARIES}")
set(CMAKE_REQUIRED_INCLUDES "${OPENSSL_INCLUDE_DIR}") include_directories("${OPENSSL_INCLUDE_DIR}")
check_include_file("openssl/md5.h" HAVE_OPENSSL_MD5_H) set(CMAKE_REQUIRED_INCLUDES "${OPENSSL_INCLUDE_DIR}")
set(optlibsfound "${optlibsfound} OpenSSL") check_include_file("openssl/md5.h" HAVE_OPENSSL_MD5_H)
else(OPENSSL_FOUND) set(optlibsfound "${optlibsfound} OpenSSL")
set(optlibsnotfound "${optlibsnotfound} OpenSSL") else(OPENSSL_FOUND)
endif(OPENSSL_FOUND) set(optlibsnotfound "${optlibsnotfound} OpenSSL")
endif(OPENSSL_FOUND)
endif(NOT HAVE_LIBGNUTLS OR NOT HAVE_LIBJWT)
# pthread (using find_library and find_path) # pthread (using find_library and find_path)
find_library(PTHREAD_LIBRARIES pthread) find_library(PTHREAD_LIBRARIES pthread)
@ -416,28 +447,6 @@ else(GCRYPT_LIBRARIES)
set(optlibsnotfound "${optlibsnotfound} GCrypt") set(optlibsnotfound "${optlibsnotfound} GCrypt")
endif(GCRYPT_LIBRARIES) 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) # mysqlclient (using find_library and find_path)
find_library(MYSQLCLIENT_LIBRARIES mysqlclient PATH_SUFFIXES mysql) find_library(MYSQLCLIENT_LIBRARIES mysqlclient PATH_SUFFIXES mysql)
if(MYSQLCLIENT_LIBRARIES) 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" "unsigned char *MD5(const unsigned char *d, size_t n, unsigned char *md)" "NULL" "openssl/md5.h"
HAVE_MD5_OPENSSL) HAVE_MD5_OPENSSL)
endif(HAVE_OPENSSL_MD5_H) 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) if(HAVE_GNUTLS_GNUTLS_H)
set(CMAKE_REQUIRED_LIBRARIES "${GNUTLS_LIBRARIES}") set(CMAKE_REQUIRED_LIBRARIES "${GNUTLS_LIBRARIES}")
set(CMAKE_REQUIRED_INCLUDES "${GNUTLS_INCLUDE_DIR}") 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" "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) HAVE_DECL_GNUTLS_FINGERPRINT)
endif(HAVE_GNUTLS_GNUTLS_H) endif(HAVE_GNUTLS_GNUTLS_H)
if(HAVE_MD5_OPENSSL OR HAVE_MD5_GNUTLS)
if(HAVE_MD5_OPENSSL)
set(HAVE_DECL_MD5 1) 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 message(AUTHOR_WARNING
"ZoneMinder requires a working MD5 function for hashed authenication but "ZoneMinder requires a working MD5 function for hashed authentication but
none were found - hashed authenication will not be available") none were found - hashed authentication will not be available")
endif(HAVE_MD5_OPENSSL OR HAVE_MD5_GNUTLS) 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. # 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 # 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) 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 # Run ZM configuration generator
message(STATUS "Running ZoneMinder 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) if(zmconfgen_result EQUAL 0)
message(STATUS message(STATUS
"ZoneMinder configuration generator completed successfully") "ZoneMinder configuration generator completed successfully")

View File

@ -1,9 +1,10 @@
ZoneMinder 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) [![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 All documentation for ZoneMinder is now online at https://zoneminder.readthedocs.org
@ -21,12 +22,6 @@ https://github.com/ZoneMinder/zmdockerfiles
## Installation Methods ## 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 ### Install from a Package Repository
This is the recommended method to install ZoneMinder onto your system. ZoneMinder packages are maintained for the following distros: 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. 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 a ZoneMinder Package ###
Building ZoneMinder into a package is not any harder than building from source. As a matter of fact, if you have successfully built ZoneMinder from source in the past, then you may find these steps to be easier. Building ZoneMinder into a package is not any harder than building from source. As a matter of fact, if you have successfully built ZoneMinder from source in the past, then you may find these steps to be easier.

View File

@ -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)

View File

@ -434,6 +434,7 @@ DROP TABLE IF EXISTS `Monitors`;
CREATE TABLE `Monitors` ( CREATE TABLE `Monitors` (
`Id` int(10) unsigned NOT NULL auto_increment, `Id` int(10) unsigned NOT NULL auto_increment,
`Name` varchar(64) NOT NULL default '', `Name` varchar(64) NOT NULL default '',
`Notes` TEXT,
`ServerId` int(10) unsigned, `ServerId` int(10) unsigned,
`StorageId` smallint(5) unsigned default 0, `StorageId` smallint(5) unsigned default 0,
`Type` enum('Local','Remote','File','Ffmpeg','Libvlc','cURL','WebSite','NVSocket') NOT NULL default 'Local', `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-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,'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-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,'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,'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); 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','<ip-address>',554,'/mpeg4/media.amp','/trackID=',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); INSERT into MonitorPresets VALUES (NULL,'Axis IP, mpeg4, RTP/RTSP','Remote','rtsp',0,255,'rtsp','rtpRtsp','<ip-address>',554,'/mpeg4/media.amp','/trackID=',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT into MonitorPresets VALUES (NULL,'Axis IP, mpeg4, RTP/RTSP/HTTP','Remote',NULL,NULL,NULL,'rtsp','rtpRtspHttp','<ip-address>',554,'/mpeg4/media.amp','/trackID=',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100); INSERT into MonitorPresets VALUES (NULL,'Axis IP, mpeg4, RTP/RTSP/HTTP','Remote',NULL,NULL,NULL,'rtsp','rtpRtspHttp','<ip-address>',554,'/mpeg4/media.amp','/trackID=',NULL,NULL,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'D-link DCS-930L, 640x480, mjpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/mjpeg.cgi',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100); INSERT INTO MonitorPresets VALUES (NULL,'D-link DCS-930L, 640x480, mjpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/mjpeg.cgi',NULL,640,480,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'D-Link DCS-5020L, 640x480, mjpeg','Remote','http',0,0,'http','simple','<username>:<pwd>@<ip-address>','80','/video.cgi',NULL,640,480,0,NULL,1,'34',NULL,'<username>:<pwd>@<ip-address>',100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP, 320x240, mpjpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/nphMotionJpeg?Resolution=320x240&Quality=Standard',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100); INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP, 320x240, mpjpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/nphMotionJpeg?Resolution=320x240&Quality=Standard',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP, 320x240, jpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/SnapshotJPEG?Resolution=320x240&Quality=Standard',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100); INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP, 320x240, jpeg','Remote','http',0,0,'http','simple','<ip-address>',80,'/SnapshotJPEG?Resolution=320x240&Quality=Standard',NULL,320,240,3,NULL,0,NULL,NULL,NULL,100,100);
INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP, 320x240, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','<ip-address>',80,'/SnapshotJPEG?Resolution=320x240&Quality=Standard',NULL,320,240,3,5.0,0,NULL,NULL,NULL,100,100); INSERT INTO MonitorPresets VALUES (NULL,'Panasonic IP, 320x240, jpeg, max 5 FPS','Remote','http',0,0,'http','simple','<ip-address>',80,'/SnapshotJPEG?Resolution=320x240&Quality=Standard',NULL,320,240,3,5.0,0,NULL,NULL,NULL,100,100);

12
db/zm_update-1.33.16.sql Normal file
View File

@ -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;

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

@ -0,0 +1,5 @@
--
-- This updates a 1.33.16 database to 1.34.0
--
-- No changes required
--

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

@ -0,0 +1,5 @@
--
-- This updates a 1.34.0 database to 1.34.1
--
-- No changes required
--

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

@ -0,0 +1,5 @@
--
-- This updates a 1.34.1 database to 1.34.2
--
-- No changes required
--

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

@ -0,0 +1,5 @@
--
-- This updates a 1.34.2 database to 1.34.3
--
-- No changes required
--

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

@ -0,0 +1,5 @@
--
-- This updates a 1.34.3 database to 1.34.4
--
-- No changes required
--

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

@ -0,0 +1,5 @@
--
-- This updates a 1.34.4 database to 1.34.5
--
-- No changes required
--

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

@ -0,0 +1,5 @@
--
-- This updates a 1.34.5 database to 1.34.6
--
-- No changes required
--

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

@ -0,0 +1,5 @@
--
-- This updates a 1.34.6 database to 1.34.7
--
-- No changes required
--

View File

@ -22,14 +22,10 @@ What's New
switching between httpd <-> nginx requires manaully changing ownership of switching between httpd <-> nginx requires manaully changing ownership of
all event folders and the php session folder after the change. all event folders and the php session folder after the change.
4. If you have installed ZoneMinder from the FedBerry repositories, this build 4. The timezone must now be set from the ZoneMinder web console. See the
of ZoneMinder has support for Raspberry Pi hardware acceleration when using appropriate README, mentioned in the next step, for details.
ffmpeg. Unforunately, there is a problem with the same hardware acceleration
when using libvlc. Consequently, libvlc support in this build of ZoneMinder
has been disabled until the problem is resolved. See the following bug
report for details: https://trac.videolan.org/vlc/ticket/18594
5. Continue on to the next README that corresponds to the chosen webserver: 6. Continue on to the next README that corresponds to your chosen webserver:
README.httpd - Follow these steps when using Apache README.httpd - Follow these steps when using Apache
README.nginx - Follow these steps when using Nginx README.nginx - Follow these steps when using Nginx

View File

@ -36,20 +36,17 @@ NOTE: EL7 users should replace "dnf" with "yum" in the instructions below.
sudo chown root:apache *.conf sudo chown root:apache *.conf
sudo chmod 640 *.conf sudo chmod 640 *.conf
4. Edit /etc/php.ini, uncomment the date.timezone line, and add your local 4. Manually setting the timezone in /etc/php.ini is deprecated.
timezone. PHP will complain loudly if this is not set, or if it is set
incorrectly, and these complaints will show up in the zoneminder logging
system as errors.
If you are not sure of the proper timezone specification to use, look at Instead, navigate to Options -> System from the ZoneMinder web console.
http://php.net/date.timezone 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 5. Disable SELinux
We currently do not have the resources to create and maintain an accurate SELinux must be disabled or put into permissive mode. This is not optional!
SELinux policy for ZoneMinder on Fedora. We will gladly accept pull
reqeusts from anyone who wishes to do the work. In the meantime, SELinux
will need to be disabled or put into permissive mode.
To immediately disbale SELinux for the current seesion, issue the following To immediately disbale SELinux for the current seesion, issue the following
from the command line: from the command line:
@ -166,3 +163,11 @@ Upgrades
sudo systemctl restart httpd sudo systemctl restart httpd
sudo systemctl start zoneminder 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.

View File

@ -34,13 +34,13 @@ New installs
sudo chown root:nginx *.conf sudo chown root:nginx *.conf
sudo chmod 640 *.conf sudo chmod 640 *.conf
4. Edit /etc/php.ini, uncomment the date.timezone line, and add your local 4. Manually setting the timezone in /etc/php.ini is deprecated.
timezone. PHP will complain loudly if this is not set, or if it is set
incorrectly, and these complaints will show up in the zoneminder logging
system as errors.
If you are not sure of the proper timezone specification to use, look at Instead, navigate to Options -> System from the ZoneMinder web console.
http://php.net/date.timezone 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 5. Disable SELinux
@ -169,3 +169,11 @@ Upgrades
sudo systemctl restart php-fpm sudo systemctl restart php-fpm
sudo systemctl start zoneminder 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.

View File

@ -14,16 +14,21 @@
# This will tell zoneminder's cmake process we are building against a known distro # This will tell zoneminder's cmake process we are building against a known distro
%global zmtargetdistro %{?rhel:el%{rhel}}%{!?rhel:fc%{fedora}} %global zmtargetdistro %{?rhel:el%{rhel}}%{!?rhel:fc%{fedora}}
# Fedora >= 25 needs apcu backwards compatibility module # Fedora needs apcu backwards compatibility module
%if 0%{?fedora} >= 25 %if 0%{?fedora}
%global with_apcu_bc 1 %global with_apcu_bc 1
%endif %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 # The default for everything but el7 these days
%global _hardened_build 1 %global _hardened_build 1
Name: zoneminder Name: zoneminder
Version: 1.33.14 Version: 1.34.10
Release: 1%{?dist} Release: 1%{?dist}
Summary: A camera monitoring and analysis tool Summary: A camera monitoring and analysis tool
Group: System Environment/Daemons 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-mysqli
Requires: php-common Requires: php-common
Requires: php-gd Requires: php-gd
%{?fedora:Requires: php-json} %{?with_php_json:Requires: php-json}
Requires: php-pecl-apcu Requires: php-pecl-apcu
%{?with_apcu_bc:Requires: php-pecl-apcu-bc} %{?with_apcu_bc:Requires: php-pecl-apcu-bc}
Requires: cambozola Requires: cambozola
@ -411,26 +416,26 @@ EOF
%dir %attr(755,nginx,nginx) %{_localstatedir}/spool/zoneminder-upload %dir %attr(755,nginx,nginx) %{_localstatedir}/spool/zoneminder-upload
%changelog %changelog
* Sun Aug 11 2019 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.33.14-1 * Tue Feb 04 2020 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.34.2-1
- Bump to 1.33.13 Development - 1.34.2 Release
* Sun Jul 07 2019 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.33.12-1 * Fri Jan 31 2020 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.34.1-1
- Bump to 1.33.12 Development - 1.34.1 Release
* Sun Jun 23 2019 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.33.9-1 * Sat Jan 18 2020 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.34.0-1
- Bump to 1.33.9 Development - 1.34.0 Release
* Tue Apr 30 2019 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.33.8-1 * Tue Dec 17 2019 Leigh Scott <leigh123linux@gmail.com> - 1.32.3-5
- Bump to 1.33.8 Development - Mass rebuild for x264
* Sun Apr 07 2019 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.33.6-1 * Wed Aug 07 2019 Leigh Scott <leigh123linux@gmail.com> - 1.32.3-4
- Bump to 1.33.6 Development - Rebuild for new ffmpeg version
* Sat Mar 30 2019 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.33.4-1 * Tue Mar 12 2019 Sérgio Basto <sergio@serjux.com> - 1.32.3-3
- Bump to 1.33.4 Development - Mass rebuild for x264
* Tue Dec 11 2018 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.33.0-1 * Tue Mar 05 2019 RPM Fusion Release Engineering <leigh123linux@gmail.com> - 1.32.3-2
- Bump to 1.33.0 Development - Rebuilt for https://fedoraproject.org/wiki/Fedora_30_Mass_Rebuild
* Sat Dec 08 2018 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.32.3-1 * Sat Dec 08 2018 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.32.3-1
- 1.32.3 Release - 1.32.3 Release

View File

@ -6,6 +6,7 @@ ScriptAlias /zm/cgi-bin "/usr/lib/zoneminder/cgi-bin"
Require all granted Require all granted
</Directory> </Directory>
# Order matters. This alias must come first. # Order matters. This alias must come first.
Alias /zm/cache /var/cache/zoneminder/cache Alias /zm/cache /var/cache/zoneminder/cache
<Directory /var/cache/zoneminder/cache> <Directory /var/cache/zoneminder/cache>

View File

@ -61,7 +61,7 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}
,libjson-maybexs-perl ,libjson-maybexs-perl
,libsys-mmap-perl [!hurd-any] ,libsys-mmap-perl [!hurd-any]
,liburi-encode-perl ,liburi-encode-perl
,libwww-perl ,libwww-perl, liburi-perl
,libdata-dump-perl ,libdata-dump-perl
,libdatetime-perl ,libdatetime-perl
,libclass-std-fast-perl ,libclass-std-fast-perl
@ -74,7 +74,7 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends}
,libfile-slurp-perl ,libfile-slurp-perl
,mysql-client | mariadb-client | virtual-mysql-client ,mysql-client | mariadb-client | virtual-mysql-client
,perl-modules ,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 ,policykit-1
,rsyslog | system-log-daemon ,rsyslog | system-log-daemon
,zip ,zip

View File

@ -127,7 +127,7 @@ If you are using the old credentials mechanism present in v1.0, then the credent
Key lifetime (v2.0) 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) Understanding access/refresh tokens (v2.0)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -482,6 +482,7 @@ Create a Zone
&Zone[Units]=Percent\ &Zone[Units]=Percent\
&Zone[NumCoords]=4\ &Zone[NumCoords]=4\
&Zone[Coords]=0,0 639,0 639,479 0,479\ &Zone[Coords]=0,0 639,0 639,479 0,479\
&Zone[Area]=307200\
&Zone[AlarmRGB]=16711680\ &Zone[AlarmRGB]=16711680\
&Zone[CheckMethod]=Blobs\ &Zone[CheckMethod]=Blobs\
&Zone[MinPixelThreshold]=25\ &Zone[MinPixelThreshold]=25\

View File

@ -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. Update repo and upgrade.

View File

@ -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 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:: .. 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) 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) * 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 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:: .. todo::
add GPU docs add GPU docs

View File

@ -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. 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_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. 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_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 <https://github.com/pliablepixels/zmeventserver>`__ 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 <https://github.com/pliablepixels/zmeventserver>`__ 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 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. 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_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. 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. 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). 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://<proxy host>:<proxy port>/`` UPDATE_CHECK_PROXY - If you use a proxy to access the internet then ZoneMinder needs to know so it can access zoneminder.com to check for updates. If you do use a proxy enter the full proxy url here in the form of ``http://<proxy host>:<proxy port>/``.
SHM_KEY - ZoneMinder uses shared memory to speed up communication between modules. To identify the right area to use shared memory keys are used. This option controls what the base key is, each monitor will have it's Id or'ed with this to get the actual key used. You will not normally need to change this value unless it clashes with another instance of ZoneMinder on the same machine. Only the first four hex digits are used, the lower four will be masked out and ignored. SHM_KEY - ZoneMinder uses shared memory to speed up communication between modules. To identify the right area to use shared memory keys are used. This option controls what the base key is, each monitor will have it's Id or'ed with this to get the actual key used. You will not normally need to change this value unless it clashes with another instance of ZoneMinder on the same machine. Only the first four hex digits are used, the lower four will be masked out and ignored.

View File

@ -28,3 +28,4 @@ zmtelemetry\[[[:digit:]]+\]: INF \[Telemetry data uploaded successfully.\]$
zmtelemetry\[[[:digit:]]+\]: INF \[Sending data to ZoneMinder Telemetry server.\]$ zmtelemetry\[[[:digit:]]+\]: INF \[Sending data to ZoneMinder Telemetry server.\]$
zmtelemetry\[[[:digit:]]+\]: INF \[Collec?ting data to send 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:]]+"\]$ web_php\[[[:digit:]]+\]: INF \[Login successful for user "[[:alnum:]]+"\]$
zmeventnotification\[[[:digit:]]+\]: INF

View File

@ -76,7 +76,7 @@ my %soap_version_of :ATTR(:default<('1.1')>);
sub service { sub service {
my ($self, $serviceName, $attr) = @_; 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. # Please note that the Std::Class::Fast docs say not to use ident.
$services_of{ident $self}{$serviceName}{$attr}; $services_of{ident $self}{$serviceName}{$attr};
} }
@ -114,18 +114,18 @@ sub get_service_urls {
my $result = $self->service('device', 'ep')->GetServices( { my $result = $self->service('device', 'ep')->GetServices( {
IncludeCapability => 'true', # boolean IncludeCapability => 'true', # boolean
},, }
); );
if ( $result ) { if ( $result ) {
foreach my $svc ( @{ $result->get_Service() } ) { foreach my $svc ( @{ $result->get_Service() } ) {
my $short_name = $namespace_map{$svc->get_Namespace()}; my $short_name = $namespace_map{$svc->get_Namespace()};
my $url_svc = $svc->get_XAddr()->get_value(); my $url_svc = $svc->get_XAddr()->get_value();
if(defined $short_name && defined $url_svc) { if ( defined $short_name && defined $url_svc ) {
# print "Got $short_name service\n"; #print "Got $short_name service\n";
$self->set_service($short_name, 'url', $url_svc); $self->set_service($short_name, 'url', $url_svc);
} }
} }
# } else { #} else {
#print "No results from GetServices: $result\n"; #print "No results from GetServices: $result\n";
} }
@ -142,14 +142,14 @@ sub get_service_urls {
if ( my $function = $capabilities->can( "get_$capability" ) ) { if ( my $function = $capabilities->can( "get_$capability" ) ) {
my $Services = $function->( $capabilities ); my $Services = $function->( $capabilities );
if ( !$Services ) { if ( !$Services ) {
print "Nothing returned ffrom get_$capability\n"; #print "Nothing returned from get_$capability\n";
} else { } else {
foreach my $svc ( @{ $Services } ) { foreach my $svc ( @{ $Services } ) {
# The capability versions don't have a namespace, so just lowercase them. # The capability versions don't have a namespace, so just lowercase them.
my $short_name = lc $capability; my $short_name = lc $capability;
my $url_svc = $svc->get_XAddr()->get_value(); my $url_svc = $svc->get_XAddr()->get_value();
if( defined $url_svc) { if ( defined $url_svc ) {
# print "Got $short_name service\n"; #print "Got $short_name service\n";
$self->set_service($short_name, 'url', $url_svc); $self->set_service($short_name, 'url', $url_svc);
} }
} # end foreach svr } # end foreach svr
@ -202,10 +202,10 @@ sub BUILD {
# deserializer_args => { strict => 0 } # 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 # Can't, don't have credentials yet
#$self->get_service_urls(); # $self->get_service_urls();
} }
sub get_users { sub get_users {
@ -260,7 +260,7 @@ sub set_credentials {
sub create_services { sub create_services {
my ($self) = @_; my ($self) = @_;
#$self->get_service_urls(); $self->get_service_urls();
if ( defined $self->service('media', 'url') ) { if ( defined $self->service('media', 'url') ) {
$self->set_service('media', 'ep', ONVIF::Media::Interfaces::Media::MediaPort->new({ $self->set_service('media', 'ep', ONVIF::Media::Interfaces::Media::MediaPort->new({

View File

@ -785,7 +785,7 @@ our @options = (
}, },
{ {
name => 'ZM_TIMEZONE', name => 'ZM_TIMEZONE',
default => 'UTC', default => '',
description => 'The timezone that php should use.', description => 'The timezone that php should use.',
help => q` help => q`
This should be set equal to the system timezone of the mysql server`, This should be set equal to the system timezone of the mysql server`,
@ -1127,7 +1127,7 @@ our @options = (
}, },
{ {
name => 'ZM_LOG_LEVEL_FILE', name => 'ZM_LOG_LEVEL_FILE',
default => '-5', default => '1',
description => 'Save logging output to component files', description => 'Save logging output to component files',
help => q` help => q`
ZoneMinder logging is now more integrated between ZoneMinder logging is now more integrated between
@ -1312,7 +1312,7 @@ our @options = (
}, },
{ {
name => 'ZM_LOG_DEBUG_FILE', name => 'ZM_LOG_DEBUG_FILE',
default => '@ZM_LOGDIR@/zm_debug.log+', default => '',
description => 'Where extra debug is output to', description => 'Where extra debug is output to',
help => q` help => q`
This option allows you to specify a different target for debug This option allows you to specify a different target for debug
@ -2468,7 +2468,7 @@ our @options = (
}, },
{ {
name => 'ZM_WATCH_MAX_DELAY', name => 'ZM_WATCH_MAX_DELAY',
default => '5', default => '45',
description => 'The maximum delay allowed since the last captured image', description => 'The maximum delay allowed since the last captured image',
help => q` help => q`
The zmwatch daemon checks the image capture performance of the The zmwatch daemon checks the image capture performance of the

View File

@ -1,16 +1,6 @@
# ========================================================================== # ==========================================================================
# #
# ZoneMinder Acrest HTTP API Control Protocol Module, 20180214, Rev 3.0 # ZoneMinder Amcrest HTTP API Control Protocol Module
#
# 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.
# #
# This program is free software; you can redistribute it and/or # This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License # 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::Base;
require ZoneMinder::Control; require ZoneMinder::Control;
require LWP::UserAgent;
use URI;
our @ISA = qw(ZoneMinder::Control); our @ISA = qw(ZoneMinder::Control);
@ -50,130 +42,130 @@ our @ISA = qw(ZoneMinder::Control);
use ZoneMinder::Logger qw(:all); use ZoneMinder::Logger qw(:all);
use ZoneMinder::Config qw(:all); use ZoneMinder::Config qw(:all);
sub new sub new {
{ my $class = shift;
my $class = shift; my $id = shift;
my $id = shift; my $self = ZoneMinder::Control->new($id);
my $self = ZoneMinder::Control->new( $id ); bless($self, $class);
bless( $self, $class ); return $self;
srand( time() );
return $self;
} }
our $AUTOLOAD; sub open {
my $self = shift;
sub AUTOLOAD $self->loadMonitor();
{ if ( $self->{Monitor}->{ControlAddress} !~ /^\w+:\/\// ) {
my $self = shift; # Has no scheme at the beginning, so won't parse as a URI
my $class = ref($self) || croak( "$self not object" ); $self->{Monitor}->{ControlAddress} = 'http://'.$self->{Monitor}->{ControlAddress};
my $name = $AUTOLOAD; }
$name =~ s/.*://; my $uri = URI->new($self->{Monitor}->{ControlAddress});
Debug( "Received command: $name" );
if ( exists($self->{$name}) )
{
return( $self->{$name} );
}
Fatal( "Can't access $name member of object of class $class" );
}
sub open $self->{ua} = LWP::UserAgent->new;
{ $self->{ua}->agent('ZoneMinder Control Agent/'.ZoneMinder::Base::ZM_VERSION);
my $self = shift; 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'; $self->{state} = 'open';
} return;
}
sub initUA if ( $res->status_line() eq '401 Unauthorized' ) {
{
my $self = shift;
my $user = undef;
my $password = undef;
my $address = undef;
if ( $self->{Monitor}->{ControlAddress} =~ /(.*):(.*)@(.*)/ ) my $headers = $res->headers();
{ foreach my $k ( keys %$headers ) {
$user = $1; Debug("Initial Header $k => $$headers{$k}");
$password = $2;
$address = $3;
} }
use LWP::UserAgent; if ( $$headers{'www-authenticate'} ) {
$self->{ua} = LWP::UserAgent->new; my ( $auth, $tokens ) = $$headers{'www-authenticate'} =~ /^(\w+)\s+(.*)$/;
$self->{ua}->credentials("$address", "Login to " . $self->{Monitor}->{ControlDevice}, "$user", "$password"); if ( $tokens =~ /realm="([^"]+)"/i ) {
$self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION ); 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 sub close {
{ my $self = shift;
my $self = shift; $self->{state} = 'closed';
$self->{ua} = undef;
} }
sub close sub sendCmd {
{ my $self = shift;
my $self = shift; my $cmd = shift;
$self->{state} = 'closed'; my $result = undef;
}
sub printMsg $self->printMsg($cmd, 'Tx');
{
my $self = shift;
my $msg = shift;
my $msg_len = length($msg);
Debug( $msg."[".$msg_len."]" ); my $res = $self->{ua}->get($$self{base_url}.$cmd);
}
sub sendCmd if ( $res->is_success ) {
{ $result = !undef;
my $self = shift; # Command to camera appears successful, write Info item to log
my $cmd = shift; Info('Camera control: \''.$res->status_line().'\' for URL '.$$self{base_url}.$cmd);
my $result = undef; # TODO: Add code to retrieve $res->message_decode or some such. Then we could do things like check the camera status.
} else {
destroyUA($self); # Try again
initUA($self); $res = $self->{ua}->get($$self{base_url}.$cmd);
if ( $res->is_success ) {
my $user = undef; # Command to camera appears successful, write Info item to log
my $password = undef; Info('Camera control 2: \''.$res->status_line().'\' for URL '.$$self{base_url}.$cmd);
my $address = undef; } else {
Error('Camera control command FAILED: \''.$res->status_line().'\' for URL '.$$self{base_url}.$cmd);
if ( $self->{Monitor}->{ControlAddress} =~ /(.*):(.*)@(.*)/ ) $res = $self->{ua}->get('http://'.$self->{Monitor}->{ControlAddress}.'/'.$cmd);
{
$user = $1;
$password = $2;
$address = $3;
} }
}
printMsg( $cmd, "Tx" ); return $result;
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 );
} }
sub reset sub reset {
{ my $self = shift;
my $self = shift; # This reboots the camera effectively resetting it
# This reboots the camera effectively resetting it $self->sendCmd('cgi-bin/magicBox.cgi?action=reboot');
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);
} }
# NOTE: I'm putting this in, but absolute camera movement does not seem to be well supported in the classic skin ATM. # 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... sub moveAbs ## Up, Down, Left, Right, etc. ??? Doesn't make sense here...
{ {
my $self = shift; my $self = shift;
my $pan_degrees = shift || 0; my $pan_degrees = shift || 0;
my $tilt_degrees = shift || 0; my $tilt_degrees = shift || 0;
my $speed = shift || 1; my $speed = shift || 1;
Debug( "Move ABS" ); 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 ); $self->sendCmd('cgi-bin/ptz.cgi?action=start&code=PositionABS&channel=0&arg1='.$pan_degrees.'&arg2='.$tilt_degrees.'&arg3=0&arg4='.$speed);
} }
sub moveConUp sub moveConUp {
{ my $self = shift;
my $self = shift; Debug('Move Up');
Debug( "Move Up" ); $self->sendCmd('cgi-bin/ptz.cgi?action=start&code=Up&channel=0&arg1=0&arg2=1&arg3=0');
$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?
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');
$self->sendCmd( 'cgi-bin/ptz.cgi?action=stop&code=Up&channel=0&arg1=0&arg2=1&arg3=0' );
} }
sub moveConDown sub moveConDown {
{ my $self = shift;
my $self = shift; Debug('Move Down');
Debug( "Move Down" ); $self->sendCmd('cgi-bin/ptz.cgi?action=start&code=Down&channel=0&arg1=0&arg2=1&arg3=0');
$self->sendCmd( 'cgi-bin/ptz.cgi?action=start&code=Down&channel=0&arg1=0&arg2=1&arg3=0' ); usleep(500);
usleep (500); $self->sendCmd('cgi-bin/ptz.cgi?action=stop&code=Down&channel=0&arg1=0&arg2=1&arg3=0');
$self->sendCmd( 'cgi-bin/ptz.cgi?action=stop&code=Down&channel=0&arg1=0&arg2=1&arg3=0' );
} }
sub moveConLeft sub moveConLeft {
{ my $self = shift;
my $self = shift; Debug('Move Left');
Debug( "Move Left" ); $self->sendCmd('cgi-bin/ptz.cgi?action=start&code=Left&channel=0&arg1=0&arg2=1&arg3=0');
$self->sendCmd( 'cgi-bin/ptz.cgi?action=start&code=Left&channel=0&arg1=0&arg2=1&arg3=0' ); usleep(500);
usleep (500); $self->sendCmd('cgi-bin/ptz.cgi?action=stop&code=Left&channel=0&arg1=0&arg2=1&arg3=0');
$self->sendCmd( 'cgi-bin/ptz.cgi?action=stop&code=Left&channel=0&arg1=0&arg2=1&arg3=0' );
} }
sub moveConRight sub moveConRight {
{ my $self = shift;
my $self = shift; Debug('Move Right');
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=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');
$self->sendCmd( 'cgi-bin/ptz.cgi?action=start&code=Right&channel=0&arg1=0&arg2=1&arg3=0' ); usleep(500);
usleep (500); Debug('Move Right Stop');
Debug( "Move Right Stop" ); $self->sendCmd('cgi-bin/ptz.cgi?action=stop&code=Right&channel=0&arg1=0&arg2=1&arg3=0');
$self->sendCmd( 'cgi-bin/ptz.cgi?action=stop&code=Right&channel=0&arg1=0&arg2=1&arg3=0' );
} }
sub moveConUpRight sub moveConUpRight {
{ my $self = shift;
my $self = shift; Debug('Move Diagonally Up Right');
Debug( "Move Diagonally Up Right" ); $self->sendCmd('cgi-bin/ptz.cgi?action=start&code=RightUp&channel=0&arg1=1&arg2=1&arg3=0');
$self->sendCmd( 'cgi-bin/ptz.cgi?action=start&code=RightUp&channel=0&arg1=1&arg2=1&arg3=0' ); usleep(500);
usleep (500); $self->sendCmd('cgi-bin/ptz.cgi?action=stop&code=RightUp&channel=0&arg1=0&arg2=1&arg3=0');
$self->sendCmd( 'cgi-bin/ptz.cgi?action=stop&code=RightUp&channel=0&arg1=0&arg2=1&arg3=0' );
} }
sub moveConDownRight sub moveConDownRight {
{ my $self = shift;
my $self = shift; Debug('Move Diagonally Down Right');
Debug( "Move Diagonally Down Right" ); $self->sendCmd('cgi-bin/ptz.cgi?action=start&code=RightDown&channel=0&arg1=1&arg2=1&arg3=0');
$self->sendCmd( 'cgi-bin/ptz.cgi?action=start&code=RightDown&channel=0&arg1=1&arg2=1&arg3=0' ); usleep(500);
usleep (500); $self->sendCmd('cgi-bin/ptz.cgi?action=stop&code=RightDown&channel=0&arg1=0&arg2=1&arg3=0');
$self->sendCmd( 'cgi-bin/ptz.cgi?action=stop&code=RightDown&channel=0&arg1=0&arg2=1&arg3=0' );
} }
sub moveConUpLeft sub moveConUpLeft {
{ my $self = shift;
my $self = shift; Debug('Move Diagonally Up Left');
Debug( "Move Diagonally Up Left" ); $self->sendCmd('cgi-bin/ptz.cgi?action=start&code=LeftUp&channel=0&arg1=1&arg2=1&arg3=0');
$self->sendCmd( 'cgi-bin/ptz.cgi?action=start&code=LeftUp&channel=0&arg1=1&arg2=1&arg3=0' ); usleep(500);
usleep (500); $self->sendCmd('cgi-bin/ptz.cgi?action=stop&code=LeftUp&channel=0&arg1=0&arg2=1&arg3=0');
$self->sendCmd( 'cgi-bin/ptz.cgi?action=stop&code=LeftUp&channel=0&arg1=0&arg2=1&arg3=0' );
} }
sub moveConDownLeft sub moveConDownLeft {
{ my $self = shift;
my $self = shift; Debug('Move Diagonally Down Left');
Debug( "Move Diagonally Down Left" ); $self->sendCmd('cgi-bin/ptz.cgi?action=start&code=LeftDown&channel=0&arg1=1&arg2=1&arg3=0');
$self->sendCmd( 'cgi-bin/ptz.cgi?action=start&code=LeftDown&channel=0&arg1=1&arg2=1&arg3=0' ); usleep (500);
usleep (500); $self->sendCmd('cgi-bin/ptz.cgi?action=stop&code=LeftDown&channel=0&arg1=0&arg2=1&arg3=0');
$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" # 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 # 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. # support a generic stop-all-current-action command.
sub moveStop sub moveStop {
{ my $self = shift;
my $self = shift; Debug('Move Stop/Center');
Debug( "Move Stop/Center" ); $self->sendCmd('cgi-bin/ptz.cgi?action=start&code=PositionABS&channel=0&arg1=0&arg2=0&arg3=0&arg4=1');
$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 # 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 # 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. # NOTE: It goes without saying that the user must have set up preset #1 for this to work.
sub presetHome sub presetHome {
{ my $self = shift;
my $self = shift; Debug('Home Preset');
Debug( "Home Preset" ); $self->sendCmd('cgi-bin/ptz.cgi?action=start&channel=0&code=GotoPreset&&arg1=0&arg2=1&arg3=0&arg4=0');
$self->sendCmd( 'cgi-bin/ptz.cgi?action=start&channel=0&code=GotoPreset&&arg1=0&arg2=1&arg3=0&arg4=0' );
} }
sub presetGoto sub presetGoto {
{ my $self = shift;
my $self = shift; my $params = shift;
my $params = shift; my $preset = $self->getParam($params, 'preset');
my $preset = $self->getParam( $params, 'preset' ); Debug("Go To Preset $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');
$self->sendCmd( 'cgi-bin/ptz.cgi?action=start&channel=0&code=GotoPreset&&arg1=0&arg2='.$preset.'&arg3=0&arg4=0' );
} }
sub presetSet sub presetSet {
{ my $self = shift;
my $self = shift; my $params = shift;
my $params = shift; my $preset = $self->getParam($params, 'preset');
my $preset = $self->getParam( $params, 'preset' ); Debug('Set Preset');
Debug( "Set Preset" ); $self->sendCmd('cgi-bin/ptz.cgi?action=start&channel=0&code=SetPreset&arg1=0&arg2='.$preset.'&arg3=0&arg4=0');
$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. # NOTE: This does not appear to be implemented in the classic skin. But we'll leave it here for later.
sub moveMap sub moveMap {
{ my $self = shift;
my $self = shift; my $params = shift;
my $params = shift;
my $xcoord = $self->getParam( $params, 'xcoord', $self->{Monitor}{Width}/2 ); my $xcoord = $self->getParam( $params, 'xcoord', $self->{Monitor}{Width}/2 );
my $ycoord = $self->getParam( $params, 'ycoord', $self->{Monitor}{Height}/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 # 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 # just use 360 minus pan instead of pan, 90 minus tilt instead of tilt
# Convert xcoord into pan position 0 to 359 # Convert xcoord into pan position 0 to 359
my $pan = int(360 * $xcoord / $self->{Monitor}{Width}); my $pan = int(360 * $xcoord / $self->{Monitor}{Width});
# Convert ycoord into tilt position 0 to 89 # Convert ycoord into tilt position 0 to 89
my $tilt = 90 - int(90 * $ycoord / $self->{Monitor}{Height}); my $tilt = 90 - int(90 * $ycoord / $self->{Monitor}{Height});
# Now get the following url: # 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'); $self->sendCmd('cgi-bin/ptz.cgi?action=start&code=PositionABS&channel=0&arg1='.$pan.'&arg2='.$tilt.'&arg3=1&arg4=1');
} }
sub zoomConTele sub zoomConTele {
{ my $self = shift;
my $self = shift; Debug('Zoom continuous tele');
Debug( "Zoom continuous tele" ); $self->sendCmd('cgi-bin/ptz.cgi?action=start&channel=0&code=ZoomTele&arg1=0&arg2=0&arg3=0&arg4=0');
$self->sendCmd( 'cgi-bin/ptz.cgi?action=start&channel=0&code=ZoomTele&arg1=0&arg2=0&arg3=0&arg4=0' ); usleep(100000);
usleep (100000); $self->sendCmd('cgi-bin/ptz.cgi?action=stop&channel=0&code=ZoomTele&arg1=0&arg2=0&arg3=0&arg4=0');
$self->sendCmd( 'cgi-bin/ptz.cgi?action=stop&channel=0&code=ZoomTele&arg1=0&arg2=0&arg3=0&arg4=0' );
} }
sub zoomConWide sub zoomConWide {
{ my $self = shift;
my $self = shift; Debug('Zoom continuous wide');
Debug( "Zoom continuous wide" ); $self->sendCmd('cgi-bin/ptz.cgi?action=start&channel=0&code=ZoomWide&arg1=0&arg2=0&arg3=0&arg4=0');
$self->sendCmd( 'cgi-bin/ptz.cgi?action=start&channel=0&code=ZoomWide&arg1=0&arg2=0&arg3=0&arg4=0' ); usleep (100000);
usleep (100000); $self->sendCmd('cgi-bin/ptz.cgi?action=stop&channel=0&code=ZoomWide&arg1=0&arg2=0&arg3=0&arg4=0');
$self->sendCmd( 'cgi-bin/ptz.cgi?action=stop&channel=0&code=ZoomWide&arg1=0&arg2=0&arg3=0&arg4=0' );
} }
1; 1;
@ -355,7 +332,7 @@ ZoneMinder::Control::Amcrest_HTTP - Amcrest camera control
=head1 DESCRIPTION =head1 DESCRIPTION
This module contains the implementation of the Amcrest Camera This module contains the implementation of the Amcrest Camera
controllable SDK API. controllable SDK API.
NOTE: This module implements interaction with the camera in clear text. NOTE: This module implements interaction with the camera in clear text.

View File

@ -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 # Copyright (C) 2001-2008 Philip Coombes
# #
# This program is free software; you can redistribute it and/or # 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 ZoneMinder::Config qw(:all);
use Time::HiRes qw( usleep ); use Time::HiRes qw( usleep );
use URI;
sub open our $ADDRESS;
{
my $self = shift;
$self->loadMonitor(); sub open {
my $self = shift;
use LWP::UserAgent; $self->loadMonitor();
$self->{ua} = LWP::UserAgent->new; if ( $self->{Monitor}->{ControlAddress} !~ /^\w+:\/\// ) {
$self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION ); # 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'; $self->{state} = 'open';
} return;
}
sub printMsg if ( $res->status_line() eq '401 Unauthorized' ) {
{
my $self = shift;
my $msg = shift;
my $msg_len = length($msg);
Debug( $msg."[".$msg_len."]" ); my $headers = $res->headers();
} foreach my $k ( keys %$headers ) {
Debug("Initial Header $k => $$headers{$k}");
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()."'" );
} }
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 sub cameraReset {
{ my $self = shift;
my $self = shift; Debug('Camera Reset');
Debug( "Camera Reset" ); my $cmd = '/axis-cgi/admin/restart.cgi';
my $cmd = "/axis-cgi/admin/restart.cgi"; $self->sendCmd($cmd);
$self->sendCmd( $cmd );
} }
sub moveConUp sub moveConUp {
{ my $self = shift;
my $self = shift; Debug('Move Up');
Debug( "Move Up" ); my $cmd = '/axis-cgi/com/ptz.cgi?move=up';
my $cmd = "/axis-cgi/com/ptz.cgi?move=up"; $self->sendCmd($cmd);
$self->sendCmd( $cmd );
} }
sub moveConDown sub moveConDown {
{ my $self = shift;
my $self = shift; Debug('Move Down');
Debug( "Move Down" ); my $cmd = '/axis-cgi/com/ptz.cgi?move=down';
my $cmd = "/axis-cgi/com/ptz.cgi?move=down"; $self->sendCmd($cmd);
$self->sendCmd( $cmd );
} }
sub moveConLeft sub moveConLeft {
{ my $self = shift;
my $self = shift; Debug('Move Left');
Debug( "Move Left" ); my $cmd = '/axis-cgi/com/ptz.cgi?move=left';
my $cmd = "/axis-cgi/com/ptz.cgi?move=left"; $self->sendCmd($cmd);
$self->sendCmd( $cmd );
} }
sub moveConRight sub moveConRight {
{ my $self = shift;
my $self = shift; Debug('Move Right');
Debug( "Move Right" ); my $cmd = '/axis-cgi/com/ptz.cgi?move=right';
my $cmd = "/axis-cgi/com/ptz.cgi?move=right"; $self->sendCmd($cmd);
$self->sendCmd( $cmd );
} }
sub moveConUpRight sub moveConUpRight {
{ my $self = shift;
my $self = shift; Debug('Move Up/Right');
Debug( "Move Up/Right" ); my $cmd = '/axis-cgi/com/ptz.cgi?move=upright';
my $cmd = "/axis-cgi/com/ptz.cgi?move=upright"; $self->sendCmd($cmd);
$self->sendCmd( $cmd );
} }
sub moveConUpLeft sub moveConUpLeft {
{ my $self = shift;
my $self = shift; Debug('Move Up/Left');
Debug( "Move Up/Left" ); my $cmd = '/axis-cgi/com/ptz.cgi?move=upleft';
my $cmd = "/axis-cgi/com/ptz.cgi?move=upleft"; $self->sendCmd($cmd);
$self->sendCmd( $cmd );
} }
sub moveConDownRight sub moveConDownRight {
{ my $self = shift;
my $self = shift; Debug('Move Down/Right');
Debug( "Move Down/Right" ); my $cmd = '/axis-cgi/com/ptz.cgi?move=downright';
my $cmd = "/axis-cgi/com/ptz.cgi?move=downright"; $self->sendCmd( $cmd );
$self->sendCmd( $cmd );
} }
sub moveConDownLeft sub moveConDownLeft {
{ my $self = shift;
my $self = shift; Debug('Move Down/Left');
Debug( "Move Down/Left" ); my $cmd = '/axis-cgi/com/ptz.cgi?move=downleft';
my $cmd = "/axis-cgi/com/ptz.cgi?move=downleft"; $self->sendCmd($cmd);
$self->sendCmd( $cmd );
} }
sub moveMap sub moveMap {
{ my $self = shift;
my $self = shift; my $params = shift;
my $params = shift; my $xcoord = $self->getParam($params, 'xcoord');
my $xcoord = $self->getParam( $params, 'xcoord' ); my $ycoord = $self->getParam($params, 'ycoord');
my $ycoord = $self->getParam( $params, 'ycoord' ); Debug("Move Map to $xcoord,$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};
my $cmd = "/axis-cgi/com/ptz.cgi?center=$xcoord,$ycoord&imagewidth=".$self->{Monitor}->{Width}."&imageheight=".$self->{Monitor}->{Height}; $self->sendCmd($cmd);
$self->sendCmd( $cmd );
} }
sub moveRelUp sub moveRelUp {
{ my $self = shift;
my $self = shift; my $params = shift;
my $params = shift; my $step = $self->getParam($params, 'tiltstep');
my $step = $self->getParam( $params, 'tiltstep' ); Debug("Step Up $step");
Debug( "Step Up $step" ); my $cmd = '/axis-cgi/com/ptz.cgi?rtilt='.$step;
my $cmd = "/axis-cgi/com/ptz.cgi?rtilt=$step"; $self->sendCmd($cmd);
$self->sendCmd( $cmd );
} }
sub moveRelDown sub moveRelDown {
{ my $self = shift;
my $self = shift; my $params = shift;
my $params = shift; my $step = $self->getParam($params, 'tiltstep');
my $step = $self->getParam( $params, 'tiltstep' ); Debug("Step Down $step");
Debug( "Step Down $step" ); my $cmd = '/axis-cgi/com/ptz.cgi?rtilt=-'.$step;
my $cmd = "/axis-cgi/com/ptz.cgi?rtilt=-$step"; $self->sendCmd($cmd);
$self->sendCmd( $cmd );
} }
sub moveRelLeft sub moveRelLeft {
{ my $self = shift;
my $self = shift; my $params = shift;
my $params = shift; my $step = $self->getParam($params, 'panstep');
my $step = $self->getParam( $params, 'panstep' ); Debug("Step Left $step");
Debug( "Step Left $step" ); my $cmd = '/axis-cgi/com/ptz.cgi?rpan=-'.$step;
my $cmd = "/axis-cgi/com/ptz.cgi?rpan=-$step"; $self->sendCmd($cmd);
$self->sendCmd( $cmd );
} }
sub moveRelRight sub moveRelRight {
{ my $self = shift;
my $self = shift; my $params = shift;
my $params = shift; my $step = $self->getParam($params, 'panstep');
my $step = $self->getParam( $params, 'panstep' ); Debug("Step Right $step");
Debug( "Step Right $step" ); my $cmd = '/axis-cgi/com/ptz.cgi?rpan='.$step;
my $cmd = "/axis-cgi/com/ptz.cgi?rpan=$step"; $self->sendCmd($cmd);
$self->sendCmd( $cmd );
} }
sub moveRelUpRight sub moveRelUpRight {
{ my $self = shift;
my $self = shift; my $params = shift;
my $params = shift; my $panstep = $self->getParam($params, 'panstep');
my $panstep = $self->getParam( $params, 'panstep' ); my $tiltstep = $self->getParam($params, 'tiltstep');
my $tiltstep = $self->getParam( $params, 'tiltstep' ); Debug("Step Up/Right $tiltstep/$panstep");
Debug( "Step Up/Right $tiltstep/$panstep" ); my $cmd = "/axis-cgi/com/ptz.cgi?rpan=$panstep&rtilt=$tiltstep";
my $cmd = "/axis-cgi/com/ptz.cgi?rpan=$panstep&rtilt=$tiltstep"; $self->sendCmd($cmd);
$self->sendCmd( $cmd );
} }
sub moveRelUpLeft sub moveRelUpLeft {
{ my $self = shift;
my $self = shift; my $params = shift;
my $params = shift; my $panstep = $self->getParam($params, 'panstep');
my $panstep = $self->getParam( $params, 'panstep' ); my $tiltstep = $self->getParam($params, 'tiltstep');
my $tiltstep = $self->getParam( $params, 'tiltstep' ); Debug("Step Up/Left $tiltstep/$panstep");
Debug( "Step Up/Left $tiltstep/$panstep" ); my $cmd = "/axis-cgi/com/ptz.cgi?rpan=-$panstep&rtilt=$tiltstep";
my $cmd = "/axis-cgi/com/ptz.cgi?rpan=-$panstep&rtilt=$tiltstep"; $self->sendCmd($cmd);
$self->sendCmd( $cmd );
} }
sub moveRelDownRight sub moveRelDownRight {
{ my $self = shift;
my $self = shift; my $params = shift;
my $params = shift; my $panstep = $self->getParam($params, 'panstep');
my $panstep = $self->getParam( $params, 'panstep' ); my $tiltstep = $self->getParam($params, 'tiltstep');
my $tiltstep = $self->getParam( $params, 'tiltstep' ); Debug("Step Down/Right $tiltstep/$panstep");
Debug( "Step Down/Right $tiltstep/$panstep" ); my $cmd = "/axis-cgi/com/ptz.cgi?rpan=$panstep&rtilt=-$tiltstep";
my $cmd = "/axis-cgi/com/ptz.cgi?rpan=$panstep&rtilt=-$tiltstep"; $self->sendCmd($cmd);
$self->sendCmd( $cmd );
} }
sub moveRelDownLeft sub moveRelDownLeft {
{ my $self = shift;
my $self = shift; my $params = shift;
my $params = shift; my $panstep = $self->getParam($params, 'panstep');
my $panstep = $self->getParam( $params, 'panstep' ); my $tiltstep = $self->getParam($params, 'tiltstep');
my $tiltstep = $self->getParam( $params, 'tiltstep' ); Debug("Step Down/Left $tiltstep/$panstep");
Debug( "Step Down/Left $tiltstep/$panstep" ); my $cmd = "/axis-cgi/com/ptz.cgi?rpan=-$panstep&rtilt=-$tiltstep";
my $cmd = "/axis-cgi/com/ptz.cgi?rpan=-$panstep&rtilt=-$tiltstep"; $self->sendCmd($cmd);
$self->sendCmd( $cmd );
} }
sub zoomRelTele sub zoomRelTele {
{ my $self = shift;
my $self = shift; my $params = shift;
my $params = shift; my $step = $self->getParam($params, 'step');
my $step = $self->getParam( $params, 'step' ); Debug('Zoom Tele');
Debug( "Zoom Tele" ); my $cmd = "/axis-cgi/com/ptz.cgi?rzoom=$step";
my $cmd = "/axis-cgi/com/ptz.cgi?rzoom=$step"; $self->sendCmd($cmd);
$self->sendCmd( $cmd );
} }
sub zoomRelWide sub zoomRelWide {
{ my $self = shift;
my $self = shift; my $params = shift;
my $params = shift; my $step = $self->getParam($params, 'step');
my $step = $self->getParam( $params, 'step' ); Debug('Zoom Wide');
Debug( "Zoom Wide" ); my $cmd = "/axis-cgi/com/ptz.cgi?rzoom=-$step";
my $cmd = "/axis-cgi/com/ptz.cgi?rzoom=-$step"; $self->sendCmd($cmd);
$self->sendCmd( $cmd );
} }
sub focusRelNear sub focusRelNear {
{ my $self = shift;
my $self = shift; my $params = shift;
my $params = shift; my $step = $self->getParam($params, 'step');
my $step = $self->getParam( $params, 'step' ); Debug('Focus Near');
Debug( "Focus Near" ); my $cmd = "/axis-cgi/com/ptz.cgi?rfocus=-$step";
my $cmd = "/axis-cgi/com/ptz.cgi?rfocus=-$step"; $self->sendCmd($cmd);
$self->sendCmd( $cmd );
} }
sub focusRelFar sub focusRelFar {
{ my $self = shift;
my $self = shift; my $params = shift;
my $params = shift; my $step = $self->getParam($params, 'step');
my $step = $self->getParam( $params, 'step' ); Debug('Focus Far');
Debug( "Focus Far" ); my $cmd = "/axis-cgi/com/ptz.cgi?rfocus=$step";
my $cmd = "/axis-cgi/com/ptz.cgi?rfocus=$step"; $self->sendCmd($cmd);
$self->sendCmd( $cmd );
} }
sub focusAuto sub focusAuto {
{ my $self = shift;
my $self = shift; Debug('Focus Auto');
Debug( "Focus Auto" ); my $cmd = '/axis-cgi/com/ptz.cgi?autofocus=on';
my $cmd = "/axis-cgi/com/ptz.cgi?autofocus=on"; $self->sendCmd($cmd);
$self->sendCmd( $cmd );
} }
sub focusMan sub focusMan {
{ my $self = shift;
my $self = shift; Debug('Focus Manual');
Debug( "Focus Manual" ); my $cmd = '/axis-cgi/com/ptz.cgi?autofocus=off';
my $cmd = "/axis-cgi/com/ptz.cgi?autofocus=off"; $self->sendCmd($cmd);
$self->sendCmd( $cmd );
} }
sub irisRelOpen sub irisRelOpen {
{ my $self = shift;
my $self = shift; my $params = shift;
my $params = shift; my $step = $self->getParam($params, 'step');
my $step = $self->getParam( $params, 'step' ); Debug('Iris Open');
Debug( "Iris Open" ); my $cmd = "/axis-cgi/com/ptz.cgi?riris=$step";
my $cmd = "/axis-cgi/com/ptz.cgi?riris=$step"; $self->sendCmd($cmd);
$self->sendCmd( $cmd );
} }
sub irisRelClose sub irisRelClose {
{ my $self = shift;
my $self = shift; my $params = shift;
my $params = shift; my $step = $self->getParam($params, 'step');
my $step = $self->getParam( $params, 'step' ); Debug('Iris Close');
Debug( "Iris Close" ); my $cmd = "/axis-cgi/com/ptz.cgi?riris=-$step";
my $cmd = "/axis-cgi/com/ptz.cgi?riris=-$step"; $self->sendCmd($cmd);
$self->sendCmd( $cmd );
} }
sub irisAuto sub irisAuto {
{ my $self = shift;
my $self = shift; Debug('Iris Auto');
Debug( "Iris Auto" ); my $cmd = '/axis-cgi/com/ptz.cgi?autoiris=on';
my $cmd = "/axis-cgi/com/ptz.cgi?autoiris=on"; $self->sendCmd($cmd);
$self->sendCmd( $cmd );
} }
sub irisMan sub irisMan {
{ my $self = shift;
my $self = shift; Debug('Iris Manual');
Debug( "Iris Manual" ); my $cmd = '/axis-cgi/com/ptz.cgi?autoiris=off';
my $cmd = "/axis-cgi/com/ptz.cgi?autoiris=off"; $self->sendCmd($cmd);
$self->sendCmd( $cmd );
} }
sub presetClear sub presetClear {
{ my $self = shift;
my $self = shift; my $params = shift;
my $params = shift; my $preset = $self->getParam($params, 'preset');
my $preset = $self->getParam( $params, 'preset' ); Debug("Clear Preset $preset");
Debug( "Clear Preset $preset" ); my $cmd = "/axis-cgi/com/ptz.cgi?removeserverpresetno=$preset";
my $cmd = "/axis-cgi/com/ptz.cgi?removeserverpresetno=$preset"; $self->sendCmd($cmd);
$self->sendCmd( $cmd );
} }
sub presetSet sub presetSet {
{ my $self = shift;
my $self = shift; my $params = shift;
my $params = shift; my $preset = $self->getParam($params, 'preset');
my $preset = $self->getParam( $params, 'preset' ); Debug("Set Preset $preset");
Debug( "Set Preset $preset" ); my $cmd = "/axis-cgi/com/ptz.cgi?setserverpresetno=$preset";
my $cmd = "/axis-cgi/com/ptz.cgi?setserverpresetno=$preset"; $self->sendCmd($cmd);
$self->sendCmd( $cmd );
} }
sub presetGoto sub presetGoto {
{ my $self = shift;
my $self = shift; my $params = shift;
my $params = shift; my $preset = $self->getParam($params, 'preset');
my $preset = $self->getParam( $params, 'preset' ); Debug("Goto Preset $preset");
Debug( "Goto Preset $preset" ); my $cmd = "/axis-cgi/com/ptz.cgi?gotoserverpresetno=$preset";
my $cmd = "/axis-cgi/com/ptz.cgi?gotoserverpresetno=$preset"; $self->sendCmd($cmd);
$self->sendCmd( $cmd );
} }
sub presetHome sub presetHome {
{ my $self = shift;
my $self = shift; Debug('Home Preset');
Debug( "Home Preset" ); my $cmd = '/axis-cgi/com/ptz.cgi?move=home';
my $cmd = "/axis-cgi/com/ptz.cgi?move=home"; $self->sendCmd($cmd);
$self->sendCmd( $cmd );
} }
1; 1;

View File

@ -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 <lt>ascheel (at) gmail<gt>
=head1 COPYRIGHT AND LICENSE
Copyright (C) 2018 ZoneMinder LLC
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
=cut

View File

@ -95,9 +95,9 @@ sub PutCmd {
my $self = shift; my $self = shift;
my $cmd = shift; my $cmd = shift;
my $content = 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) ) { 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('<?xml version="1.0" encoding="UTF-8"?>' . "\n" . $content); $req->content('<?xml version="1.0" encoding="UTF-8"?>' . "\n" . $content);
} }
my $res = $self->{UA}->request($req); my $res = $self->{UA}->request($req);
@ -135,13 +135,13 @@ sub PutCmd {
# Check for username/password # Check for username/password
# #
if ( $self->{Monitor}{ControlAddress} =~ /.+:(.+)@.+/ ) { if ( $self->{Monitor}{ControlAddress} =~ /.+:(.+)@.+/ ) {
Info("Check username/password is correct"); Info('Check username/password is correct');
} elsif ( $self->{Monitor}{ControlAddress} =~ /^[^:]+@.+/ ) { } 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} =~ /^:.+@.+/ ) { } elsif ( $self->{Monitor}{ControlAddress} =~ /^:.+@.+/ ) {
Info("Password but no username in Control Address."); Info('Password but no username in Control Address.');
} else { } else {
Info("Missing username and password in Control Address."); Info('Missing username and password in Control Address.');
} }
Fatal($res->status_line); Fatal($res->status_line);
} }
@ -382,7 +382,7 @@ sub irisRelOpen {
sub reset { sub reset {
my $self = shift; my $self = shift;
$self->PutCmd("ISAPI/System/reboot"); $self->PutCmd('ISAPI/System/reboot');
} }
1; 1;

View File

@ -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) # Copyright (C) 2008 Brian Rudy (brudyNO@SPAMpraecogito.com)
# #
# This program is free software; you can redistribute it and/or # 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::Logger qw(:all);
use ZoneMinder::Config qw(:all); use ZoneMinder::Config qw(:all);
use Time::HiRes qw( usleep );
sub open { sub open {
my $self = shift; my $self = shift;
@ -52,58 +50,50 @@ sub open {
use LWP::UserAgent; use LWP::UserAgent;
$self->{ua} = LWP::UserAgent->new; $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'; $self->{state} = 'open';
} }
sub printMsg {
my $self = shift;
my $msg = shift;
my $msg_len = length($msg);
Debug( $msg."[".$msg_len."]" );
}
sub sendCmd { sub sendCmd {
my $self = shift; my $self = shift;
my $cmd = shift; my $cmd = shift;
my $result = undef; my $result = undef;
printMsg( $cmd, "Tx" ); $self->printMsg($cmd, 'Tx');
my $url; my $url;
if ( $self->{Monitor}->{ControlAddress} =~ /^http/ ) { if ( $self->{Monitor}->{ControlAddress} =~ /^http/i ) {
$url = $self->{Monitor}->{ControlAddress}.$cmd; $url = $self->{Monitor}->{ControlAddress}.$cmd;
} else { } else {
$url = 'http://'.$self->{Monitor}->{ControlAddress}.$cmd; $url = 'http://'.$self->{Monitor}->{ControlAddress}.$cmd;
} # en dif } # end if
my $req = HTTP::Request->new( GET=>$url ); my $req = HTTP::Request->new(GET=>$url);
my $res = $self->{ua}->request($req); my $res = $self->{ua}->request($req);
if ( $res->is_success ) { if ( $res->is_success ) {
$result = !undef; $result = !undef;
} else { } else {
Error( "Error check failed: '".$res->status_line()."'" ); Error('Error check failed: \''.$res->status_line().'\'');
} }
return( $result ); return $result;
} }
sub reset { sub reset {
my $self = shift; my $self = shift;
Debug( "Camera Reset" ); Debug('Camera Reset');
my $cmd = "/admin/ptctl.cgi?move=reset"; my $cmd = '/admin/ptctl.cgi?move=reset';
$self->sendCmd( $cmd ); $self->sendCmd($cmd);
} }
sub moveMap { sub moveMap {
my $self = shift; my $self = shift;
my $params = shift; my $params = shift;
my $xcoord = $self->getParam( $params, 'xcoord' ); my $xcoord = $self->getParam($params, 'xcoord');
my $ycoord = $self->getParam( $params, 'ycoord' ); my $ycoord = $self->getParam($params, 'ycoord');
my $hor = $xcoord * 100 / $self->{Monitor}->{Width}; my $hor = $xcoord * 100 / $self->{Monitor}->{Width};
my $ver = $ycoord * 100 / $self->{Monitor}->{Height}; my $ver = $ycoord * 100 / $self->{Monitor}->{Height};
@ -125,81 +115,81 @@ sub moveMap {
elsif ( $hor > 50 ) { elsif ( $hor > 50 ) {
# right # right
$horSteps = (($hor - 50) / 50) * $maxhor; $horSteps = (($hor - 50) / 50) * $maxhor;
$horDir = "right"; $horDir = 'right';
} }
# Vertical movement # Vertical movement
if ( $ver < 50 ) { if ( $ver < 50 ) {
# up # up
$verSteps = ((50 - $ver) / 50) * $maxver; $verSteps = ((50 - $ver) / 50) * $maxver;
$verDir = "up"; $verDir = 'up';
} }
elsif ( $ver > 50 ) { elsif ( $ver > 50 ) {
# down # down
$verSteps = (($ver - 50) / 50) * $maxver; $verSteps = (($ver - 50) / 50) * $maxver;
$verDir = "down"; $verDir = 'down';
} }
my $v = int($verSteps); my $v = int($verSteps);
my $h = int($horSteps); 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"; 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"; $cmd = "/cgi/admin/ptctrl.cgi?action=movedegree&Cmd=$verDir&Degree=$v";
$self->sendCmd( $cmd ); $self->sendCmd($cmd);
} }
sub moveRelUp { sub moveRelUp {
my $self = shift; my $self = shift;
my $params = shift; my $params = shift;
my $step = $self->getParam( $params, 'tiltstep' ); my $step = $self->getParam($params, 'tiltstep');
Debug( "Step Up $step" ); Debug("Step Up $step");
my $cmd = "/admin/ptctl.cgi?move=up"; my $cmd = '/admin/ptctl.cgi?move=up';
$self->sendCmd( $cmd ); $self->sendCmd($cmd);
} }
sub moveRelDown { sub moveRelDown {
my $self = shift; my $self = shift;
my $params = shift; my $params = shift;
my $step = $self->getParam( $params, 'tiltstep' ); my $step = $self->getParam($params, 'tiltstep');
Debug( "Step Down $step" ); Debug("Step Down $step");
my $cmd = "/admin/ptctl.cgi?move=down"; my $cmd = '/admin/ptctl.cgi?move=down';
$self->sendCmd( $cmd ); $self->sendCmd($cmd);
} }
sub moveRelLeft { sub moveRelLeft {
my $self = shift; my $self = shift;
my $params = shift; my $params = shift;
my $step = $self->getParam( $params, 'panstep' ); my $step = $self->getParam($params, 'panstep');
if ( $self->{Monitor}->{Orientation} eq "hori" ) { if ( $self->{Monitor}->{Orientation} eq 'FLIP_HORI' ) {
Debug( "Stepping Right because flipped horizontally " ); Debug('Stepping Right because flipped horizontally');
$self->sendCmd( "/admin/ptctl.cgi?move=right" ); $self->sendCmd('/admin/ptctl.cgi?move=right');
} else { } else {
Debug( "Step Left" ); Debug('Step Left');
$self->sendCmd( "/admin/ptctl.cgi?move=left" ); $self->sendCmd('/admin/ptctl.cgi?move=left');
} }
} }
sub moveRelRight { sub moveRelRight {
my $self = shift; my $self = shift;
my $params = shift; my $params = shift;
my $step = $self->getParam( $params, 'panstep' ); my $step = $self->getParam($params, 'panstep');
if ( $self->{Monitor}->{Orientation} eq "hori" ) { if ( $self->{Monitor}->{Orientation} eq 'FLIP_HORI' ) {
Debug( "Stepping Left because flipped horizontally " ); Debug('Stepping Left because flipped horizontally');
$self->sendCmd( "/admin/ptctl.cgi?move=left" ); $self->sendCmd('/admin/ptctl.cgi?move=left');
} else { } else {
Debug( "Step Right" ); Debug('Step Right');
$self->sendCmd( "/admin/ptctl.cgi?move=right" ); $self->sendCmd('/admin/ptctl.cgi?move=right');
} }
} }
sub presetClear { sub presetClear {
my $self = shift; my $self = shift;
my $params = shift; my $params = shift;
my $preset = $self->getParam( $params, 'preset' ); my $preset = $self->getParam($params, 'preset');
Debug( "Clear Preset $preset" ); Debug("Clear Preset $preset");
#my $cmd = "/axis-cgi/com/ptz.cgi?removeserverpresetno=$preset"; #my $cmd = "/axis-cgi/com/ptz.cgi?removeserverpresetno=$preset";
#$self->sendCmd( $cmd ); #$self->sendCmd( $cmd );
} }
@ -207,26 +197,26 @@ sub presetClear {
sub presetSet { sub presetSet {
my $self = shift; my $self = shift;
my $params = shift; my $params = shift;
my $preset = $self->getParam( $params, 'preset' ); my $preset = $self->getParam($params, 'preset');
Debug( "Set Preset $preset" ); Debug("Set Preset $preset");
my $cmd = "/admin/ptctl.cgi?position=" . ($preset - 1) . "&positionname=zm$preset"; my $cmd = '/admin/ptctl.cgi?position=' . ($preset - 1) . "&positionname=zm$preset";
$self->sendCmd( $cmd ); $self->sendCmd( $cmd );
} }
sub presetGoto { sub presetGoto {
my $self = shift; my $self = shift;
my $params = shift; my $params = shift;
my $preset = $self->getParam( $params, 'preset' ); my $preset = $self->getParam($params, 'preset');
Debug( "Goto Preset $preset" ); Debug("Goto Preset $preset");
my $cmd = "/admin/ptctl.cgi?move=p" . ($preset - 1); my $cmd = '/admin/ptctl.cgi?move=p'.($preset - 1);
$self->sendCmd( $cmd ); $self->sendCmd($cmd);
} }
sub presetHome { sub presetHome {
my $self = shift; my $self = shift;
Debug( "Home Preset" ); Debug('Home Preset');
my $cmd = "/admin/ptctl.cgi?move=h"; my $cmd = '/admin/ptctl.cgi?move=h';
$self->sendCmd( $cmd ); $self->sendCmd($cmd);
} }
1; 1;

View File

@ -301,7 +301,7 @@ sub zmMemVerify {
} }
return !undef; return !undef;
} } # end sub zmMemVerify
sub zmMemRead { sub zmMemRead {
my $monitor = shift; my $monitor = shift;
@ -375,10 +375,8 @@ sub zmMemInvalidate {
my $mem_key = zmMemKey($monitor); my $mem_key = zmMemKey($monitor);
if ( $mem_key ) { if ( $mem_key ) {
zmMemDetach($monitor); zmMemDetach($monitor);
} else {
Warning('no memkey in zmMemInvalidate');
} }
} } # end sub zmMemInvalidate
sub zmMemTidy { sub zmMemTidy {
zmMemClean(); zmMemClean();
@ -504,10 +502,10 @@ sub zmHasAlarmed {
my $last_event_id = shift; my $last_event_id = shift;
my ( $state, $last_event ) = zmMemRead($monitor, 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; return $last_event;
} elsif( $last_event != $last_event_id ) { } elsif( $last_event != $last_event_id ) {
return $last_event; return $last_event;

View File

@ -51,7 +51,7 @@ our %EXPORT_TAGS = (
); );
push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS;
our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } );
our @EXPORT = @EXPORT_OK; our @EXPORT = @EXPORT_OK;
@ -77,17 +77,17 @@ sub zmMemAttach {
my ( $monitor, $size ) = @_; my ( $monitor, $size ) = @_;
if ( !$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; return undef;
} }
if ( defined($monitor->{MMapAddr}) ) { if ( defined($monitor->{MMapAddr}) ) {
Debug("zmMemAttach already attached at $monitor->{MMapAddr}"); Debug("zmMemAttach already attached at $monitor->{MMapAddr} for $$monitor{Id}");
return !undef; return !undef;
} }
my $mmap_file = $Config{ZM_PATH_MAP}.'/zm.mmap.'.$monitor->{Id}; my $mmap_file = $Config{ZM_PATH_MAP}.'/zm.mmap.'.$monitor->{Id};
if ( ! -e $mmap_file ) { 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; return undef;
} }
my $mmap_file_size = -s $mmap_file; my $mmap_file_size = -s $mmap_file;
@ -119,18 +119,24 @@ sub zmMemDetach {
if ( $monitor->{MMap} ) { if ( $monitor->{MMap} ) {
if ( ! munmap(${$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}; delete $monitor->{MMap};
} else {
Warn("No MMap for $$monitor{Id}");
} }
if ( $monitor->{MMapAddr} ) { if ( $monitor->{MMapAddr} ) {
delete $monitor->{MMapAddr}; delete $monitor->{MMapAddr};
} else {
Warn("No MMapAddr in $$monitor{Id}");
} }
if ( $monitor->{MMapHandle} ) { if ( $monitor->{MMapHandle} ) {
close($monitor->{MMapHandle}); close($monitor->{MMapHandle});
delete $monitor->{MMapHandle}; delete $monitor->{MMapHandle};
} else {
Warn("No MMapHandle in $$monitor{Id}");
} }
} } # end sub zmMemDetach
sub zmMemGet { sub zmMemGet {
my $monitor = shift; my $monitor = shift;
@ -162,7 +168,7 @@ sub zmMemPut {
} }
sub zmMemClean { sub zmMemClean {
Debug("Removing memory map files"); Debug('Removing memory map files');
my $mapPath = $Config{ZM_PATH_MAP}.'/zm.mmap.*'; my $mapPath = $Config{ZM_PATH_MAP}.'/zm.mmap.*';
foreach my $mapFile( glob( $mapPath ) ) { foreach my $mapFile( glob( $mapPath ) ) {
( $mapFile ) = $mapFile =~ /^(.+)$/; ( $mapFile ) = $mapFile =~ /^(.+)$/;

View File

@ -36,9 +36,172 @@ require ZoneMinder::Server;
#our @ISA = qw(Exporter ZoneMinder::Base); #our @ISA = qw(Exporter ZoneMinder::Base);
use parent qw(ZoneMinder::Object); use parent qw(ZoneMinder::Object);
use vars qw/ $table $primary_key /; use vars qw/ $table $primary_key %fields $serial %defaults $debug/;
$table = 'Monitors'; $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 { sub Server {
return new ZoneMinder::Server( $_[0]{ServerId} ); return new ZoneMinder::Server( $_[0]{ServerId} );

View File

@ -255,15 +255,15 @@ sub discover {
sub profiles { sub profiles {
my ( $client ) = @_; my ( $client ) = @_;
my $endpoint = $client->get_endpoint('media'); my $media = $client->get_endpoint('media');
if ( ! $endpoint ) { if ( ! $media ) {
print "No media enpoint for client.\n"; print "No media endpoint for client.\n";
return; return;
} }
my $result = $endpoint->GetProfiles( { } ,, ); my $result = $media->GetProfiles( { } ,, );
if ( ! $result ) { if ( ! $result ) {
print "No result from GetProfiles\n"; print "No result from GetProfiles.\n";
return; return;
} }
if ( $verbose ) { if ( $verbose ) {
@ -272,48 +272,52 @@ sub profiles {
my $profiles = $result->get_Profiles(); my $profiles = $result->get_Profiles();
foreach my $profile ( @{ $profiles } ) { foreach my $profile ( @{ $profiles } ) {
my $token = $profile->attr()->get_token() ; my $token = $profile->attr()->get_token() ;
my $video_encoder_configuration = $profile->get_VideoEncoderConfiguration(); my $Name = $profile->get_Name();
if ( ! $video_encoder_configuration ) {
print "Unknown profile $token " . $profile->get_Name()."\n"; my $VideoEncoderConfiguration = $profile->get_VideoEncoderConfiguration();
if ( ! $VideoEncoderConfiguration ) {
print "Unknown profile $token $Name.\n";
next; 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. # Specification gives conflicting values for unicast stream types, try both.
# http://www.onvif.org/onvif/ver10/media/wsdl/media.wsdl#op.GetStreamUri # http://www.onvif.org/onvif/ver10/media/wsdl/media.wsdl#op.GetStreamUri
foreach my $streamtype ( 'RTP_unicast', 'RTP-Unicast' ) { foreach my $streamtype ( 'RTP_unicast', 'RTP-Unicast', 'RTP-multicast', 'RTP-Multicast' ) {
$result = $client->get_endpoint('media')->GetStreamUri( { my $StreamUri = $media->GetStreamUri( {
StreamSetup => { # ONVIF::Media::Types::StreamSetup StreamSetup => { # ONVIF::Media::Types::StreamSetup
Stream => $streamtype, # StreamType Stream => $streamtype, # StreamType
Transport => { # ONVIF::Media::Types::Transport Transport => { # ONVIF::Media::Types::Transport
Protocol => 'RTSP', # TransportProtocol Protocol => 'RTSP', # TransportProtocol
}, },
}, },
ProfileToken => $token, # ReferenceToken ProfileToken => $token, # ReferenceToken
} ,, ); } );
last if $result; next if ! ( $StreamUri and $StreamUri->can('get_MediaUri') );
} my $MediaUri = $StreamUri->get_MediaUri();
die $result if not $result; next if ! $MediaUri;
# print $result . "\n"; 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 } # end foreach profile
# #
# use message parser without schema validation ??? # use message parser without schema validation ???
# #
} } # end sub profiles
sub move { sub move {
my ($client, $dir) = @_; my ($client, $dir) = @_;
@ -326,13 +330,22 @@ sub move {
sub metadata { sub metadata {
my ( $client ) = @_; my ( $client ) = @_;
my $result = $client->get_endpoint('media')->GetMetadataConfigurations( { } ,, ); my $media = $client->get_endpoint('media');
die $result if not $result; die 'No media endpoint.' if !$media;
print $result . "\n";
$result = $client->get_endpoint('media')->GetVideoAnalyticsConfigurations( { } ,, ); my $result = $media->GetMetadataConfigurations( { } ,, );
die $result if not $result; if ( ! $result ) {
print $result . "\n"; 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( { } ,, ); # $result = $client->get_endpoint('analytics')->GetServiceCapabilities( { } ,, );
# die $result if not $result; # die $result if not $result;

View File

@ -420,15 +420,15 @@ MAIN: while( $loop ) {
Debug("Checking for Medium Scheme Events under $$Storage{Path}/$monitor_dir"); Debug("Checking for Medium Scheme Events under $$Storage{Path}/$monitor_dir");
{ {
my @event_dirs = glob("$monitor_dir/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/*"); my @event_dirs = glob("$monitor_dir/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/*");
Debug(qq`glob("$monitor_dir/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/*") returned ` . scalar @event_dirs . ' entries.' ); Debug('glob("'.$monitor_dir.'/[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]/*") returned '.(scalar @event_dirs).' entries.');
foreach my $event_dir ( @event_dirs ) { foreach my $event_dir ( @event_dirs ) {
if ( ! -d $event_dir ) { if ( ! -d $event_dir ) {
Debug( "$event_dir is not a dir. Skipping" ); Debug("$event_dir is not a dir. Skipping");
next; next;
} }
my ( $date, $event_id ) = $event_dir =~ /^$monitor_dir\/(\d{4}\-\d{2}\-\d{2})\/(\d+)$/; my ( $date, $event_id ) = $event_dir =~ /^$monitor_dir\/(\d{4}\-\d{2}\-\d{2})\/(\d+)$/;
if ( ! $event_id ) { if ( !$event_id ) {
Debug("Unable to parse date/event_id from $event_dir"); Debug('Unable to parse date/event_id from '.$event_dir);
next; next;
} }
my $Event = $fs_events->{$event_id} = new ZoneMinder::Event(); my $Event = $fs_events->{$event_id} = new ZoneMinder::Event();
@ -438,8 +438,9 @@ MAIN: while( $loop ) {
$Event->MonitorId( $monitor_dir ); $Event->MonitorId( $monitor_dir );
$Event->StorageId( $Storage->Id() ); $Event->StorageId( $Storage->Id() );
$Event->Path(); $Event->Path();
$Event->age();
Debug("Have event $$Event{Id} at $$Event{Path}"); 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 } # end foreach event
} }
@ -466,13 +467,13 @@ MAIN: while( $loop ) {
} # end foreach event } # end foreach event
chdir( $Storage->Path() ); chdir( $Storage->Path() );
} # if USE_DEEP_STORAGE } # if USE_DEEP_STORAGE
Debug( 'Got '.int(keys(%$fs_events))." filesystem events for monitor $monitor_dir" ); Debug('Got '.int(keys(%$fs_events)).' filesystem events for monitor '.$monitor_dir);
delete_empty_subdirs($$Storage{Path}.'/'.$monitor_dir); delete_empty_subdirs($$Storage{Path}.'/'.$monitor_dir);
} # end foreach monitor } # end foreach monitor
if ( $cleaned ) { if ( $cleaned ) {
Debug("First stage cleaning done. Restarting."); Debug('First stage cleaning done. Restarting.');
redo MAIN; redo MAIN;
} }
@ -484,7 +485,7 @@ MAIN: while( $loop ) {
next; next;
} }
my @event_ids = keys %$fs_events; my @event_ids = keys %$fs_events;
Debug('Have ' .scalar @event_ids . " events for monitor $monitor_id"); Debug('Have ' .scalar @event_ids . ' events for monitor '.$monitor_id);
foreach my $fs_event_id ( sort { $a <=> $b } keys %$fs_events ) { foreach my $fs_event_id ( sort { $a <=> $b } keys %$fs_events ) {
@ -499,8 +500,8 @@ MAIN: while( $loop ) {
} }
my $age = $Event->age(); my $age = $Event->age();
if ( $age > $Config{ZM_AUDIT_MIN_AGE} ) { 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" ); aud_print("Filesystem event $fs_event_id at $$Event{Path} does not exist in database and is $age seconds old");
if ( confirm() ) { if ( confirm() ) {
$Event->delete_files(); $Event->delete_files();
$cleaned = 1; $cleaned = 1;
@ -586,7 +587,7 @@ EVENT: while ( my ( $db_event, $age ) = each( %$db_events ) ) {
} else { } else {
Debug("$$Event{Id} Not found at $path"); Debug("$$Event{Id} Not found at $path");
} }
} } # end foreach Storage
if ( $Event->Archived() ) { if ( $Event->Archived() ) {
Warning("Event $$Event{Id} is Archived. Taking no further action on it."); Warning("Event $$Event{Id} is Archived. Taking no further action on it.");
next; 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}"); Info("Updating storage area on event $$Event{Id} from $$Event{StorageId} to $$fs_events{$db_event}{StorageId}");
$Event->StorageId($$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}"); 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());
$Event->StartTime($$fs_events{$db_event}->StartTime());
} else {
$Event->StartTime($$fs_events{$db_event}->StartTime());
}
$Event->save();
} }
$Event->save(); $Event->save();
} } # end if Event exists in db and not in filesystem
} # end if ! in fs_events } # end if ! in fs_events
} # foreach db_event } # foreach db_event
} # end foreach db_monitor } # end foreach db_monitor

View File

@ -351,8 +351,14 @@ sub exportsql {
} }
} }
if ($ARGV[0]) { my $name = $ARGV[0];
$command .= qq( --where="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"; $command .= " zm Controls MonitorPresets";

View File

@ -108,6 +108,9 @@ if ( $options{command} ) {
Fatal("Unable to load control data for monitor $id"); Fatal("Unable to load control data for monitor $id");
} }
my $protocol = $monitor->{Protocol}; 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 ) { if ( -x $protocol ) {
# Protocol is actually a script! # Protocol is actually a script!

View File

@ -860,7 +860,7 @@ sub sendEmail {
From => $Config{ZM_FROM_EMAIL}, From => $Config{ZM_FROM_EMAIL},
To => $Config{ZM_EMAIL_ADDRESS}, To => $Config{ZM_EMAIL_ADDRESS},
Subject => $subject, Subject => $subject,
Type => (($body=~/<html>/)?'text/html':'text/plain'), Type => (($body=~/<html/)?'text/html':'text/plain'),
Data => $body Data => $body
); );
@ -962,7 +962,7 @@ sub sendMessage {
From => $Config{ZM_FROM_EMAIL}, From => $Config{ZM_FROM_EMAIL},
To => $Config{ZM_MESSAGE_ADDRESS}, To => $Config{ZM_MESSAGE_ADDRESS},
Subject => $subject, Subject => $subject,
Type => (($body=~/<html>/)?'text/html':'text/plain'), Type => (($body=~/<html/)?'text/html':'text/plain'),
Data => $body Data => $body
); );

View File

@ -23,6 +23,7 @@
use strict; use strict;
use bytes; use bytes;
use utf8;
@EXTRA_PERL_LIB@ @EXTRA_PERL_LIB@
use ZoneMinder; use ZoneMinder;
@ -34,6 +35,7 @@ use Sys::MemInfo qw(totalmem);
use Sys::CPU qw(cpu_count); use Sys::CPU qw(cpu_count);
use POSIX qw(strftime uname); use POSIX qw(strftime uname);
use JSON::MaybeXS; use JSON::MaybeXS;
use Encode;
$ENV{PATH} = '/bin:/usr/bin:/usr/local/bin'; $ENV{PATH} = '/bin:/usr/bin:/usr/local/bin';
$ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL}; $ENV{SHELL} = '/bin/sh' if exists $ENV{SHELL};
@ -43,6 +45,7 @@ delete @ENV{qw(IFS CDPATH ENV BASH_ENV)};
my $help = 0; my $help = 0;
my $force = 0; my $force = 0;
my $show = 0;
# Interval between version checks # Interval between version checks
my $interval; my $interval;
my $version; my $version;
@ -50,6 +53,7 @@ my $version;
GetOptions( GetOptions(
force => \$force, force => \$force,
help => \$help, help => \$help,
show => \$show,
interval => \$interval, interval => \$interval,
version => \$version version => \$version
); );
@ -57,6 +61,14 @@ if ( $version ) {
print( ZoneMinder::Base::ZM_VERSION . "\n"); print( ZoneMinder::Base::ZM_VERSION . "\n");
exit(0); exit(0);
} }
if ($show) {
my %telemetry;
my $dbh = zmDbConnect();
collectData($dbh, \%telemetry);
my $result = jsonEncode(\%telemetry);
print ($result);
exit(0);
}
if ( $help ) { if ( $help ) {
pod2usage(-exitstatus => -1); pod2usage(-exitstatus => -1);
} }
@ -87,21 +99,9 @@ while( 1 ) {
my $dbh = zmDbConnect(); my $dbh = zmDbConnect();
# Build the telemetry hash # Build the telemetry hash
# We should keep *BSD systems in mind when calling system commands # We should keep *BSD systems in mind when calling system commands
my %telemetry; my %telemetry;
$telemetry{uuid} = getUUID($dbh); collectData($dbh,\%telemetry);
@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.');
my $result = jsonEncode(\%telemetry); my $result = jsonEncode(\%telemetry);
if ( sendData($result) ) { if ( sendData($result) ) {
@ -124,6 +124,24 @@ print 'ZoneMinder Telemetry Agent exiting at '.strftime('%y/%m/%d %H:%M:%S', loc
# SUBROUTINES # # 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 # Find, verify, then run the supplied system command
sub runSysCmd { sub runSysCmd {
my $msg = shift; my $msg = shift;
@ -166,7 +184,7 @@ sub sendData {
$req->header('content-length' => length($msg)); $req->header('content-length' => length($msg));
$req->header('connection' => 'Close'); $req->header('connection' => 'Close');
$req->content($msg); $req->content(encode('UTF-8',$msg));
my $resp = $ua->request($req); my $resp = $ua->request($req);
my $resp_msg = $resp->decoded_content; my $resp_msg = $resp->decoded_content;
@ -196,7 +214,7 @@ sub getUUID {
$uuid = $Config{ZM_TELEMETRY_UUID} = $sth->fetchrow_array(); $uuid = $Config{ZM_TELEMETRY_UUID} = $sth->fetchrow_array();
$sth->finish(); $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() ); $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() );
$res = $sth->execute( "$uuid" ) or die( "Can't execute: ".$sth->errstr() ); $res = $sth->execute( "$uuid" ) or die( "Can't execute: ".$sth->errstr() );
$sth->finish(); $sth->finish();
@ -232,9 +250,9 @@ sub countQuery {
my $dbh = shift; my $dbh = shift;
my $table = shift; my $table = shift;
my $sql = "SELECT count(*) FROM $table"; my $sql = "SELECT count(*) FROM `$table`";
my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); 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 $res = $sth->execute() or die 'Can\'t execute: '.$sth->errstr();
my $count = $sth->fetchrow_array(); my $count = $sth->fetchrow_array();
$sth->finish(); $sth->finish();
@ -245,7 +263,7 @@ sub countQuery {
sub getMonitorRef { sub getMonitorRef {
my $dbh = shift; 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 $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 $res = $sth->execute() or die( "Can't execute: ".$sth->errstr() );
my $arrayref = $sth->fetchall_arrayref({}); my $arrayref = $sth->fetchall_arrayref({});
@ -363,7 +381,7 @@ zmtelemetry.pl - Send usage information to the ZoneMinder development team
=head1 SYNOPSIS =head1 SYNOPSIS
zmtelemetry.pl [--force] [--help] [--interval=seconds] [--version] zmtelemetry.pl [--force] [--help] [--show] [--interval=seconds] [--version]
=head1 DESCRIPTION =head1 DESCRIPTION
@ -380,6 +398,7 @@ console under Options.
--force Force the script to upload it's data instead of waiting --force Force the script to upload it's data instead of waiting
for the defined interval since last upload. for the defined interval since last upload.
--help Display usage information --help Display usage information
--show Displays telemetry data that is sent to zoneminder
--interval Override the default configured interval since last upload. --interval Override the default configured interval since last upload.
The value should be given in seconds, but can be an expression The value should be given in seconds, but can be an expression
such as 24*60*60. such as 24*60*60.

View File

@ -329,14 +329,13 @@ sub loadMonitor {
} # end sub loadMonitor } # end sub loadMonitor
sub loadMonitors { sub loadMonitors {
Debug('Loading monitors');
$monitor_reload_time = time(); $monitor_reload_time = time();
my %new_monitors = (); my %new_monitors = ();
my $sql = q`SELECT * FROM Monitors my $sql = 'SELECT * FROM `Monitors`
WHERE find_in_set( Function, 'Modect,Mocord,Nodect' )`. WHERE find_in_set( `Function`, \'Modect,Mocord,Nodect\' )'.
( $Config{ZM_SERVER_ID} ? ' AND ServerId=?' : '' ) ( $Config{ZM_SERVER_ID} ? ' AND `ServerId`=?' : '' )
; ;
my $sth = $dbh->prepare_cached( $sql ) my $sth = $dbh->prepare_cached( $sql )
or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); or Fatal( "Can't prepare '$sql': ".$dbh->errstr() );
@ -357,11 +356,16 @@ sub handleMessage {
my $connection = shift; my $connection = shift;
my $message = 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 ) my ( $id, $action, $score, $cause, $text, $showtext )
= split( /\|/, $message ); = split( /\|/, $message );
$score = 0 if ( !defined($score) ); $score = 0 if !defined($score);
$cause = '' if ( !defined($cause) ); $cause = '' if !defined($cause);
$text = '' if ( !defined($text) ); $text = '' if !defined($text);
my $monitor = $monitors{$id}; my $monitor = $monitors{$id};
if ( !$monitor ) { if ( !$monitor ) {
@ -373,7 +377,7 @@ sub handleMessage {
next if !zmMemVerify($monitor); next if !zmMemVerify($monitor);
Debug("Handling action '$action'"); Debug("Handling action '$action'");
if ( $action =~ /^(enable|disable)(?:\+(\d+))?$/ ) { if ( $action =~ /^(enable|disable)(?:[\+ ](\d+))?$/ ) {
my $state = $1; my $state = $1;
my $delay = $2; my $delay = $2;
if ( $state eq 'enable' ) { if ( $state eq 'enable' ) {

View File

@ -847,9 +847,9 @@ if ( $version ) {
} }
$cascade = !undef; $cascade = !undef;
} }
if ( $cascade || $version eq "1.24.4" ) { if ( $cascade || $version eq '1.24.4' ) {
# Patch the database # Patch the database
patchDB( $dbh, "1.24.4" ); patchDB($dbh, '1.24.4');
# Copy the FTP specific values to the new general config # Copy the FTP specific values to the new general config
my $fetchSql = "select * from Config where Name like 'ZM_UPLOAD_FTP_%'"; my $fetchSql = "select * from Config where Name like 'ZM_UPLOAD_FTP_%'";
@ -863,12 +863,12 @@ if ( $version ) {
} }
$cascade = !undef; $cascade = !undef;
} }
if ( $cascade || $version lt "1.26.0" ) { if ( $cascade || $version lt '1.26.0' ) {
my $sth = $dbh->prepare_cached( 'select * from Monitors LIMIT 0,1' ); my $sth = $dbh->prepare_cached('SELECT * FROM Monitors LIMIT 0,1');
die "Error: " . $dbh->errstr . "\n" unless ($sth); die "Error: " . $dbh->errstr . "\n" unless ($sth);
die "Error: " . $sth->errstr . "\n" unless ($sth->execute); die "Error: " . $sth->errstr . "\n" unless ($sth->execute);
my $columns = $sth->{'NAME'}; my $columns = $sth->{NAME};
if ( ! grep(/^Colours$/, @$columns ) ) { if ( ! grep(/^Colours$/, @$columns ) ) {
$dbh->do(q{alter table Monitors add column `Colours` tinyint(3) unsigned NOT NULL default '1' after `Height`;}); $dbh->do(q{alter table Monitors add column `Colours` tinyint(3) unsigned NOT NULL default '1' after `Height`;});
} # end if } # end if
@ -898,28 +898,31 @@ if ( $version ) {
die "Should have found upgrade scripts at $updateDir\n"; die "Should have found upgrade scripts at $updateDir\n";
} # end if } # 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 ) { foreach my $patch ( @files ) {
my ( $v ) = $patch =~ /^zm_update\-([\d\.]+)\.sql$/; my ( $v ) = $patch =~ /^zm_update\-([\d\.]+)\.sql$/;
#PP make sure we use version compare #PP make sure we use version compare
if ( version->parse('v' . $v) > version->parse('v' . $version) ) { if ( version->parse('v'.$v) > version->parse('v'.$version) ) {
print( "Upgrading DB to $v from $version\n" ); print("Upgrading DB to $v from $version\n");
patchDB( $dbh, $v ); if ( patchDB($dbh, $v) ) {
my $sql = "update Config set Value = ? where Name = 'ZM_DYN_DB_VERSION'"; my $res = $sth->execute($version) or die( "Can't execute: ".$sth->errstr() );
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();
#patchDB_using_do( $dbh, $version, $updateDir.'/'.$patch ); #patchDB_using_do( $dbh, $version, $updateDir.'/'.$patch );
} # end if newer version } # end if newer version
} # end foreach patchfile } # end foreach patchfile
$sth->finish();
$cascade = !undef; $cascade = !undef;
} # end if } # end if
if ( $cascade ) { if ( $cascade ) {
my $installed_version = ZM_VERSION; # 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 $sql = 'UPDATE `Config` SET `Value` = ? WHERE `Name` = ?';
my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); 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() ); $sth->execute(ZM_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_CURR_VERSION') or die( "Can't execute: ".$sth->errstr() );
$sth->finish(); $sth->finish();
} else { } else {
zmDbDisconnect(); zmDbDisconnect();
@ -930,41 +933,42 @@ if ( $version ) {
#my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); #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 $res = $sth->execute( ) or die( "Can't execute: ".$sth->errstr() );
#$sth->finish(); #$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(); zmDbDisconnect();
exit( 0 ); exit(0);
sub patchDB_using_do { sub patchDB_using_do {
my ( $dbh, $version, $file ) = @_; 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; $/ = undef;
my $sql = <$fh>; my $sql = <$fh>;
close $fh; close $fh;
if ( $sql ) { if ( $sql ) {
$dbh->{'AutoCommit'} = 0; $dbh->{AutoCommit} = 0;
$dbh->do($sql); $dbh->do($sql);
if ( $dbh->errstr() ) { if ( $dbh->errstr() ) {
$dbh->rollback(); $dbh->rollback();
die "Error: " . $dbh->errstr(). ". Rolled back.\n"; die 'Error: '.$dbh->errstr().". Rolled back.\n";
} # end if error } # end if error
my $sql = "update Config set Value = ? where Name = 'ZM_DYN_DB_VERSION'"; 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 $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 $res = $sth->execute($version) or die 'Can\'t execute: '.$sth->errstr();
$sth->finish(); $sth->finish();
$dbh->{'AutoCommit'} = 1; $dbh->{AutoCommit} = 1;
} else { } else {
Warning("Empty db update file at $file"); Warning("Empty db update file at $file");
} }
} } # end sub patchDB_using_do
sub patchDB { sub patchDB {
my $dbh = shift; my $dbh = shift;
my $version = shift; my $version = shift;
my ( $host, $portOrSocket ) = ( $Config{ZM_DB_HOST} =~ /^([^:]+)(?::(.+))?$/ ); my ( $host, $portOrSocket ) = ( $Config{ZM_DB_HOST} =~ /^([^:]+)(?::(.+))?$/ );
my $command = 'mysql'; my $command = 'mysql';
if ( defined($portOrSocket) ) { if ( defined($portOrSocket) ) {
@ -988,39 +992,38 @@ sub patchDB {
} }
$command .= '/zm_update-'.$version.'.sql'; $command .= '/zm_update-'.$version.'.sql';
print( "Executing '$command'\n" ) if ( logDebugging() ); print("Executing '$command'\n") if logDebugging();
my $output = qx($command); my $output = qx($command);
my $status = $? >> 8; my $status = $? >> 8;
if ( $status || logDebugging() ) { if ( $status || logDebugging() ) {
chomp( $output ); chomp($output);
print( "Output: $output\n" ); print("Output: $output\n");
} }
if ( $status ) { 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 { sub migratePasswords {
print ("Migratings passwords, if any...\n"); print ("Migratings passwords, if any...\n");
my $sql = "select * from Users"; my $sql = 'SELECT * FROM `Users`';
my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); 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 $res = $sth->execute() or die("Can't execute: ".$sth->errstr());
while( my $user = $sth->fetchrow_hashref() ) { while( my $user = $sth->fetchrow_hashref() ) {
my $scheme = substr($user->{Password}, 0, 1); my $scheme = substr($user->{Password}, 0, 1);
if ($scheme eq "*") { if ($scheme eq '*') {
print ("-->".$user->{Username}. " password will be migrated\n"); print ('-->'.$user->{Username}." password will be migrated\n");
my $salt = Crypt::Eksblowfish::Bcrypt::en_base64(rand_bits(16*8)); my $salt = Crypt::Eksblowfish::Bcrypt::en_base64(rand_bits(16*8));
my $settings = '$2a$10$'.$salt; my $settings = '$2a$10$'.$salt;
my $pass_hash = Crypt::Eksblowfish::Bcrypt::bcrypt($user->{Password},$settings); my $pass_hash = Crypt::Eksblowfish::Bcrypt::bcrypt($user->{Password},$settings);
my $new_pass_hash = "-ZM-".$pass_hash; my $new_pass_hash = '-ZM-'.$pass_hash;
$sql = "UPDATE Users SET PASSWORD=? WHERE Username=?"; $sql = 'UPDATE Users SET `Password`=? WHERE `Username`=?';
my $sth = $dbh->prepare_cached( $sql ) or die( "Can't prepare '$sql': ".$dbh->errstr() ); 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() ); my $res = $sth->execute($new_pass_hash, $user->{Username}) or die("Can't execute: ".$sth->errstr());
}
} }
} }
} # end sub migratePasswords
sub migratePaths { sub migratePaths {

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +1,81 @@
#include "zm.h" #include "zm.h"
#include "zm_crypt.h" #include "zm_crypt.h"
#include "BCrypt.hpp" #include "BCrypt.hpp"
#include "jwt.h" #if HAVE_LIBJWT
#include <jwt.h>
#else
#include "jwt_cpp.h"
#endif
#include <algorithm> #include <algorithm>
#if HAVE_LIBCRYPTO
#include <openssl/sha.h> #include <openssl/sha.h>
#elif HAVE_GNUTLS_GNUTLS_H
#include <gnutls/gnutls.h>
#include <gnutls/crypto.h>
#endif
#include <string.h> #include <string.h>
// returns username if valid, "" if not // returns username if valid, "" if not
#if HAVE_LIBJWT
std::pair <std::string, unsigned int> 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 <std::string, unsigned int> verifyToken(std::string jwt_token_str, std::string key) { std::pair <std::string, unsigned int> verifyToken(std::string jwt_token_str, std::string key) {
std::string username = ""; std::string username = "";
unsigned int token_issued_at = 0; unsigned int token_issued_at = 0;
@ -58,6 +127,7 @@ std::pair <std::string, unsigned int> verifyToken(std::string jwt_token_str, std
} }
return std::make_pair(username, token_issued_at); 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 verifyPassword(const char *username, const char *input_password, const char *db_password_hash) {
bool password_correct = false; bool password_correct = false;
@ -70,10 +140,16 @@ bool verifyPassword(const char *username, const char *input_password, const char
// MYSQL PASSWORD // MYSQL PASSWORD
Debug(1, "%s is using an MD5 encoded password", username); 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_interim[SHA_DIGEST_LENGTH];
unsigned char digest_final[SHA_DIGEST_LENGTH]; unsigned char digest_final[SHA_DIGEST_LENGTH];
#if HAVE_LIBCRYPTO
SHA_CTX ctx1, ctx2;
//get first iteration //get first iteration
SHA1_Init(&ctx1); SHA1_Init(&ctx1);
SHA1_Update(&ctx1, input_password, strlen(input_password)); 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_Init(&ctx2);
SHA1_Update(&ctx2, digest_interim,SHA_DIGEST_LENGTH); SHA1_Update(&ctx2, digest_interim,SHA_DIGEST_LENGTH);
SHA1_Final (digest_final, &ctx2); 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]; char final_hash[SHA_DIGEST_LENGTH * 2 +2];
final_hash[0] = '*'; final_hash[0] = '*';

View File

@ -40,7 +40,7 @@ bool zmDbConnect() {
Error("Can't initialise database connection: %s", mysql_error(&dbconn)); Error("Can't initialise database connection: %s", mysql_error(&dbconn));
return false; return false;
} }
my_bool reconnect = 1; bool reconnect = 1;
if ( mysql_options(&dbconn, MYSQL_OPT_RECONNECT, &reconnect) ) if ( mysql_options(&dbconn, MYSQL_OPT_RECONNECT, &reconnect) )
Error("Can't set database auto reconnect option: %s", mysql_error(&dbconn)); Error("Can't set database auto reconnect option: %s", mysql_error(&dbconn));
if ( !staticConfig.DB_SSL_CA_CERT.empty() ) if ( !staticConfig.DB_SSL_CA_CERT.empty() )

View File

@ -76,7 +76,7 @@ bool EventStream::loadInitialEventData(int monitor_id, time_t event_time) {
curr_frame_id = 1; // curr_frame_id is 1-based curr_frame_id = 1; // curr_frame_id is 1-based
if ( event_time >= event_data->start_time ) { if ( event_time >= event_data->start_time ) {
Debug(2, "event time is after event start"); 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 ); //Info( "eft %d > et %d", event_data->frames[i].timestamp, event_time );
if ( event_data->frames[i].timestamp >= event_time ) { if ( event_data->frames[i].timestamp >= event_time ) {
curr_frame_id = i+1; 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 ) { if ( init_frame_id >= event_data->frame_count ) {
Error("Invalid frame id specified. %d > %d", 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_stream_time = event_data->start_time;
curr_frame_id = 1;
} else { } else {
curr_stream_time = event_data->frames[init_frame_id-1].timestamp; curr_stream_time = event_data->frames[init_frame_id-1].timestamp;
curr_frame_id = init_frame_id; curr_frame_id = init_frame_id;
@ -117,7 +118,7 @@ bool EventStream::loadEventData(uint64_t event_id) {
snprintf(sql, sizeof(sql), snprintf(sql, sizeof(sql),
"SELECT `MonitorId`, `StorageId`, `Frames`, unix_timestamp( `StartTime` ) AS StartTimestamp, " "SELECT `MonitorId`, `StorageId`, `Frames`, unix_timestamp( `StartTime` ) AS StartTimestamp, "
"(SELECT max(`Delta`)-min(`Delta`) FROM `Frames` WHERE `EventId`=`Events`.`Id`) AS Duration, " "(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) ) { if ( mysql_query(&dbconn, sql) ) {
Error("Can't run query: %s", mysql_error(&dbconn)); 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->scheme = Storage::SHALLOW;
} }
event_data->SaveJPEGs = dbrow[7] == NULL ? 0 : atoi(dbrow[7]); 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); 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(); const char *storage_path = storage->Path();
if ( event_data->scheme == Storage::DEEP ) { 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, staticConfig.PATH_WEB.c_str(), storage_path, event_data->monitor_id,
event_data->event_id); event_data->event_id);
} }
delete storage; storage = NULL;
updateFrameRate((double)event_data->frame_count/event_data->duration); updateFrameRate((double)event_data->frame_count/event_data->duration);
Debug(3, "fps set by frame_count(%d)/duration(%f)", 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); 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); std::string filepath = std::string(event_data->path) + "/" + std::string(event_data->video_file);
//char filepath[PATH_MAX]; //char filepath[PATH_MAX];
//snprintf(filepath, sizeof(filepath), "%s/%s", event_data->path, event_data->video_file); //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; paused = true;
replay_rate = ZM_RATE_BASE; replay_rate = ZM_RATE_BASE;
step = 1; step = 1;
if ( (unsigned int)curr_frame_id < event_data->frame_count )
curr_frame_id += 1;
break; break;
case CMD_SLOWREV : case CMD_SLOWREV :
Debug(1, "Got SLOW REV command"); Debug(1, "Got SLOW REV command");
paused = true; paused = true;
replay_rate = ZM_RATE_BASE; replay_rate = ZM_RATE_BASE;
step = -1; step = -1;
curr_frame_id -= 1;
if ( curr_frame_id < 1 ) curr_frame_id = 1;
break; break;
case CMD_FASTREV : case CMD_FASTREV :
Debug(1, "Got FAST REV command"); Debug(1, "Got FAST REV command");
@ -697,9 +720,10 @@ Debug(1, "Loading image");
} else if ( ffmpeg_input ) { } else if ( ffmpeg_input ) {
// Get the frame from the mp4 input // Get the frame from the mp4 input
Debug(1,"Getting frame from ffmpeg"); Debug(1,"Getting frame from ffmpeg");
AVFrame *frame;
FrameData *frame_data = &event_data->frames[curr_frame_id-1]; 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 ) { if ( frame ) {
image = new Image(frame); image = new Image(frame);
//av_frame_free(&frame); //av_frame_free(&frame);
@ -707,6 +731,34 @@ Debug(1, "Loading image");
Error("Failed getting a frame."); Error("Failed getting a frame.");
return false; 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 { } else {
Error("Unable to get a frame"); Error("Unable to get a frame");
return false; return false;
@ -736,6 +788,10 @@ Debug(1, "Loading image");
Fatal("Unexpected frame type %d", type); Fatal("Unexpected frame type %d", type);
break; break;
} }
if ( send_image != image ) {
delete send_image;
send_image = NULL;
}
delete image; delete image;
image = NULL; image = NULL;
} // end if send_raw or not } // end if send_raw or not
@ -824,17 +880,13 @@ void EventStream::runStream() {
// commands may set send_frame to true // commands may set send_frame to true
while ( checkCommandQueue() && !zm_terminate ) { while ( checkCommandQueue() && !zm_terminate ) {
// The idea is to loop here processing all commands before proceeding. // 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. // 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 ) { if ( now.tv_sec - last_comm_update.tv_sec > 3600 ) {
touch(sock_path_lock); touch(sock_path_lock);
last_comm_update = now; last_comm_update = now;
} }
} else {
Debug(2, "Not checking command queue");
} }
// Get current frame data // Get current frame data
@ -851,7 +903,7 @@ void EventStream::runStream() {
send_frame = true; send_frame = true;
} }
} else if ( step != 0 ) { } 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 // We are paused and are just stepping forward or backward one frame
step = 0; step = 0;
send_frame = true; send_frame = true;
@ -990,30 +1042,30 @@ void EventStream::runStream() {
//if ( step != 0 )// Adding 0 is cheaper than an if 0 //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 starts at 1 though, so we might skip the first frame?
curr_frame_id += step; curr_frame_id += step;
} // end if !paused
// Detects when we hit end of event and will load the next event or previous event // Detects when we hit end of event and will load the next event or previous event
if ( checkEventLoaded() ) { if ( checkEventLoaded() ) {
// Have change of event // Have change of event
// This next bit is to determine if we are in the current event time wise // 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. // and whether to show an image saying how long until the next event.
if ( replay_rate > 0 ) { if ( replay_rate > 0 ) {
// This doesn't make sense unless we have hit the end of the event. // 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; 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)", Debug(1, "replay rate(%d) time_to_event(%f)=frame timestamp:%f - curr_stream_time(%f)",
replay_rate, time_to_event, replay_rate, time_to_event,
event_data->frames[0].timestamp, event_data->frames[0].timestamp,
curr_stream_time); curr_stream_time);
} else if ( replay_rate < 0 ) { } else if ( replay_rate < 0 ) {
time_to_event = curr_stream_time - event_data->frames[event_data->frame_count-1].timestamp; 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", 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); replay_rate, time_to_event, curr_stream_time, event_data->frames[event_data->frame_count-1].timestamp);
} } // end if forward or reverse
} } // end if checkEventLoaded
} // end while ! zm_terminate } // end if !paused
} // end while ! zm_terminate
#if HAVE_LIBAVCODEC #if HAVE_LIBAVCODEC
if ( type == STREAM_MPEG ) if ( type == STREAM_MPEG )
delete vid_stream; delete vid_stream;
@ -1022,18 +1074,12 @@ void EventStream::runStream() {
closeComms(); closeComms();
} // void EventStream::runStream() } // 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); loadInitialEventData(init_event_id, init_frame_id);
if ( !(monitor = Monitor::Load(event_data->monitor_id, false, Monitor::QUERY)) ) { } // end void EventStream::setStreamStart(init_event_id,init_frame_id=0)
Fatal("Unable to load monitor id %d for streaming", event_data->monitor_id);
return;
}
}
void EventStream::setStreamStart(int monitor_id, time_t event_time) { void EventStream::setStreamStart(int monitor_id, time_t event_time) {
loadInitialEventData(monitor_id, 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;
}
} }

View File

@ -66,6 +66,7 @@ class EventStream : public StreamBase {
char video_file[PATH_MAX]; char video_file[PATH_MAX];
Storage::Schemes scheme; Storage::Schemes scheme;
int SaveJPEGs; int SaveJPEGs;
Monitor::Orientation Orientation;
}; };
protected: protected:
@ -82,6 +83,7 @@ class EventStream : public StreamBase {
struct timeval start; // clock time when started the event struct timeval start; // clock time when started the event
EventData *event_data; EventData *event_data;
Storage *storage;
FFmpeg_Input *ffmpeg_input; FFmpeg_Input *ffmpeg_input;
protected: protected:
@ -96,7 +98,7 @@ class EventStream : public StreamBase {
public: public:
EventStream() { EventStream() {
mode = DEFAULT_MODE; mode = DEFAULT_MODE;
replay_rate = DEFAULT_RATE; replay_rate = DEFAULT_RATE;
forceEventChange = false; forceEventChange = false;
@ -104,13 +106,32 @@ class EventStream : public StreamBase {
curr_stream_time = 0.0; curr_stream_time = 0.0;
send_frame = false; send_frame = false;
event_data = 0; event_data = NULL;
// Used when loading frames from an mp4 // Used when loading frames from an mp4
input_codec_context = 0; input_codec_context = 0;
input_codec = 0; input_codec = 0;
ffmpeg_input = NULL; 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( uint64_t init_event_id, unsigned int init_frame_id );
void setStreamStart( int monitor_id, time_t event_time ); void setStreamStart( int monitor_id, time_t event_time );

View File

@ -81,7 +81,7 @@ void FFMPEGInit() {
av_log_set_callback(log_libav_callback); av_log_set_callback(log_libav_callback);
Info("Enabling ffmpeg logs, as LOG_DEBUG+LOG_FFMPEG are enabled in options"); Info("Enabling ffmpeg logs, as LOG_DEBUG+LOG_FFMPEG are enabled in options");
} else { } 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); av_log_set_level(AV_LOG_QUIET);
} }
#if !LIBAVFORMAT_VERSION_CHECK(58, 9, 0, 64, 0) #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) #if LIBAVCODEC_VERSION_CHECK(57, 64, 0, 64, 0)
void zm_dump_codecpar ( const AVCodecParameters *par ) { 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)", 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, par->codec_type,
par->codec_id, av_get_media_type_string(par->codec_type),
avcodec_get_name(par->codec_id), par->codec_id,
par->codec_tag, avcodec_get_name(par->codec_id),
par->width, par->codec_tag,
par->height, par->width,
par->bit_rate, par->height,
par->format, par->bit_rate,
((AVPixelFormat)par->format == AV_PIX_FMT_NONE ? "none" : av_get_pix_fmt_name((AVPixelFormat)par->format)) par->format,
); (((AVPixelFormat)par->format == AV_PIX_FMT_NONE) ? "none" : av_get_pix_fmt_name((AVPixelFormat)par->format))
);
} }
#endif #endif

View File

@ -517,19 +517,19 @@ int FfmpegCamera::OpenFfmpeg() {
Debug(1, "Selected hw_pix_fmt %d %s", Debug(1, "Selected hw_pix_fmt %d %s",
hw_pix_fmt, av_get_pix_fmt_name(hw_pix_fmt)); 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, ret = av_hwdevice_ctx_create(&hw_device_ctx, type,
(hwaccel_device != "" ? hwaccel_device.c_str(): NULL), NULL, 0); (hwaccel_device != "" ? hwaccel_device.c_str(): NULL), NULL, 0);
if ( ret < 0 ) { if ( ret < 0 ) {
Error("Failed to create hwaccel device."); Error("Failed to create hwaccel device. %s",av_make_error_string(ret).c_str());
return -1; 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 { } else {
Debug(1, "Failed to setup hwaccel."); Debug(1, "Failed to find suitable hw_pix_fmt.");
} }
#else #else
Debug(1, "AVCodec not new enough for hwaccel"); Debug(1, "AVCodec not new enough for hwaccel");
@ -537,7 +537,7 @@ int FfmpegCamera::OpenFfmpeg() {
#else #else
Warning("HWAccel support not compiled in."); Warning("HWAccel support not compiled in.");
#endif #endif
} // end if hwacel_name } // end if hwaccel_name
// Open the codec // Open the codec
#if !LIBAVFORMAT_VERSION_CHECK(53, 8, 0, 8, 0) #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()); Error("Unable to allocate frame for %s", mPath.c_str());
return -1; return -1;
} }
mFrame->width = width;
mFrame->height = height;
#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) #if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0)
int pSize = av_image_get_buffer_size(imagePixFormat, width, height, 1); int pSize = av_image_get_buffer_size(imagePixFormat, width, height, 1);
@ -630,20 +632,6 @@ int FfmpegCamera::OpenFfmpeg() {
return -1; 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 #else // HAVE_LIBSWSCALE
Fatal("You must compile ffmpeg with the --enable-swscale " Fatal("You must compile ffmpeg with the --enable-swscale "
"option to use ffmpeg cameras"); "option to use ffmpeg cameras");
@ -1087,9 +1075,12 @@ int FfmpegCamera::transfer_to_image(
return -1; return -1;
} }
#if LIBAVUTIL_VERSION_CHECK(54, 6, 0, 6, 0) #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( int size = av_image_fill_arrays(
output_frame->data, output_frame->linesize, 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 ) { if ( size < 0 ) {
Error("Problem setting up data pointers into image %s", Error("Problem setting up data pointers into image %s",
av_make_error_string(size).c_str()); av_make_error_string(size).c_str());
@ -1128,19 +1119,31 @@ int FfmpegCamera::transfer_to_image(
mConvertContext, input_frame->data, input_frame->linesize, mConvertContext, input_frame->data, input_frame->linesize,
0, mVideoCodecContext->height, 0, mVideoCodecContext->height,
output_frame->data, output_frame->linesize); output_frame->data, output_frame->linesize);
if ( ret <= 0 ) { 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", 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->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, imagePixFormat,
av_get_pix_fmt_name(imagePixFormat), av_get_pix_fmt_name(imagePixFormat),
output_frame->linesize, output_frame->linesize[0], output_frame->linesize[1],
frameCount, frameCount,
mVideoCodecContext->pix_fmt, av_get_pix_fmt_name(mVideoCodecContext->pix_fmt), mVideoCodecContext->pix_fmt, av_get_pix_fmt_name(mVideoCodecContext->pix_fmt),
mVideoCodecContext->height,
ret ret
); );
return -1; 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 #else // HAVE_LIBSWSCALE
Fatal("You must compile ffmpeg with the --enable-swscale " Fatal("You must compile ffmpeg with the --enable-swscale "
"option to use ffmpeg cameras"); "option to use ffmpeg cameras");

View File

@ -10,14 +10,31 @@ FFmpeg_Input::FFmpeg_Input() {
FFMPEGInit(); FFMPEGInit();
streams = NULL; streams = NULL;
frame = NULL; frame = NULL;
last_seek_request = -1;
} }
FFmpeg_Input::~FFmpeg_Input() { FFmpeg_Input::~FFmpeg_Input() {
if ( streams ) { 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; delete streams;
streams = NULL; 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) { int FFmpeg_Input::Open(const char *filepath) {
@ -89,6 +106,7 @@ int FFmpeg_Input::Open(const char *filepath) {
avcodec_free_context(&streams[i].context); avcodec_free_context(&streams[i].context);
#endif #endif
avformat_close_input(&input_format_context); avformat_close_input(&input_format_context);
input_format_context = NULL;
return error; return error;
} }
} // end foreach stream } // end foreach stream
@ -102,8 +120,6 @@ int FFmpeg_Input::Open(const char *filepath) {
} // end int FFmpeg_Input::Open( const char * filepath ) } // end int FFmpeg_Input::Open( const char * filepath )
AVFrame *FFmpeg_Input::get_frame(int stream_id) { AVFrame *FFmpeg_Input::get_frame(int stream_id) {
Debug(4, "Getting frame from stream %d", stream_id);
int frameComplete = false; int frameComplete = false;
AVPacket packet; AVPacket packet;
av_init_packet(&packet); av_init_packet(&packet);
@ -144,6 +160,12 @@ AVFrame *FFmpeg_Input::get_frame(int stream_id) {
zm_av_packet_unref(&packet); zm_av_packet_unref(&packet);
av_frame_free(&frame); av_frame_free(&frame);
continue; 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; 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 // Have to grab a frame to update our current frame to know where we are
get_frame(stream_id); get_frame(stream_id);
zm_dump_frame(frame, "Got first frame, returning it"); } // end if ! frame
return 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"); zm_dump_frame(frame, "frame->pts > seek_target, seek backwards");
// our frame must be beyond our seek target. so go backwards to before it // 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, 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"); zm_dump_frame(frame, "frame->pts > seek_target, got");
} // end if frame->pts > seek_target } // 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. // 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 ) { if ( frame->pts <= seek_target ) {
zm_dump_frame(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); 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)

View File

@ -42,6 +42,7 @@ class FFmpeg_Input {
int audio_stream_id; int audio_stream_id;
AVFormatContext *input_format_context; AVFormatContext *input_format_context;
AVFrame *frame; AVFrame *frame;
int64_t last_seek_request;
}; };
#endif #endif

View File

@ -48,6 +48,8 @@ static short *g_v_table;
static short *g_u_table; static short *g_u_table;
static short *b_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::writejpg_ccinfo[101] = { 0 };
jpeg_compress_struct *Image::encodejpg_ccinfo[101] = { 0 }; jpeg_compress_struct *Image::encodejpg_ccinfo[101] = { 0 };
jpeg_decompress_struct *Image::readjpg_dcinfo = 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(); update_function_pointers();
} }
Image::Image( const AVFrame *frame ) { Image::Image(const AVFrame *frame) {
AVFrame *dest_frame = zm_av_frame_alloc(); AVFrame *dest_frame = zm_av_frame_alloc();
text[0] = '\0'; text[0] = '\0';
@ -183,26 +185,28 @@ Image::Image( const AVFrame *frame ) {
#endif #endif
#if HAVE_LIBSWSCALE #if HAVE_LIBSWSCALE
struct SwsContext *mConvertContext = sws_getContext( sws_convert_context = sws_getCachedContext(
sws_convert_context,
width, width,
height, height,
(AVPixelFormat)frame->format, (AVPixelFormat)frame->format,
width, height, width, height,
AV_PIX_FMT_RGBA, SWS_BICUBIC, NULL, AV_PIX_FMT_RGBA, SWS_BICUBIC, NULL,
NULL, NULL); NULL, NULL);
if ( mConvertContext == NULL ) if ( sws_convert_context == NULL )
Fatal( "Unable to create conversion context" ); 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); Fatal("Unable to convert raw format %u to target format %u", frame->format, AV_PIX_FMT_RGBA);
#else // HAVE_LIBSWSCALE #else // HAVE_LIBSWSCALE
Fatal("You must compile ffmpeg with the --enable-swscale option to use ffmpeg cameras"); Fatal("You must compile ffmpeg with the --enable-swscale option to use ffmpeg cameras");
#endif // HAVE_LIBSWSCALE #endif // HAVE_LIBSWSCALE
av_frame_free( &dest_frame ); av_frame_free(&dest_frame);
update_function_pointers(); update_function_pointers();
} } // end Image::Image(const AVFrame *frame)
Image::Image( const Image &p_image ) { Image::Image(const Image &p_image) {
if ( !initialised ) if ( !initialised )
Initialise(); Initialise();
width = p_image.width; width = p_image.width;
@ -215,7 +219,7 @@ Image::Image( const Image &p_image ) {
holdbuffer = 0; holdbuffer = 0;
AllocImgBuffer(size); AllocImgBuffer(size);
(*fptr_imgbufcpy)(buffer, p_image.buffer, 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(); update_function_pointers();
} }
@ -225,35 +229,39 @@ Image::~Image() {
/* Should be called as part of program shutdown to free everything */ /* Should be called as part of program shutdown to free everything */
void Image::Deinitialise() { void Image::Deinitialise() {
if ( initialised ) { if ( !initialised ) return;
/* /*
delete[] y_table; delete[] y_table;
delete[] uv_table; delete[] uv_table;
delete[] r_v_table; delete[] r_v_table;
delete[] g_v_table; delete[] g_v_table;
delete[] g_u_table; delete[] g_u_table;
delete[] b_u_table; delete[] b_u_table;
*/ */
initialised = false; initialised = false;
if ( readjpg_dcinfo ) { if ( readjpg_dcinfo ) {
jpeg_destroy_decompress( readjpg_dcinfo ); jpeg_destroy_decompress( readjpg_dcinfo );
delete readjpg_dcinfo; delete readjpg_dcinfo;
readjpg_dcinfo = 0; 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 ( 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() { void Image::Initialise() {
/* Assign the blend pointer to function */ /* Assign the blend pointer to function */
@ -655,15 +663,16 @@ void Image::Assign( const Image &image ) {
return; 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 ( holdbuffer && buffer ) {
if (new_size > allocation) { if ( new_size > allocation ) {
Error("Held buffer is undersized for assigned buffer"); Error("Held buffer is undersized for assigned buffer");
return; return;
} }
} else { } else {
if(new_size > allocation || !buffer) { if ( new_size > allocation || !buffer) {
// DumpImgBuffer(); This is also done in AllocImgBuffer // DumpImgBuffer(); This is also done in AllocImgBuffer
AllocImgBuffer(new_size); AllocImgBuffer(new_size);
} }

View File

@ -121,7 +121,7 @@ protected:
}; };
inline void DumpImgBuffer() { inline void DumpImgBuffer() {
DumpBuffer(buffer,buffertype); DumpBuffer(buffer, buffertype);
buffer = NULL; buffer = NULL;
allocation = 0; allocation = 0;
} }

View File

@ -156,6 +156,7 @@ void Logger::initialise(const std::string &id, const Options &options) {
if ( options.mTerminalLevel != NOOPT ) if ( options.mTerminalLevel != NOOPT )
tempTerminalLevel = options.mTerminalLevel; tempTerminalLevel = options.mTerminalLevel;
// DEBUG1 == 1. So >= DEBUG1, we set to DEBUG9?! Why?
if ( options.mDatabaseLevel != NOOPT ) if ( options.mDatabaseLevel != NOOPT )
tempDatabaseLevel = options.mDatabaseLevel; tempDatabaseLevel = options.mDatabaseLevel;
else else
@ -359,7 +360,7 @@ Logger::Level Logger::databaseLevel(Logger::Level databaseLevel) {
if ( databaseLevel > NOOPT ) { if ( databaseLevel > NOOPT ) {
databaseLevel = limit(databaseLevel); databaseLevel = limit(databaseLevel);
if ( mDatabaseLevel != databaseLevel ) { if ( mDatabaseLevel != databaseLevel ) {
if ( databaseLevel > NOLOG && mDatabaseLevel <= NOLOG ) { if ( (databaseLevel > NOLOG) && (mDatabaseLevel <= NOLOG) ) { // <= NOLOG would be NOOPT
if ( !zmDbConnect() ) { if ( !zmDbConnect() ) {
databaseLevel = NOLOG; databaseLevel = NOLOG;
} }
@ -535,8 +536,11 @@ void Logger::logPrint(bool hex, const char * const filepath, const int line, con
fflush(stdout); fflush(stdout);
} }
if ( level <= mFileLevel ) { if ( level <= mFileLevel ) {
if ( !mLogFileFP ) if ( !mLogFileFP ) {
log_mutex.unlock();
openFile(); openFile();
log_mutex.lock();
}
if ( mLogFileFP ) { if ( mLogFileFP ) {
fputs(logString, mLogFileFP); fputs(logString, mLogFileFP);
if ( mFlush ) if ( mFlush )

View File

@ -413,14 +413,14 @@ Monitor::Monitor(
auto_resume_time = 0; 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; 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; event_close_mode = CLOSE_ALARM;
else else
event_close_mode = CLOSE_IDLE; event_close_mode = CLOSE_IDLE;
Debug( 1, "monitor purpose=%d", purpose ); Debug(1, "monitor purpose=%d", purpose);
mem_size = sizeof(SharedData) mem_size = sizeof(SharedData)
+ sizeof(TriggerData) + sizeof(TriggerData)
@ -440,17 +440,15 @@ Monitor::Monitor(
storage = new Storage(storage_id); storage = new Storage(storage_id);
Debug(1, "Storage path: %s", storage->Path()); Debug(1, "Storage path: %s", storage->Path());
// Should maybe store this for later use // 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); snprintf(monitor_dir, sizeof(monitor_dir), "%s/%d", storage->Path(), id);
if ( purpose == CAPTURE ) { if ( purpose == CAPTURE ) {
if ( mkdir(monitor_dir, 0755) ) { if ( mkdir(monitor_dir, 0755) && ( errno != EEXIST ) ) {
if ( errno != EEXIST ) { Error("Can't mkdir %s: %s", monitor_dir, strerror(errno));
Error("Can't mkdir %s: %s", monitor_dir, strerror(errno));
}
} }
if ( ! this->connect() ) { if ( !this->connect() ) {
Error("unable to connect, but doing capture"); Error("unable to connect, but doing capture");
exit(-1); exit(-1);
} }
@ -486,20 +484,16 @@ Monitor::Monitor(
video_store_data->size = sizeof(VideoStoreData); video_store_data->size = sizeof(VideoStoreData);
//video_store_data->frameNumber = 0; //video_store_data->frameNumber = 0;
} else if ( purpose == ANALYSIS ) { } 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->state = IDLE;
shared_data->last_read_time = 0; shared_data->last_read_time = 0;
shared_data->alarm_x = -1; shared_data->alarm_x = -1;
shared_data->alarm_y = -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 ); start_time = last_fps_time = time( 0 );
event = 0; event = 0;
@ -545,9 +539,8 @@ Monitor::Monitor(
FifoStream::fifo_create_if_missing(diag_path_d.c_str()); FifoStream::fifo_create_if_missing(diag_path_d.c_str());
} }
} }
} // end if purpose == ANALYSIS
} // end if purpose == ANALYSIS } // Monitor::Monitor
} // Monitor::Monitor
bool Monitor::connect() { bool Monitor::connect() {
Debug(3, "Connecting to monitor. Purpose is %d", purpose ); Debug(3, "Connecting to monitor. Purpose is %d", purpose );
@ -1815,7 +1808,7 @@ void Monitor::Reload() {
"`AlarmFrameCount`, `SectionLength`, `MinSectionLength`, `FrameSkip`, " "`AlarmFrameCount`, `SectionLength`, `MinSectionLength`, `FrameSkip`, "
"`MotionFrameSkip`, `AnalysisFPSLimit`, `AnalysisUpdateDelay`, `MaxFPS`, `AlarmMaxFPS`, " "`MotionFrameSkip`, `AnalysisFPSLimit`, `AnalysisUpdateDelay`, `MaxFPS`, `AlarmMaxFPS`, "
"`FPSReportInterval`, `RefBlendPerc`, `AlarmRefBlendPerc`, `TrackMotion`, " "`FPSReportInterval`, `RefBlendPerc`, `AlarmRefBlendPerc`, `TrackMotion`, "
"`SignalCheckColour` FROM `Monitors` WHERE `Id` = '%d'", id); "`SignalCheckPoints`, `SignalCheckColour` FROM `Monitors` WHERE `Id` = '%d'", id);
zmDbRow *row = zmDbFetchOne(sql); zmDbRow *row = zmDbFetchOne(sql);
if ( !row ) { if ( !row ) {
@ -1861,13 +1854,8 @@ void Monitor::Reload() {
alarm_ref_blend_perc = atoi(dbrow[index++]); alarm_ref_blend_perc = atoi(dbrow[index++]);
track_motion = atoi(dbrow[index++]); track_motion = atoi(dbrow[index++]);
signal_check_points = dbrow[index]?atoi(dbrow[index]):0;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++;
if ( dbrow[index][0] == '#' )
signal_check_colour = strtol(dbrow[index]+1,0,16);
else
signal_check_colour = strtol(dbrow[index],0,16);
index++;
shared_data->state = state = IDLE; shared_data->state = state = IDLE;
shared_data->alarm_x = shared_data->alarm_y = -1; shared_data->alarm_x = shared_data->alarm_y = -1;
@ -1880,7 +1868,7 @@ void Monitor::Reload() {
} // end if row } // end if row
ReloadZones(); ReloadZones();
} // end void Monitor::Reload() } // end void Monitor::Reload()
void Monitor::ReloadZones() { void Monitor::ReloadZones() {
Debug(1, "Reloading zones for monitor %s", name); 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 } // end int Monitor::LoadFfmpegMonitors
#endif // HAVE_LIBAVFORMAT #endif // HAVE_LIBAVFORMAT
/* /* For reference
std::string load_monitor_sql = std::string load_monitor_sql =
"SELECT Id, Name, ServerId, StorageId, Type, Function+0, Enabled, LinkedMonitors, " "SELECT `Id`, `Name`, `ServerId`, `StorageId`, `Type`, `Function`+0, `Enabled`, `LinkedMonitors`, "
"AnalysisFPSLimit, AnalysisUpdateDelay, MaxFPS, AlarmMaxFPS," "`AnalysisFPSLimit`, `AnalysisUpdateDelay`, `MaxFPS`, `AlarmMaxFPS`,"
"Device, Channel, Format, V4LMultiBuffer, V4LCapturesPerFrame, " // V4L Settings "`Device`, `Channel`, `Format`, `V4LMultiBuffer`, `V4LCapturesPerFrame`, " // V4L Settings
"Protocol, Method, Options, User, Pass, Host, Port, Path, Width, Height, Colours, Palette, Orientation+0, Deinterlacing, RTSPDescribe, " "`Protocol`, `Method`, `Options`, `User`, `Pass`, `Host`, `Port`, `Path`, `Width`, `Height`, `Colours`, `Palette`, `Orientation`+0, `Deinterlacing`, "
"SaveJPEGs, VideoWriter, EncoderParameters, "`DecoderHWAccelName`, `DecoderHWAccelDevice`, `RTSPDescribe`, "
"OutputCodec, Encoder, OutputContainer," "`SaveJPEGs`, `VideoWriter`, `EncoderParameters`, "
" RecordAudio, " //" OutputCodec, Encoder, OutputContainer, "
"Brightness, Contrast, Hue, Colour, " "`RecordAudio`, "
"EventPrefix, LabelFormat, LabelX, LabelY, LabelSize," "`Brightness`, `Contrast`, `Hue`, `Colour`, "
"ImageBufferCount, WarmupCount, PreEventCount, PostEventCount, StreamReplayBuffer, AlarmFrameCount, " "`EventPrefix`, `LabelFormat`, `LabelX`, `LabelY`, `LabelSize`,"
"SectionLength, MinSectionLength, FrameSkip, MotionFrameSkip, " "`ImageBufferCount`, `WarmupCount`, `PreEventCount`, `PostEventCount`, `StreamReplayBuffer`, `AlarmFrameCount`, "
"FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif, SignalCheckColour FROM Monitors"; "`SectionLength`, `MinSectionLength`, `FrameSkip`, `MotionFrameSkip`, "
"`FPSReportInterval`, `RefBlendPerc`, `AlarmRefBlendPerc`, `TrackMotion`, `Exif`, `SignalCheckPoints`, `SignalCheckColour` FROM `Monitors`";
*/ */
Monitor *Monitor::Load(MYSQL_ROW dbrow, bool load_zones, Purpose purpose) { 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_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; 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++; 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++; 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 ref_blend_perc = atoi(dbrow[col]); col++;
int alarm_ref_blend_perc = atoi(dbrow[col]); col++; int alarm_ref_blend_perc = atoi(dbrow[col]); col++;
int track_motion = 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_points = dbrow[col] ? atoi(dbrow[col]) : 0;col++;
int signal_check_color = strtol(dbrow[col][0] == '#' ? dbrow[col]+1 : dbrow[col], 0, 16); 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; Camera *camera = 0;
if ( type == "Local" ) { if ( type == "Local" ) {

View File

@ -1,21 +1,21 @@
// //
// ZoneMinder Monitor Class Implementation, $Date$, $Revision$ // ZoneMinder Monitor Class Implementation, $Date$, $Revision$
// Copyright (C) 2001-2008 Philip Coombes // Copyright (C) 2001-2008 Philip Coombes
// //
// This program is free software; you can redistribute it and/or // This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License // modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2 // as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version. // of the License, or (at your option) any later version.
// //
// This program is distributed in the hope that it will be useful, // This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details. // GNU General Public License for more details.
// //
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software // along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
// //
#include "zm.h" #include "zm.h"
#include "zm_db.h" #include "zm_db.h"
@ -73,7 +73,7 @@ bool MonitorStream::checkSwapPath(const char *path, bool create_path) {
return false; return false;
} }
return true; 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) { void MonitorStream::processCommand(const CmdMsg *msg) {
Debug( 2, "Got message, type %d, msg %d", msg->msg_type, msg->msg_data[0] ); 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->shared_data->active;
status_data.enabled = monitor->trigger_data->trigger_state!=Monitor::TRIGGER_OFF; status_data.enabled = monitor->trigger_data->trigger_state!=Monitor::TRIGGER_OFF;
status_data.forced = monitor->trigger_data->trigger_state==Monitor::TRIGGER_ON; 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.buffer_level,
status_data.delayed, status_data.delayed,
status_data.paused, 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 // Calculate how long it takes to actually send the frame
struct timeval frameStartTime; struct timeval frameStartTime;
gettimeofday(&frameStartTime, NULL); gettimeofday(&frameStartTime, NULL);
fputs("--ZoneMinderFrame\r\nContent-Type: image/jpeg\r\n", stdout); fputs("--ZoneMinderFrame\r\nContent-Type: image/jpeg\r\n", stdout);
fprintf(stdout, "Content-Length: %d\r\n\r\n", img_buffer_size); fprintf(stdout, "Content-Length: %d\r\n\r\n", img_buffer_size);
if ( fwrite(img_buffer, img_buffer_size, 1, stdout) != 1 ) { 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 // Calculate how long it takes to actually send the frame
struct timeval frameStartTime; struct timeval frameStartTime;
gettimeofday(&frameStartTime, NULL); gettimeofday(&frameStartTime, NULL);
fputs("--ZoneMinderFrame\r\n", stdout); fputs("--ZoneMinderFrame\r\n", stdout);
switch( type ) { switch( type ) {
case STREAM_JPEG : 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); fprintf(stdout, "Content-Length: %d\r\n\r\n", img_buffer_size);
if ( fwrite(img_buffer, img_buffer_size, 1, stdout) != 1 ) { 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 // 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)); 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", Warning("Frame send time %d msec too slow, throttling maxfps to %.2f",
frameSendTime, maxfps); frameSendTime, maxfps);
} }
} } // Not mpeg
last_frame_sent = TV_2_FLOAT(now); last_frame_sent = TV_2_FLOAT(now);
return true; return true;
} // end bool MonitorStream::sendFrame( Image *image, struct timeval *timestamp ) } // 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); 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 // 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_t stream_start_time;
time(&stream_start_time); time(&stream_start_time);
@ -474,22 +474,21 @@ void MonitorStream::runStream() {
Image *paused_image = NULL; Image *paused_image = NULL;
struct timeval paused_timestamp; 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 ) ) { 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 ) { 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); Error("Swap Path is too long. %d > %d ", total_swap_path_length+max_swap_len_suffix, PATH_MAX);
} else { } else {
swap_path = staticConfig.PATH_SWAP; swap_path = staticConfig.PATH_SWAP;
Debug( 3, "Checking swap path folder: %s", swap_path.c_str() ); Debug(3, "Checking swap path folder: %s", swap_path.c_str());
if ( checkSwapPath(swap_path.c_str(), true) ) { if ( checkSwapPath(swap_path.c_str(), true) ) {
swap_path += stringtf("/zmswap-m%d", monitor->Id()); swap_path += stringtf("/zmswap-m%d", monitor->Id());
@ -509,8 +508,8 @@ void MonitorStream::runStream() {
} else { } else {
Debug(2, "Assigning temporary buffer"); Debug(2, "Assigning temporary buffer");
temp_image_buffer = new SwapImage[temp_image_buffer_count]; temp_image_buffer = new SwapImage[temp_image_buffer_count];
memset( temp_image_buffer, 0, sizeof(*temp_image_buffer)*temp_image_buffer_count ); memset(temp_image_buffer, 0, sizeof(*temp_image_buffer)*temp_image_buffer_count);
Debug( 2, "Assigned temporary buffer" ); Debug(2, "Assigned temporary buffer");
} }
} }
} else { } else {
@ -525,7 +524,7 @@ void MonitorStream::runStream() {
Debug(1, "Using %.3f for fps instead of current fps %.3f", capture_max_fps, capture_fps); Debug(1, "Using %.3f for fps instead of current fps %.3f", capture_max_fps, capture_fps);
capture_fps = capture_max_fps; capture_fps = capture_max_fps;
} }
if ( capture_fps < 1 ) { if ( capture_fps < 1 ) {
max_secs_since_last_sent_frame = 10/capture_fps; max_secs_since_last_sent_frame = 10/capture_fps;
Debug(1, "Adjusting max_secs_since_last_sent_frame to %.2f from current fps %.2f", 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); touch(sock_path_lock);
last_comm_update = now; last_comm_update = now;
} }
} // end if connkey } // end if connkey
if ( paused ) { if ( paused ) {
if ( !was_paused ) { if ( !was_paused ) {
@ -589,7 +588,7 @@ void MonitorStream::runStream() {
} else { } else {
if ( !paused ) { if ( !paused ) {
int temp_index = MOD_ADD(temp_read_index, 0, temp_image_buffer_count); 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]; SwapImage *swap_image = &temp_image_buffer[temp_index];
if ( !swap_image->valid ) { if ( !swap_image->valid ) {
@ -597,51 +596,61 @@ void MonitorStream::runStream() {
delayed = true; delayed = true;
temp_read_index = MOD_ADD(temp_read_index, (replay_rate>=0?-1:1), temp_image_buffer_count); temp_read_index = MOD_ADD(temp_read_index, (replay_rate>=0?-1:1), temp_image_buffer_count);
} else { } else {
//Debug( 3, "siT: %f, lfT: %f", TV_2_FLOAT( swap_image->timestamp ), TV_2_FLOAT( last_frame_timestamp ) ); // 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 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; 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 the next frame is due
if ( actual_delta_time > expected_delta_time ) { 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 ) { 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 // 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; zm_terminate = true;
}
memcpy(&last_frame_timestamp, &(swap_image->timestamp), sizeof(last_frame_timestamp)); 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); temp_read_index = MOD_ADD(temp_read_index, (replay_rate>0?1:-1), temp_image_buffer_count);
} }
} }
} else if ( step != 0 ) { } 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]; SwapImage *swap_image = &temp_image_buffer[temp_read_index];
// Send the next frame // 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; 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; step = 0;
} else { } else {
//paused? //paused?
int temp_index = MOD_ADD(temp_read_index, 0, temp_image_buffer_count); int temp_index = MOD_ADD(temp_read_index, 0, temp_image_buffer_count);
double actual_delta_time = TV_2_FLOAT( now ) - last_frame_sent; double actual_delta_time = TV_2_FLOAT(now) - last_frame_sent;
if ( got_command || actual_delta_time > 5 ) { if ( got_command || (actual_delta_time > 5) ) {
// Send keepalive // Send keepalive
Debug(2, "Sending keepalive frame %d", temp_index); Debug(2, "Sending keepalive frame %d", temp_index);
// Send the next frame // 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; zm_terminate = true;
//frame_sent = true; }
// frame_sent = true;
} }
} // end if (!paused) or step or paused } // end if (!paused) or step or paused
} // end if have exceeded buffer or not } // end if have exceeded buffer or not
if ( temp_read_index == temp_write_index ) { if ( temp_read_index == temp_write_index ) {
// Go back to live viewing // Go back to live viewing
@ -652,16 +661,16 @@ void MonitorStream::runStream() {
delayed = false; delayed = false;
replay_rate = ZM_RATE_BASE; 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 ) { if ( last_read_index != monitor->shared_data->last_write_index ) {
// have a new image to send // 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 ( (frame_mod == 1) || ((frame_count%frame_mod) == 0) ) {
if ( !paused && !delayed ) { if ( !paused && !delayed ) {
last_read_index = monitor->shared_data->last_write_index; last_read_index = monitor->shared_data->last_write_index;
Debug(2, "index: %d: frame_mod: %d frame count: %d paused(%d) delayed(%d)", 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 // Send the next frame
Monitor::Snapshot *snap = &monitor->image_buffer[index]; Monitor::Snapshot *snap = &monitor->image_buffer[index];
@ -670,9 +679,13 @@ void MonitorStream::runStream() {
Debug(2, "sendFrame failed, quiting."); Debug(2, "sendFrame failed, quiting.");
zm_terminate = true; zm_terminate = true;
} }
// Perhaps we should use NOW instead. // Perhaps we should use NOW instead.
memcpy(&last_frame_timestamp, snap->timestamp, sizeof(last_frame_timestamp)); memcpy(
//frame_sent = true; &last_frame_timestamp,
snap->timestamp,
sizeof(last_frame_timestamp)
);
// frame_sent = true;
temp_read_index = temp_write_index; temp_read_index = temp_write_index;
} else { } else {
@ -697,7 +710,7 @@ void MonitorStream::runStream() {
if ( !sendFrame(paused_image, &paused_timestamp) ) if ( !sendFrame(paused_image, &paused_timestamp) )
zm_terminate = true; zm_terminate = true;
} else { } 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 actual_delta_time > 5
} // end if change in zoom } // end if change in zoom
@ -718,27 +731,27 @@ void MonitorStream::runStream() {
temp_index); temp_index);
temp_image_buffer[temp_index].valid = true; 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) ); 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 ); 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 ); temp_write_index = MOD_ADD(temp_write_index, 1, temp_image_buffer_count);
if ( temp_write_index == temp_read_index ) { if ( temp_write_index == temp_read_index ) {
// Go back to live viewing // Go back to live viewing
Warning( "Exceeded temporary buffer, resuming live play" ); Warning("Exceeded temporary buffer, resuming live play");
paused = false; paused = false;
delayed = false; delayed = false;
replay_rate = ZM_RATE_BASE; replay_rate = ZM_RATE_BASE;
} }
} else { } else {
Warning( "Unable to store frame as timestamp invalid" ); Warning("Unable to store frame as timestamp invalid");
} }
} else { } else {
Warning( "Unable to store frame as shared memory invalid" ); Warning("Unable to store frame as shared memory invalid");
} }
} // end if buffered playback } // end if buffered playback
frame_count++; frame_count++;
} else { } else {
Debug(3, "Waiting for capture last_write_index=%u", monitor->shared_data->last_write_index); 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))); 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); Debug(3, "Sleeping for (%d)", sleep_time);
@ -755,10 +768,10 @@ void MonitorStream::runStream() {
break; 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. // 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; 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); frame_mod, frame_count);
} else if ( } else if (
(!paused) (!paused)
@ -799,9 +812,9 @@ void MonitorStream::runStream() {
} }
} }
} }
globfree( &pglob ); globfree(&pglob);
if ( rmdir(swap_path.c_str()) < 0 ) { 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 checking for swap_path
} // end if buffered_playback } // end if buffered_playback
@ -809,7 +822,7 @@ void MonitorStream::runStream() {
closeComms(); closeComms();
} // end MonitorStream::runStream } // end MonitorStream::runStream
void MonitorStream::SingleImage( int scale ) { void MonitorStream::SingleImage(int scale) {
int img_buffer_size = 0; int img_buffer_size = 0;
static JOCTET img_buffer[ZM_MAX_IMAGE_SIZE]; static JOCTET img_buffer[ZM_MAX_IMAGE_SIZE];
Image scaled_image; Image scaled_image;
@ -817,42 +830,45 @@ void MonitorStream::SingleImage( int scale ) {
Image *snap_image = snap->image; Image *snap_image = snap->image;
if ( scale != ZM_SCALE_BASE ) { if ( scale != ZM_SCALE_BASE ) {
scaled_image.Assign( *snap_image ); scaled_image.Assign(*snap_image);
scaled_image.Scale( scale ); scaled_image.Scale(scale);
snap_image = &scaled_image; snap_image = &scaled_image;
} }
if ( !config.timestamp_on_capture ) { 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 ); snap_image->EncodeJpeg(img_buffer, &img_buffer_size);
fprintf( stdout, "Content-Length: %d\r\n", img_buffer_size ); fprintf(stdout,
fprintf( stdout, "Content-Type: image/jpeg\r\n\r\n" ); "Content-Length: %d\r\n"
fwrite( img_buffer, img_buffer_size, 1, stdout ); "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; Image scaled_image;
Monitor::Snapshot *snap = monitor->getSnapshot(); Monitor::Snapshot *snap = monitor->getSnapshot();
Image *snap_image = snap->image; Image *snap_image = snap->image;
if ( scale != ZM_SCALE_BASE ) { if ( scale != ZM_SCALE_BASE ) {
scaled_image.Assign( *snap_image ); scaled_image.Assign(*snap_image);
scaled_image.Scale( scale ); scaled_image.Scale(scale);
snap_image = &scaled_image; snap_image = &scaled_image;
} }
if ( !config.timestamp_on_capture ) { 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,
fprintf( stdout, "Content-Type: image/x-rgb\r\n\r\n" ); "Content-Length: %d\r\n"
fwrite( snap_image->Buffer(), snap_image->Size(), 1, stdout ); "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 #ifdef HAVE_ZLIB_H
void MonitorStream::SingleImageZip( int scale ) { void MonitorStream::SingleImageZip(int scale) {
unsigned long img_buffer_size = 0; unsigned long img_buffer_size = 0;
static Bytef img_buffer[ZM_MAX_IMAGE_SIZE]; static Bytef img_buffer[ZM_MAX_IMAGE_SIZE];
Image scaled_image; Image scaled_image;
@ -861,17 +877,19 @@ void MonitorStream::SingleImageZip( int scale ) {
Image *snap_image = snap->image; Image *snap_image = snap->image;
if ( scale != ZM_SCALE_BASE ) { if ( scale != ZM_SCALE_BASE ) {
scaled_image.Assign( *snap_image ); scaled_image.Assign(*snap_image);
scaled_image.Scale( scale ); scaled_image.Scale(scale);
snap_image = &scaled_image; snap_image = &scaled_image;
} }
if ( !config.timestamp_on_capture ) { 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 ); snap_image->Zip(img_buffer, &img_buffer_size);
fprintf( stdout, "Content-Length: %ld\r\n", img_buffer_size ); fprintf(stdout,
fprintf( stdout, "Content-Type: image/x-rgbz\r\n\r\n" ); "Content-Length: %ld\r\n"
fwrite( img_buffer, img_buffer_size, 1, stdout ); "Content-Type: image/x-rgbz\r\n\r\n",
img_buffer_size);
fwrite(img_buffer, img_buffer_size, 1, stdout);
} }
#endif // HAVE_ZLIB_H #endif // HAVE_ZLIB_H

View File

@ -1060,7 +1060,6 @@ int RemoteCameraHttp::PreCapture() {
if ( sd < 0 ) { if ( sd < 0 ) {
Connect(); Connect();
if ( sd < 0 ) { if ( sd < 0 ) {
Error("Unable to connect to camera");
return -1; return -1;
} }
mode = SINGLE_IMAGE; mode = SINGLE_IMAGE;

View File

@ -144,7 +144,7 @@ std::string Authenticator::computeDigestResponse(std::string &method, std::strin
#if HAVE_DECL_MD5 #if HAVE_DECL_MD5
MD5((unsigned char*)ha1Data.c_str(), ha1Data.length(), md5buf); MD5((unsigned char*)ha1Data.c_str(), ha1Data.length(), md5buf);
#elif HAVE_DECL_GNUTLS_FINGERPRINT #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 ); gnutls_fingerprint( GNUTLS_DIG_MD5, &md5dataha1, md5buf, &md5len );
#endif #endif
for ( unsigned int j = 0; j < md5len; j++ ) { 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 #if HAVE_DECL_MD5
MD5((unsigned char*)ha2Data.c_str(), ha2Data.length(), md5buf ); MD5((unsigned char*)ha2Data.c_str(), ha2Data.length(), md5buf );
#elif HAVE_DECL_GNUTLS_FINGERPRINT #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 ); gnutls_fingerprint( GNUTLS_DIG_MD5, &md5dataha2, md5buf, &md5len );
#endif #endif
for ( unsigned int j = 0; j < md5len; j++ ) { 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 #if HAVE_DECL_MD5
MD5((unsigned char*)digestData.c_str(), digestData.length(), md5buf); MD5((unsigned char*)digestData.c_str(), digestData.length(), md5buf);
#elif HAVE_DECL_GNUTLS_FINGERPRINT #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 ); gnutls_fingerprint( GNUTLS_DIG_MD5, &md5datadigest, md5buf, &md5len );
#endif #endif
for ( unsigned int j = 0; j < md5len; j++ ) { for ( unsigned int j = 0; j < md5len; j++ ) {

View File

@ -19,9 +19,6 @@
#ifndef ZM_RTSP_AUTH_H #ifndef ZM_RTSP_AUTH_H
#define ZM_RTSP_AUTH_H #define ZM_RTSP_AUTH_H
#if HAVE_GNUTLS_OPENSSL_H
#include <gnutls/openssl.h>
#endif
#if HAVE_GNUTLS_GNUTLS_H #if HAVE_GNUTLS_GNUTLS_H
#include <gnutls/gnutls.h> #include <gnutls/gnutls.h>
#endif #endif

View File

@ -44,7 +44,12 @@ bool StreamBase::loadMonitor(int monitor_id) {
Error("Unable to load monitor id %d for streaming", monitor_id); Error("Unable to load monitor id %d for streaming", monitor_id);
return false; 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); Error("Unable to connect to monitor id %d for streaming", monitor_id);
return false; return false;
} }
@ -109,7 +114,7 @@ bool StreamBase::checkCommandQueue() {
return false; 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 // 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. // 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 mag = (scale * zoom) / ZM_SCALE_BASE;
int act_mag = optimisedScaling?(mag > ZM_SCALE_BASE?ZM_SCALE_BASE:mag):mag; 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_mag = (last_scale * last_zoom) / ZM_SCALE_BASE;
int last_act_mag = last_mag > ZM_SCALE_BASE?ZM_SCALE_BASE:last_mag; 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(); 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; 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; 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; 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; 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; 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; 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; 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; 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 ) { Debug(3,
if ( act_mag != ZM_SCALE_BASE ) { "Scaling by %d, zooming by %d = magnifying by %d(%d)\n"
Debug(3, "Magnifying by %d", mag); "Last scaling by %d, zooming by %d = magnifying by %d(%d)\n"
if ( !image_copied ) { "Base image width = %d, height = %d\n"
static Image copy_image; "Virtual image width = %d, height = %d\n"
copy_image.Assign(*image); "Last virtual image width = %d, height = %d\n"
image = &copy_image; "Actual image width = %d, height = %d\n"
image_copied = true; "Last actual image width = %d, height = %d\n"
} "Display image width = %d, height = %d\n"
image->Scale(mag); "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 = &copy_image;
image_copied = true;
image->Scale(mag);
} }
Debug(3, "Real image width = %d, height = %d", image->Width(), image->Height()); 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; static Box last_crop;
if ( mag != last_mag || x != last_x || y != last_y ) { if ( mag != last_mag || x != last_x || y != last_y ) {
Debug( 3, "Got click at %d,%d x %d", x, y, mag ); Debug(3, "Got click at %d,%d x %d", x, y, mag);
//if ( !last_mag )
//last_mag = mag;
if ( !(last_disp_image_width < last_virt_image_width || last_disp_image_height < last_virt_image_height) ) if ( !(last_disp_image_width < last_virt_image_width || last_disp_image_height < last_virt_image_height) )
last_crop = Box(); last_crop = Box();
Debug( 3, "Recalculating crop" );
// Recalculate crop parameters, as %ges // Recalculate crop parameters, as %ges
int click_x = (last_crop.LoX() * 100 ) / last_act_image_width; // Initial crop offset from last image 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; 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 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; 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 // Convert the click locations to the current image pixels
click_x = ( click_x * act_image_width ) / 100; click_x = ( click_x * act_image_width ) / 100;
click_y = ( click_y * act_image_height ) / 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); int lo_x = click_x - (send_image_width/2);
if ( lo_x < 0 ) if ( lo_x < 0 )
@ -209,23 +211,25 @@ Image *StreamBase::prepareImage( Image *image ) {
lo_y = hi_y - (send_image_height - 1); lo_y = hi_y - (send_image_height - 1);
} }
last_crop = Box( lo_x, lo_y, hi_x, hi_y ); last_crop = Box( lo_x, lo_y, hi_x, hi_y );
} } // 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() );
Debug(3, "Cropping to %d,%d -> %d,%d", last_crop.LoX(), last_crop.LoY(), last_crop.HiX(), last_crop.HiY());
if ( !image_copied ) { if ( !image_copied ) {
static Image copy_image; static Image copy_image;
copy_image.Assign( *image ); copy_image.Assign(*image);
image = &copy_image; image = &copy_image;
image_copied = true; image_copied = true;
} }
image->Crop( last_crop ); image->Crop(last_crop);
} } // end if difference in image vs displayed dimensions
last_scale = scale; last_scale = scale;
last_zoom = zoom; last_zoom = zoom;
last_x = x; last_x = x;
last_y = y; last_y = y;
return image; return image;
} } // end Image *StreamBase::prepareImage(Image *image)
bool StreamBase::sendTextFrame(const char *frame_text) { bool StreamBase::sendTextFrame(const char *frame_text) {
Debug(2, "Sending %dx%d * %d text frame '%s'", Debug(2, "Sending %dx%d * %d text frame '%s'",

View File

@ -27,9 +27,6 @@
#include <string.h> #include <string.h>
#include <time.h> #include <time.h>
#if HAVE_GNUTLS_OPENSSL_H
#include <gnutls/openssl.h>
#endif
#if HAVE_GNUTLS_GNUTLS_H #if HAVE_GNUTLS_GNUTLS_H
#include <gnutls/gnutls.h> #include <gnutls/gnutls.h>
#endif #endif
@ -112,12 +109,13 @@ User *zmLoadUser(const char *username, const char *password) {
snprintf(sql, sizeof(sql), snprintf(sql, sizeof(sql),
"SELECT `Id`, `Username`, `Password`, `Enabled`, `Stream`+0, `Events`+0, `Control`+0, `Monitors`+0, `System`+0, `MonitorIds`" "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); " FROM `Users` WHERE `Username` = '%s' AND `Enabled` = 1", safer_username);
delete safer_username;
safer_username = NULL;
if ( mysql_query(&dbconn, sql) ) { if ( mysql_query(&dbconn, sql) ) {
Error("Can't run query: %s", mysql_error(&dbconn)); Error("Can't run query: %s", mysql_error(&dbconn));
exit(mysql_errno(&dbconn)); exit(mysql_errno(&dbconn));
} }
delete safer_username;
MYSQL_RES *result = mysql_store_result(&dbconn); MYSQL_RES *result = mysql_store_result(&dbconn);
if ( !result ) { if ( !result ) {
@ -125,35 +123,30 @@ User *zmLoadUser(const char *username, const char *password) {
exit(mysql_errno(&dbconn)); 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); mysql_free_result(result);
Warning("Unable to authenticate user %s", username);
return NULL;
}
MYSQL_ROW dbrow = mysql_fetch_row(result); if (
User *user = new User(dbrow); (! 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); 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); Warning("Unable to authenticate user %s", username);
return NULL; 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 key = config.auth_hash_secret;
std::string remote_addr = ""; std::string remote_addr = "";
if (use_remote_addr) { if ( use_remote_addr ) {
remote_addr = std::string(getenv( "REMOTE_ADDR" )); remote_addr = std::string(getenv( "REMOTE_ADDR" ));
if ( remote_addr == "" ) { if ( remote_addr == "" ) {
Warning( "Can't determine remote address, using null" ); 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; 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<std::string, unsigned int> ans = verifyToken(jwt_token_str, key); std::pair<std::string, unsigned int> ans = verifyToken(jwt_token_str, key);
std::string username = ans.first; std::string username = ans.first;
unsigned int iat = ans.second; 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 != "") { 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 {
return NULL; 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 // Function to validate an authentication string
User *zmLoadAuthUser( const char *auth, bool use_remote_addr ) { User *zmLoadAuthUser( const char *auth, bool use_remote_addr ) {
#if HAVE_DECL_MD5 || HAVE_DECL_GNUTLS_FINGERPRINT #if HAVE_DECL_MD5 || HAVE_DECL_GNUTLS_FINGERPRINT
#ifdef HAVE_GCRYPT_H #ifdef HAVE_GCRYPT_H
// Special initialisation for libgcrypt // Special initialisation for libgcrypt
if ( !gcry_check_version( GCRYPT_VERSION ) ) { if ( !gcry_check_version(GCRYPT_VERSION) ) {
Fatal( "Unable to initialise libgcrypt" ); Fatal( "Unable to initialise libgcrypt" );
} }
gcry_control( GCRYCTL_DISABLE_SECMEM, 0 ); gcry_control(GCRYCTL_DISABLE_SECMEM, 0);
gcry_control( GCRYCTL_INITIALIZATION_FINISHED, 0 ); gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
#endif // HAVE_GCRYPT_H #endif // HAVE_GCRYPT_H
const char *remote_addr = ""; const char *remote_addr = "";
if ( use_remote_addr ) { if ( use_remote_addr ) {
remote_addr = getenv( "REMOTE_ADDR" ); remote_addr = getenv("REMOTE_ADDR");
if ( !remote_addr ) { if ( !remote_addr ) {
Warning( "Can't determine remote address, using null" ); Warning("Can't determine remote address, using null");
remote_addr = ""; 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] = ""; 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 ) ) { if ( mysql_query(&dbconn, sql) ) {
Error( "Can't run query: %s", mysql_error( &dbconn ) ); Error("Can't run query: %s", mysql_error(&dbconn));
exit( mysql_errno( &dbconn ) ); return NULL;
} }
MYSQL_RES *result = mysql_store_result( &dbconn ); MYSQL_RES *result = mysql_store_result( &dbconn );
if ( !result ) { if ( !result ) {
Error( "Can't use query result: %s", mysql_error( &dbconn ) ); Error("Can't use query result: %s", mysql_error(&dbconn));
exit( mysql_errno( &dbconn ) ); return NULL;
} }
int n_users = mysql_num_rows( result ); int n_users = mysql_num_rows(result);
if ( n_users < 1 ) { if ( n_users < 1 ) {
mysql_free_result( result ); mysql_free_result(result);
Warning( "Unable to authenticate user" ); Warning("Unable to authenticate user");
return( 0 ); 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 *user = dbrow[1];
const char *pass = dbrow[2]; 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 ) { 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, config.auth_hash_secret,
user, user,
pass, pass,
@ -292,47 +283,48 @@ User *zmLoadAuthUser( const char *auth, bool use_remote_addr ) {
); );
#if HAVE_DECL_MD5 #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 #elif HAVE_DECL_GNUTLS_FINGERPRINT
gnutls_datum_t md5data = { (unsigned char *)auth_key, strlen(auth_key) }; 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 #endif
auth_md5[0] = '\0'; auth_md5[0] = '\0';
for ( unsigned int j = 0; j < md5len; j++ ) { 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 ) ) { if ( !strcmp( auth, auth_md5 ) ) {
// We have a match // We have a match
User *user = new User( dbrow ); User *user = new User( dbrow );
Debug(1, "Authenticated user '%s'", user->getUsername() ); Debug(1, "Authenticated user '%s'", user->getUsername() );
mysql_free_result( result ); mysql_free_result(result);
return( user ); return user;
} else { } 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 #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 #endif // HAVE_DECL_MD5
Debug(1, "No user found for auth_key %s", auth ); Debug(1, "No user found for auth_key %s", auth);
return 0; return NULL;
} } // end User *zmLoadAuthUser(const char *auth, bool use_remote_addr)
//Function to check Username length //Function to check Username length
bool checkUser ( const char *username) { bool checkUser(const char *username) {
if ( ! username ) if ( !username )
return false; return false;
if ( strlen(username) > 32 ) if ( strlen(username) > 32 )
return false; return false;
return true; return true;
} }
//Function to check password length //Function to check password length
bool checkPass (const char *password) { bool checkPass(const char *password) {
if ( !password ) if ( !password )
return false; return false;
if ( strlen(password) > 64 ) if ( strlen(password) > 64 )

View File

@ -393,12 +393,12 @@ void timespec_diff(struct timespec *start, struct timespec *end, struct timespec
char *timeval_to_string( struct timeval tv ) { char *timeval_to_string( struct timeval tv ) {
time_t nowtime; time_t nowtime;
struct tm *nowtm; struct tm *nowtm;
static char tmbuf[64], buf[64]; static char tmbuf[20], buf[28];
nowtime = tv.tv_sec; nowtime = tv.tv_sec;
nowtm = localtime(&nowtime); nowtm = localtime(&nowtime);
strftime(tmbuf, sizeof tmbuf, "%Y-%m-%d %H:%M:%S", nowtm); 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; return buf;
} }

View File

@ -409,7 +409,7 @@ bool VideoStore::open() {
AVDictionary *opts = NULL; AVDictionary *opts = NULL;
// av_dict_set(&opts, "movflags", "frag_custom+dash+delay_moov", 0); // av_dict_set(&opts, "movflags", "frag_custom+dash+delay_moov", 0);
// Shiboleth reports that this may break seeking in mp4 before it downloads // 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", // av_dict_set(&opts, "movflags",
// "frag_keyframe+empty_moov+default_base_moof", 0); // "frag_keyframe+empty_moov+default_base_moof", 0);
if ( (ret = avformat_write_header(oc, &opts)) < 0 ) { if ( (ret = avformat_write_header(oc, &opts)) < 0 ) {

View File

@ -53,7 +53,7 @@ void Zone::Setup(
id = p_id; id = p_id;
label = new char[strlen(p_label)+1]; label = new char[strlen(p_label)+1];
strcpy( label, p_label ); strcpy(label, p_label);
type = p_type; type = p_type;
polygon = p_polygon; polygon = p_polygon;
alarm_rgb = p_alarm_rgb; alarm_rgb = p_alarm_rgb;
@ -89,10 +89,10 @@ void Zone::Setup(
overload_count = 0; overload_count = 0;
extend_alarm_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->Clear();
pg_image->Fill( 0xff, polygon ); pg_image->Fill(0xff, polygon);
pg_image->Outline( 0xff, polygon ); pg_image->Outline(0xff, polygon);
ranges = new Range[monitor->Height()]; ranges = new Range[monitor->Height()];
for ( unsigned int y = 0; y < monitor->Height(); y++ ) { for ( unsigned int y = 0; y < monitor->Height(); y++ ) {
@ -113,8 +113,10 @@ void Zone::Setup(
} }
if ( config.record_diag_images ) { 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); snprintf(diag_path, sizeof(diag_path),
if (config.record_diag_images_fifo) 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); FifoStream::fifo_create_if_missing(diag_path);
pg_image->WriteJpeg(diag_path, config.record_diag_images_fifo); pg_image->WriteJpeg(diag_path, config.record_diag_images_fifo);
} else { } else {

View File

@ -350,7 +350,7 @@ int main(int argc, char *argv[]) {
} }
if ( result < 0 ) { if ( result < 0 ) {
// Failure, try reconnecting // Failure, try reconnecting
sleep(1); sleep(5);
break; break;
} }
} // end while ! zm_terminate } // end while ! zm_terminate

View File

@ -95,7 +95,7 @@ int main(int argc, const char *argv[]) {
Debug(1, "Query: %s", query); Debug(1, "Query: %s", query);
char temp_query[1024]; 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 *q_ptr = temp_query;
char *parms[16]; // Shouldn't be more than this char *parms[16]; // Shouldn't be more than this
int parm_no = 0; int parm_no = 0;
@ -106,9 +106,9 @@ int main(int argc, const char *argv[]) {
for ( int p = 0; p < parm_no; p++ ) { for ( int p = 0; p < parm_no; p++ ) {
char *name = strtok(parms[p], "="); char *name = strtok(parms[p], "=");
char *value = strtok(NULL, "="); char const *value = strtok(NULL, "=");
if ( !value ) if ( !value )
value = (char *)""; value = "";
if ( !strcmp(name, "source") ) { if ( !strcmp(name, "source") ) {
source = !strcmp(value, "event")?ZMS_EVENT:ZMS_MONITOR; source = !strcmp(value, "event")?ZMS_EVENT:ZMS_MONITOR;
if ( !strcmp(value, "fifo") ) if ( !strcmp(value, "fifo") )
@ -127,10 +127,10 @@ int main(int argc, const char *argv[]) {
} else if ( !strcmp(name, "time") ) { } else if ( !strcmp(name, "time") ) {
event_time = atoi(value); event_time = atoi(value);
} else if ( !strcmp(name, "event") ) { } else if ( !strcmp(name, "event") ) {
event_id = strtoull(value, (char **)NULL, 10); event_id = strtoull(value, NULL, 10);
source = ZMS_EVENT; source = ZMS_EVENT;
} else if ( !strcmp(name, "frame") ) { } else if ( !strcmp(name, "frame") ) {
frame_id = strtoull(value, (char **)NULL, 10); frame_id = strtoull(value, NULL, 10);
source = ZMS_EVENT; source = ZMS_EVENT;
} else if ( !strcmp(name, "scale") ) { } else if ( !strcmp(name, "scale") ) {
scale = atoi(value); scale = atoi(value);
@ -159,7 +159,7 @@ int main(int argc, const char *argv[]) {
} else if ( !strcmp(name, "buffer") ) { } else if ( !strcmp(name, "buffer") ) {
playback_buffer = atoi(value); playback_buffer = atoi(value);
} else if ( !strcmp(name, "auth") ) { } else if ( !strcmp(name, "auth") ) {
strncpy( auth, value, sizeof(auth)-1 ); strncpy(auth, value, sizeof(auth)-1);
} else if ( !strcmp(name, "token") ) { } else if ( !strcmp(name, "token") ) {
jwt_token_str = value; jwt_token_str = value;
Debug(1, "ZMS: JWT token found: %s", jwt_token_str.c_str()); 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); logInit(log_id_string);
if ( config.opt_use_auth ) { if ( config.opt_use_auth ) {
User *user = 0; User *user = NULL;
if ( jwt_token_str != "" ) { if ( jwt_token_str != "" ) {
// user = zmLoadTokenUser(jwt_token_str, config.auth_hash_ips); // user = zmLoadTokenUser(jwt_token_str, config.auth_hash_ips);
@ -195,19 +195,11 @@ int main(int argc, const char *argv[]) {
} else { } else {
Error("Bad username"); Error("Bad username");
} }
} else { } else {
// if ( strcmp( config.auth_relay, "hashed" ) == 0 ) if ( *auth ) {
{ user = zmLoadAuthUser(auth, config.auth_hash_ips);
if ( *auth ) { } else if ( username.length() && password.length() ) {
user = zmLoadAuthUser(auth, config.auth_hash_ips); user = zmLoadUser(username.c_str(), password.c_str());
}
}
// else if ( strcmp( config.auth_relay, "plain" ) == 0 )
{
if ( username.length() && password.length() ) {
user = zmLoadUser(username.c_str(), password.c_str());
}
} }
} }
if ( !user ) { if ( !user ) {
@ -218,11 +210,15 @@ int main(int argc, const char *argv[]) {
return 0; return 0;
} }
if ( !ValidateAccess(user, monitor_id) ) { if ( !ValidateAccess(user, monitor_id) ) {
delete user;
user = NULL;
fputs("HTTP/1.0 403 Forbidden\r\n\r\n", stdout); fputs("HTTP/1.0 403 Forbidden\r\n\r\n", stdout);
logTerm(); logTerm();
zmDbClose(); zmDbClose();
return 0; return 0;
} }
delete user;
user = NULL;
} // end if config.opt_use_auth } // end if config.opt_use_auth
hwcaps_detect(); hwcaps_detect();
@ -237,11 +233,13 @@ int main(int argc, const char *argv[]) {
time_t now = time(0); time_t now = time(0);
char date_string[64]; 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( 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: no-store, no-cache, must-revalidate\r\n"
"Cache-Control: post-check=0, pre-check=0\r\n" "Cache-Control: post-check=0, pre-check=0\r\n"
"Pragma: no-cache\r\n", "Pragma: no-cache\r\n",
@ -279,7 +277,9 @@ int main(int argc, const char *argv[]) {
stream.setStreamType(MonitorStream::STREAM_MPEG); stream.setStreamType(MonitorStream::STREAM_MPEG);
#else // HAVE_LIBAVCODEC #else // HAVE_LIBAVCODEC
Error("MPEG streaming of '%s' attempted while disabled", query); 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(); logTerm();
zmDbClose(); zmDbClose();
return -1; return -1;
@ -332,5 +332,5 @@ int main(int argc, const char *argv[]) {
logTerm(); logTerm();
zmDbClose(); zmDbClose();
return(0); return 0;
} }

View File

@ -430,7 +430,9 @@ int main(int argc, char *argv[]) {
User *user = 0; User *user = 0;
if ( config.opt_use_auth ) { 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 ) { if ( !username ) {
Error("Username must be supplied"); Error("Username must be supplied");
exit_zmu(-1); exit_zmu(-1);
@ -444,13 +446,10 @@ int main(int argc, char *argv[]) {
user = zmLoadUser(username); user = zmLoadUser(username);
} else { } else {
if ( !(username && password) && !auth && (jwt_token_str=="")) { if ( !(username && password) && !auth ) {
Error("Username and password or auth/token string must be supplied"); Error("Username and password or auth/token string must be supplied");
exit_zmu(-1); exit_zmu(-1);
} }
if (jwt_token_str != "") {
user = zmLoadTokenUser(jwt_token_str, false);
}
if ( auth ) { if ( auth ) {
user = zmLoadAuthUser(auth, false); user = zmLoadAuthUser(auth, false);
} }
@ -477,14 +476,12 @@ int main(int argc, char *argv[]) {
} // end if auth } // end if auth
if ( mon_id > 0 ) { if ( mon_id > 0 ) {
//fprintf(stderr,"Monitor %d\n", mon_id);
Monitor *monitor = Monitor::Load(mon_id, function&(ZMU_QUERY|ZMU_ZONES), Monitor::QUERY); Monitor *monitor = Monitor::Load(mon_id, function&(ZMU_QUERY|ZMU_ZONES), Monitor::QUERY);
if ( monitor ) { if ( monitor ) {
//fprintf(stderr,"Monitor %d(%s)\n", monitor->Id(), monitor->Name());
if ( verbose ) { if ( verbose ) {
printf("Monitor %d(%s)\n", monitor->Id(), monitor->Name()); 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()); Error("Can't connect to capture daemon: %d %s", monitor->Id(), monitor->Name());
exit_zmu(-1); exit_zmu(-1);
} }
@ -496,13 +493,13 @@ int main(int argc, char *argv[]) {
if ( verbose ) { if ( verbose ) {
printf("Current state: %s\n", state==Monitor::ALARM?"Alarm":(state==Monitor::ALERT?"Alert":"Idle")); printf("Current state: %s\n", state==Monitor::ALARM?"Alarm":(state==Monitor::ALERT?"Alert":"Idle"));
} else { } else {
if ( have_output ) printf("%c", separator); if ( have_output ) fputc(separator, stdout);
printf("%d", state); printf("%d", state);
have_output = true; have_output = true;
} }
} }
if ( function & ZMU_TIME ) { if ( function & ZMU_TIME ) {
struct timeval timestamp = monitor->GetTimestamp( image_idx ); struct timeval timestamp = monitor->GetTimestamp(image_idx);
if ( verbose ) { if ( verbose ) {
char timestamp_str[64] = "None"; char timestamp_str[64] = "None";
if ( timestamp.tv_sec ) if ( timestamp.tv_sec )
@ -512,7 +509,7 @@ int main(int argc, char *argv[]) {
else else
printf("Time of image %d capture: %s.%02ld\n", image_idx, timestamp_str, timestamp.tv_usec/10000); printf("Time of image %d capture: %s.%02ld\n", image_idx, timestamp_str, timestamp.tv_usec/10000);
} else { } else {
if ( have_output ) printf("%c", separator); if ( have_output ) fputc(separator, stdout);
printf("%ld.%02ld", timestamp.tv_sec, timestamp.tv_usec/10000); printf("%ld.%02ld", timestamp.tv_sec, timestamp.tv_usec/10000);
have_output = true; have_output = true;
} }
@ -521,7 +518,7 @@ int main(int argc, char *argv[]) {
if ( verbose ) if ( verbose )
printf("Last read index: %d\n", monitor->GetLastReadIndex()); printf("Last read index: %d\n", monitor->GetLastReadIndex());
else { else {
if ( have_output ) printf("%c", separator); if ( have_output ) fputc(separator, stdout);
printf("%d", monitor->GetLastReadIndex()); printf("%d", monitor->GetLastReadIndex());
have_output = true; have_output = true;
} }
@ -530,7 +527,7 @@ int main(int argc, char *argv[]) {
if ( verbose ) { if ( verbose ) {
printf("Last write index: %d\n", monitor->GetLastWriteIndex()); printf("Last write index: %d\n", monitor->GetLastWriteIndex());
} else { } else {
if ( have_output ) printf("%c", separator); if ( have_output ) fputc(separator, stdout);
printf("%d", monitor->GetLastWriteIndex()); printf("%d", monitor->GetLastWriteIndex());
have_output = true; have_output = true;
} }
@ -539,16 +536,16 @@ int main(int argc, char *argv[]) {
if ( verbose ) { if ( verbose ) {
printf("Last event id: %" PRIu64 "\n", monitor->GetLastEventId()); printf("Last event id: %" PRIu64 "\n", monitor->GetLastEventId());
} else { } else {
if ( have_output ) printf("%c", separator); if ( have_output ) fputc(separator, stdout);
printf("%" PRIu64, monitor->GetLastEventId()); printf("%" PRIu64, monitor->GetLastEventId());
have_output = true; have_output = true;
} }
} }
if ( function & ZMU_FPS ) { if ( function & ZMU_FPS ) {
if ( verbose ) if ( verbose ) {
printf("Current capture rate: %.2f frames per second\n", monitor->GetFPS()); printf("Current capture rate: %.2f frames per second\n", monitor->GetFPS());
else { } else {
if ( have_output ) printf("%c", separator); if ( have_output ) fputc(separator, stdout);
printf("%.2f", monitor->GetFPS()); printf("%.2f", monitor->GetFPS());
have_output = true; have_output = true;
} }
@ -574,10 +571,16 @@ int main(int argc, char *argv[]) {
if ( monitor->GetFunction() == Monitor::Function::MONITOR ) { if ( monitor->GetFunction() == Monitor::Function::MONITOR ) {
printf("A Monitor in monitor mode cannot handle alarms. Please use NoDect\n"); printf("A Monitor in monitor mode cannot handle alarms. Please use NoDect\n");
} else { } else {
if ( verbose ) Monitor::State state = monitor->GetState();
printf("Forcing alarm on\n");
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"); 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. // Wait for monitor to notice.
usleep(1000); usleep(1000);
} }
@ -631,7 +634,7 @@ int main(int argc, char *argv[]) {
else else
printf("Current brightness: %d\n", monitor->actionBrightness()); printf("Current brightness: %d\n", monitor->actionBrightness());
} else { } else {
if ( have_output ) printf("%c", separator); if ( have_output ) fputc(separator, stdout);
if ( brightness >= 0 ) if ( brightness >= 0 )
printf("%d", monitor->actionBrightness(brightness)); printf("%d", monitor->actionBrightness(brightness));
else else
@ -646,7 +649,7 @@ int main(int argc, char *argv[]) {
else else
printf("Current contrast: %d\n", monitor->actionContrast()); printf("Current contrast: %d\n", monitor->actionContrast());
} else { } else {
if ( have_output ) printf("%c", separator); if ( have_output ) fputc(separator, stdout);
if ( contrast >= 0 ) if ( contrast >= 0 )
printf("%d", monitor->actionContrast(contrast)); printf("%d", monitor->actionContrast(contrast));
else else
@ -661,7 +664,7 @@ int main(int argc, char *argv[]) {
else else
printf("Current hue: %d\n", monitor->actionHue()); printf("Current hue: %d\n", monitor->actionHue());
} else { } else {
if ( have_output ) printf("%c", separator); if ( have_output ) fputc(separator, stdout);
if ( hue >= 0 ) if ( hue >= 0 )
printf("%d", monitor->actionHue(hue)); printf("%d", monitor->actionHue(hue));
else else
@ -676,7 +679,7 @@ int main(int argc, char *argv[]) {
else else
printf("Current colour: %d\n", monitor->actionColour()); printf("Current colour: %d\n", monitor->actionColour());
} else { } else {
if ( have_output ) printf("%c", separator); if ( have_output ) fputc(separator, stdout);
if ( colour >= 0 ) if ( colour >= 0 )
printf("%d", monitor->actionColour(colour)); printf("%d", monitor->actionColour(colour));
else else

View File

@ -80,7 +80,7 @@ fi;
if [ "$DISTROS" == "" ]; then if [ "$DISTROS" == "" ]; then
if [ "$RELEASE" != "" ]; then if [ "$RELEASE" != "" ]; then
DISTROS="xenial,bionic,disco,eoan,trusty" DISTROS="xenial,bionic,disco,eoan,focal,trusty"
else else
DISTROS=`lsb_release -a 2>/dev/null | grep Codename | awk '{print $2}'`; DISTROS=`lsb_release -a 2>/dev/null | grep Codename | awk '{print $2}'`;
fi; fi;
@ -128,14 +128,14 @@ else
fi; fi;
fi fi
IFS='.' read -r -a VERSION_PARTS <<< "$RELEASE"
if [ "$PPA" == "" ]; then if [ "$PPA" == "" ]; then
if [ "$RELEASE" != "" ]; then if [ "$RELEASE" != "" ]; then
# We need to use our official tarball for the original source, so grab it and overwrite our generated one. # 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_PARTS[0]}.${VERSION_PARTS[1]}" == "1.30" ]; then
if [ "${VERSION[0]}.${VERSION[1]}" == "1.30" ]; then
PPA="ppa:iconnor/zoneminder-stable" PPA="ppa:iconnor/zoneminder-stable"
else else
PPA="ppa:iconnor/zoneminder-${VERSION[0]}.${VERSION[1]}" PPA="ppa:iconnor/zoneminder-${VERSION_PARTS[0]}.${VERSION_PARTS[1]}"
fi; fi;
else else
if [ "$BRANCH" == "" ]; then if [ "$BRANCH" == "" ]; then
@ -175,7 +175,7 @@ cd ../
VERSION=`cat ${GITHUB_FORK}_zoneminder_release/version` VERSION=`cat ${GITHUB_FORK}_zoneminder_release/version`
if [ $VERSION == "" ]; then if [ -z "$VERSION" ]; then
exit 1; exit 1;
fi; fi;
if [ "$SNAPSHOT" != "stable" ] && [ "$SNAPSHOT" != "" ]; then if [ "$SNAPSHOT" != "stable" ] && [ "$SNAPSHOT" != "" ]; then
@ -316,7 +316,7 @@ EOF
read -p "Do you want to upload this binary to zmrepo? (y/N)" read -p "Do you want to upload this binary to zmrepo? (y/N)"
if [[ $REPLY == [yY] ]]; then if [[ $REPLY == [yY] ]]; then
if [ "$RELEASE" != "" ]; 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 else
if [ "$BRANCH" == "" ]; then 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/" 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/"

View File

@ -1,5 +1,10 @@
#!/bin/bash #!/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 # Check to see if this script has access to all the commands it needs
for CMD in sshfs rsync find fusermount mkdir; do for CMD in sshfs rsync find fusermount mkdir; do
type $CMD 2>&1 > /dev/null type $CMD 2>&1 > /dev/null
@ -12,53 +17,35 @@ for CMD in sshfs rsync find fusermount mkdir; do
fi fi
done done
# We only want to deploy packages during cron events if [ "${OS}" == "debian" ] || [ "${OS}" == "ubuntu" ] || [ "${OS}" == "raspbian" ]; then
# See https://docs.travis-ci.com/user/cron-jobs/ if [ "${RELEASE}" != "" ]; then
if [ "${TRAVIS_EVENT_TYPE}" == "cron" ] || [ "${OS}" == "debian" ] || [ "${OS}" == "ubuntu" ]; then IFS='.' read -r -a VERSION_PARTS <<< "$RELEASE"
if [ "${VERSION_PARTS[0]}.${VERSION_PARTS[1]}" == "1.30" ]; then
if [ "${OS}" == "debian" ] || [ "${OS}" == "ubuntu" ]; then targetfolder="debian/release/mini-dinstall/incoming"
targetfolder="debian/master/mini-dinstall/incoming"
else else
targetfolder="travis" targetfolder="debian/release-${VERSION_PARTS[0]}.${VERSION_PARTS[1]}/mini-dinstall/incoming"
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
fi 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 fi

View File

@ -9,7 +9,7 @@
# General sanity checks # General sanity checks
checksanity () { checksanity () {
# Check to see if this script has access to all the commands it needs # 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 type $CMD 2>&1 > /dev/null
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
@ -30,7 +30,7 @@ checksanity () {
ARCH="x86_64" ARCH="x86_64"
fi fi
if [[ "${ARCH}" != "x86_64" && "${ARCH}" != "i386" && "${ARCH}" != "armhf" ]]; then if [[ "${ARCH}" != "x86_64" && "${ARCH}" != "i386" && "${ARCH}" != "armhf" && "${ARCH}" != "aarch64" ]]; then
echo echo
echo "ERROR: Unsupported architecture specified \"${ARCH}\"." echo "ERROR: Unsupported architecture specified \"${ARCH}\"."
echo echo
@ -150,7 +150,7 @@ install_deb () {
exit 1 exit 1
fi 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" pkgname="build/zoneminder_${VERSION}-${RELEASE}_amd64.deb"
if [ -e $pkgname ]; then if [ -e $pkgname ]; then
@ -275,6 +275,8 @@ checkdeploytarget () {
echo "*** TRACEROUTE ***" echo "*** TRACEROUTE ***"
echo echo
traceroute -w 2 -m 15 ${DEPLOYTARGET} traceroute -w 2 -m 15 ${DEPLOYTARGET}
exit 97
fi fi
} }
@ -291,43 +293,43 @@ if [ "${TRAVIS}" == "true" ]; then
fi fi
checksanity 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 # Steps common to Redhat distros
if [ "${OS}" == "el" ] || [ "${OS}" == "fedora" ]; then if [ "${OS}" == "el" ] || [ "${OS}" == "fedora" ]; then
if [ "${TRAVIS_EVENT_TYPE}" == "cron" ] || [ "${TRAVIS}" != "true" ]; then commonprep
commonprep echo "Begin Redhat build..."
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 ln -sfT distros/redhat rpm
rm -rf web/api/app/Plugin/Crud
mkdir web/api/app/Plugin/Crud
reporpm="rpmfusion-free-release" # The rpm specfile requires the Crud submodule folder to be empty
dlurl="https://download1.rpmfusion.org/free/${OS}/${reporpm}-${DIST}.noarch.rpm" 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 reporpm="rpmfusion-free-release"
if [ -n "$dlurl" ] && [ $? -eq 0 ]; then dlurl="https://download1.rpmfusion.org/free/${OS}/${reporpm}-${DIST}.noarch.rpm"
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
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..." setrpmchangelog
execpackpack
fi; echo "Starting packpack..."
# Steps common to Debian based distros execpackpack
# Steps common to Debian based distros
elif [ "${OS}" == "debian" ] || [ "${OS}" == "ubuntu" ] || [ "${OS}" == "raspbian" ]; then elif [ "${OS}" == "debian" ] || [ "${OS}" == "ubuntu" ] || [ "${OS}" == "raspbian" ]; then
commonprep commonprep
echo "Begin ${OS} ${DIST} build..." echo "Begin ${OS} ${DIST} build..."
@ -348,14 +350,27 @@ elif [ "${OS}" == "debian" ] || [ "${OS}" == "ubuntu" ] || [ "${OS}" == "raspbia
echo "Starting packpack..." echo "Starting packpack..."
execpackpack execpackpack
# We were not triggered via cron so just build and test trusty # Try to install and run the newly built zoneminder package
if [ "${OS}" == "ubuntu" ] && [ "${DIST}" == "xenial" ] && [ "${ARCH}" == "x86_64" ]; then if [ "${OS}" == "ubuntu" ] && [ "${DIST}" == "xenial" ] && [ "${ARCH}" == "x86_64" ] && [ "${TRAVIS}" == "true" ]; then
# If we are running inside Travis then attempt to install the deb we just built echo "Begin Deb package installation..."
if [ "${TRAVIS}" == "true" ]; then
install_deb install_deb
fi
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 fi
exit 0

View File

@ -1 +1 @@
1.33.15 1.34.10

View File

@ -46,18 +46,18 @@ if ( 0 ) {
SOL_SOCKET, // socket level SOL_SOCKET, // socket level
SO_SNDTIMEO, // timeout option SO_SNDTIMEO, // timeout option
array( array(
"sec"=>0, // Timeout in seconds 'sec'=>0, // Timeout in seconds
"usec"=>500 // I assume timeout in microseconds 'usec'=>500 // I assume timeout in microseconds
) )
); );
$new_stream = null; $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 ) ) { if ( socket_connect( $socket, $url_bits['host'], $port ) ) {
$new_stream = $url_bits; // make a copy $new_stream = $url_bits; // make a copy
$new_stream['port'] = $port; $new_stream['port'] = $port;
} else { } else {
socket_close($socket); 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; continue;
} }
if ( $new_stream ) { if ( $new_stream ) {
@ -92,10 +92,10 @@ Info("Testing connection to " . $url_bits['host'].':'.$port);
} }
foreach ( $available_streams as &$stream ) { foreach ( $available_streams as &$stream ) {
# check for existence in db. # check for existence in db.
$stream['url'] = unparse_url( $stream, array('path'=>'/','query'=>'action=stream') ); $stream['url'] = unparse_url($stream, array('path'=>'/','query'=>'action=stream'));
$monitors = ZM\Monitor::find( array('Path'=>$stream['url']) ); $monitors = ZM\Monitor::find(array('Path'=>$stream['url']));
if ( count($monitors) ) { if ( count($monitors) ) {
ZM\Info("Found monitors matching " . $stream['url'] ); ZM\Info('Found monitors matching ' . $stream['url'] );
$stream['Monitor'] = $monitors[0]; $stream['Monitor'] = $monitors[0];
if ( isset( $stream['Width'] ) and ( $stream['Monitor']->Width() != $stream['Width'] ) ) { 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"; $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 { } else {
$stream['Monitor'] = clone $defaultMonitor; $stream['Monitor'] = clone $defaultMonitor;
if ( isset($stream['Width']) ) { if ( isset($stream['Width']) ) {
$stream['Monitor']->Width( $stream['Width'] ); $stream['Monitor']->Width($stream['Width']);
$stream['Monitor']->Height( $stream['Height'] ); $stream['Monitor']->Height($stream['Height']);
} }
if ( isset($stream['Name']) ) { if ( isset($stream['Name']) ) {
$stream['Monitor']->Name( $stream['Name'] ); $stream['Monitor']->Name($stream['Name']);
} }
} // Monitor found or not } // Monitor found or not
} // end foreach Stream } // end foreach Stream
@ -121,16 +121,16 @@ Info("Testing connection to " . $url_bits['host'].':'.$port);
return $available_streams; return $available_streams;
} // end function probe } // end function probe
if ( canEdit( 'Monitors' ) ) { if ( canEdit('Monitors') ) {
switch ( $_REQUEST['action'] ) { switch ( $_REQUEST['action'] ) {
case 'probe' : case 'probe' :
{ {
$available_streams = array(); $available_streams = array();
$url_bits = null; $url_bits = null;
if ( preg_match('/(\d+)\.(\d+)\.(\d+)\.(\d+)/', $_REQUEST['url'] ) ) { if ( preg_match('/(\d+)\.(\d+)\.(\d+)\.(\d+)/', $_REQUEST['url']) ) {
$url_bits = array( 'host'=>$_REQUEST['url'] ); $url_bits = array('host'=>$_REQUEST['url']);
} else { } else {
$url_bits = parse_url( $_REQUEST['url'] ); $url_bits = parse_url($_REQUEST['url']);
} }
if ( 0 ) { if ( 0 ) {
@ -147,13 +147,13 @@ if ( 0 ) {
} }
if ( ! $url_bits ) { if ( ! $url_bits ) {
ajaxError("The given URL was too malformed to parse."); ajaxError('The given URL was too malformed to parse.');
return; return;
} }
$available_streams = probe( $url_bits ); $available_streams = probe($url_bits);
ajaxResponse( array('Streams'=>$available_streams) ); ajaxResponse(array('Streams'=>$available_streams));
return; return;
} // end case url_probe } // end case url_probe
case 'import': case 'import':
@ -161,16 +161,16 @@ if ( 0 ) {
$file = $_FILES['import_file']; $file = $_FILES['import_file'];
if ($file["error"] > 0) { if ( $file['error'] > 0 ) {
ajaxError($file["error"]); ajaxError($file['error']);
return; return;
} else { } else {
$filename = $file["name"]; $filename = $file['name'];
$available_streams = array(); $available_streams = array();
$row = 1; $row = 1;
if (($handle = fopen($file['tmp_name'], 'r')) !== FALSE) { if ( ($handle = fopen($file['tmp_name'], 'r')) !== FALSE ) {
while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) { while ( ($data = fgetcsv($handle, 1000, ',')) !== FALSE ) {
$name = $data[0]; $name = $data[0];
$url = $data[1]; $url = $data[1];
$group = $data[2]; $group = $data[2];
@ -178,16 +178,16 @@ if ( 0 ) {
$url_bits = null; $url_bits = null;
if ( preg_match('/(\d+)\.(\d+)\.(\d+)\.(\d+)/', $url) ) { if ( preg_match('/(\d+)\.(\d+)\.(\d+)\.(\d+)/', $url) ) {
$url_bits = array( 'host'=>$url, 'scheme'=>'http' ); $url_bits = array('host'=>$url, 'scheme'=>'http');
} else { } else {
$url_bits = parse_url( $url ); $url_bits = parse_url($url);
} }
if ( ! $url_bits ) { if ( ! $url_bits ) {
ZM\Info("Bad url, skipping line $name $url $group"); ZM\Info("Bad url, skipping line $name $url $group");
continue; continue;
} }
$available_streams += probe( $url_bits ); $available_streams += probe($url_bits);
//$url_bits['url'] = unparse_url( $url_bits ); //$url_bits['url'] = unparse_url( $url_bits );
//$url_bits['Monitor'] = $defaultMonitor; //$url_bits['Monitor'] = $defaultMonitor;
@ -197,23 +197,19 @@ if ( 0 ) {
} // end while rows } // end while rows
fclose($handle); fclose($handle);
ajaxResponse( array('Streams'=>$available_streams) ); ajaxResponse(array('Streams'=>$available_streams));
} else { } else {
ajaxError("Uploaded file does not exist"); ajaxError('Uploaded file does not exist');
return; return;
} }
} }
} // end case import } // end case import
default: default:
{ ZM\Warning('unknown action '.$_REQUEST['action']);
ZM\Warning("unknown action " . $_REQUEST['action'] ); } // end switch action
} // end ddcase default
}
} else { } 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']);
?> ?>

View File

@ -1,35 +1,33 @@
<?php <?php
if ( canEdit('Monitors') ) { if ( canEdit('Monitors') ) {
switch ( $_REQUEST['action'] ) { switch ( $_REQUEST['action'] ) {
case 'sort' : case 'sort' :
{ {
$monitor_ids = $_POST['monitor_ids']; $monitor_ids = $_POST['monitor_ids'];
# Two concurrent sorts could generate odd sortings... so lock the table. # Two concurrent sorts could generate odd sortings... so lock the table.
global $dbConn; global $dbConn;
$dbConn->beginTransaction(); $dbConn->beginTransaction();
$dbConn->exec('LOCK TABLES Monitors WRITE'); $dbConn->exec('LOCK TABLES Monitors WRITE');
for ( $i = 0; $i < count($monitor_ids); $i += 1 ) { for ( $i = 0; $i < count($monitor_ids); $i += 1 ) {
$monitor_id = $monitor_ids[$i]; $monitor_id = $monitor_ids[$i];
$monitor_id = preg_replace( '/^monitor_id-/', '', $monitor_id ); $monitor_id = preg_replace('/^monitor_id-/', '', $monitor_id);
if ( ( ! $monitor_id ) or ! ( is_integer( $monitor_id ) or ctype_digit( $monitor_id ) ) ) { if ( ( !$monitor_id ) or ! ( is_integer($monitor_id) or ctype_digit($monitor_id) ) ) {
Warning("Got $monitor_id from " . $monitor_ids[$i]); Warning('Got '.$monitor_id.' from '.$monitor_ids[$i]);
continue; continue;
} }
dbQuery('UPDATE Monitors SET Sequence=? WHERE Id=?', array($i, $monitor_id)); dbQuery('UPDATE Monitors SET Sequence=? WHERE Id=?', array($i, $monitor_id));
} // end for each monitor_id } // end for each monitor_id
$dbConn->commit(); $dbConn->commit();
$dbConn->exec('UNLOCK TABLES'); $dbConn->exec('UNLOCK TABLES');
return; return;
} // end case sort } // end case sort
default: default:
{ ZM\Warning('unknown action '.$_REQUEST['action']);
ZM\Warning('unknown action ' . $_REQUEST['action']); }
} // end ddcase default
}
} else { } 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']);
?> ?>

View File

@ -1,5 +1,5 @@
<?php <?php
error_reporting(E_ERROR); ini_set('display_errors','0');
if ( empty($_REQUEST['id']) && empty($_REQUEST['eids']) ) { if ( empty($_REQUEST['id']) && empty($_REQUEST['eids']) ) {
ajaxError('No event id(s) supplied'); ajaxError('No event id(s) supplied');
@ -155,5 +155,5 @@ if ( canEdit('Events') ) {
} // end switch action } // end switch action
} // end if canEdit('Events') } // end if canEdit('Events')
ajaxError('Unrecognised action or insufficient permissions'); ajaxError('Unrecognised action '.$_REQUEST['action'].' or insufficient permissions for user '.$user['Username']);
?> ?>

View File

@ -1,14 +1,15 @@
<?php <?php
ini_set('display_errors', '0');
# Moved up here because it is used in several spots. # Moved up here because it is used in several spots.
# These are the valid columns that you can filter on. # These are the valid columns that you can filter on.
$filterFields = array( 'Component', 'ServerId', 'Pid', 'Level', 'File', 'Line' ); $filterFields = array('Component', 'ServerId', 'Pid', 'Level', 'File', 'Line');
function buildLogQuery($action) { function buildLogQuery($action) {
global $filterFields; global $filterFields;
$minTime = isset($_REQUEST['minTime'])?$_REQUEST['minTime']:NULL; $minTime = isset($_REQUEST['minTime']) ? $_REQUEST['minTime'] : NULL;
$maxTime = isset($_REQUEST['maxTime'])?$_REQUEST['maxTime']:NULL; $maxTime = isset($_REQUEST['maxTime']) ? $_REQUEST['maxTime'] : NULL;
$limit = 100; $limit = 100;
if ( isset($_REQUEST['limit']) ) { if ( isset($_REQUEST['limit']) ) {
@ -41,11 +42,11 @@ function buildLogQuery($action) {
} }
foreach ( $filter as $field=>$value ) { foreach ( $filter as $field=>$value ) {
if ( ! in_array($field, $filterFields) ) { if ( !in_array($field, $filterFields) ) {
ZM\Error("'$field' is not in valid filter fields " . print_r($filterField,true)); ZM\Error("'$field' is not in valid filter fields " . print_r($filterField, true));
continue; continue;
} }
if ( $field == 'Level' ){ if ( $field == 'Level' ) {
$where[] = $field.' <= ?'; $where[] = $field.' <= ?';
$values[] = $value; $values[] = $value;
} else { } else {
@ -58,7 +59,7 @@ function buildLogQuery($action) {
$sql .= ' ORDER BY '.$sortField.' '.$sortOrder.' LIMIT '.$limit; $sql .= ' ORDER BY '.$sortField.' '.$sortOrder.' LIMIT '.$limit;
return array('sql'=>$sql, 'values'=>$values); return array('sql'=>$sql, 'values'=>$values);
} } # function buildLogQuery($action)
switch ( $_REQUEST['task'] ) { switch ( $_REQUEST['task'] ) {
case 'create' : case 'create' :
@ -69,17 +70,21 @@ switch ( $_REQUEST['task'] ) {
$string = $_POST['message']; $string = $_POST['message'];
$file = !empty($_POST['file']) ? preg_replace( '/\w+:\/\/[\w.:]+\//', '', $_POST['file'] ) : ''; $file = !empty($_POST['file']) ? preg_replace('/\w+:\/\/[\w.:]+\//', '', $_POST['file']) : '';
if ( !empty( $_POST['line'] ) ) if ( !empty($_POST['line']) ) {
$line = validInt($_POST['line']); $line = validInt($_POST['line']);
else } else {
$line = NULL; $line = NULL;
}
$levels = array_flip(ZM\Logger::$codes); $levels = array_flip(ZM\Logger::$codes);
if ( !isset($levels[$_POST['level']]) ) if ( !isset($levels[$_POST['level']]) ) {
ZM\Panic('Unexpected logger level '.$_POST['level']); ZM\Panic('Unexpected logger level '.$_POST['level']);
}
$level = $levels[$_POST['level']]; $level = $levels[$_POST['level']];
ZM\Logger::fetch()->logPrint($level, $string, $file, $line); ZM\Logger::fetch()->logPrint($level, $string, $file, $line);
} else {
ZM\Error('Invalid log create: '.print_r($_POST, true));
} }
ajaxResponse(); ajaxResponse();
break; break;
@ -91,12 +96,7 @@ switch ( $_REQUEST['task'] ) {
$query = buildLogQuery('DELETE'); $query = buildLogQuery('DELETE');
$result = dbQuery($query['sql'], $query['values']); $result = dbQuery($query['sql'], $query['values']);
ajaxResponse( array( ajaxResponse(array('result'=>'Ok', 'deleted'=>$result->rowCount()));
'result'=>'Ok',
'deleted'=>$result->rowCount(),
) );
} }
case 'query' : case 'query' :
{ {
@ -105,10 +105,12 @@ switch ( $_REQUEST['task'] ) {
$total = dbFetchOne('SELECT count(*) AS Total FROM Logs', 'Total'); $total = dbFetchOne('SELECT count(*) AS Total FROM Logs', 'Total');
$query = buildLogQuery('SELECT *'); $query = buildLogQuery('SELECT *');
$servers = ZM\Server::find(); global $Servers;
if ( !$Servers )
$Servers = ZM\Server::find();
$servers_by_Id = array(); $servers_by_Id = array();
# There is probably a better way to do this. # There is probably a better way to do this.
foreach ( $servers as $server ) { foreach ( $Servers as $server ) {
$servers_by_Id[$server->Id()] = $server; $servers_by_Id[$server->Id()] = $server;
} }
@ -118,11 +120,9 @@ switch ( $_REQUEST['task'] ) {
foreach ( dbFetchAll($query['sql'], NULL, $query['values']) as $log ) { foreach ( dbFetchAll($query['sql'], NULL, $query['values']) as $log ) {
$log['DateTime'] = strftime('%Y-%m-%d %H:%M:%S', intval($log['TimeKey'])); $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['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']); $log['Message'] = preg_replace('/[\x00-\x1F\x7F-\xFF]/', '', $log['Message']);
foreach( $filterFields as $field ) { foreach ( $filterFields as $field ) {
if ( !isset($options[$field]) ) if ( !isset($options[$field]) )
$options[$field] = array(); $options[$field] = array();
$value = $log[$field]; $value = $log[$field];
@ -139,17 +139,21 @@ switch ( $_REQUEST['task'] ) {
} }
} }
$logs[] = $log; $logs[] = $log;
} # end foreach log db row
foreach ( $options as $field => $values ) {
asort($options[$field]);
} }
$available = count($logs); $available = count($logs);
ajaxResponse( array( ajaxResponse(array(
'updated' => preg_match('/%/', DATE_FMT_CONSOLE_LONG)?strftime(DATE_FMT_CONSOLE_LONG):date(DATE_FMT_CONSOLE_LONG), 'updated' => preg_match('/%/', DATE_FMT_CONSOLE_LONG) ? strftime(DATE_FMT_CONSOLE_LONG) : date(DATE_FMT_CONSOLE_LONG),
'total' => $total, 'total' => $total,
'available' => isset($available) ? $available : $total, 'available' => isset($available) ? $available : $total,
'logs' => $logs, 'logs' => $logs,
'state' => logState(), 'state' => logState(),
'options' => $options, 'options' => $options,
) ); ));
break; break;
} }
case 'export' : case 'export' :
@ -157,9 +161,9 @@ switch ( $_REQUEST['task'] ) {
if ( !canView('System') ) if ( !canView('System') )
ajaxError('Insufficient permissions to export logs'); ajaxError('Insufficient permissions to export logs');
$minTime = isset($_POST['minTime'])?$_POST['minTime']:NULL; $minTime = isset($_POST['minTime']) ? $_POST['minTime'] : NULL;
$maxTime = isset($_POST['maxTime'])?$_POST['maxTime']:NULL; $maxTime = isset($_POST['maxTime']) ? $_POST['maxTime'] : NULL;
if ( !is_null($minTime) && !is_null($maxTime) && $minTime > $maxTime ) { if ( !is_null($minTime) && !is_null($maxTime) && ($minTime > $maxTime) ) {
$tempTime = $minTime; $tempTime = $minTime;
$minTime = $maxTime; $minTime = $maxTime;
$maxTime = $tempTime; $maxTime = $tempTime;
@ -168,15 +172,17 @@ switch ( $_REQUEST['task'] ) {
$filter = isset($_POST['filter'])?$_POST['filter']:array(); $filter = isset($_POST['filter'])?$_POST['filter']:array();
$sortField = 'TimeKey'; $sortField = 'TimeKey';
if ( isset($_POST['sortField']) ) { if ( isset($_POST['sortField']) ) {
if ( ! in_array( $_POST['sortField'], $filterFields ) and ( $_POST['sortField'] != 'TimeKey' ) ) { if ( !in_array($_POST['sortField'], $filterFields) and ($_POST['sortField'] != 'TimeKey') ) {
ZM\Error("Invalid sort field " . $_POST['sortField'] ); ZM\Error('Invalid sort field '.$_POST['sortField']);
} else { } else {
$sortField = $_POST['sortField']; $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(); $servers_by_Id = array();
# There is probably a better way to do this. # There is probably a better way to do this.
foreach ( $servers as $server ) { foreach ( $servers as $server ) {
@ -187,11 +193,9 @@ switch ( $_REQUEST['task'] ) {
$where = array(); $where = array();
$values = array(); $values = array();
if ( $minTime ) { if ( $minTime ) {
ZM\Logger::Debug("MinTime: $minTime");
if ( preg_match('/(.+)(\.\d+)/', $minTime, $matches) ) { if ( preg_match('/(.+)(\.\d+)/', $minTime, $matches) ) {
# This handles sub second precision # This handles sub second precision
$minTime = strtotime($matches[1]).$matches[2]; $minTime = strtotime($matches[1]).$matches[2];
ZM\Logger::Debug("MinTime: $minTime");
} else { } else {
$minTime = strtotime($minTime); $minTime = strtotime($minTime);
} }
@ -219,11 +223,11 @@ switch ( $_REQUEST['task'] ) {
} }
} }
if ( count($where) ) if ( count($where) )
$sql.= ' WHERE '.join( ' AND ', $where ); $sql.= ' WHERE '.join(' AND ', $where);
$sql .= ' ORDER BY '.$sortField.' '.$sortOrder; $sql .= ' ORDER BY '.$sortField.' '.$sortOrder;
//$sql .= " limit ".dbEscape($limit); //$sql .= " limit ".dbEscape($limit);
$format = isset($_POST['format'])?$_POST['format']:'text'; $format = isset($_POST['format']) ? $_POST['format'] : 'text';
switch( $format ) { switch ( $format ) {
case 'text' : case 'text' :
$exportExt = 'txt'; $exportExt = 'txt';
break; break;
@ -239,43 +243,40 @@ switch ( $_REQUEST['task'] ) {
default : default :
ZM\Fatal("Unrecognised log export format '$format'"); ZM\Fatal("Unrecognised log export format '$format'");
} }
$exportKey = substr(md5(rand()),0,8); $exportKey = substr(md5(rand()), 0, 8);
$exportFile = "zm-log.$exportExt"; $exportFile = 'zm-log.'.$exportExt;
if ( ! file_exists(ZM_DIR_EXPORTS) ) { if ( ! ( mkdir(ZM_DIR_EXPORTS) || file_exists(ZM_DIR_EXPORTS) ) ) {
ZM\Logger::Debug('Creating ' . ZM_DIR_EXPORTS); ZM\Fatal('Can\'t create exports dir at \''.ZM_DIR_EXPORTS.'\'');
if ( ! mkdir(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"); ZM\Logger::Debug("Exporting to $exportPath");
if ( !($exportFP = fopen($exportPath, 'w')) ) if ( !($exportFP = fopen($exportPath, 'w')) )
ZM\Fatal("Unable to open log export file $exportPath"); ZM\Fatal("Unable to open log export file $exportPath");
$logs = array(); $logs = array();
foreach ( dbFetchAll($sql, NULL, $values) as $log ) { 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() : ''; $log['Server'] = ( $log['ServerId'] and isset($servers_by_Id[$log['ServerId']]) ) ? $servers_by_Id[$log['ServerId']]->Name() : '';
$logs[] = $log; $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 ) { switch( $format ) {
case 'text' : case 'text' :
{ {
foreach ( $logs as $log ) { foreach ( $logs as $log ) {
if ( $log['Line'] ) if ( $log['Line'] )
fprintf( $exportFP, "%s %s[%d].%s-%s/%d [%s]\n", fprintf($exportFP, "%s %s[%d].%s-%s/%d [%s]\n",
$log['DateTime'], $log['Component'], $log['Pid'], $log['Code'], $log['File'], $log['Line'], $log['Message'] ); $log['DateTime'], $log['Component'], $log['Pid'], $log['Code'], $log['File'], $log['Line'], $log['Message']);
else else
fprintf( $exportFP, "%s %s[%d].%s-%s [%s]\n", fprintf($exportFP, "%s %s[%d].%s-%s [%s]\n",
$log['DateTime'], $log['Component'], $log['Pid'], $log['Code'], $log['File'], $log['Message'] ); $log['DateTime'], $log['Component'], $log['Pid'], $log['Code'], $log['File'], $log['Message']);
} }
break; break;
} }
case 'tsv' : case 'tsv' :
{ {
# This line doesn't need fprintf, it could use fwrite # This line doesn't need fprintf, it could use fwrite
fprintf( $exportFP, join( "\t", fprintf($exportFP, join("\t",
translate('DateTime'), translate('DateTime'),
translate('Component'), translate('Component'),
translate('Server'), translate('Server'),
@ -284,17 +285,17 @@ switch ( $_REQUEST['task'] ) {
translate('Message'), translate('Message'),
translate('File'), translate('File'),
translate('Line') translate('Line')
)."\n" ); )."\n");
foreach ( $logs as $log ) { 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; break;
} }
case 'html' : case 'html' :
{ {
fwrite( $exportFP, fwrite($exportFP,
' '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"> <html xmlns="http://www.w3.org/1999/xhtml">
<head> <head>
<title>'.translate('ZoneMinderLog').'</title> <title>'.translate('ZoneMinderLog').'</title>
@ -333,34 +334,36 @@ switch ( $_REQUEST['task'] ) {
</head> </head>
<body> <body>
<h3>'.translate('ZoneMinderLog').'</h3> <h3>'.translate('ZoneMinderLog').'</h3>
<p>'.htmlspecialchars(preg_match( '/%/', DATE_FMT_CONSOLE_LONG )?strftime( DATE_FMT_CONSOLE_LONG ):date( DATE_FMT_CONSOLE_LONG )).'</p> <p>'.htmlspecialchars(preg_match('/%/', DATE_FMT_CONSOLE_LONG) ? strftime(DATE_FMT_CONSOLE_LONG) : date(DATE_FMT_CONSOLE_LONG)).'</p>
<p>'.count($logs).' '.translate('Logs').'</p> <p>'.count($logs).' '.translate('Logs').'</p>
<table> <table>
<tbody> <tbody>
<tr><th>'.translate('DateTime').'</th><th>'.translate('Component').'</th><th>'.translate('Server').'</th><th>'.translate('Pid').'</th><th>'.translate('Level').'</th><th>'.translate('Message').'</th><th>'.translate('File').'</th><th>'.translate('Line').'</th></tr> <tr><th>'.translate('DateTime').'</th><th>'.translate('Component').'</th><th>'.translate('Server').'</th><th>'.translate('Pid').'</th><th>'.translate('Level').'</th><th>'.translate('Message').'</th><th>'.translate('File').'</th><th>'.translate('Line').'</th></tr>
' ); ');
foreach ( $logs as $log ) { foreach ( $logs as $log ) {
$classLevel = $log['Level']; $classLevel = $log['Level'];
if ( $classLevel < ZM\Logger::FATAL ) if ( $classLevel < ZM\Logger::FATAL ) {
$classLevel = ZM\Logger::FATAL; $classLevel = ZM\Logger::FATAL;
elseif ( $classLevel > ZM\Logger::DEBUG ) } else if ( $classLevel > ZM\Logger::DEBUG ) {
$classLevel = ZM\Logger::DEBUG; $classLevel = ZM\Logger::DEBUG;
}
$logClass = 'log-'.strtolower(ZM\Logger::$codes[$classLevel]); $logClass = 'log-'.strtolower(ZM\Logger::$codes[$classLevel]);
fprintf( $exportFP, " <tr class=\"%s\"><td>%s</td><td>%s</td><td>%s</td><td>%d</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>\n", $logClass, $log['DateTime'], $log['Component'], $log['Server'], $log['Pid'], $log['Code'], $log['Message'], $log['File'], $log['Line'] ); fprintf($exportFP, ' <tr class="%s"><td>%s</td><td>%s</td><td>%s</td><td>%d</td><td>%s</td><td>%s</td><td>%s</td><td>%s</td></tr>
', $logClass, $log['DateTime'], $log['Component'], $log['Server'], $log['Pid'], $log['Code'], $log['Message'], $log['File'], $log['Line']);
} }
fwrite( $exportFP, fwrite($exportFP,
' </tbody> ' </tbody>
</table> </table>
</body> </body>
</html>' ); </html>');
break; break;
} }
case 'xml' : case 'xml' :
{ {
fwrite( $exportFP, fwrite($exportFP,
'<?xml version="1.0" encoding="utf-8"?> '<?xml version="1.0" encoding="utf-8"?>
<logexport title="'.translate('ZoneMinderLog').'" date="'.htmlspecialchars(preg_match( '/%/', DATE_FMT_CONSOLE_LONG )?strftime( DATE_FMT_CONSOLE_LONG ):date( DATE_FMT_CONSOLE_LONG )).'"> <logexport title="'.translate('ZoneMinderLog').'" date="'.htmlspecialchars(preg_match('/%/', DATE_FMT_CONSOLE_LONG) ? strftime(DATE_FMT_CONSOLE_LONG) : date(DATE_FMT_CONSOLE_LONG)).'">
<selector>'.$_POST['selector'].'</selector>' ); <selector>'.$_POST['selector'].'</selector>');
foreach ( $filter as $field=>$value ) foreach ( $filter as $field=>$value )
if ( $value != '' ) if ( $value != '' )
fwrite( $exportFP, fwrite( $exportFP,
@ -375,7 +378,7 @@ switch ( $_REQUEST['task'] ) {
' ); ' );
foreach ( $logs as $log ) { foreach ( $logs as $log ) {
fprintf( $exportFP, fprintf( $exportFP,
" <log> ' <log>
<datetime>%s</datetime> <datetime>%s</datetime>
<component>%s</component> <component>%s</component>
<server>%s</server> <server>%s</server>
@ -384,7 +387,8 @@ switch ( $_REQUEST['task'] ) {
<message><![CDATA[%s]]></message> <message><![CDATA[%s]]></message>
<file>%s</file> <file>%s</file>
<line>%d</line> <line>%d</line>
</log>\n", $log['DateTime'], $log['Component'], $log['Server'], $log['Pid'], $log['Code'], utf8_decode( $log['Message'] ), $log['File'], $log['Line'] ); </log>
', $log['DateTime'], $log['Component'], $log['Server'], $log['Pid'], $log['Code'], utf8_decode( $log['Message'] ), $log['File'], $log['Line'] );
} }
fwrite( $exportFP, fwrite( $exportFP,
' </logs> ' </logs>
@ -413,7 +417,7 @@ switch ( $_REQUEST['task'] ) {
ZM\Fatal('No log export format given'); ZM\Fatal('No log export format given');
$format = $_REQUEST['format']; $format = $_REQUEST['format'];
switch( $format ) { switch ( $format ) {
case 'text' : case 'text' :
$exportExt = 'txt'; $exportExt = 'txt';
break; break;
@ -430,15 +434,15 @@ switch ( $_REQUEST['task'] ) {
ZM\Fatal("Unrecognised log export format '$format'"); ZM\Fatal("Unrecognised log export format '$format'");
} }
$exportFile = "zm-log.$exportExt"; $exportFile = 'zm-log.'.$exportExt;
$exportPath = ZM_DIR_EXPORTS."/zm-log-$exportKey.$exportExt"; $exportPath = ZM_DIR_EXPORTS.'/zm-log-'.$exportKey.$exportExt;
header('Pragma: public'); header('Pragma: public');
header('Expires: 0'); header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=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-Description: File Transfer');
header('Content-Disposition: attachment; filename="'.$exportFile.'"' ); header('Content-Disposition: attachment; filename="'.$exportFile.'"');
header('Content-Transfer-Encoding: binary'); header('Content-Transfer-Encoding: binary');
header('Content-Type: application/force-download'); header('Content-Type: application/force-download');
header('Content-Length: '.filesize($exportPath)); header('Content-Length: '.filesize($exportPath));
@ -446,6 +450,6 @@ switch ( $_REQUEST['task'] ) {
exit(0); exit(0);
break; break;
} }
} } // end switch ( $_REQUEST['task'] )
ajaxError('Unrecognised action or insufficient permissions'); ajaxError('Unrecognised action or insufficient permissions');
?> ?>

View File

@ -1,8 +1,11 @@
<?php <?php
if ($_REQUEST['entity'] == 'navBar') { if ( $_REQUEST['entity'] == 'navBar' ) {
$data = array(); $data = array();
if ( ZM_OPT_USE_AUTH && ZM_AUTH_RELAY == 'hashed' ) { if ( ZM_OPT_USE_AUTH && (ZM_AUTH_RELAY == 'hashed') ) {
$data['auth'] = generateAuthHash( ZM_AUTH_HASH_IPS ); $auth_hash = generateAuthHash(ZM_AUTH_HASH_IPS);
if ( isset($_REQUEST['auth']) and ($_REQUEST['auth'] != $auth_hash) ) {
$data['auth'] = $auth_hash;
}
} }
$data['message'] = getNavBarHtml('reload'); $data['message'] = getNavBarHtml('reload');
ajaxResponse($data); ajaxResponse($data);
@ -83,8 +86,8 @@ $statusData = array(
'MinEventId' => array( 'sql' => '(SELECT min(Events.Id) FROM Events WHERE Events.MonitorId = Monitors.Id' ), 'MinEventId' => 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' ), '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' ), 'TotalEvents' => array( 'sql' => '(SELECT count(Events.Id) FROM Events WHERE Events.MonitorId = Monitors.Id' ),
'Status' => array( 'zmu' => '-m '.escapeshellarg($_REQUEST['id'][0]).' -s' ), 'Status' => (isset($_REQUEST['id'])?array( 'zmu' => '-m '.escapeshellarg($_REQUEST['id'][0]).' -s' ):null),
'FrameRate' => array( 'zmu' => '-m '.escapeshellarg($_REQUEST['id'][0]).' -f' ), 'FrameRate' => (isset($_REQUEST['id'])?array( 'zmu' => '-m '.escapeshellarg($_REQUEST['id'][0]).' -f' ):null),
), ),
), ),
'events' => array( 'events' => array(
@ -93,6 +96,7 @@ $statusData = array(
'selector' => 'Events.MonitorId', 'selector' => 'Events.MonitorId',
'elements' => array( 'elements' => array(
'Id' => true, 'Id' => true,
'MonitorId' => true,
'Name' => true, 'Name' => true,
'Cause' => true, 'Cause' => true,
'Notes' => true, 'Notes' => true,
@ -204,6 +208,7 @@ function collectData() {
$fieldSql = array(); $fieldSql = array();
$joinSql = array(); $joinSql = array();
$groupSql = array(); $groupSql = array();
$values = array();
$elements = &$entitySpec['elements']; $elements = &$entitySpec['elements'];
$lc_elements = array_change_key_case( $elements ); $lc_elements = array_change_key_case( $elements );
@ -258,7 +263,6 @@ function collectData() {
if ( $id && !empty($entitySpec['selector']) ) { if ( $id && !empty($entitySpec['selector']) ) {
$index = 0; $index = 0;
$where = array(); $where = array();
$values = array();
foreach( $entitySpec['selector'] as $selIndex => $selector ) { foreach( $entitySpec['selector'] as $selIndex => $selector ) {
$selectorParamName = ':selector' . $selIndex; $selectorParamName = ':selector' . $selIndex;
if ( is_array( $selector ) ) { if ( is_array( $selector ) ) {

View File

@ -86,10 +86,8 @@ if ( sem_acquire($semaphore,1) !== false ) {
$numSockets = socket_select($rSockets, $wSockets, $eSockets, intval($timeout/1000), ($timeout%1000)*1000); $numSockets = socket_select($rSockets, $wSockets, $eSockets, intval($timeout/1000), ($timeout%1000)*1000);
if ( $numSockets === false ) { if ( $numSockets === false ) {
ZM\Error('socket_select failed: ' . socket_strerror(socket_last_error()));
ajaxError('socket_select failed: '.socket_strerror(socket_last_error())); ajaxError('socket_select failed: '.socket_strerror(socket_last_error()));
} else if ( $numSockets < 0 ) { } else if ( $numSockets < 0 ) {
ZM\Error("Socket closed $remSockFile");
ajaxError("Socket closed $remSockFile"); ajaxError("Socket closed $remSockFile");
} else if ( $numSockets == 0 ) { } else if ( $numSockets == 0 ) {
ZM\Error("Timed out waiting for msg $remSockFile"); 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"); #ajaxError("Timed out waiting for msg $remSockFile");
} else if ( $numSockets > 0 ) { } else if ( $numSockets > 0 ) {
if ( count($rSockets) != 1 ) { if ( count($rSockets) != 1 ) {
ZM\Error('Bogus return from select, '.count($rSockets).' sockets available');
ajaxError('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'] ) { switch ( $data['type'] ) {
case MSG_DATA_WATCH : case MSG_DATA_WATCH :
$data = unpack('ltype/imonitor/istate/dfps/ilevel/irate/ddelay/izoom/Cdelayed/Cpaused/Cenabled/Cforced', $msg); $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 ); $data['fps'] = round( $data['fps'], 2 );
ZM\Logger::Debug('FPS: ' . $data['fps'] );
$data['rate'] /= RATE_BASE; $data['rate'] /= RATE_BASE;
$data['delay'] = round( $data['delay'], 2 ); $data['delay'] = round( $data['delay'], 2 );
$data['zoom'] = round( $data['zoom']/SCALE_BASE, 1 ); $data['zoom'] = round( $data['zoom']/SCALE_BASE, 1 );
if ( ZM_OPT_USE_AUTH && ZM_AUTH_RELAY == 'hashed' ) { if ( ZM_OPT_USE_AUTH && (ZM_AUTH_RELAY == 'hashed') ) {
$time = time(); $auth_hash = generateAuthHash(ZM_AUTH_HASH_IPS);
// Regenerate auth hash after half the lifetime of the hash if ( isset($_REQUEST['auth']) and ($_REQUEST['auth'] != $auth_hash) ) {
if ( (!isset($_SESSION['AuthHashGeneratedAt'])) or ( $_SESSION['AuthHashGeneratedAt'] < $time - (ZM_AUTH_HASH_TTL * 1800) ) ) { $data['auth'] = $auth_hash;
$data['auth'] = generateAuthHash(ZM_AUTH_HASH_IPS); 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)); ajaxResponse(array('status'=>$data));
@ -143,12 +140,11 @@ if ( sem_acquire($semaphore,1) !== false ) {
$data = unpack('ltype/Qevent/iprogress/irate/izoom/Cpaused', $msg); $data = unpack('ltype/Qevent/iprogress/irate/izoom/Cpaused', $msg);
} }
$data['rate'] /= RATE_BASE; $data['rate'] /= RATE_BASE;
$data['zoom'] = round( $data['zoom']/SCALE_BASE, 1 ); $data['zoom'] = round($data['zoom']/SCALE_BASE, 1);
if ( ZM_OPT_USE_AUTH && ZM_AUTH_RELAY == 'hashed' ) { if ( ZM_OPT_USE_AUTH && (ZM_AUTH_RELAY == 'hashed') ) {
$time = time(); $auth_hash = generateAuthHash(ZM_AUTH_HASH_IPS);
// Regenerate auth hash after half the lifetime of the hash if ( isset($_REQUEST['auth']) and ($_REQUEST['auth'] != $auth_hash) ) {
if ( (!isset($_SESSION['AuthHashGeneratedAt'])) or ( $_SESSION['AuthHashGeneratedAt'] < $time - (ZM_AUTH_HASH_TTL * 1800) ) ) { $data['auth'] = $auth_hash;
$data['auth'] = generateAuthHash(ZM_AUTH_HASH_IPS);
} }
} }
ajaxResponse(array('status'=>$data)); ajaxResponse(array('status'=>$data));

View File

@ -149,6 +149,10 @@ class EventsController extends AppController {
)); ));
$event['Event']['NextOfMonitor'] = $event_monitor_neighbors['next']['Event']['Id']; $event['Event']['NextOfMonitor'] = $event_monitor_neighbors['next']['Event']['Id'];
$event['Event']['PrevOfMonitor'] = $event_monitor_neighbors['prev']['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( $this->set(array(
'event' => $event, 'event' => $event,

View File

@ -77,16 +77,24 @@ class GroupsController extends AppController {
} }
$this->Group->create(); $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( return $this->flash(
__('The group has been saved.'), __('The group has been saved.'),
array('action' => 'index') array('action' => 'index')
); );
} } else {
} ZM\Error("Failed to save Group");
$monitors = $this->Group->Monitor->find('list'); debug($this->Group->invalidFields());
}
} # end if post
$monitors = $this->Group->Monitor->find('list');
$this->set(compact('monitors')); $this->set(compact('monitors'));
} } # end add
/** /**
* edit method * edit method

View File

@ -50,8 +50,10 @@ class HostController extends AppController {
$cred_depr = []; $cred_depr = [];
if ( $username && $password ) { if ( $username && $password ) {
ZM\Logger::Debug('Username and password provided, generating access and refresh tokens');
$cred = $this->_getCredentials(true, '', $username); // generate refresh $cred = $this->_getCredentials(true, '', $username); // generate refresh
} else { } else {
ZM\Logger::Debug('Only generating access token');
$cred = $this->_getCredentials(false, $token); // don't generate refresh $cred = $this->_getCredentials(false, $token); // don't generate refresh
} }
@ -69,6 +71,8 @@ class HostController extends AppController {
$cred_depr = $this->_getCredentialsDeprecated(); $cred_depr = $this->_getCredentialsDeprecated();
$login_array['credentials'] = $cred_depr[0]; $login_array['credentials'] = $cred_depr[0];
$login_array['append_password'] = $cred_depr[1]; $login_array['append_password'] = $cred_depr[1];
} else {
ZM\Logger::Debug('Legacy Auth is disabled, not generating auth= credentials');
} }
$login_array['version'] = $ver[0]; $login_array['version'] = $ver[0];
@ -108,8 +112,11 @@ class HostController extends AppController {
private function _getCredentials($generate_refresh_token=false, $token='', $username='') { 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; return;
}
if ( !ZM_AUTH_HASH_SECRET ) if ( !ZM_AUTH_HASH_SECRET )
throw new ForbiddenException(__('Please create a valid AUTH_HASH_SECRET in ZoneMinder')); 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_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 // by default access token will expire in 2 hrs
// you can change it by changing the value of ZM_AUTH_HASH_TLL // you can change it by changing the value of ZM_AUTH_HASH_TLL

View File

@ -44,33 +44,26 @@ class MonitorsController extends AppController {
} else { } else {
$conditions = array(); $conditions = array();
} }
global $user; global $user;
$allowedMonitors = $user ? preg_split('@,@', $user['MonitorIds'], NULL, PREG_SPLIT_NO_EMPTY) : null; $allowedMonitors = $user ? preg_split('@,@', $user['MonitorIds'], NULL, PREG_SPLIT_NO_EMPTY) : null;
if ( $allowedMonitors ) { if ( $allowedMonitors ) {
$conditions['Monitor.Id' ] = $allowedMonitors; $conditions['Monitor.Id' ] = $allowedMonitors;
} }
$find_array = array('conditions'=>$conditions,'contain'=>array('Group'));
if ( isset($conditions['GroupId']) ) { $find_array = array(
$find_array['joins'] = array( 'conditions' => &$conditions,
'contain' => array('Group'),
'joins' => array(
array( array(
'table' => 'Groups_Monitors', 'table' => 'Groups_Monitors',
'type' => 'inner', 'type' => 'left',
'conditions' => array( 'conditions' => array(
'Groups_Monitors.MonitorId = Monitor.Id' 'Groups_Monitors.MonitorId = Monitor.Id',
), ),
), ),
//array( ),
//'table' => 'Groups', 'group' => '`Monitor`.`Id`',
//'type' => 'inner', );
//'conditions' => array(
//'Groups.Id = Groups_Monitors.GroupId',
//'Groups.Id' => $this->request->params['GroupId'],
//),
//)
);
}
$monitors = $this->Monitor->find('all',$find_array); $monitors = $this->Monitor->find('all',$find_array);
$this->set(array( $this->set(array(
'monitors' => $monitors, 'monitors' => $monitors,
@ -386,6 +379,8 @@ class MonitorsController extends AppController {
$args = ''; $args = '';
if ( $daemon == 'zmc' and $monitor['Type'] == 'Local' ) { if ( $daemon == 'zmc' and $monitor['Type'] == 'Local' ) {
$args = '-d ' . $monitor['Device']; $args = '-d ' . $monitor['Device'];
} else if ( $daemon == 'zmcontrol.pl' ) {
$args = '--id '.$id;
} else { } else {
$args = '-m ' . $id; $args = '-m ' . $id;
} }

View File

@ -17,12 +17,17 @@ class ServersController extends AppController {
public function beforeFilter() { public function beforeFilter() {
parent::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; global $user;
$canView = (!$user) || ($user['System'] != 'None'); $canView = (!$user) || ($user['System'] != 'None');
if ( !$canView ) { if ( !$canView ) {
throw new UnauthorizedException(__('Insufficient Privileges')); throw new UnauthorizedException(__('Insufficient Privileges'));
return; return;
} }
*/
} }
/** /**
@ -34,7 +39,7 @@ class ServersController extends AppController {
$this->Server->recursive = 0; $this->Server->recursive = 0;
$options = ''; $options = '';
$servers = $this->Server->find('all',$options); $servers = $this->Server->find('all', $options);
$this->set(array( $this->set(array(
'servers' => $servers, 'servers' => $servers,
'_serialize' => array('servers') '_serialize' => array('servers')
@ -50,13 +55,13 @@ class ServersController extends AppController {
*/ */
public function view($id = null) { public function view($id = null) {
$this->Server->recursive = 0; $this->Server->recursive = 0;
if (!$this->Server->exists($id)) { if ( !$this->Server->exists($id) ) {
throw new NotFoundException(__('Invalid server')); throw new NotFoundException(__('Invalid server'));
} }
$restricted = ''; $restricted = '';
$options = array('conditions' => array( $options = array('conditions' => array(
array('Server.' . $this->Server->primaryKey => $id), array('Server.'.$this->Server->primaryKey => $id),
$restricted $restricted
) )
); );

View File

@ -59,7 +59,7 @@ class Group extends AppModel {
* *
* @var array * @var array
*/ */
public $hasMany = array( public $hasAndBelongsToMany = array(
'Monitor' => array( 'Monitor' => array(
'className' => 'Monitor', 'className' => 'Monitor',
'joinTable' => 'Groups_Monitors', 'joinTable' => 'Groups_Monitors',
@ -77,4 +77,5 @@ class Group extends AppModel {
'counterQuery' => '' 'counterQuery' => ''
), ),
); );
var $actsAs = array( 'Containable' );
} }

View File

@ -128,12 +128,16 @@ class Control extends ZM_Object {
$cmds['PresetHome'] = 'presetHome'; $cmds['PresetHome'] = 'presetHome';
if ( $this->CanZoom() ) { if ( $this->CanZoom() ) {
if ( $this->CanZoomCon() ) if ( $this->CanZoomCon() ) {
$cmds['ZoomRoot'] = 'zoomCon'; $cmds['ZoomRoot'] = 'zoomCon';
elseif ( $this->CanZoomRel() ) } else if ( $this->CanZoomRel() ) {
$cmds['ZoomRoot'] = 'zoomRel'; $cmds['ZoomRoot'] = 'zoomRel';
elseif ( $this->CanZoomAbs() ) } else if ( $this->CanZoomAbs() ) {
$cmds['ZoomRoot'] = 'zoomAbs'; $cmds['ZoomRoot'] = 'zoomAbs';
} else {
$cmds['ZoomRoot'] = '';
Error('No zoom type selected. Please select Continuous, Relative, Absolute');
}
$cmds['ZoomTele'] = $cmds['ZoomRoot'].'Tele'; $cmds['ZoomTele'] = $cmds['ZoomRoot'].'Tele';
$cmds['ZoomWide'] = $cmds['ZoomRoot'].'Wide'; $cmds['ZoomWide'] = $cmds['ZoomRoot'].'Wide';
$cmds['ZoomStop'] = 'zoomStop'; $cmds['ZoomStop'] = 'zoomStop';
@ -142,12 +146,16 @@ class Control extends ZM_Object {
} }
if ( $this->CanFocus() ) { if ( $this->CanFocus() ) {
if ( $this->CanFocusCon() ) if ( $this->CanFocusCon() ) {
$cmds['FocusRoot'] = 'focusCon'; $cmds['FocusRoot'] = 'focusCon';
elseif ( $this->CanFocusRel() ) } else if ( $this->CanFocusRel() ) {
$cmds['FocusRoot'] = 'focusRel'; $cmds['FocusRoot'] = 'focusRel';
elseif ( $this->CanFocusAbs() ) } else if ( $this->CanFocusAbs() ) {
$cmds['FocusRoot'] = 'focusAbs'; $cmds['FocusRoot'] = 'focusAbs';
} else {
$cmds['FocusRoot'] = '';
Error('No focus type selected. Please select Continuous, Relative, Absolute');
}
$cmds['FocusFar'] = $cmds['FocusRoot'].'Far'; $cmds['FocusFar'] = $cmds['FocusRoot'].'Far';
$cmds['FocusNear'] = $cmds['FocusRoot'].'Near'; $cmds['FocusNear'] = $cmds['FocusRoot'].'Near';
$cmds['FocusStop'] = 'focusStop'; $cmds['FocusStop'] = 'focusStop';
@ -156,12 +164,16 @@ class Control extends ZM_Object {
} }
if ( $this->CanIris() ) { if ( $this->CanIris() ) {
if ( $this->CanIrisCon() ) if ( $this->CanIrisCon() ) {
$cmds['IrisRoot'] = 'irisCon'; $cmds['IrisRoot'] = 'irisCon';
elseif ( $this->CanIrisRel() ) } else if ( $this->CanIrisRel() ) {
$cmds['IrisRoot'] = 'irisRel'; $cmds['IrisRoot'] = 'irisRel';
elseif ( $this->CanIrisAbs() ) } else if ( $this->CanIrisAbs() ) {
$cmds['IrisRoot'] = 'irisAbs'; $cmds['IrisRoot'] = 'irisAbs';
} else {
$cmds['IrisRoot'] = '';
Error('No iris type selected. Please select Continuous, Relative, Absolute');
}
$cmds['IrisOpen'] = $cmds['IrisRoot'].'Open'; $cmds['IrisOpen'] = $cmds['IrisRoot'].'Open';
$cmds['IrisClose'] = $cmds['IrisRoot'].'Close'; $cmds['IrisClose'] = $cmds['IrisRoot'].'Close';
$cmds['IrisStop'] = 'irisStop'; $cmds['IrisStop'] = 'irisStop';
@ -170,12 +182,16 @@ class Control extends ZM_Object {
} }
if ( $this->CanWhite() ) { if ( $this->CanWhite() ) {
if ( $this->CanWhiteCon() ) if ( $this->CanWhiteCon() ) {
$cmds['WhiteRoot'] = 'whiteCon'; $cmds['WhiteRoot'] = 'whiteCon';
elseif ( $this->CanWhiteRel() ) } else if ( $this->CanWhiteRel() ) {
$cmds['WhiteRoot'] = 'whiteRel'; $cmds['WhiteRoot'] = 'whiteRel';
elseif ( $this->CanWhiteAbs() ) } else if ( $this->CanWhiteAbs() ) {
$cmds['WhiteRoot'] = 'whiteAbs'; $cmds['WhiteRoot'] = 'whiteAbs';
} else {
Error('No White type selected. Please select Continuous, Relative, Absolute');
$cmds['WhiteRoot'] = '';
}
$cmds['WhiteIn'] = $cmds['WhiteRoot'].'In'; $cmds['WhiteIn'] = $cmds['WhiteRoot'].'In';
$cmds['WhiteOut'] = $cmds['WhiteRoot'].'Out'; $cmds['WhiteOut'] = $cmds['WhiteRoot'].'Out';
$cmds['WhiteAuto'] = 'whiteAuto'; $cmds['WhiteAuto'] = 'whiteAuto';
@ -183,12 +199,16 @@ class Control extends ZM_Object {
} }
if ( $this->CanGain() ) { if ( $this->CanGain() ) {
if ( $this->CanGainCon() ) if ( $this->CanGainCon() ) {
$cmds['GainRoot'] = 'gainCon'; $cmds['GainRoot'] = 'gainCon';
elseif ( $this->CanGainRel() ) } else if ( $this->CanGainRel() ) {
$cmds['GainRoot'] = 'gainRel'; $cmds['GainRoot'] = 'gainRel';
elseif ( $this->CanGainAbs() ) } else if ( $this->CanGainAbs() ) {
$cmds['GainRoot'] = 'gainAbs'; $cmds['GainRoot'] = 'gainAbs';
} else {
Error('No Gain type selected');
$cmds['GainRoot'] = '';
}
$cmds['GainUp'] = $cmds['GainRoot'].'Up'; $cmds['GainUp'] = $cmds['GainRoot'].'Up';
$cmds['GainDown'] = $cmds['GainRoot'].'Down'; $cmds['GainDown'] = $cmds['GainRoot'].'Down';
$cmds['GainAuto'] = 'gainAuto'; $cmds['GainAuto'] = 'gainAuto';
@ -207,6 +227,7 @@ class Control extends ZM_Object {
$cmds['Center'] = $cmds['PresetHome']; $cmds['Center'] = $cmds['PresetHome'];
} else { } else {
$cmds['MoveRoot'] = ''; $cmds['MoveRoot'] = '';
Error('No move type selected. Please select Continuous, Relative, Absolute');
} }
$cmds['MoveUp'] = $cmds['MoveRoot'].'Up'; $cmds['MoveUp'] = $cmds['MoveRoot'].'Up';

View File

@ -47,14 +47,18 @@ class Event extends ZM_Object {
return ZM_Object::_find_one(get_class(), $parameters, $options); 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 ) { public function Storage( $new = null ) {
if ( $new ) { if ( $new ) {
$this->{'Storage'} = $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'} ) if ( isset($this->{'StorageId'}) and $this->{'StorageId'} )
$this->{'Storage'} = Storage::find_one(array('Id'=>$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); $this->{'Storage'} = new Storage(NULL);
} }
return $this->{'Storage'}; return $this->{'Storage'};
@ -64,10 +68,10 @@ class Event extends ZM_Object {
if ( $new ) { if ( $new ) {
$this->{'SecondaryStorage'} = $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'} ) if ( isset($this->{'SecondaryStorageId'}) and $this->{'SecondaryStorageId'} )
$this->{'SecondaryStorage'} = Storage::find_one(array('Id'=>$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); $this->{'SecondaryStorage'} = new Storage(NULL);
} }
return $this->{'SecondaryStorage'}; return $this->{'SecondaryStorage'};
@ -262,7 +266,7 @@ class Event extends ZM_Object {
if ( is_null($new) or ( $new != '' ) ) { if ( is_null($new) or ( $new != '' ) ) {
$this->{'DiskSpace'} = $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()); $this->{'DiskSpace'} = folder_size($this->Path());
dbQuery('UPDATE Events SET DiskSpace=? WHERE Id=?', array($this->{'DiskSpace'}, $this->{'Id'})); dbQuery('UPDATE Events SET DiskSpace=? WHERE Id=?', array($this->{'DiskSpace'}, $this->{'Id'}));
} }
@ -298,7 +302,7 @@ class Event extends ZM_Object {
} // end function createListThumbnail } // end function createListThumbnail
function ThumbnailWidth( ) { function ThumbnailWidth( ) {
if ( ! ( array_key_exists('ThumbnailWidth', $this) ) ) { if ( ! ( property_exists($this, 'ThumbnailWidth') ) ) {
if ( ZM_WEB_LIST_THUMB_WIDTH ) { if ( ZM_WEB_LIST_THUMB_WIDTH ) {
$this->{'ThumbnailWidth'} = ZM_WEB_LIST_THUMB_WIDTH; $this->{'ThumbnailWidth'} = ZM_WEB_LIST_THUMB_WIDTH;
$scale = (SCALE_BASE*ZM_WEB_LIST_THUMB_WIDTH)/$this->{'Width'}; $scale = (SCALE_BASE*ZM_WEB_LIST_THUMB_WIDTH)/$this->{'Width'};
@ -315,7 +319,7 @@ class Event extends ZM_Object {
} // end function ThumbnailWidth } // end function ThumbnailWidth
function ThumbnailHeight( ) { function ThumbnailHeight( ) {
if ( ! ( array_key_exists('ThumbnailHeight', $this) ) ) { if ( ! ( property_exists($this, 'ThumbnailHeight') ) ) {
if ( ZM_WEB_LIST_THUMB_WIDTH ) { if ( ZM_WEB_LIST_THUMB_WIDTH ) {
$this->{'ThumbnailWidth'} = ZM_WEB_LIST_THUMB_WIDTH; $this->{'ThumbnailWidth'} = ZM_WEB_LIST_THUMB_WIDTH;
$scale = (SCALE_BASE*ZM_WEB_LIST_THUMB_WIDTH)/$this->{'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 capture file exists
} // end if analyze file exists } // end if analyze file exists
} } // end if frame or snapshot
$captPath = $eventPath.'/'.$captImage; $captPath = $eventPath.'/'.$captImage;
if ( ! file_exists($captPath) ) { if ( ! file_exists($captPath) ) {
Error("Capture file does not exist at $captPath"); Error("Capture file does not exist at $captPath");
} }
//echo "CI:$captImage, CP:$captPath, TCP:$captPath<br>";
$analImage = sprintf('%0'.ZM_EVENT_IMAGE_DIGITS.'d-analyse.jpg', $frame['FrameId']); $analImage = sprintf('%0'.ZM_EVENT_IMAGE_DIGITS.'d-analyse.jpg', $frame['FrameId']);
$analPath = $eventPath.'/'.$analImage; $analPath = $eventPath.'/'.$analImage;
//echo "AI:$analImage, AP:$analPath, TAP:$analPath<br>";
$alarmFrame = $frame['Type']=='Alarm'; $alarmFrame = $frame['Type']=='Alarm';
$hasAnalImage = $alarmFrame && file_exists($analPath) && filesize($analPath); $hasAnalImage = $alarmFrame && file_exists($analPath) && filesize($analPath);
$isAnalImage = $hasAnalImage && !$captureOnly; $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; $imagePath = $thumbPath = $isAnalImage ? $analPath : $captPath;
$imageFile = $imagePath; $imageFile = $imagePath;
$thumbFile = $thumbPath; $thumbFile = $thumbPath;
} else { } else {
if ( version_compare( phpversion(), '4.3.10', '>=') ) if ( version_compare(phpversion(), '4.3.10', '>=') )
$fraction = sprintf('%.3F', $scale/SCALE_BASE); $fraction = sprintf('%.3F', $scale/SCALE_BASE);
else else
$fraction = sprintf('%.3f', $scale/SCALE_BASE); $fraction = sprintf('%.3f', $scale/SCALE_BASE);
@ -455,19 +455,19 @@ class Event extends ZM_Object {
} }
$thumbFile = $thumbPath; $thumbFile = $thumbPath;
if ( $overwrite || ! file_exists( $thumbFile ) || ! filesize( $thumbFile ) ) { if ( $overwrite || ! file_exists($thumbFile) || ! filesize($thumbFile) ) {
// Get new dimensions // Get new dimensions
list( $imageWidth, $imageHeight ) = getimagesize( $imagePath ); list( $imageWidth, $imageHeight ) = getimagesize($imagePath);
$thumbWidth = $imageWidth * $fraction; $thumbWidth = $imageWidth * $fraction;
$thumbHeight = $imageHeight * $fraction; $thumbHeight = $imageHeight * $fraction;
// Resample // Resample
$thumbImage = imagecreatetruecolor( $thumbWidth, $thumbHeight ); $thumbImage = imagecreatetruecolor($thumbWidth, $thumbHeight);
$image = imagecreatefromjpeg( $imagePath ); $image = imagecreatefromjpeg($imagePath);
imagecopyresampled( $thumbImage, $image, 0, 0, 0, 0, $thumbWidth, $thumbHeight, $imageWidth, $imageHeight ); imagecopyresampled($thumbImage, $image, 0, 0, 0, 0, $thumbWidth, $thumbHeight, $imageWidth, $imageHeight);
if ( !imagejpeg( $thumbImage, $thumbPath ) ) if ( !imagejpeg($thumbImage, $thumbPath) )
Error( "Can't create thumbnail '$thumbPath'" ); Error("Can't create thumbnail '$thumbPath'");
} }
} # Create thumbnails } # Create thumbnails
@ -507,11 +507,12 @@ class Event extends ZM_Object {
if ( ZM_OPT_USE_AUTH ) { if ( ZM_OPT_USE_AUTH ) {
if ( ZM_AUTH_RELAY == 'hashed' ) { if ( ZM_AUTH_RELAY == 'hashed' ) {
$url .= '?auth='.generateAuthHash( ZM_AUTH_HASH_IPS ); $url .= '?auth='.generateAuthHash( ZM_AUTH_HASH_IPS );
} elseif ( ZM_AUTH_RELAY == 'plain' ) { } else if ( ZM_AUTH_RELAY == 'plain' ) {
$url = '?user='.$_SESSION['username']; $url .= '?user='.$_SESSION['username'];
$url = '?pass='.$_SESSION['password']; $url .= '?pass='.$_SESSION['password'];
} elseif ( ZM_AUTH_RELAY == 'none' ) { } else {
$url = '?user='.$_SESSION['username']; Error('Multi-Server requires AUTH_RELAY be either HASH or PLAIN');
return;
} }
} }
Logger::Debug("sending command to $url"); Logger::Debug("sending command to $url");
@ -550,15 +551,16 @@ class Event extends ZM_Object {
$Server = $Storage->ServerId() ? $Storage->Server() : $this->Monitor()->Server(); $Server = $Storage->ServerId() ? $Storage->Server() : $this->Monitor()->Server();
if ( $Server->Id() != ZM_SERVER_ID ) { 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_OPT_USE_AUTH ) {
if ( ZM_AUTH_RELAY == 'hashed' ) { if ( ZM_AUTH_RELAY == 'hashed' ) {
$url .= '?auth='.generateAuthHash( ZM_AUTH_HASH_IPS ); $url .= '?auth='.generateAuthHash(ZM_AUTH_HASH_IPS);
} elseif ( ZM_AUTH_RELAY == 'plain' ) { } elseif ( ZM_AUTH_RELAY == 'plain' ) {
$url = '?user='.$_SESSION['username']; $url .= '?user='.$_SESSION['username'];
$url = '?pass='.$_SESSION['password']; $url .= '?pass='.$_SESSION['password'];
} elseif ( ZM_AUTH_RELAY == 'none' ) { } else {
$url = '?user='.$_SESSION['username']; Error('Multi-Server requires AUTH_RELAY be either HASH or PLAIN');
return;
} }
} }
Logger::Debug("sending command to $url"); Logger::Debug("sending command to $url");

View File

@ -39,8 +39,8 @@ class Filter extends ZM_Object {
$this->{'Query'} = func_get_arg(0);; $this->{'Query'} = func_get_arg(0);;
$this->{'Query_json'} = jsonEncode($this->{'Query'}); $this->{'Query_json'} = jsonEncode($this->{'Query'});
} }
if ( !array_key_exists('Query', $this) ) { if ( !property_exists($this, 'Query') ) {
if ( array_key_exists('Query_json', $this) and $this->{'Query_json'} ) { if ( property_exists($this, 'Query_json') and $this->{'Query_json'} ) {
$this->{'Query'} = jsonDecode($this->{'Query_json'}); $this->{'Query'} = jsonDecode($this->{'Query_json'});
} else { } else {
$this->{'Query'} = array(); $this->{'Query'} = array();
@ -115,13 +115,17 @@ class Filter extends ZM_Object {
public function control($command, $server_id=null) { public function control($command, $server_id=null) {
$Servers = $server_id ? Server::find(array('Id'=>$server_id)) : Server::find(array('Status'=>'Running')); $Servers = $server_id ? Server::find(array('Id'=>$server_id)) : Server::find(array('Status'=>'Running'));
if ( !count($Servers) and !$server_id ) { if ( !count($Servers) ) {
# This will be the non-multi-server case if ( !$server_id ) {
$Servers = array(new Server()); # 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 ) { 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 # Local
Logger::Debug("Controlling filter locally $command for server ".$Server->Id()); Logger::Debug("Controlling filter locally $command for server ".$Server->Id());
daemonControl($command, 'zmfilter.pl', '--filter_id='.$this->{'Id'}.' --daemon'); daemonControl($command, 'zmfilter.pl', '--filter_id='.$this->{'Id'}.' --daemon');
@ -139,7 +143,7 @@ class Filter extends ZM_Object {
$url = '?user='.$_SESSION['username']; $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"); Logger::Debug("sending command to $url");
$data = array(); $data = array();
if ( defined('ZM_ENABLE_CSRF_MAGIC') ) { if ( defined('ZM_ENABLE_CSRF_MAGIC') ) {

View File

@ -18,7 +18,7 @@ class Group extends ZM_Object {
} }
public function delete() { 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('DELETE FROM Groups_Monitors WHERE GroupId=?', array($this->{'Id'}));
dbQuery('UPDATE Groups SET ParentId=NULL WHERE ParentId=?', array($this->{'Id'})); dbQuery('UPDATE Groups SET ParentId=NULL WHERE ParentId=?', array($this->{'Id'}));
dbQuery('DELETE FROM Groups WHERE Id=?', array($this->{'Id'})); dbQuery('DELETE FROM Groups WHERE Id=?', array($this->{'Id'}));
@ -35,7 +35,7 @@ class Group extends ZM_Object {
if ( isset($new) ) { if ( isset($new) ) {
$this->{'depth'} = $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; $this->{'depth'} = 0;
if ( $this->{'ParentId'} != null ) { if ( $this->{'ParentId'} != null ) {
$Parent = Group::find_one(array('Id'=>$this->{'ParentId'})); $Parent = Group::find_one(array('Id'=>$this->{'ParentId'}));
@ -46,7 +46,7 @@ class Group extends ZM_Object {
} // end public function depth } // end public function depth
public function MonitorIds( ) { 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'})); $this->{'MonitorIds'} = dbFetchAll('SELECT MonitorId FROM Groups_Monitors WHERE GroupId=?', 'MonitorId', array($this->{'Id'}));
} }
return $this->{'MonitorIds'}; return $this->{'MonitorIds'};

View File

@ -9,125 +9,133 @@ require_once('Storage.php');
class Monitor extends ZM_Object { class Monitor extends ZM_Object {
protected static $table = 'Monitors'; protected static $table = 'Monitors';
protected $defaults = array( protected $defaults = array(
'Id' => null, 'Id' => null,
'Name' => '', 'Name' => array('type'=>'text','filter_regexp'=>'/[^\w\-\.\(\)\:\/ ]/'),
'ServerId' => 0, 'Notes' => '',
'StorageId' => 0, 'ServerId' => 0,
'Type' => 'Ffmpeg', 'StorageId' => 0,
'Function' => 'Mocord', 'Type' => 'Ffmpeg',
'Enabled' => array('type'=>'boolean','default'=>1), 'Function' => 'Mocord',
'LinkedMonitors' => array('type'=>'set', 'default'=>null), 'Enabled' => array('type'=>'boolean','default'=>1),
'Triggers' => array('type'=>'set','default'=>''), 'LinkedMonitors' => array('type'=>'set', 'default'=>null),
'Device' => '', 'Triggers' => array('type'=>'set','default'=>''),
'Channel' => 0, 'Device' => '',
'Format' => '0', 'Channel' => 0,
'V4LMultiBuffer' => null, 'Format' => '0',
'V4LCapturesPerFrame' => 1, 'V4LMultiBuffer' => null,
'Protocol' => null, 'V4LCapturesPerFrame' => 1,
'Method' => '', 'Protocol' => null,
'Host' => null, 'Method' => '',
'Port' => '', 'Host' => null,
'SubPath' => '', 'Port' => '',
'Path' => null, 'SubPath' => '',
'Options' => null, 'Path' => null,
'User' => null, 'Options' => null,
'Pass' => null, 'User' => null,
// These are NOT NULL default 0 in the db, but 0 is not a valid value. FIXME 'Pass' => null,
'Width' => null, // These are NOT NULL default 0 in the db, but 0 is not a valid value. FIXME
'Height' => null, 'Width' => null,
'Colours' => 4, 'Height' => null,
'Palette' => '0', 'Colours' => 4,
'Orientation' => null, 'Palette' => '0',
'Deinterlacing' => 0, 'Orientation' => null,
'DecoderHWAccelName' => null, 'Deinterlacing' => 0,
'DecoderHWAccelDevice' => null, 'DecoderHWAccelName' => null,
'SaveJPEGs' => 3, 'DecoderHWAccelDevice' => null,
'VideoWriter' => '0', 'SaveJPEGs' => 3,
'OutputCodec' => null, 'VideoWriter' => '0',
'OutputContainer' => null, 'OutputCodec' => 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", 'OutputContainer' => null,
'RecordAudio' => array('type'=>'boolean', 'default'=>0), '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",
'RTSPDescribe' => array('type'=>'boolean','default'=>0), 'RecordAudio' => array('type'=>'boolean', 'default'=>0),
'Brightness' => -1, 'RTSPDescribe' => array('type'=>'boolean','default'=>0),
'Contrast' => -1, 'Brightness' => -1,
'Hue' => -1, 'Contrast' => -1,
'Colour' => -1, 'Hue' => -1,
'EventPrefix' => 'Event-', 'Colour' => -1,
'LabelFormat' => '%N - %d/%m/%y %H:%M:%S', 'EventPrefix' => 'Event-',
'LabelX' => 0, 'LabelFormat' => '%N - %d/%m/%y %H:%M:%S',
'LabelY' => 0, 'LabelX' => 0,
'LabelSize' => 1, 'LabelY' => 0,
'ImageBufferCount' => 100, 'LabelSize' => 1,
'WarmupCount' => 0, 'ImageBufferCount' => 20,
'PreEventCount' => 0, 'WarmupCount' => 0,
'PostEventCount' => 0, 'PreEventCount' => 5,
'StreamReplayBuffer' => 0, 'PostEventCount' => 5,
'AlarmFrameCount' => 1, 'StreamReplayBuffer' => 0,
'SectionLength' => 600, 'AlarmFrameCount' => 1,
'MinSectionLength' => 10, 'SectionLength' => 600,
'FrameSkip' => 0, 'MinSectionLength' => 10,
'MotionFrameSkip' => 0, 'FrameSkip' => 0,
'AnalysisFPSLimit' => null, 'MotionFrameSkip' => 0,
'AnalysisUpdateDelay' => 0, 'AnalysisFPSLimit' => null,
'MaxFPS' => null, 'AnalysisUpdateDelay' => 0,
'AlarmMaxFPS' => null, 'MaxFPS' => null,
'FPSReportInterval' => 100, 'AlarmMaxFPS' => null,
'RefBlendPerc' => 6, 'FPSReportInterval' => 100,
'AlarmRefBlendPerc' => 6, 'RefBlendPerc' => 6,
'Controllable' => array('type'=>'boolean','default'=>0), 'AlarmRefBlendPerc' => 6,
'ControlId' => null, 'Controllable' => array('type'=>'boolean','default'=>0),
'ControlDevice' => null, 'ControlId' => null,
'ControlAddress' => null, 'ControlDevice' => null,
'AutoStopTimeout' => null, 'ControlAddress' => null,
'TrackMotion' => array('type'=>'boolean','default'=>0), 'AutoStopTimeout' => null,
'TrackDelay' => null, 'TrackMotion' => array('type'=>'boolean','default'=>0),
'ReturnLocation' => -1, 'TrackDelay' => null,
'ReturnDelay' => null, 'ReturnLocation' => -1,
'DefaultRate' => 100, 'ReturnDelay' => null,
'DefaultScale' => 100, 'DefaultRate' => 100,
'SignalCheckPoints' => 0, 'DefaultScale' => 100,
'SignalCheckColour' => '#0000BE', 'SignalCheckPoints' => 0,
'WebColour' => 'red', 'SignalCheckColour' => '#0000BE',
'Exif' => array('type'=>'boolean','default'=>0), 'WebColour' => '#ff0000',
'Sequence' => null, 'Exif' => array('type'=>'boolean','default'=>0),
'TotalEvents' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), 'Sequence' => null,
'TotalEventDiskSpace' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), 'TotalEvents' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1),
'HourEvents' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), 'TotalEventDiskSpace' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1),
'HourEventDiskSpace' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), 'HourEvents' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1),
'DayEvents' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), 'HourEventDiskSpace' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1),
'DayEventDiskSpace' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), 'DayEvents' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1),
'WeekEvents' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), 'DayEventDiskSpace' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1),
'WeekEventDiskSpace' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), 'WeekEvents' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1),
'MonthEvents' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), 'WeekEventDiskSpace' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1),
'MonthEventDiskSpace' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), 'MonthEvents' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1),
'ArchivedEvents' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), 'MonthEventDiskSpace' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1),
'ArchivedEventDiskSpace' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1), 'ArchivedEvents' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1),
'ZoneCount' => 0, 'ArchivedEventDiskSpace' => array('type'=>'integer', 'default'=>null, 'do_not_update'=>1),
'Refresh' => null, 'ZoneCount' => 0,
'DefaultCodec' => 'auto', 'Refresh' => null,
'GroupIds' => array('default'=>array(), 'do_not_update'=>1), 'DefaultCodec' => 'auto',
); 'GroupIds' => array('default'=>array(), 'do_not_update'=>1),
private $status_fields = array( );
'Status' => null, private $status_fields = array(
'AnalysisFPS' => null, 'Status' => null,
'CaptureFPS' => null, 'AnalysisFPS' => null,
'CaptureBandwidth' => null, 'CaptureFPS' => null,
); 'CaptureBandwidth' => null,
);
public function Control() { public function Control() {
if ( !array_key_exists('Control', $this) ) { if ( !property_exists($this, 'Control') ) {
if ( $this->ControlId() ) if ( $this->ControlId() )
$this->{'Control'} = Control::find_one(array('Id'=>$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(); $this->{'Control'} = new Control();
} }
return $this->{'Control'}; return $this->{'Control'};
} }
public function Server() { 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){ public function __call($fn, array $args){
@ -138,7 +146,7 @@ private $status_fields = array(
$this->{$fn} = $args[0]; $this->{$fn} = $args[0];
} }
} }
if ( array_key_exists($fn, $this) ) { if ( property_exists($this, $fn) ) {
return $this->{$fn}; return $this->{$fn};
} else if ( array_key_exists($fn, $this->defaults) ) { } else if ( array_key_exists($fn, $this->defaults) ) {
if ( is_array($this->defaults[$fn]) ) { if ( is_array($this->defaults[$fn]) ) {
@ -211,9 +219,9 @@ private $status_fields = array(
$this->{'Width'} = $new; $this->{'Width'} = $new;
$field = ( $this->Orientation() == 'ROTATE_90' or $this->Orientation() == 'ROTATE_270' ) ? 'Height' : 'Width'; $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->{$field};
return $this->defaults{$field}; return $this->defaults[$field];
} // end function Width } // end function Width
public function ViewHeight($new=null) { public function ViewHeight($new=null) {
@ -221,9 +229,9 @@ private $status_fields = array(
$this->{'Height'} = $new; $this->{'Height'} = $new;
$field = ( $this->Orientation() == 'ROTATE_90' or $this->Orientation() == 'ROTATE_270' ) ? 'Width' : 'Height'; $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->{$field};
return $this->defaults{$field}; return $this->defaults[$field];
} // end function Height } // end function Height
public function SignalCheckColour($new=null) { 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). // Validate that it's a valid colour (we seem to allow color names, not just hex).
// This also helps prevent XSS. // 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->{$field};
} }
return $this->defaults{$field}; return $this->defaults[$field];
} // end function SignalCheckColour } // end function SignalCheckColour
public static function find( $parameters = array(), $options = array() ) { 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'); Warning('Attempt to control a monitor with no Id');
return; 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' ) { if ( $this->Type() == 'Local' ) {
$zmcArgs = '-d '.$this->{'Device'}; $zmcArgs = '-d '.$this->{'Device'};
} else { } else {
@ -276,12 +284,13 @@ private $status_fields = array(
$url = $Server->UrlToApi().'/monitors/daemonControl/'.$this->{'Id'}.'/'.$mode.'/zmc.json'; $url = $Server->UrlToApi().'/monitors/daemonControl/'.$this->{'Id'}.'/'.$mode.'/zmc.json';
if ( ZM_OPT_USE_AUTH ) { if ( ZM_OPT_USE_AUTH ) {
if ( ZM_AUTH_RELAY == 'hashed' ) { if ( ZM_AUTH_RELAY == 'hashed' ) {
$url .= '?auth='.generateAuthHash( ZM_AUTH_HASH_IPS ); $url .= '?auth='.generateAuthHash(ZM_AUTH_HASH_IPS);
} elseif ( ZM_AUTH_RELAY == 'plain' ) { } else if ( ZM_AUTH_RELAY == 'plain' ) {
$url = '?user='.$_SESSION['username']; $url .= '?user='.$_SESSION['username'];
$url = '?pass='.$_SESSION['password']; $url .= '?pass='.$_SESSION['password'];
} elseif ( ZM_AUTH_RELAY == 'none' ) { } else {
$url = '?user='.$_SESSION['username']; Error('Multi-Server requires AUTH_RELAY be either HASH or PLAIN');
return;
} }
} }
Logger::Debug("sending command to $url"); Logger::Debug("sending command to $url");
@ -306,7 +315,7 @@ private $status_fields = array(
return; 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 ( $this->{'Function'} == 'None' || $this->{'Function'} == 'Monitor' || $mode == 'stop' ) {
if ( ZM_OPT_CONTROL ) { if ( ZM_OPT_CONTROL ) {
daemonControl('stop', 'zmtrack.pl', '-m '.$this->{'Id'}); 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'; $url = ZM_BASE_PROTOCOL . '://'.$Server->Hostname().'/zm/api/monitors/daemonControl/'.$this->{'Id'}.'/'.$mode.'/zma.json';
if ( ZM_OPT_USE_AUTH ) { if ( ZM_OPT_USE_AUTH ) {
if ( ZM_AUTH_RELAY == 'hashed' ) { if ( ZM_AUTH_RELAY == 'hashed' ) {
$url .= '?auth='.generateAuthHash( ZM_AUTH_HASH_IPS ); $url .= '?auth='.generateAuthHash(ZM_AUTH_HASH_IPS);
} elseif ( ZM_AUTH_RELAY == 'plain' ) { } else if ( ZM_AUTH_RELAY == 'plain' ) {
$url = '?user='.$_SESSION['username']; $url .= '?user='.$_SESSION['username'];
$url = '?pass='.$_SESSION['password']; $url .= '?pass='.$_SESSION['password'];
} elseif ( ZM_AUTH_RELAY == 'none' ) { } else {
$url = '?user='.$_SESSION['username']; Error('Multi-Server requires AUTH_RELAY be either HASH or PLAIN');
return;
} }
} }
Logger::Debug("sending command to $url"); Logger::Debug("sending command to $url");
@ -367,8 +377,8 @@ private $status_fields = array(
} }
} }
if ( !array_key_exists('GroupIds', $this) ) { if ( !property_exists($this, 'GroupIds') ) {
if ( array_key_exists('Id', $this) and $this->{'Id'} ) { if ( property_exists($this, 'Id') and $this->{'Id'} ) {
$this->{'GroupIds'} = dbFetchAll('SELECT `GroupId` FROM `Groups_Monitors` WHERE `MonitorId`=?', 'GroupId', array($this->{'Id'}) ); $this->{'GroupIds'} = dbFetchAll('SELECT `GroupId` FROM `Groups_Monitors` WHERE `MonitorId`=?', 'GroupId', array($this->{'Id'}) );
if ( ! $this->{'GroupIds'} ) if ( ! $this->{'GroupIds'} )
$this->{'GroupIds'} = array(); $this->{'GroupIds'} = array();
@ -417,7 +427,7 @@ private $status_fields = array(
if ( $new ) { if ( $new ) {
$this->{'Storage'} = $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'}) ? $this->{'Storage'} = isset($this->{'StorageId'}) ?
Storage::find_one(array('Id'=>$this->{'StorageId'})) : Storage::find_one(array('Id'=>$this->{'StorageId'})) :
new Storage(NULL); new Storage(NULL);
@ -467,61 +477,65 @@ private $status_fields = array(
return $source; return $source;
} // end function Source } // end function Source
public function UrlToIndex() { public function UrlToIndex($port=null) {
return $this->Server()->UrlToIndex(); return $this->Server()->UrlToIndex($port);
//ZM_MIN_STREAMING_PORT ? (ZM_MIN_STREAMING_PORT+$this->Id()) : null); //ZM_MIN_STREAMING_PORT ? (ZM_MIN_STREAMING_PORT+$this->Id()) : null);
} }
public function sendControlCommand($command) { public function sendControlCommand($command) {
// command is generally a command option list like --command=blah but might be just the word quit // command is generally a command option list like --command=blah but might be just the word quit
$options = array(); $options = array();
# Convert from a command line params to an option array # Convert from a command line params to an option array
foreach ( explode(' ', $command) as $option ) { foreach ( explode(' ', $command) as $option ) {
if ( preg_match('/--([^=]+)(?:=(.+))?/', $option, $matches) ) { if ( preg_match('/--([^=]+)(?:=(.+))?/', $option, $matches) ) {
$options[$matches[1]] = $matches[2]?$matches[2]:1; $options[$matches[1]] = $matches[2]?$matches[2]:1;
} else if ( $option != '' and $option != 'quit' ) { } else if ( $option != '' and $option != 'quit' ) {
Warning("Ignored command for zmcontrol $option in $command"); 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;
} }
} 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); if ( !count($options) ) {
} else if ( $this->ServerId() ) { 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(); $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_OPT_USE_AUTH ) {
if ( ZM_AUTH_RELAY == 'hashed' ) { if ( ZM_AUTH_RELAY == 'hashed' ) {
$url .= '?auth='.generateAuthHash( ZM_AUTH_HASH_IPS ); $url .= '?auth='.generateAuthHash( ZM_AUTH_HASH_IPS );
@ -537,13 +551,13 @@ public function sendControlCommand($command) {
$context = stream_context_create(); $context = stream_context_create();
try { try {
$result = file_get_contents($url, false, $context); $result = file_get_contents($url, false, $context);
if ($result === FALSE) { /* Handle error */ if ( $result === FALSE ) { /* Handle error */
Error("Error restarting zma using $url"); Error("Error sending command using $url");
return false; return false;
} }
} catch ( Exception $e ) { } catch ( Exception $e ) {
Error("Except $e thrown trying to restart zma"); Error("Exception $e thrown trying to send command to $url");
return false; return false;
} }
} else { } else {
Error('Server not assigned to Monitor in a multi-server setup. Please assign a server to the Monitor.'); Error('Server not assigned to Monitor in a multi-server setup. Please assign a server to the Monitor.');

View File

@ -1,133 +1,22 @@
<?php <?php
namespace ZM; namespace ZM;
require_once('Object.php');
require_once('database.php'); class MontageLayout extends ZM_Object {
protected static $table = 'MontageLayouts';
class MontageLayout { protected $defaults = array(
private $defaults = array(
'Id' => null, 'Id' => null,
'Name' => '', 'Name' => '',
'Positions' => 0, 'Positions' => 0,
); );
public function __construct( $IdOrRow = NULL ) { public static function find( $parameters = array(), $options = array() ) {
if ( $IdOrRow ) { return ZM_Object::_find(get_class(), $parameters, $options);
$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 function set( $data ) { public static function find_one( $parameters = array(), $options = array() ) {
foreach ($data as $k => $v) { return ZM_Object::_find_one(get_class(), $parameters, $options);
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( $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 } // end class MontageLayout
?> ?>

View File

@ -39,13 +39,16 @@ class ZM_Object {
public function __call($fn, array $args){ public function __call($fn, array $args){
$type = (array_key_exists($fn, $this->defaults) && is_array($this->defaults[$fn])) ? $this->defaults[$fn]['type'] : 'scalar'; $type = (array_key_exists($fn, $this->defaults) && is_array($this->defaults[$fn])) ? $this->defaults[$fn]['type'] : 'scalar';
if ( count($args) ) { if ( count($args) ) {
if ( $type == 'set' and is_array($args[0]) ) if ( $type == 'set' and is_array($args[0]) ) {
$this->{$fn} = implode(',', $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]; $this->{$fn} = $args[0];
}
} }
if ( array_key_exists($fn, $this) ) { if ( property_exists($this, $fn) ) {
return $this->{$fn}; return $this->{$fn};
} else { } else {
if ( array_key_exists($fn, $this->defaults) ) { if ( array_key_exists($fn, $this->defaults) ) {
@ -63,7 +66,7 @@ class ZM_Object {
public static function _find($class, $parameters = null, $options = null ) { public static function _find($class, $parameters = null, $options = null ) {
$table = $class::$table; $table = $class::$table;
$filters = array(); $filters = array();
$sql = "SELECT * FROM `$table` "; $sql = 'SELECT * FROM `'.$table.'` ';
$values = array(); $values = array();
if ( $parameters ) { if ( $parameters ) {
@ -110,8 +113,9 @@ class ZM_Object {
public static function _find_one($class, $parameters = array(), $options = array() ) { public static function _find_one($class, $parameters = array(), $options = array() ) {
global $object_cache; global $object_cache;
if ( ! isset($object_cache[$class]) ) if ( ! isset($object_cache[$class]) ) {
$object_cache[$class] = array(); $object_cache[$class] = array();
}
$cache = &$object_cache[$class]; $cache = &$object_cache[$class];
if ( if (
( count($parameters) == 1 ) and ( count($parameters) == 1 ) and
@ -127,6 +131,11 @@ class ZM_Object {
return $results[0]; return $results[0];
} }
public static function _clear_cache($class) {
global $object_cache;
$object_cache[$class] = array();
}
public static function Objects_Indexed_By_Id($class) { public static function Objects_Indexed_By_Id($class) {
$results = array(); $results = array();
foreach ( ZM_Object::_find($class, null, array('order'=>'lower(Name)')) as $Object ) { 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) { foreach ($this->defaults as $key => $value) {
if ( is_callable(array($this, $key)) ) { if ( is_callable(array($this, $key)) ) {
$json[$key] = $this->$key(); $json[$key] = $this->$key();
} else if ( array_key_exists($key, $this) ) { } else if ( property_exists($this, $key) ) {
$json[$key] = $this->{$key}; $json[$key] = $this->{$key};
} else { } else {
$json[$key] = $this->defaults{$key}; $json[$key] = $this->defaults[$key];
} }
} }
return json_encode($json); return json_encode($json);
@ -158,13 +167,10 @@ class ZM_Object {
# perhaps should turn into a comma-separated string # perhaps should turn into a comma-separated string
$this->{$k} = implode(',', $v); $this->{$k} = implode(',', $v);
} else if ( is_string($v) ) { } else if ( is_string($v) ) {
if ( $v == '' and array_key_exists($k, $this->defaults) ) { if ( array_key_exists($k, $this->defaults) && is_array($this->defaults[$k]) && isset($this->defaults[$k]['filter_regexp']) ) {
if ( is_array($this->defaults[$k]) ) $this->{$k} = preg_replace($this->defaults[$k]['filter_regexp'], '', trim($v));
$this->{$k} = $this->defaults[$k]['default'];
else
$this->{$k} = $this->defaults[$k];
} else { } else {
$this->{$k} = trim($v); $this->{$k} = trim($v);
} }
} else if ( is_integer($v) ) { } else if ( is_integer($v) ) {
$this->{$k} = $v; $this->{$k} = $v;
@ -200,7 +206,8 @@ class ZM_Object {
} }
} }
} # end foreach default } # end foreach default
} } # end if defaults
foreach ( $new_values as $field => $value ) { foreach ( $new_values as $field => $value ) {
if ( method_exists($this, $field) ) { if ( method_exists($this, $field) ) {
@ -215,7 +222,7 @@ class ZM_Object {
} else if ( $this->$field() != $value ) { } else if ( $this->$field() != $value ) {
$changes[$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'; $type = (array_key_exists($field, $this->defaults) && is_array($this->defaults[$field])) ? $this->defaults[$field]['type'] : 'scalar';
Logger::Debug("Checking field $field => current ". Logger::Debug("Checking field $field => current ".
(is_array($this->{$field}) ? implode(',',$this->{$field}) : $this->{$field}) . ' ?= ' . (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 # Input might be a command separated string, or an array
} else { } 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 ) { if ( $this->{$field} != $value ) {
$changes[$field] = $value; $changes[$field] = $value;
} }
@ -254,17 +264,6 @@ class ZM_Object {
$changes[$field] = $value; $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 } # end foreach newvalue
@ -280,6 +279,18 @@ class ZM_Object {
$this->set($new_values); $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( $fields = array_filter(
$this->defaults, $this->defaults,
function($v) { function($v) {

View File

@ -80,7 +80,7 @@ class Server extends ZM_Object {
public function PathToZMS( $new = null ) { public function PathToZMS( $new = null ) {
if ( $new != null ) if ( $new != null )
$this{'PathToZMS'} = $new; $this->{'PathToZMS'} = $new;
if ( $this->Id() and $this->{'PathToZMS'} ) { if ( $this->Id() and $this->{'PathToZMS'} ) {
return $this->{'PathToZMS'}; return $this->{'PathToZMS'};
} else { } else {

View File

@ -58,6 +58,13 @@ class Storage extends ZM_Object {
return $this->{'Events'}; 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() { public function disk_usage_percent() {
$path = $this->Path(); $path = $this->Path();
if ( ! $path ) { if ( ! $path ) {
@ -80,7 +87,7 @@ class Storage extends ZM_Object {
} }
public function disk_total_space() { public function disk_total_space() {
if ( !array_key_exists('disk_total_space', $this) ) { if ( !property_exists($this, 'disk_total_space') ) {
$path = $this->Path(); $path = $this->Path();
if ( file_exists($path) ) { if ( file_exists($path) ) {
$this->{'disk_total_space'} = disk_total_space($path); $this->{'disk_total_space'} = disk_total_space($path);
@ -94,7 +101,7 @@ class Storage extends ZM_Object {
public function disk_used_space() { 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. # 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' ) { if ( $this->{'Type'} == 's3fs' ) {
$this->{'disk_used_space'} = $this->event_disk_space(); $this->{'disk_used_space'} = $this->event_disk_space();
} else { } else {
@ -112,17 +119,18 @@ class Storage extends ZM_Object {
public function event_disk_space() { 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. # 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())); $used = dbFetchOne('SELECT SUM(DiskSpace) AS DiskSpace FROM Events WHERE StorageId=? AND DiskSpace IS NOT NULL', 'DiskSpace', array($this->Id()));
do { 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)); $events = Event::find(array('StorageId'=>$this->Id(), 'DiskSpace'=>null), array('limit'=>1000));
foreach ( $events as $Event ) { foreach ( $events as $Event ) {
$Event->Storage($this); // Prevent further db hit $Event->Storage($this); // Prevent further db hit
# DiskSpace will update the event # DiskSpace will update the event
$used += $Event->DiskSpace(); $used += $Event->DiskSpace();
} #end foreach } #end foreach
Event::clear_cache();
} while ( count($events) == 1000 ); } while ( count($events) == 1000 );
$this->{'DiskSpace'} = $used; $this->{'DiskSpace'} = $used;
} }
@ -130,8 +138,8 @@ class Storage extends ZM_Object {
} // end function event_disk_space } // end function event_disk_space
public function Server() { public function Server() {
if ( ! array_key_exists('Server',$this) ) { if ( ! property_exists($this, 'Server') ) {
if ( array_key_exists('ServerId', $this) ) { if ( property_exists($this, 'ServerId') ) {
$this->{'Server'} = Server::find_one(array('Id'=>$this->{'ServerId'})); $this->{'Server'} = Server::find_one(array('Id'=>$this->{'ServerId'}));
if ( !$this->{'Server'} ) { if ( !$this->{'Server'} ) {

View File

@ -28,6 +28,65 @@ if ( $action == 'controlcap' ) {
require_once('includes/Control.php'); require_once('includes/Control.php');
$Control = new ZM\Control( !empty($_REQUEST['cid']) ? $_REQUEST['cid'] : null ); $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 ); //$changes = getFormChanges( $control, $_REQUEST['newControl'], $types, $columns );
$Control->save($_REQUEST['newControl']); $Control->save($_REQUEST['newControl']);
$refreshParent = true; $refreshParent = true;

View File

@ -64,35 +64,12 @@ if ( isset($_REQUEST['object']) and ( $_REQUEST['object'] == 'filter' ) ) {
$_REQUEST['filter']['Background'] = empty($_REQUEST['filter']['Background']) ? 0 : 1; $_REQUEST['filter']['Background'] = empty($_REQUEST['filter']['Background']) ? 0 : 1;
$_REQUEST['filter']['Concurrent'] = empty($_REQUEST['filter']['Concurrent']) ? 0 : 1; $_REQUEST['filter']['Concurrent'] = empty($_REQUEST['filter']['Concurrent']) ? 0 : 1;
$changes = $filter->changes($_REQUEST['filter']); $changes = $filter->changes($_REQUEST['filter']);
ZM\Logger::Debug("Changes: " . print_r($changes,true)); 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);
}
if ( $_REQUEST['Id'] and ( $action == 'Save' ) ) { if ( $_REQUEST['Id'] and ( $action == 'Save' ) ) {
if ( 0 ) {
dbQuery('UPDATE Filters SET '.$sql.' WHERE Id=?', array($_REQUEST['Id']));
}
$filter->save($changes);
if ( $filter->Background() ) if ( $filter->Background() )
$filter->control('stop'); $filter->control('stop');
$filter->save($changes);
} else { } else {
if ( $action == 'execute' ) { if ( $action == 'execute' ) {

View File

@ -39,7 +39,7 @@ if ( $action == 'function' ) {
$oldFunction = $monitor['Function']; $oldFunction = $monitor['Function'];
$oldEnabled = $monitor['Enabled']; $oldEnabled = $monitor['Enabled'];
if ( $newFunction != $oldFunction || $newEnabled != $oldEnabled ) { 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)); array($newFunction, $newEnabled, $mid));
$monitor['Function'] = $newFunction; $monitor['Function'] = $newFunction;

View File

@ -21,42 +21,31 @@
// Group edit actions // Group edit actions
# Should probably verify that each monitor id is a valid monitor, that we have access to. # 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 # 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'); ZM\Warning('Need group edit permissions to edit groups');
return; return;
} }
if ( $action == 'Save' ) { if ( $action == 'Save' ) {
$monitors = empty($_POST['newGroup']['MonitorIds']) ? '' : implode(',', $_POST['newGroup']['MonitorIds']);
$group_id = null; $group_id = null;
if ( !empty($_POST['gid']) ) { if ( !empty($_POST['gid']) )
$group_id = $_POST['gid']; $group_id = $_POST['gid'];
dbQuery( $group = new ZM\Group($group_id);
'UPDATE Groups SET Name=?, ParentId=? WHERE Id=?', $group->save(
array( array(
$_POST['newGroup']['Name'], 'Name'=> $_POST['newGroup']['Name'],
( $_POST['newGroup']['ParentId'] == '' ? null : $_POST['newGroup']['ParentId'] ), 'ParentId'=>( $_POST['newGroup']['ParentId'] == '' ? null : $_POST['newGroup']['ParentId'] ),
$group_id,
) )
); );
dbQuery('DELETE FROM Groups_Monitors WHERE GroupId=?', array($group_id)); dbQuery('DELETE FROM `Groups_Monitors` WHERE `GroupId`=?', array($group_id));
} else { $group_id = $group->Id();
dbQuery(
'INSERT INTO Groups (Name,ParentId) VALUES (?,?)',
array(
$_POST['newGroup']['Name'],
( $_POST['newGroup']['ParentId'] == '' ? null : $_POST['newGroup']['ParentId'] ),
)
);
$group_id = dbInsertId();
}
if ( $group_id ) { if ( $group_id ) {
foreach ( $_POST['newGroup']['MonitorIds'] as $mid ) { 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'; $view = 'none';
$refreshParent = true; $refreshParent = true;
$closePopup = true; $closePopup = true;
} }
?> ?>

View File

@ -29,8 +29,13 @@ if ( ('login' == $action) && isset($_REQUEST['username']) && ( ZM_AUTH_TYPE == '
&& defined('ZM_OPT_GOOG_RECAPTCHA_SITEKEY') && defined('ZM_OPT_GOOG_RECAPTCHA_SITEKEY')
&& ZM_OPT_USE_GOOG_RECAPTCHA && ZM_OPT_USE_GOOG_RECAPTCHA
&& ZM_OPT_GOOG_RECAPTCHA_SECRETKEY && 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'; $url = 'https://www.google.com/recaptcha/api/siteverify';
$fields = array ( $fields = array (
'secret' => ZM_OPT_GOOG_RECAPTCHA_SECRETKEY, '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 // 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 ( isset($responseData['error-codes']) && is_array($responseData['error-codes']) ) {
if ( !in_array('invalid-input-secret', $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 unset($user); // unset should be ok here because we aren't in a function
return; return;
} else { } else {
Error('Invalid recaptcha secret detected'); ZM\Error('Invalid recaptcha secret detected');
} }
} }
} // end if success==false } // 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 } // end if using reCaptcha
// if captcha existed, it was passed // if captcha existed, it was passed

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