diff --git a/.travis.yml b/.travis.yml index cd9a44b9a..e9a5116ac 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,6 +20,7 @@ addons: - git - curl - sshfs + - sed env: matrix: - OS=el DIST=6 diff --git a/distros/ubuntu1204/changelog b/distros/ubuntu1204/changelog new file mode 100644 index 000000000..c7e86f69d --- /dev/null +++ b/distros/ubuntu1204/changelog @@ -0,0 +1,573 @@ +zoneminder (1.28.1+1-vivid-SNAPSHOT2015081701) vivid; urgency=medium + + * include api, switch to cmake build + + -- Isaac Connor Mon, 17 Aug 2015 10:29:23 -0400 + + +zoneminder (1.28.1-8) unstable; urgency=medium + + * Patchworks: + + New upstream "980-fix-image-size.patch". + + New "default_cgi-path.patch" to correct default ZM_PATH_ZMS. + * postinst: set "root" as group owner for "/var/log/zm" to silence + logrotate warnings. + * Minor correction to README.Debian. + + -- Dmitry Smirnov Sun, 16 Aug 2015 19:19:50 +1000 + +zoneminder (1.28.1-7) unstable; urgency=medium + + * Build-Depends += "cakephp (<< 3.0.0~)"; + Zoneminder is not compatible with latest CakePHP. + * Handle conffile removal from maintscript. + * rules: build man pages reproducibly. + * gbp.conf: renamed old style config section [git-dch] to [dch]. + * README + + added instructions to update owner of the "/etc/zm/zm.conf" + (Closes: #789327). + + zmupdate.pl needs CREATE rights. + + added note about required number of "fcgiwrap" workers. + * New upstream patch: "zmtrigger-plus.patch". + + -- Dmitry Smirnov Mon, 20 Jul 2015 16:30:15 +1000 + +zoneminder (1.28.1-6) unstable; urgency=low + + * New "zoneminder-doc" and "zoneminder-dbg" packages. + + -- Dmitry Smirnov Sun, 19 Apr 2015 14:50:41 +1000 + +zoneminder (1.28.1-5) unstable; urgency=low + + * Move handling of "/var/run/zm" and "/tmp/zm" from .service into .tmpfile. + Let dh_installinit do the job. Thanks, Andrew Bauer. + * Use dh_apache2 to install Apache conf file; remove old conf and symlink. + * Promote "libapache2-mod-php5 | php5-fpm" to Recommends. + * Build-Depends: + + dh-linktree + + cakephp (>= 2.6.3) + + libjs-jquery + + libjs-mootools + * Depends: + - libjs-jquery + - libjs-mootools + * Build-time replace bundled CakePHP with system one using "dh-linktree". + * Use "dh-linktree" to handle mootools and jquery symlinks. + + -- Dmitry Smirnov Sun, 19 Apr 2015 11:45:01 +1000 + +zoneminder (1.28.1-4) unstable; urgency=low + + * New patch to fix HTML export with USE_DEEP_STORAGE (closes: #723706). + * New "783.patch" to describe potential data loss in ZM_USE_DEEP_STORAGE. + * New patch to change default date format to region-neutral ISO notation + with time zone. + * Build sphinx documentation: + + Install "zoneminder.1" man page. + + Build-Depends += "python-sphinx | python3-sphinx" + + Added commented "zoneminder-doc" package. + + Added "docs.patch" to unlink distro-specific installation docs. + * rules: + + set ZM_CONTENTDIR, ZM_SOCKDIR and ZM_TMPDIR. + + remove mistakengly installed Perl module templates. + * Updated startup scripts to create ZM_TMPDIR. + * Hurd improvements: + + New patch to add PATH_MAX definitions. + + Build without MMAP support on Hurd. + + libsys-mmap-perl [!hurd-any]. + + -- Dmitry Smirnov Mon, 06 Apr 2015 18:18:55 +1000 + +zoneminder (1.28.1-3) unstable; urgency=low + + * Updated Apache2 and nginx configuration templates to support CGI. + * Updated README.Debian to document cgi-bin setup. + * Removed "/usr/share/zoneminder/www/cgi-bin" symlink. + * Added "apache2.patch" to correct Apache2 site configuration example. + * control: Suggests += "fcgiwrap". + * rules: added dh_systemd overrides to prevent automatic service + activation and start. + * Added note about manual service activation to README.Debian + (Closes: #781733). + + -- Dmitry Smirnov Thu, 02 Apr 2015 23:20:20 +1100 + +zoneminder (1.28.1-2) unstable; urgency=low + + * Removed word "Linux" from short package description. + * Build-Depends: do not require "libv4l-dev" on Hurd i.e. [!hurd-any]. + * Added run-time Perl Depends: + + libdbd-mysql-perl + + libimage-info-perl + + libmodule-load-conditional-perl + + libnet-sftp-foreign-perl + + liburi-encode-perl + * Prepare for package split: added commented "libzoneminder-perl" + and "zoneminder-dbg" packages to "debian/control". + * rules: do not install worthless ".packlist" file. + * Updated "libv4l1-videodev.h.patch" to fix v4lv1 detection in CMake. + + -- Dmitry Smirnov Thu, 02 Apr 2015 13:25:19 +1100 + +zoneminder (1.28.1-1) unstable; urgency=low + + [ Dmitry Smirnov ] + * New upstream release [February 2015]. + * Upload to unstable. + * Disabled automatic database upgrades: post(inst|rm) scripts no longer + touch database or do unexpected stuff (Closes: #779254). + See README.Debian for details. + * Updated installation paths: + + /usr/share/zoneminder --> /usr/share/zoneminder/www + + /usr/lib/cgi-bin --> /usr/lib/zoneminder/cgi-bin + * Added logrotate config (Closes: #544826). + Thanks, Alberto Reyes. + * Native systemd service; "--with systemd" added to dh. + * Build with CMake instead of autoconf; rules clean-up. + * Build with all hardening. + * Build and install "zmupdate.pl.1" man page. + * Added nginx/php5-fpm configuration example. + * Install upstream "apache.conf" example. + * Described setup of Zoneminer web site and database in README.Debian. + * Install "/etc/zm/zm.conf" with tighter permissions. + * Added TODO.Debian. + * Added "debian/clean"; "debian/gbp.conf"; bug-presubj. + * Remove bundled Cake tests to take ~5 MB off big-usr-share. + * Standards-Version: 3.9.6; compat/debhelper to version 9. + * Vcs links to new git repository at collab-maint. + * Build-Depends: + + dh-systemd + + libgcrypt11-dev --> libgcrypt-dev + + libcurl4-gnutls-dev + + libvlc-dev + + policykit-1 (required by "zmsystemctl.pl") + - dh-autoreconf, autoconf, automake + * Depends: + - apache2 + - libapache2-mod-php5 (moved to Suggests) + - libpcre3 (invalid) + - libmodule-load-perl (obsolete; replaced with perl-modules) + - libarchive-tar-perl (obsolete; replaced with perl-modules) + - mysql-server (moved to Recommends, Closes: #759504). + - php5 + + libav-tools + + libjs-jquery (replaces bundled component) + + libjs-mootool (replaces bundled component) + + libjson-any-perl (Closes: #690803). + + perl-modules (Closes: #745819). + * Recommends: + + apache2 | httpd + + mysql-server | virtual-mysql-server (Closes: #732874). + * Suggests: + + libapache2-mod-php5 | php5-fpm + + logrotate + * Refreshed, renamed and re-ordered patches; added DEP-3 headers. + * Removed "vendor_perl" patch (applied-upstream). + * New patches: + + cmake-fix-confpath.patch + + cmake-gnutls.patch + + cmake-nossl.patch + + cmake.patch + + format-hardening.patch + + pod_man_fixes.patch + + pod_name_fixes.patch + + pod_zmupdate-to-pod2usage.patch + * Lintianisation (incomplete): + - extra-license-file + - init.d-script-missing-lsb-description + - init.d-script-does-not-source-init-functions + - privacy-breach-generic + - package-contains-empty-directory + - manpage-has-errors-from-pod2man + - manpage-has-bad-whatis-entry + - quilt-patch-missing-description + - no-dep5-copyright + * Lintian-overrides: + + unusual-interpreter usr/bin/zmsystemctl.pl #!/usr/bin/pkexec + + script-not-executable usr/share/zoneminder/www/api/* + + script-with-language-extension usr/bin/*.pl + + source-is-missing web/tools/mootools/mootools-*-yc.js + + source-is-missing web/skins/*/js/jquery-1.4.2.min.js + + source-contains-prebuilt-javascript-object + * Renamed files in "debian". + * watch: dfsg repacksuffix and dversionmangle. + * "debian/copyright" to Copyright-Format-1.0. + * Set myself as new Maintainer (Closes: #760314). + + [ Vagrant Cascadian ] + * Removed obsolete DM-Upload-Allowed flag. + * Update debian/watch to use tarballs from github. + * Add Build-Depends on libgcrypt11-dev (Closes: #745819). + * Use canonical alioth Vcs-Hg URL. + * debian/control: Add Build-Depends: libpolkit-gobject-1-dev. + * Removed configure flag "--enable-crashtrace=no", which is no longer + present upstream. + + -- Dmitry Smirnov Tue, 31 Mar 2015 15:11:13 +1100 + +zoneminder (1.26.5-3.1) experimental; urgency=low + + * Non-maintainer upload. + * Add libav10.patch and compile against libav10 (Closes: #739461) + + -- Reinhard Tartler Wed, 19 Mar 2014 00:31:22 +0000 + +zoneminder (1.26.5-3) unstable; urgency=low + + + * Previous release still didn't build on PPC - this has been corrected. + (Closes: #736516) + + -- Peter Howard Tue, 4 Feb 2014 02:02:10 +1000 + +zoneminder (1.26.5-2) unstable; urgency=low + + * Remove dependency on ffmpeg + (Closes: #721161) + + * Builds again on non-x86 target architectures. + + -- Peter Howard Thu, 23 Jan 2014 01:02:10 +1000 + +zoneminder (1.26.5-1) unstable; urgency=low + + * New upstream version + (Closes: #694131) + * Change Build-Depends on libgnutls-dev to libgnutls-openssl-dev + (Closes: #731560) + -- Peter Howard Tue, 17 Dec 2013 01:02:10 +1000 + +zoneminder (1.25.0-4) unstable; urgency=high + + * Add CVE-2013-0232 patch + [SECURITY] CVE-2013-0232: Shell escape commands with untrusted content. + Thanks to James McCoy (Closes: #698910) + Thanks also to Salvatore Bonaccorso + + -- Peter Howard Tue, 12 Jun 2013 12:02:10 +1000 + +zoneminder (1.25.0-3) unstable; urgency=low + + * debian/rules: Export CFLAGS, CPPFLAGS, CXXFLAGS and LDFLAGS, to ensure + hardening build flags are enabled. + + -- Vagrant Cascadian Tue, 28 Aug 2012 12:10:03 -0700 + +zoneminder (1.25.0-2) unstable; urgency=low + + [ Vagrant Cascadian ] + * Add a patch to disable checking for updated versions by default, as + upgrades should happen through package management. + * Use dpkg-buildflags in debian/rules to set default compiler flags. + * Ensure zoneminder is stopped before starting (Closes: #657407). + + [ Peter Howard ] + * Fix postinst to add permission for table creation during upgrade + (Closes: #657407). + + -- Vagrant Cascadian Thu, 23 Aug 2012 12:40:34 -0700 + +zoneminder (1.25.0-1.1) unstable; urgency=low + + * Non-maintainer upload. + * Fix "ftbfs with GCC-4.7": add patch Fix-FTBFS-with-gcc-4.7 from Cyril + Brulebois: fix missing includes. + (Closes: #667428) + + -- gregor herrmann Sun, 13 May 2012 17:02:21 +0200 + +zoneminder (1.25.0-1) unstable; urgency=low + + * Fix typo in libv4l1-videodev.h patch that caused v4l1 support to be + dropped. + * Fail to build if version in postinst doesn't match upstream version. + * Add Build-Depends: libavdevice-dev to fix MPEG streaming (Closes: #515558). + * debian/rules: Convert to using debhelper overrides. + * Set debian/compat to 7. + * Simplify debian/watch file. + * Refresh debian/patches/use_libjs-mootools. + * Refresh debian/patches/libv4l1-videodev.h. + * Remove dependencies on php4 and related packages. + * Remove build-dependencies on libmysqlclient14-dev and + libmysqlclient15-dev. + * Update Build-Depends to use libjpeg-dev instead of libjpeg62-dev + (Closes: #647114). + * Add patch to fix build by testing for C headers rather than C++ headers. + Thanks to Ryan Niebur. (Closes: #654230) + * Add a patch to fix build problems caused by API changes in libav 0.8. + Thanks again to Ryan Niebur. (Closes: #654230) + + -- Vagrant Cascadian Mon, 16 Jan 2012 11:58:05 -0800 + +zoneminder (1.24.4-1) unstable; urgency=low + + [ Peter Howard ] + * Initial release of 1.24.4 (Closes: #634985). + - Fix 32/64-bit type declarations (Closes: #614404). + * Update patches. + + [ Vagrant Cascadian ] + * Add patch to fix FTBFS by using libv4l1-videodev.h from libv4l-dev. + Thanks to Andreas Metzler for reporting the issue. + (Closes: #619813). + * Document adding the www-data user to the video group in README.Debian. + (Closes: #611324) + * Depend on libsys-mmap-perl to enable mapped memory support. + (Closes: #607331) + * Update libjs-mootools patch to use -nc variants (Closes: #635075). + * Depend on javascript-common, to ensure that /javascript is available in + the web server. + * Set the upstream version in postinst at build time. + * Use dh-autoreconf to properly clean up autogenerated files during build. + * Add Vcs-HG to debian/control. + * Add Build-Depends: libv4l-dev, libbz2-dev, dh-autoreconf, libsys-mmap-perl. + + -- Vagrant Cascadian Sun, 24 Jul 2011 16:44:30 +0200 + +zoneminder (1.24.2-9) unstable; urgency=low + + * Apply patch from Ubuntu to fix FTBFS with ffmpeg 0.6: + - Add -D__STDC_CONSTANT_MACROS to CPPFLAGS (closes: 614080). + * Update Standards-Version to 3.9.1, no changes necessary. + + -- Vagrant Cascadian Sun, 20 Feb 2011 23:43:02 -0800 + +zoneminder (1.24.2-8) unstable; urgency=medium + + [ Vagrant Cascadian ] + * Apply patch to fix V4L2 cameras without crop support (closes: #608790). + Thanks to piratebab. + * Add preinst script which aborts if dangerous symlinks exist. + (closes: #608793) + + [ Peter Howard ] + * Added to README.Debian with info about images and events directories. + (closes: #608793) + + -- Vagrant Cascadian Sat, 15 Jan 2011 19:39:26 -0800 + +zoneminder (1.24.2-7) unstable; urgency=medium + + * Do not set ownership of /var/cache/zoneminder when upgrading, which fixes a + regression causing upgrades to take inordinately long with large + installations (closes: #597040). + + -- Vagrant Cascadian Fri, 17 Sep 2010 11:24:41 -0700 + +zoneminder (1.24.2-6) unstable; urgency=low + + * Only remove database on purge. This requires only creating the database if + it doesn't already exist, and upgrading the database only if the database + is an older version (closes: #497107). + + * Do not prompt the user on database upgrades by using the --nointeractive + flag when calling zmupdate.pl from postinst (closes: #595902). + + -- Vagrant Cascadian Fri, 10 Sep 2010 10:06:06 -0700 + +zoneminder (1.24.2-5) unstable; urgency=low + + [ Peter Howard ] + * Add zip dependency + (closes: #494261) + * Add debian/watch file + (closes: #545552) + * Use packaged libjs-mootools + (closes: #585590) + * Miscellaneous cleanups + + [ Vagrant Cascadian ] + * Add vagrant@debian.org as uploader + * Update Standards-Version to 3.9.0, no changes necessary. + + -- Vagrant Cascadian Fri, 23 Jul 2010 18:12:50 -0500 + +zoneminder (1.24.2-4.1) unstable; urgency=low + + * Non-maintainer upload. + * Fix "package removed, processes still running": apply patch to + debian/postinst by Vagrant Cascadian: use invoke-rc.d and run + mysql-related actions only when mysql is running (closes: #583648). + + -- gregor herrmann Thu, 01 Jul 2010 19:47:10 +0200 + +zoneminder (1.24.2-4) unstable; urgency=high + * Update init.d to list mysql dependency + (closes: #583505) + * Change dependency from libmime-perl to libmime-tools-perl + (closes: #585589) + * Problems in changelog format fixed + (closes: #585592) + * Fix debian-rules-ignores-make-clean-error + (closes: #585593) + -- Peter Howard Mon, 14 jun 2010 15:02:10 +1000 + +zoneminder (1.24.2-3) unstable; urgency=high + * Changes symbols to build with libjpeg8 + (closes: #565326, #568327) + * Note: location of all perl files should have been fixed in previous release + (closes: #553096) + -- Peter Howard Mon, 26 apr 2010 15:02:10 +1000 + +zoneminder (1.24.2-2) unstable; urgency=high + + * Remove custom perl parth from zmpkg.pl, fix location of manpages. + (closes: #551746, #553092) + * Fix GCC4.4 bug + (closes: #531717) + * Fix potential bug in postinst script + + -- Peter Howard Sat, 14 Nov 2009 15:02:10 +1000 + +zoneminder (1.24.2-1) unstable; urgency=high + + * Initial release of zoneminder 1.24.2 + -- Peter Howard Fri, 11 Sep 2009 07:02:50 +1000 + +zoneminder (1.24.1-1) unstable; urgency=high + + * Initial release of zoneminder 1.24.1, closing CVE-2008-3882, + CVE-2008-3881, CVE-2008-3880 + (closes: #497640) + * Change syslog dependency to rsyslog. + (closes: #526918) + * Add missing perl dependency. + * Restore patch to disable "check for updates" by default. + * Removed spurious '$' in init script. + (closes: #486064) + * Change permission of zm.conf from 0600 to 0400 for CVE-2008-6755 + (closes: #528252) + -- Peter Howard Sat, 16 May 2009 07:02:50 +1000 + +zoneminder (1.23.3-4) unstable; urgency=high + + * update to get it building with latest unstable. Thanks to waldi@debian.org + (closes: #517569) + -- Peter Howard Thu, 16 Apr 2009 01:02:50 +1000 + +zoneminder (1.23.3-3) unstable; urgency=high + + * ffmpeg confirmed working + (closes: #475145) + * Fix upgrade problem intrudouced in 1.23.3-1 + (closes: #481637) + * Include libmime-lite-perl in dependencies + (closes: #486312) + -- Peter Howard Thu, 18 Sep 2008 01:02:50 +1000 + +zoneminder (1.23.3-2) unstable; urgency=high + + * ffmpeg finally working? + + -- Peter Howard Wed, 13 Aug 2008 01:02:50 +1000 + +zoneminder (1.23.3-1) unstable; urgency=high + + * Initial version for 1.23.3 - security fix. + (closes: #479034) + + -- Peter Howard Wed, 19 Mar 2008 01:02:50 +1000 + +zoneminder (1.23.2-2) unstable; urgency=low + + * Update to init.d + (closes: #468856) + * Add dependency on logging daemon + (closes: #471277) + + -- Peter Howard Wed, 19 Mar 2008 01:02:50 +1000 + +zoneminder (1.23.2-1) unstable; urgency=low + + * Initial version for 1.23.2 + (closes: #464152) + * Zoneminder 1.23.2 upstream includes fix for GCC 4.3 + (closes: #454980) + * Includes ffmpeg patch by Alexander Kushnirenko + + -- Peter Howard Sat, 01 Mar 2008 16:02:50 +1000 + +zoneminder (1.22.3-10) unstable; urgency=low + + * Fix bug introduced in -9 where perl is put under /usr/local + (closes: #457507) + + -- Peter Howard Mon, 24 Dec 2007 16:02:50 +1000 + +zoneminder (1.22.3-9) unstable; urgency=low + + * Starting zoneminder via init script now invokes "zmfix -a" + (closes: #481637) + * Change apache2-mpm-prefork dependency to apache2 + * Temp dir for export under /var/cache/zoneminder (but linked back to + /usr/share/zoneminder for now) + * Redo use of gnutls rather than openssl for md5 hashes + + -- Peter Howard Mon, 10 Dec 2007 16:02:50 +1000 + +zoneminder (1.22.3-8) unstable; urgency=low + + * Build now includes libpcre3 + (closes: #437533) + * "Monitor Presets" patch now applied to package during build. + + -- Peter Howard Sat, 18 Aug 2007 14:35:23 +1000 + +zoneminder (1.22.3-7) unstable; urgency=low + + * Turn off debug trace and crash dump on build + (closes:#414857,#414891) + * Additional perl libraries added in dependencies + (closes:#416291) + * Change preferred PHP version from 4 to 5 + -- Peter Howard Sun, 29 Jul 2007 15:11:13 +1000 + +zoneminder (1.22.3-6) unstable; urgency=low + + * Removed a similar bash only statement from zmpkg.pl + (closes:414882) + + -- Peter Howard Sat, 14 Apr 2007 11:46:56 +1000 + +zoneminder (1.22.3-5) unstable; urgency=low + + * Installs with "phone home" feature turned off by default, and permissions + on /etc/zm/zm.conf fixed (now the 0600 it s hould be) + (closes:415349) + * Removed "stupid bash-ism" on mysqld check in postinst file. + + -- Peter Howard Fri, 6 Apr 2007 15:50:00 +1000 + +zoneminder (1.22.3-4) unstable; urgency=low + + * Put libmysqlclient-15-dev in front of -14-dev so sbuild works + (closes: #414410) + + -- Peter Howard Mon, 12 Mar 2007 11:38:56 +1100 + +zoneminder (1.22.3-3) unstable; urgency=low + + * Clean up of postinstall, postrm ; user "zm" definitely was a mistake + * Also in postinstall: check and start MySQL if it's not running. + * init.d script now checks if zoneminder isn't running and still returns 0 + (which helps uninstalling) + * Addition of php5 dependency options as well as php4. + + -- Peter Howard Mon, 26 Feb 2007 10:40:52 +1100 + +zoneminder (1.22.3-2) unstable; urgency=low + + * Added zmuser in the mysql creation; this should fix the install problem + for people, but needs to be cleaned up (in -3) + + -- Peter Howard Fri, 16 Feb 2007 14:16:03 +1100 + +zoneminder (1.22.3-1) unstable; urgency=low + + * Initial Version. (closes: #248393) + * Patched out use of openssl; uses gnutls instead for MD5 hashes. + * Removed MakeMaker-inserted Perl licensing (with authors permission) in + various scripts; replaced with GPL. + + -- Peter Howard Wed, 7 Feb 2007 14:09:01 +1100 diff --git a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in index 3b2f255a8..294564dae 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/ConfigData.pm.in @@ -397,7 +397,6 @@ our @options = ( type => $types{boolean}, category => 'system', }, - { name => 'ZM_OPT_GOOG_RECAPTCHA_SITEKEY', default => '...Insert your recaptcha site-key here...', @@ -428,8 +427,6 @@ our @options = ( type => $types{string}, category => 'system', }, - - { name => 'ZM_DIR_EVENTS', default => 'events', @@ -1580,7 +1577,7 @@ our @options = ( }, { name => 'ZM_WEB_EVENT_DISK_SPACE', - default => '', + default => 'no', description => 'Whether to show disk space used by each event.', help => q`Adds another column to the listing of events showing the disk space used by the event. This will impart a small @@ -1815,7 +1812,7 @@ our @options = ( ZM_CAPTURES_PER_FRAME option and you should normally change the value of only one of the options at a time. If you have different capture cards that need different values you can - ovveride them in each individual monitor on the source page. + override them in each individual monitor on the source page. `, type => $types{boolean}, category => 'config', @@ -1839,7 +1836,7 @@ our @options = ( ZM_V4L_MULTI_BUFFER option and you should normally change the value of only one of the options at a time. If you have different capture cards that need different values you can - ovveride them in each individual monitor on the source page. + override them in each individual monitor on the source page. `, type => $types{integer}, category => 'config', @@ -2498,7 +2495,6 @@ our @options = ( category => 'system', }, { - name => 'ZM_RUN_AUDIT', default => 'yes', description => 'Run zmaudit to check data consistency', @@ -3370,7 +3366,7 @@ our @options = ( { name => 'ZM_WEB_M_DEFAULT_SCALE', default => '100', - description => 'What the default scaling factor applied to \'live\' or \'event\' views is (%)', + description => q`'What the default scaling factor applied to 'live' or 'event' views is (%)`, help => q` Normally ZoneMinder will display 'live' or 'event' streams in their native size. However if you have monitors with large @@ -3633,7 +3629,7 @@ our @options = ( { name => 'ZM_WEB_L_DEFAULT_SCALE', default => '100', - description => 'What the default scaling factor applied to \'live\' or \'event\' views is (%)', + description => q`What the default scaling factor applied to 'live' or 'event' views is (%)`, help => q` Normally ZoneMinder will display 'live' or 'event' streams in their native size. However if you have monitors with large @@ -3654,7 +3650,7 @@ our @options = ( { name => 'ZM_WEB_L_DEFAULT_RATE', default => '100', - description => 'What the default replay rate factor applied to \'event\' views is (%)', + description => q`What the default replay rate factor applied to 'event' views is (%)`, help => q` Normally ZoneMinder will display 'event' streams at their native rate, i.e. as close to real-time as possible. However if diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Control/SkyIPCam7xx.pm b/scripts/ZoneMinder/lib/ZoneMinder/Control/SkyIPCam7xx.pm index e5917536f..b3f9418ce 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Control/SkyIPCam7xx.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Control/SkyIPCam7xx.pm @@ -45,265 +45,239 @@ use ZoneMinder::Config qw(:all); use Time::HiRes qw( usleep ); -sub new -{ - my $class = shift; - my $id = shift; - my $self = ZoneMinder::Control->new( $id ); - bless( $self, $class ); - srand( time() ); - return $self; +sub new { + my $class = shift; + my $id = shift; + my $self = ZoneMinder::Control->new( $id ); + bless( $self, $class ); + srand( time() ); + return $self; } our $AUTOLOAD; -sub AUTOLOAD -{ - my $self = shift; - my $class = ref($self) || croak( "$self not object" ); - my $name = $AUTOLOAD; - $name =~ s/.*://; - if ( exists($self->{$name}) ) - { - return( $self->{$name} ); - } - Fatal( "Can't access $name member of object of class $class" ); +sub AUTOLOAD { + my $self = shift; + my $class = ref($self) || croak( "$self not object" ); + my $name = $AUTOLOAD; + $name =~ s/.*://; + if ( exists($self->{$name}) ) { + return( $self->{$name} ); + } + Fatal( "Can't access $name member of object of class $class" ); } -sub open -{ - my $self = shift; +sub open { + my $self = shift; - $self->loadMonitor(); + $self->loadMonitor(); - use LWP::UserAgent; - $self->{ua} = LWP::UserAgent->new; - $self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION ); + use LWP::UserAgent; + $self->{ua} = LWP::UserAgent->new; + $self->{ua}->agent( "ZoneMinder Control Agent/".ZoneMinder::Base::ZM_VERSION ); - $self->{state} = 'open'; + $self->{state} = 'open'; } -sub close -{ - my $self = shift; - $self->{state} = 'closed'; +sub close { + my $self = shift; + $self->{state} = 'closed'; } -sub printMsg -{ - my $self = shift; - my $msg = shift; - my $msg_len = length($msg); +sub printMsg { + my $self = shift; + my $msg = shift; + my $msg_len = length($msg); - Debug( $msg."[".$msg_len."]" ); + Debug( $msg."[".$msg_len."]" ); } -sub sendCmd -{ - my $self = shift; - my $cmd = shift; +sub sendCmd { + my $self = shift; + my $cmd = shift; - my $result = undef; + my $result = undef; - printMsg( $cmd, "Tx" ); + printMsg( $cmd, "Tx" ); - my $url; - if ( $self->{Monitor}->{ControlAddress} =~ /^http/ ) { - $url = $self->{Monitor}->{ControlAddress}.$cmd; - } else { - $url = 'http://'.$self->{Monitor}->{ControlAddress}.$cmd; - } # en dif - my $req = HTTP::Request->new( GET=>$url ); + my $url; + if ( $self->{Monitor}->{ControlAddress} =~ /^http/ ) { + $url = $self->{Monitor}->{ControlAddress}.$cmd; + } else { + $url = 'http://'.$self->{Monitor}->{ControlAddress}.$cmd; + } # en dif + my $req = HTTP::Request->new( GET=>$url ); - my $res = $self->{ua}->request($req); + my $res = $self->{ua}->request($req); - if ( $res->is_success ) - { - $result = !undef; - } - else - { - Error( "Error check failed: '".$res->status_line()."'" ); - } + if ( $res->is_success ) { + $result = !undef; + } else { + Error( "Error check failed: '".$res->status_line()."'" ); + } - return( $result ); + return( $result ); } -sub reset -{ - my $self = shift; - Debug( "Camera Reset" ); - my $cmd = "/admin/ptctl.cgi?move=reset"; - $self->sendCmd( $cmd ); +sub reset { + my $self = shift; + Debug( "Camera Reset" ); + my $cmd = "/admin/ptctl.cgi?move=reset"; + $self->sendCmd( $cmd ); } -sub moveMap -{ - my $self = shift; - my $params = shift; - my $xcoord = $self->getParam( $params, 'xcoord' ); - my $ycoord = $self->getParam( $params, 'ycoord' ); +sub moveMap { + 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 $hor = $xcoord * 100 / $self->{Monitor}->{Width}; + my $ver = $ycoord * 100 / $self->{Monitor}->{Height}; - my $maxver = 8; - my $maxhor = 30; - - my $horDir = "right"; - my $verDir = "up"; - my $horSteps = 0; - my $verSteps = 0; + my $maxver = 8; + my $maxhor = 30; - # Horizontal movement - if ($hor < 50) { - # left - $horSteps = ((50 - $hor) / 50) * $maxhor; - $horDir = "left"; - } - elsif ($hor > 50) { - # right - $horSteps = (($hor - 50) / 50) * $maxhor; - $horDir = "right"; - } - - # Vertical movement - if ($ver < 50) { - # up - $verSteps = ((50 - $ver) / 50) * $maxver; - $verDir = "up"; - } - elsif ($ver > 50) { - # down - $verSteps = (($ver - 50) / 50) * $maxver; - $verDir = "down"; - } + my $horDir = "right"; + my $verDir = "up"; + my $horSteps = 0; + my $verSteps = 0; - my $v = int($verSteps); - my $h = int($horSteps); +# Horizontal movement + if ( $hor < 50 ) { +# left + $horSteps = ((50 - $hor) / 50) * $maxhor; + $horDir = "left"; + } + elsif ( $hor > 50 ) { +# right + $horSteps = (($hor - 50) / 50) * $maxhor; + $horDir = "right"; + } - Debug( "Move Map to $xcoord,$ycoord, hor=$h $horDir, ver=$v $verDir"); - my $cmd = "/cgi/admin/ptctrl.cgi?action=movedegree&Cmd=$horDir&Degree=$h"; - $self->sendCmd( $cmd ); - $cmd = "/cgi/admin/ptctrl.cgi?action=movedegree&Cmd=$verDir&Degree=$v"; - $self->sendCmd( $cmd ); +# Vertical movement + if ( $ver < 50 ) { +# up + $verSteps = ((50 - $ver) / 50) * $maxver; + $verDir = "up"; + } + elsif ( $ver > 50 ) { +# down + $verSteps = (($ver - 50) / 50) * $maxver; + $verDir = "down"; + } + + my $v = int($verSteps); + my $h = int($horSteps); + + Debug( "Move Map to $xcoord,$ycoord, hor=$h $horDir, ver=$v $verDir"); + my $cmd = "/cgi/admin/ptctrl.cgi?action=movedegree&Cmd=$horDir&Degree=$h"; + $self->sendCmd( $cmd ); + $cmd = "/cgi/admin/ptctrl.cgi?action=movedegree&Cmd=$verDir&Degree=$v"; + $self->sendCmd( $cmd ); } -sub moveRelUp -{ - my $self = shift; - my $params = shift; - my $step = $self->getParam( $params, 'tiltstep' ); - Debug( "Step Up $step" ); - my $cmd = "/admin/ptctl.cgi?move=up"; - $self->sendCmd( $cmd ); +sub moveRelUp { + my $self = shift; + my $params = shift; + my $step = $self->getParam( $params, 'tiltstep' ); + Debug( "Step Up $step" ); + my $cmd = "/admin/ptctl.cgi?move=up"; + $self->sendCmd( $cmd ); } -sub moveRelDown -{ - my $self = shift; - my $params = shift; - my $step = $self->getParam( $params, 'tiltstep' ); - Debug( "Step Down $step" ); - my $cmd = "/admin/ptctl.cgi?move=down"; - $self->sendCmd( $cmd ); +sub moveRelDown { + my $self = shift; + my $params = shift; + my $step = $self->getParam( $params, 'tiltstep' ); + Debug( "Step Down $step" ); + my $cmd = "/admin/ptctl.cgi?move=down"; + $self->sendCmd( $cmd ); } -sub moveRelLeft -{ - my $self = shift; - my $params = shift; - my $step = $self->getParam( $params, 'panstep' ); - Debug( "Step Left $step" ); - my $cmd = "/admin/ptctl.cgi?move=left"; - $self->sendCmd( $cmd ); +sub moveRelLeft { + my $self = shift; + my $params = shift; + my $step = $self->getParam( $params, 'panstep' ); + + if ( $self->{Monitor}->{Orientation} eq "hori" ) { + Debug( "Stepping Right because flipped horizontally " ); + $self->sendCmd( "/admin/ptctl.cgi?move=right" ); + } else { + Debug( "Step Left" ); + $self->sendCmd( "/admin/ptctl.cgi?move=left" ); + } } -sub moveRelRight -{ - my $self = shift; - my $params = shift; - my $step = $self->getParam( $params, 'panstep' ); - Debug( "Step Right $step" ); - my $cmd = "/admin/ptctl.cgi?move=right"; - $self->sendCmd( $cmd ); +sub moveRelRight { + my $self = shift; + my $params = shift; + my $step = $self->getParam( $params, 'panstep' ); + if ( $self->{Monitor}->{Orientation} eq "hori" ) { + Debug( "Stepping Left because flipped horizontally " ); + $self->sendCmd( "/admin/ptctl.cgi?move=left" ); + } else { + Debug( "Step Right" ); + $self->sendCmd( "/admin/ptctl.cgi?move=right" ); + } } -sub presetClear -{ - my $self = shift; - my $params = shift; - my $preset = $self->getParam( $params, 'preset' ); - Debug( "Clear Preset $preset" ); - #my $cmd = "/axis-cgi/com/ptz.cgi?removeserverpresetno=$preset"; - #$self->sendCmd( $cmd ); +sub presetClear { + my $self = shift; + my $params = shift; + my $preset = $self->getParam( $params, 'preset' ); + Debug( "Clear Preset $preset" ); +#my $cmd = "/axis-cgi/com/ptz.cgi?removeserverpresetno=$preset"; +#$self->sendCmd( $cmd ); } -sub presetSet -{ - my $self = shift; - my $params = shift; - my $preset = $self->getParam( $params, 'preset' ); - Debug( "Set Preset $preset" ); - my $cmd = "/admin/ptctl.cgi?position=" . ($preset - 1) . "&positionname=zm$preset"; - $self->sendCmd( $cmd ); +sub presetSet { + my $self = shift; + my $params = shift; + my $preset = $self->getParam( $params, 'preset' ); + Debug( "Set Preset $preset" ); + my $cmd = "/admin/ptctl.cgi?position=" . ($preset - 1) . "&positionname=zm$preset"; + $self->sendCmd( $cmd ); } -sub presetGoto -{ - my $self = shift; - my $params = shift; - my $preset = $self->getParam( $params, 'preset' ); - Debug( "Goto Preset $preset" ); - my $cmd = "/admin/ptctl.cgi?move=p" . ($preset - 1); - $self->sendCmd( $cmd ); +sub presetGoto { + my $self = shift; + my $params = shift; + my $preset = $self->getParam( $params, 'preset' ); + Debug( "Goto Preset $preset" ); + my $cmd = "/admin/ptctl.cgi?move=p" . ($preset - 1); + $self->sendCmd( $cmd ); } -sub presetHome -{ - my $self = shift; - Debug( "Home Preset" ); - my $cmd = "/admin/ptctl.cgi?move=h"; - $self->sendCmd( $cmd ); +sub presetHome { + my $self = shift; + Debug( "Home Preset" ); + my $cmd = "/admin/ptctl.cgi?move=h"; + $self->sendCmd( $cmd ); } 1; __END__ -# Below is stub documentation for your module. You'd better edit it! =head1 NAME -ZoneMinder::Database - Perl extension for blah blah blah +ZoneMinder::Control::SkyIPCam7xx.pm - Module for controlling AirLink101 SkyIPams =head1 SYNOPSIS - use ZoneMinder::Database; - blah blah blah +use ZoneMinder::Control::SkyIPCam7xx; =head1 DESCRIPTION -Stub documentation for ZoneMinder, created by h2xs. It looks like the -author of the extension was negligent enough to leave the stub -unedited. - -Blah blah blah. +Module for controlling AirLink101 Cameras. =head2 EXPORT None by default. - - =head1 SEE ALSO -Mention other useful documentation such as the documentation of -related modules or operating system documentation (such as man pages -in UNIX), or any relevant external documentation such as RFCs or -standards. - -If you have a mailing list set up for your module, mention it here. - -If you have a web site set up for your module, mention it here. +ZoneMinder::Control =head1 AUTHOR @@ -318,5 +292,4 @@ This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available. - =cut diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm index 71ca57680..d616d893f 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Event.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Event.pm @@ -28,30 +28,12 @@ use 5.006; use strict; use warnings; -require Exporter; require ZoneMinder::Base; +require ZoneMinder::Object; require Date::Manip; -our @ISA = qw(Exporter ZoneMinder::Base); - -# Items to export into callers namespace by default. Note: do not export -# names by default without a very good reason. Use EXPORT_OK instead. -# Do not simply export all your public functions/methods/constants. - -# This allows declaration use ZoneMinder ':all'; -# If you do not need this, moving things directly into @EXPORT or @EXPORT_OK -# will save memory. -our %EXPORT_TAGS = ( - 'functions' => [ qw( - ) ] - ); -push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; - -our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); - -our @EXPORT = qw(); - -our $VERSION = $ZoneMinder::Base::VERSION; +#our @ISA = qw(ZoneMinder::Object); +use parent qw(ZoneMinder::Object); # ========================================================================== # @@ -62,39 +44,24 @@ our $VERSION = $ZoneMinder::Base::VERSION; use ZoneMinder::Config qw(:all); use ZoneMinder::Logger qw(:all); use ZoneMinder::Database qw(:all); +require Date::Parse; + +use vars qw/ $table $primary_key /; +$table = 'Events'; +$primary_key = 'Id'; use POSIX; -sub new { - my ( $parent, $id, $data ) = @_; - - my $self = {}; - bless $self, $parent; - $$self{dbh} = $ZoneMinder::Database::dbh; -#zmDbConnect(); - if ( ( $$self{Id} = $id ) or $data ) { -#$log->debug("loading $parent $id") if $debug or DEBUG_ALL; - $self->load( $data ); +sub Time { + if ( @_ > 1 ) { + $_[0]{Time} = $_[1]; } - return $self; -} # end sub new + if ( ! defined $_[0]{Time} ) { -sub load { - my ( $self, $data ) = @_; - my $type = ref $self; - if ( ! $data ) { -#$log->debug("Object::load Loading from db $type"); - $data = $$self{dbh}->selectrow_hashref( 'SELECT * FROM Events WHERE Id=?', {}, $$self{Id} ); - if ( ! $data ) { - Error( "Failure to load Event record for $$self{Id}: Reason: " . $$self{dbh}->errstr ); - } else { - Debug( 3, "Loaded Event $$self{Id}" ); - } # end if - } # end if ! $data - if ( $data and %$data ) { - @$self{keys %$data} = values %$data; - } # end if -} # end sub load + $_[0]{Time} = Date::Parse::str2time( $_[0]{StartTime} ); + } + return $_[0]{Time}; +} sub Name { if ( @_ > 1 ) { @@ -130,6 +97,7 @@ sub find { my $filter = new ZoneMinder::Event( $$db_filter{Id}, $db_filter ); push @results, $filter; } # end while + $sth->finish(); return @results; } @@ -138,36 +106,51 @@ sub find_one { return $results[0] if @results; } -sub getEventPath { +sub getPath { + return Path( @_ ); +} +sub Path { my $event = shift; - my $event_path = ""; - if ( $Config{ZM_USE_DEEP_STORAGE} ) { - $event_path = $Config{ZM_DIR_EVENTS} - .'/'.$event->{MonitorId} - .'/'.strftime( "%y/%m/%d/%H/%M/%S", - localtime($event->{Time}) - ) - ; - } else { - $event_path = $Config{ZM_DIR_EVENTS} - .'/'.$event->{MonitorId} - .'/'.$event->{Id} - ; + if ( @_ > 1 ) { + $$event{Path} = $_[1]; + if ( ! -e $$event{Path} ) { + Error("Setting path for event $$event{Id} to $_[1] but does not exist!"); + } } - if ( index($Config{ZM_DIR_EVENTS},'/') != 0 ){ - $event_path = $Config{ZM_PATH_WEB} - .'/'.$event_path - ; - } - return( $event_path ); + if ( ! $$event{Path} ) { + my $path = ($Config{ZM_DIR_EVENTS}=~/^\//) ? $Config{ZM_DIR_EVENTS} : $Config{ZM_PATH_WEB}.'/'.$Config{ZM_DIR_EVENTS}; + + if ( $Config{ZM_USE_DEEP_STORAGE} ) { + if ( $event->Time() ) { + $$event{Path} = join('/', + $path, + $event->{MonitorId}, + strftime( "%y/%m/%d/%H/%M/%S", + localtime($event->Time()) + ), + ); + } else { + Error("Event $$event{Id} has no value for Time(), unable to determine path"); + $$event{Path} = ''; + } + } else { + $$event{Path} = join('/', + $path, + $event->{MonitorId}, + $event->{Id}, + ); + } + } # end if + + return $$event{Path}; } sub GenerateVideo { my ( $self, $rate, $fps, $scale, $size, $overwrite, $format ) = @_; - my $event_path = getEventPath( $self ); + my $event_path = $self->getPath( ); chdir( $event_path ); ( my $video_name = $self->{Name} ) =~ s/\s/_/g; @@ -228,9 +211,7 @@ sub GenerateVideo { my $command = $Config{ZM_PATH_FFMPEG} ." -y -r $frame_rate " .$Config{ZM_FFMPEG_INPUT_OPTIONS} - ." -i %0" - .$Config{ZM_EVENT_IMAGE_DIGITS} - ."d-capture.jpg -s $video_size " + .' -i ' . ( $$self{DefaultVideo} ? $$self{DefaultVideo} : '%0'.$Config{ZM_EVENT_IMAGE_DIGITS} .'d-capture.jpg' ) #. " -f concat -i /tmp/event_files.txt" ." -s $video_size " .$Config{ZM_FFMPEG_OUTPUT_OPTIONS} @@ -253,54 +234,143 @@ sub GenerateVideo { Info( "Video file $video_file already exists for event $self->{Id}\n" ); return $event_path.'/'.$video_file; } - return; + return; } # end sub GenerateVideo +sub delete { + my $event = $_[0]; + Info( "Deleting event $event->{Id} from Monitor $event->{MonitorId} $event->{StartTime}\n" ); + $ZoneMinder::Database::dbh->ping(); +# Do it individually to avoid locking up the table for new events + my $sql = 'delete from Events where Id = ?'; + my $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() ); + my $res = $sth->execute( $event->{Id} ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + $sth->finish(); + + if ( ! $Config{ZM_OPT_FAST_DELETE} ) { + my $sql = 'delete from Frames where EventId = ?'; + my $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() ); + my $res = $sth->execute( $event->{Id} ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + $sth->finish(); + + $sql = 'delete from Stats where EventId = ?'; + $sth = $ZoneMinder::Database::dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$ZoneMinder::Database::dbh->errstr() ); + $res = $sth->execute( $event->{Id} ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + $sth->finish(); + + $event->delete_files( ); + } else { + Debug('Not deleting frames, stats and files for speed.'); + } +} # end sub delete + + +sub delete_files { + + my $storage_path = ($Config{ZM_DIR_EVENTS}=~/^\//) ? $Config{ZM_DIR_EVENTS} : $Config{ZM_PATH_WEB}.'/'.$Config{ZM_DIR_EVENTS}; + + if ( ! $storage_path ) { + Fatal("Empty path when deleting files for event $_[0]{Id} "); + return; + } + + chdir ( $storage_path ); + + if ( $Config{ZM_USE_DEEP_STORAGE} ) { + if ( ! $_[0]{MonitorId} ) { + Error("No monitor id assigned to event $_[0]{Id}"); + return; + } + Debug("Deleting files for Event $_[0]{Id} from $storage_path."); + my $link_path = $_[0]{MonitorId}.'/*/*/*/.'.$_[0]{Id}; +#Debug( "LP1:$link_path" ); + my @links = glob($link_path); +#Debug( "L:".$links[0].": $!" ); + if ( @links ) { + ( $link_path ) = ( $links[0] =~ /^(.*)$/ ); # De-taint +#Debug( "LP2:$link_path" ); + + ( my $day_path = $link_path ) =~ s/\.\d+//; +#Debug( "DP:$day_path" ); + my $event_path = $day_path.readlink( $link_path ); + ( $event_path ) = ( $event_path =~ /^(.*)$/ ); # De-taint +#Debug( "EP:$event_path" ); + my $command = "/bin/rm -rf $event_path"; +#Debug( "C:$command" ); + ZoneMinder::General::executeShellCommand( $command ); + + unlink( $link_path ) or Error( "Unable to unlink '$link_path': $!" ); + + my @path_parts = split( /\//, $event_path ); + for ( my $i = int(@path_parts)-2; $i >= 1; $i-- ) { + my $delete_path = join( '/', @path_parts[0..$i] ); +#Debug( "DP$i:$delete_path" ); + my @has_files = glob( join('/', $storage_path,$delete_path,'*' ) ); +#Debug( "HF1:".$has_files[0] ) if ( @has_files ); + last if ( @has_files ); + @has_files = glob( join('/', $storage_path, $delete_path, '.[0-9]*' ) ); +#Debug( "HF2:".$has_files[0] ) if ( @has_files ); + last if ( @has_files ); + my $command = "/bin/rm -rf $storage_path/$delete_path"; + ZoneMinder::General::executeShellCommand( $command ); + } + } + } else { + my $command = "/bin/rm -rf $storage_path/$_[0]{MonitorId}/$_[0]{Id}"; + ZoneMinder::General::executeShellCommand( $command ); + } +} # end sub delete_files + +sub Storage { + return new ZoneMinder::Storage( $_[0]{StorageId} ); +} + +sub check_for_in_filesystem { + my $path = $_[0]->Path(); + if ( $path ) { + my @files = glob( $path . '/*' ); +Debug("Checking for files for event $_[0]{Id} at $path using glob $path/* found " . scalar @files . " files"); + return 1 if @files; + } +Debug("Checking for files for event $_[0]{Id} at $path using glob $path/* found no files"); + return 0; +} + +sub age { + if ( ! $_[0]{age} ) { + $_[0]{age} = (time() - ($^T - ((-M $_[0]->Path() ) * 24*60*60))); + } + return $_[0]{age}; +} + 1; __END__ -# Below is stub documentation for your module. You'd better edit it! =head1 NAME -ZoneMinder::Database - Perl extension for blah blah blah +ZoneMinder::Event - Perl Class for events =head1 SYNOPSIS use ZoneMinder::Event; -blah blah blah =head1 DESCRIPTION -Stub documentation for ZoneMinder, created by h2xs. It looks like the -author of the extension was negligent enough to leave the stub -unedited. - -Blah blah blah. - -=head2 EXPORT - -None by default. - - - -=head1 SEE ALSO - -Mention other useful documentation such as the documentation of -related modules or operating system documentation (such as man pages -in UNIX), or any relevant external documentation such as RFCs or -standards. - -If you have a mailing list set up for your module, mention it here. - -If you have a web site set up for your module, mention it here. +The Event class has everything you need to deal with events from Perl. =head1 AUTHOR -Philip Coombes, Ephilip.coombes@zoneminder.comE +Isaac Connor, Eisaac@zoneminder.comE =head1 COPYRIGHT AND LICENSE -Copyright (C) 2001-2008 Philip Coombes +Copyright (C) 2001-2017 ZoneMinder LLC This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Memory.pm.in b/scripts/ZoneMinder/lib/ZoneMinder/Memory.pm.in index 38a8d3b5c..91f2d0b54 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Memory.pm.in +++ b/scripts/ZoneMinder/lib/ZoneMinder/Memory.pm.in @@ -41,7 +41,7 @@ our @ISA = qw(Exporter ZoneMinder::Base); # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK # will save memory. our %EXPORT_TAGS = ( - 'constants' => [ qw( + constants => [ qw( STATE_IDLE STATE_PREALARM STATE_ALARM @@ -56,7 +56,7 @@ our %EXPORT_TAGS = ( TRIGGER_ON TRIGGER_OFF ) ], - 'functions' => [ qw( + functions => [ qw( zmMemVerify zmMemInvalidate zmMemRead @@ -77,12 +77,12 @@ our %EXPORT_TAGS = ( zmTriggerEventOn zmTriggerEventOff zmTriggerEventCancel - zmTriggerShowtext + zmTriggerShowtext ) ], ); push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; -our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); +our @EXPORT_OK = ( @{ $EXPORT_TAGS{all} } ); our @EXPORT = qw(); @@ -115,7 +115,7 @@ use constant TRIGGER_OFF => 2; use Storable qw( freeze thaw ); -if ( "@ENABLE_MMAP@" eq 'yes' ) { +if ( '@ENABLE_MMAP@' eq 'yes' ) { # 'yes' if memory is mmapped require ZoneMinder::Memory::Mapped; ZoneMinder::Memory::Mapped->import(); @@ -141,42 +141,42 @@ our $native = $arch/8; our $mem_seq = 0; our $mem_data = { - "shared_data" => { "type"=>"SharedData", "seq"=>$mem_seq++, "contents"=> { - "size" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "last_write_index" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "last_read_index" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "state" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "last_event" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "action" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "brightness" => { "type"=>"int32", "seq"=>$mem_seq++ }, - "hue" => { "type"=>"int32", "seq"=>$mem_seq++ }, - "colour" => { "type"=>"int32", "seq"=>$mem_seq++ }, - "contrast" => { "type"=>"int32", "seq"=>$mem_seq++ }, - "alarm_x" => { "type"=>"int32", "seq"=>$mem_seq++ }, - "alarm_y" => { "type"=>"int32", "seq"=>$mem_seq++ }, - "valid" => { "type"=>"uint8", "seq"=>$mem_seq++ }, - "active" => { "type"=>"uint8", "seq"=>$mem_seq++ }, - "signal" => { "type"=>"uint8", "seq"=>$mem_seq++ }, - "format" => { "type"=>"uint8", "seq"=>$mem_seq++ }, - "imagesize" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "epadding1" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "epadding2" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "last_write_time" => { "type"=>"time_t64", "seq"=>$mem_seq++ }, - "last_read_time" => { "type"=>"time_t64", "seq"=>$mem_seq++ }, - "control_state" => { "type"=>"uint8[256]", "seq"=>$mem_seq++ }, + shared_data => { type=>'SharedData', seq=>$mem_seq++, contents=> { + size => { type=>'uint32', seq=>$mem_seq++ }, + last_write_index => { type=>'uint32', seq=>$mem_seq++ }, + last_read_index => { type=>'uint32', seq=>$mem_seq++ }, + state => { type=>'uint32', seq=>$mem_seq++ }, + last_event => { type=>'uint32', seq=>$mem_seq++ }, + action => { type=>'uint32', seq=>$mem_seq++ }, + brightness => { type=>'int32', seq=>$mem_seq++ }, + hue => { type=>'int32', seq=>$mem_seq++ }, + colour => { type=>'int32', seq=>$mem_seq++ }, + contrast => { type=>'int32', seq=>$mem_seq++ }, + alarm_x => { type=>'int32', seq=>$mem_seq++ }, + alarm_y => { type=>'int32', seq=>$mem_seq++ }, + valid => { type=>'uint8', seq=>$mem_seq++ }, + active => { type=>'uint8', seq=>$mem_seq++ }, + signal => { type=>'uint8', seq=>$mem_seq++ }, + format => { type=>'uint8', seq=>$mem_seq++ }, + imagesize => { type=>'uint32', seq=>$mem_seq++ }, + epadding1 => { type=>'uint32', seq=>$mem_seq++ }, + epadding2 => { type=>'uint32', seq=>$mem_seq++ }, + last_write_time => { type=>'time_t64', seq=>$mem_seq++ }, + last_read_time => { type=>'time_t64', seq=>$mem_seq++ }, + control_state => { type=>'uint8[256]', seq=>$mem_seq++ }, } }, - "trigger_data" => { "type"=>"TriggerData", "seq"=>$mem_seq++, "contents"=> { - "size" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "trigger_state" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "trigger_score" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "padding" => { "type"=>"uint32", "seq"=>$mem_seq++ }, - "trigger_cause" => { "type"=>"int8[32]", "seq"=>$mem_seq++ }, - "trigger_text" => { "type"=>"int8[256]", "seq"=>$mem_seq++ }, - "trigger_showtext" => { "type"=>"int8[256]", "seq"=>$mem_seq++ }, + trigger_data => { type=>'TriggerData', seq=>$mem_seq++, 'contents'=> { + size => { type=>'uint32', seq=>$mem_seq++ }, + trigger_state => { type=>'uint32', seq=>$mem_seq++ }, + trigger_score => { type=>'uint32', seq=>$mem_seq++ }, + padding => { type=>'uint32', seq=>$mem_seq++ }, + trigger_cause => { type=>'int8[32]', seq=>$mem_seq++ }, + trigger_text => { type=>'int8[256]', seq=>$mem_seq++ }, + trigger_showtext => { type=>'int8[256]', seq=>$mem_seq++ }, } }, - "end" => { "seq"=>$mem_seq++, "size"=> 0 } + end => { seq=>$mem_seq++, size=>0 } }; our $mem_size = 0; @@ -195,28 +195,28 @@ sub zmMemInit { } } foreach my $member_data ( sort { $a->{seq} <=> $b->{seq} } values( %{$section_data->{contents}} ) ) { - if ( $member_data->{type} eq "long" - || $member_data->{type} eq "ulong" - || $member_data->{type} eq "size_t" + if ( $member_data->{type} eq 'long' + || $member_data->{type} eq 'ulong' + || $member_data->{type} eq 'size_t' ) { $member_data->{size} = $member_data->{align} = $native; - } elsif ( $member_data->{type} eq "int64" - || $member_data->{type} eq "uint64" - || $member_data->{type} eq "time_t64" + } elsif ( $member_data->{type} eq 'int64' + || $member_data->{type} eq 'uint64' + || $member_data->{type} eq 'time_t64' ) { $member_data->{size} = $member_data->{align} = 8; - } elsif ( $member_data->{type} eq "int32" - || $member_data->{type} eq "uint32" - || $member_data->{type} eq "bool4" + } elsif ( $member_data->{type} eq 'int32' + || $member_data->{type} eq 'uint32' + || $member_data->{type} eq 'bool4' ) { $member_data->{size} = $member_data->{align} = 4; - } elsif ($member_data->{type} eq "int16" - || $member_data->{type} eq "uint16" + } elsif ($member_data->{type} eq 'int16' + || $member_data->{type} eq 'uint16' ) { $member_data->{size} = $member_data->{align} = 2; - } elsif ( $member_data->{type} eq "int8" - || $member_data->{type} eq "uint8" - || $member_data->{type} eq "bool1" + } elsif ( $member_data->{type} eq 'int8' + || $member_data->{type} eq 'uint8' + || $member_data->{type} eq 'bool1' ) { $member_data->{size} = $member_data->{align} = 1; } elsif ( $member_data->{type} =~ /^u?int8\[(\d+)\]$/ ) { @@ -248,51 +248,51 @@ sub zmMemVerify { return( undef ); } - my $sd_size = zmMemRead( $monitor, "shared_data:size", 1 ); - if ( $sd_size != $mem_data->{shared_data}->{size} ) { - if ( $sd_size ) { - Error( "Shared data size conflict in shared_data for monitor " - .$monitor->{Name} - .", expected " - .$mem_data->{shared_data}->{size} - .", got " - .$sd_size - ); - } else { - Debug( "Shared data size conflict in shared_data for monitor " - .$monitor->{Name} - .", expected " - .$mem_data->{shared_data}->{size} - .", got ".$sd_size - ); - } - return( undef ); + my $sd_size = zmMemRead( $monitor, 'shared_data:size', 1 ); + if ( $sd_size != $mem_data->{shared_data}->{size} ) { + if ( $sd_size ) { + Error( "Shared data size conflict in shared_data for monitor " + .$monitor->{Name} + .", expected " + .$mem_data->{shared_data}->{size} + .", got " + .$sd_size + ); + } else { + Debug( "Shared data size conflict in shared_data for monitor " + .$monitor->{Name} + .", expected " + .$mem_data->{shared_data}->{size} + .", got ".$sd_size + ); } - my $td_size = zmMemRead( $monitor, "trigger_data:size", 1 ); - if ( $td_size != $mem_data->{trigger_data}->{size} ) { - if ( $td_size ) { - Error( "Shared data size conflict in trigger_data for monitor " - .$monitor->{Name} - .", expected " - .$mem_data->{triggger_data}->{size} - .", got " - .$td_size - ); - } else { - Debug( "Shared data size conflict in trigger_data for monitor " - .$monitor->{Name} - .", expected " - .$mem_data->{triggger_data}->{size} - .", got " - .$td_size - ); - } - return( undef ); - } - if ( !zmMemRead($monitor, "shared_data:valid",1) ) { - Error( "Shared data not valid for monitor $$monitor{Id}" ); - return( undef ); + return( undef ); + } + my $td_size = zmMemRead( $monitor, 'trigger_data:size', 1 ); + if ( $td_size != $mem_data->{trigger_data}->{size} ) { + if ( $td_size ) { + Error( "Shared data size conflict in trigger_data for monitor " + .$monitor->{Name} + .", expected " + .$mem_data->{triggger_data}->{size} + .", got " + .$td_size + ); + } else { + Debug( "Shared data size conflict in trigger_data for monitor " + .$monitor->{Name} + .", expected " + .$mem_data->{triggger_data}->{size} + .", got " + .$td_size + ); } + return( undef ); + } + if ( !zmMemRead($monitor, 'shared_data:valid',1) ) { + Error( "Shared data not valid for monitor $$monitor{Id}" ); + return( undef ); + } return( !undef ); } @@ -325,32 +325,32 @@ sub zmMemRead { return( undef ); } my $value; - if ( $type eq "long" ) { - ( $value ) = unpack( "l!", $data ); - } elsif ( $type eq "ulong" || $type eq "size_t" ) { - ( $value ) = unpack( "L!", $data ); - } elsif ( $type eq "int64" || $type eq "time_t64" ) { -# The "q" type is only available on 64bit platforms, so use native. - ( $value ) = unpack( "l!", $data ); - } elsif ( $type eq "uint64" ) { -# The "q" type is only available on 64bit platforms, so use native. - ( $value ) = unpack( "L!", $data ); - } elsif ( $type eq "int32" ) { - ( $value ) = unpack( "l", $data ); - } elsif ( $type eq "uint32" || $type eq "bool4" ) { - ( $value ) = unpack( "L", $data ); - } elsif ( $type eq "int16" ) { - ( $value ) = unpack( "s", $data ); - } elsif ( $type eq "uint16" ) { - ( $value ) = unpack( "S", $data ); - } elsif ( $type eq "int8" ) { - ( $value ) = unpack( "c", $data ); - } elsif ( $type eq "uint8" || $type eq "bool1" ) { - ( $value ) = unpack( "C", $data ); + if ( $type eq 'long' ) { + ( $value ) = unpack( 'l!', $data ); + } elsif ( $type eq 'ulong' || $type eq 'size_t' ) { + ( $value ) = unpack( 'L!', $data ); + } elsif ( $type eq 'int64' || $type eq 'time_t64' ) { +# The 'q' type is only available on 64bit platforms, so use native. + ( $value ) = unpack( 'l!', $data ); + } elsif ( $type eq 'uint64' ) { +# The 'q' type is only available on 64bit platforms, so use native. + ( $value ) = unpack( 'L!', $data ); + } elsif ( $type eq 'int32' ) { + ( $value ) = unpack( 'l', $data ); + } elsif ( $type eq 'uint32' || $type eq 'bool4' ) { + ( $value ) = unpack( 'L', $data ); + } elsif ( $type eq 'int16' ) { + ( $value ) = unpack( 's', $data ); + } elsif ( $type eq 'uint16' ) { + ( $value ) = unpack( 'S', $data ); + } elsif ( $type eq 'int8' ) { + ( $value ) = unpack( 'c', $data ); + } elsif ( $type eq 'uint8' || $type eq 'bool1' ) { + ( $value ) = unpack( 'C', $data ); } elsif ( $type =~ /^int8\[\d+\]$/ ) { - ( $value ) = unpack( "Z".$size, $data ); + ( $value ) = unpack( 'Z'.$size, $data ); } elsif ( $type =~ /^uint8\[\d+\]$/ ) { - ( $value ) = unpack( "C".$size, $data ); + ( $value ) = unpack( 'C'.$size, $data ); } else { Fatal( "Unexpected type '".$type."' found for '".$field."'" ); } @@ -394,34 +394,34 @@ sub zmMemWrite { my $size = $mem_data->{$section}->{contents}->{$element}->{size}; my $data; - if ( $type eq "long" ) { - $data = pack( "l!", $value ); - } elsif ( $type eq "ulong" || $type eq "size_t" ) { - $data = pack( "L!", $value ); - } elsif ( $type eq "int64" || $type eq "time_t64" ) { -# The "q" type is only available on 64bit platforms, so use native. - $data = pack( "l!", $value ); - } elsif ( $type eq "uint64" ) { -# The "q" type is only available on 64bit platforms, so use native. - $data = pack( "L!", $value ); - } elsif ( $type eq "int32" ) { - $data = pack( "l", $value ); - } elsif ( $type eq "uint32" || $type eq "bool4" ) { - $data = pack( "L", $value ); - } elsif ( $type eq "int16" ) { - $data = pack( "s", $value ); - } elsif ( $type eq "uint16" ) { - $data = pack( "S", $value ); - } elsif ( $type eq "int8" ) { - $data = pack( "c", $value ); - } elsif ( $type eq "uint8" || $type eq "bool1" ) { - $data = pack( "C", $value ); + if ( $type eq 'long' ) { + $data = pack( 'l!', $value ); + } elsif ( $type eq 'ulong' || $type eq 'size_t' ) { + $data = pack( 'L!', $value ); + } elsif ( $type eq 'int64' || $type eq 'time_t64' ) { +# The 'q' type is only available on 64bit platforms, so use native. + $data = pack( 'l!', $value ); + } elsif ( $type eq 'uint64' ) { +# The 'q' type is only available on 64bit platforms, so use native. + $data = pack( 'L!', $value ); + } elsif ( $type eq 'int32' ) { + $data = pack( 'l', $value ); + } elsif ( $type eq 'uint32' || $type eq 'bool4' ) { + $data = pack( 'L', $value ); + } elsif ( $type eq 'int16' ) { + $data = pack( 's', $value ); + } elsif ( $type eq 'uint16' ) { + $data = pack( 'S', $value ); + } elsif ( $type eq 'int8' ) { + $data = pack( 'c', $value ); + } elsif ( $type eq 'uint8' || $type eq 'bool1' ) { + $data = pack( 'C', $value ); } elsif ( $type =~ /^int8\[\d+\]$/ ) { - $data = pack( "Z".$size, $value ); + $data = pack( 'Z'.$size, $value ); } elsif ( $type =~ /^uint8\[\d+\]$/ ) { - $data = pack( "C".$size, $value ); + $data = pack( 'C'.$size, $value ); } else { - Fatal( "Unexpected type '".$type."' found for '".$field."'" ); + Fatal( "Unexpected type \"$type\" found for \"$field\"" ); } if ( !zmMemPut( $monitor, $offset, $size, $data ) ) { @@ -438,26 +438,26 @@ sub zmMemWrite { sub zmGetMonitorState { my $monitor = shift; - return( zmMemRead( $monitor, "shared_data:state" ) ); + return( zmMemRead( $monitor, 'shared_data:state' ) ); } sub zmGetAlarmLocation { my $monitor = shift; - return( zmMemRead( $monitor, [ "shared_data:alarm_x", "shared_data:alarm_y" ] ) ); + return( zmMemRead( $monitor, [ 'shared_data:alarm_x', 'shared_data:alarm_y' ] ) ); } sub zmSetControlState { my $monitor = shift; my $control_state = shift; - zmMemWrite( $monitor, { "shared_data:control_state" => $control_state } ); + zmMemWrite( $monitor, { 'shared_data:control_state' => $control_state } ); } sub zmGetControlState { my $monitor = shift; - return( zmMemRead( $monitor, "shared_data:control_state" ) ); + return( zmMemRead( $monitor, 'shared_data:control_state' ) ); } sub zmSaveControlState { @@ -493,8 +493,8 @@ sub zmHasAlarmed { my $monitor = shift; my $last_event_id = shift; - my ( $state, $last_event ) = zmMemRead( $monitor, [ "shared_data:state" - ,"shared_data:last_event" + my ( $state, $last_event ) = zmMemRead( $monitor, [ 'shared_data:state' + ,'shared_data:last_event' ] ); @@ -509,63 +509,63 @@ sub zmHasAlarmed { sub zmGetLastEvent { my $monitor = shift; - return( zmMemRead( $monitor, "shared_data:last_event" ) ); + return( zmMemRead( $monitor, 'shared_data:last_event' ) ); } sub zmGetLastWriteTime { my $monitor = shift; - return( zmMemRead( $monitor, "shared_data:last_write_time" ) ); + return( zmMemRead( $monitor, 'shared_data:last_write_time' ) ); } sub zmGetLastReadTime { my $monitor = shift; - return( zmMemRead( $monitor, "shared_data:last_read_time" ) ); + return( zmMemRead( $monitor, 'shared_data:last_read_time' ) ); } sub zmGetMonitorActions { my $monitor = shift; - return( zmMemRead( $monitor, "shared_data:action" ) ); + return( zmMemRead( $monitor, 'shared_data:action' ) ); } sub zmMonitorEnable { my $monitor = shift; - my $action = zmMemRead( $monitor, "shared_data:action" ); + my $action = zmMemRead( $monitor, 'shared_data:action' ); $action |= ACTION_SUSPEND; - zmMemWrite( $monitor, { "shared_data:action" => $action } ); + zmMemWrite( $monitor, { 'shared_data:action' => $action } ); } sub zmMonitorDisable { my $monitor = shift; - my $action = zmMemRead( $monitor, "shared_data:action" ); + my $action = zmMemRead( $monitor, 'shared_data:action' ); $action |= ACTION_RESUME; - zmMemWrite( $monitor, { "shared_data:action" => $action } ); + zmMemWrite( $monitor, { 'shared_data:action' => $action } ); } sub zmMonitorSuspend { my $monitor = shift; - my $action = zmMemRead( $monitor, "shared_data:action" ); + my $action = zmMemRead( $monitor, 'shared_data:action' ); $action |= ACTION_SUSPEND; - zmMemWrite( $monitor, { "shared_data:action" => $action } ); + zmMemWrite( $monitor, { 'shared_data:action' => $action } ); } sub zmMonitorResume { my $monitor = shift; - my $action = zmMemRead( $monitor, "shared_data:action" ); + my $action = zmMemRead( $monitor, 'shared_data:action' ); $action |= ACTION_RESUME; - zmMemWrite( $monitor, { "shared_data:action" => $action } ); + zmMemWrite( $monitor, { 'shared_data:action' => $action } ); } sub zmGetTriggerState { my $monitor = shift; - return( zmMemRead( $monitor, "trigger_data:trigger_state" ) ); + return( zmMemRead( $monitor, 'trigger_data:trigger_state' ) ); } sub zmTriggerEventOn { @@ -576,12 +576,12 @@ sub zmTriggerEventOn { my $showtext = shift; my $values = { - "trigger_data:trigger_score" => $score, - "trigger_data:trigger_cause" => $cause, + 'trigger_data:trigger_score' => $score, + 'trigger_data:trigger_cause' => $cause, }; - $values->{"trigger_data:trigger_text"} = $text if ( defined($text) ); - $values->{"trigger_data:trigger_showtext"} = $showtext if ( defined($showtext) ); - $values->{"trigger_data:trigger_state"} = TRIGGER_ON; # Write state last so event not read incomplete + $values->{'trigger_data:trigger_text'} = $text if ( defined($text) ); + $values->{'trigger_data:trigger_showtext'} = $showtext if ( defined($showtext) ); + $values->{'trigger_data:trigger_state'} = TRIGGER_ON; # Write state last so event not read incomplete zmMemWrite( $monitor, $values ); } @@ -590,11 +590,11 @@ sub zmTriggerEventOff { my $monitor = shift; my $values = { - "trigger_data:trigger_state" => TRIGGER_OFF, - "trigger_data:trigger_score" => 0, - "trigger_data:trigger_cause" => "", - "trigger_data:trigger_text" => "", - "trigger_data:trigger_showtext" => "", + 'trigger_data:trigger_state' => TRIGGER_OFF, + 'trigger_data:trigger_score' => 0, + 'trigger_data:trigger_cause' => '', + 'trigger_data:trigger_text' => '', + 'trigger_data:trigger_showtext' => '', }; zmMemWrite( $monitor, $values ); @@ -604,11 +604,11 @@ sub zmTriggerEventCancel { my $monitor = shift; my $values = { - "trigger_data:trigger_state" => TRIGGER_CANCEL, - "trigger_data:trigger_score" => 0, - "trigger_data:trigger_cause" => "", - "trigger_data:trigger_text" => "", - "trigger_data:trigger_showtext" => "", + 'trigger_data:trigger_state' => TRIGGER_CANCEL, + 'trigger_data:trigger_score' => 0, + 'trigger_data:trigger_cause' => '', + 'trigger_data:trigger_text' => '', + 'trigger_data:trigger_showtext' => '', }; zmMemWrite( $monitor, $values ); @@ -619,7 +619,7 @@ sub zmTriggerShowtext { my $showtext = shift; my $values = { - "trigger_data:trigger_showtext" => $showtext, + 'trigger_data:trigger_showtext' => $showtext, }; zmMemWrite( $monitor, $values ); @@ -648,7 +648,7 @@ if ( zmMemVerify( $monitor ) ) { "shared_data:last_write_index" ] ); -zmMemWrite( $monitor, { "trigger_data:trigger_showtext" => "Some Text" } ); +zmMemWrite( $monitor, { 'trigger_data:trigger_showtext' => "Some Text" } ); =head1 DESCRIPTION diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Memory/Mapped.pm b/scripts/ZoneMinder/lib/ZoneMinder/Memory/Mapped.pm index b6aaf68b5..bd826c9c7 100644 --- a/scripts/ZoneMinder/lib/ZoneMinder/Memory/Mapped.pm +++ b/scripts/ZoneMinder/lib/ZoneMinder/Memory/Mapped.pm @@ -40,15 +40,15 @@ our @ISA = qw(Exporter ZoneMinder::Base); # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK # will save memory. our %EXPORT_TAGS = ( - 'functions' => [ qw( - zmMemKey - zmMemAttach - zmMemDetach - zmMemGet - zmMemPut - zmMemClean - ) ], -); + functions => [ qw( + zmMemKey + zmMemAttach + zmMemDetach + zmMemGet + zmMemPut + zmMemClean + ) ], + ); push( @{$EXPORT_TAGS{all}}, @{$EXPORT_TAGS{$_}} ) foreach keys %EXPORT_TAGS; our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); @@ -68,134 +68,119 @@ use ZoneMinder::Logger qw(:all); use Sys::Mmap; -sub zmMemKey -{ - my $monitor = shift; - return( defined($monitor->{MMapAddr})?$monitor->{MMapAddr}:undef ); +sub zmMemKey { + my $monitor = shift; + return( defined($monitor->{MMapAddr})?$monitor->{MMapAddr}:undef ); } -sub zmMemAttach -{ - my ( $monitor, $size ) = @_; - if ( ! $size ) { - Error( "No size passed to zmMemAttach for monitor $$monitor{Id}\n" ); - return( undef ); +sub zmMemAttach { + my ( $monitor, $size ) = @_; + if ( ! $size ) { + Error( "No size passed to zmMemAttach for monitor $$monitor{Id}\n" ); + return( undef ); + } + if ( !defined($monitor->{MMapAddr}) ) { + + my $mmap_file = $Config{ZM_PATH_MAP}."/zm.mmap.".$monitor->{Id}; + if ( ! -e $mmap_file ) { + Error( sprintf( "Memory map file '%s' does not exist. zmc might not be running." + , $mmap_file + ) + ); + return ( undef ); } - if ( !defined($monitor->{MMapAddr}) ) - { - my $mmap_file = $Config{ZM_PATH_MAP}."/zm.mmap.".$monitor->{Id}; - if ( ! -e $mmap_file ) { - Error( sprintf( "Memory map file '%s' does not exist. zmc might not be running." - , $mmap_file - ) - ); - return ( undef ); - } + my $mmap_file_size = -s $mmap_file; - my $mmap_file_size = -s $mmap_file; - - if ( $mmap_file_size < $size ) { - Error( sprintf( "Memory map file '%s' should have been %d but was instead %d" - , $mmap_file - , $size - , $mmap_file_size - ) - ); - return ( undef ); - } - my $MMAP; - if ( !open( $MMAP, "+<", $mmap_file ) ) - { - Error( sprintf( "Can't open memory map file '%s': $!\n", $mmap_file ) ); - return( undef ); - } - my $mmap = undef; - my $mmap_addr = mmap( $mmap, $size, PROT_READ|PROT_WRITE, MAP_SHARED, $MMAP ); - if ( !$mmap_addr || !$mmap ) - { - Error( sprintf( "Can't mmap to file '%s': $!\n", $mmap_file ) ); - close( $MMAP ); - return( undef ); - } - $monitor->{MMapHandle} = $MMAP; - $monitor->{MMapAddr} = $mmap_addr; - $monitor->{MMap} = \$mmap; + if ( $mmap_file_size < $size ) { + Error( sprintf( "Memory map file '%s' should have been %d but was instead %d" + , $mmap_file + , $size + , $mmap_file_size + ) + ); + return ( undef ); } - return( !undef ); + my $MMAP; + if ( !open( $MMAP, '+<', $mmap_file ) ) { + Error( sprintf( "Can't open memory map file '%s': $!\n", $mmap_file ) ); + return( undef ); + } + my $mmap = undef; + my $mmap_addr = mmap( $mmap, $size, PROT_READ|PROT_WRITE, MAP_SHARED, $MMAP ); + if ( !$mmap_addr || !$mmap ) { + Error( sprintf( "Can't mmap to file '%s': $!\n", $mmap_file ) ); + close( $MMAP ); + return( undef ); + } + $monitor->{MMapHandle} = $MMAP; + $monitor->{MMapAddr} = $mmap_addr; + $monitor->{MMap} = \$mmap; + } + return( !undef ); } -sub zmMemDetach -{ - my $monitor = shift; +sub zmMemDetach { + my $monitor = shift; - if ( $monitor->{MMap} ) - { - if ( ! munmap( ${$monitor->{MMap}} ) ) { - Warn( "Unable to munmap for monitor $$monitor{Id}\n"); - } - delete $monitor->{MMap}; - } - if ( $monitor->{MMapAddr} ) - { - delete $monitor->{MMapAddr}; - } - if ( $monitor->{MMapHandle} ) - { - close( $monitor->{MMapHandle} ); - delete $monitor->{MMapHandle}; + if ( $monitor->{MMap} ) { + if ( ! munmap( ${$monitor->{MMap}} ) ) { + Warn( "Unable to munmap for monitor $$monitor{Id}\n"); } + delete $monitor->{MMap}; + } + if ( $monitor->{MMapAddr} ) { + delete $monitor->{MMapAddr}; + } + if ( $monitor->{MMapHandle} ) { + close( $monitor->{MMapHandle} ); + delete $monitor->{MMapHandle}; + } } -sub zmMemGet -{ - my $monitor = shift; - my $offset = shift; - my $size = shift; +sub zmMemGet { + my $monitor = shift; + my $offset = shift; + my $size = shift; - my $mmap = $monitor->{MMap}; - if ( !$mmap || !$$mmap ) - { - Error( sprintf( "Can't read from mapped memory for monitor '%d', gone away?" - , $monitor->{Id} - ) + my $mmap = $monitor->{MMap}; + if ( !$mmap || !$$mmap ) { + Error( sprintf( "Can't read from mapped memory for monitor '%d', gone away?" + , $monitor->{Id} + ) ); - return( undef ); - } - my $data = substr( $$mmap, $offset, $size ); - return( $data ); + return( undef ); + } + my $data = substr( $$mmap, $offset, $size ); + return( $data ); } -sub zmMemPut -{ - my $monitor = shift; - my $offset = shift; - my $size = shift; - my $data = shift; +sub zmMemPut { + my $monitor = shift; + my $offset = shift; + my $size = shift; + my $data = shift; - my $mmap = $monitor->{MMap}; - if ( !$mmap || !$$mmap ) - { - Error( sprintf( "Can't write mapped memory for monitor '%d', gone away?" - , $monitor->{Id} - ) + my $mmap = $monitor->{MMap}; + if ( !$mmap || !$$mmap ) { + Error( sprintf( "Can't write mapped memory for monitor '%d', gone away?" + , $monitor->{Id} + ) ); - return( undef ); - } - substr( $$mmap, $offset, $size ) = $data; - return( !undef ); + return( undef ); + } + substr( $$mmap, $offset, $size ) = $data; + return( !undef ); } -sub zmMemClean -{ - Debug( "Removing memory map files\n" ); - my $mapPath = $Config{ZM_PATH_MAP}."/zm.mmap.*"; - foreach my $mapFile( glob( $mapPath ) ) - { - ( $mapFile ) = $mapFile =~ /^(.+)$/; - Debug( "Removing memory map file '$mapFile'\n" ); - unlink( $mapFile ); - } +sub zmMemClean { + Debug( "Removing memory map files\n" ); + my $mapPath = $Config{ZM_PATH_MAP}.'/zm.mmap.*'; + foreach my $mapFile( glob( $mapPath ) ) { + ( $mapFile ) = $mapFile =~ /^(.+)$/; + Debug( "Removing memory map file '$mapFile'\n" ); + unlink( $mapFile ); + } } 1; diff --git a/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm b/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm new file mode 100644 index 000000000..d5b7d7f53 --- /dev/null +++ b/scripts/ZoneMinder/lib/ZoneMinder/Monitor.pm @@ -0,0 +1,85 @@ +# ========================================================================== +# +# ZoneMinder Monitor Module, $Date$, $Revision$ +# Copyright (C) 2001-2008 Philip Coombes +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# ========================================================================== +# +# This module contains the common definitions and functions used by the rest +# of the ZoneMinder scripts +# +package ZoneMinder::Monitor; + +use 5.006; +use strict; +use warnings; + +require ZoneMinder::Base; +require ZoneMinder::Object; +require ZoneMinder::Server; + +#our @ISA = qw(Exporter ZoneMinder::Base); +use parent qw(ZoneMinder::Object); + +# ========================================================================== +# +# General Utility Functions +# +# ========================================================================== + +use ZoneMinder::Config qw(:all); +use ZoneMinder::Logger qw(:all); +use ZoneMinder::Database qw(:all); + +use POSIX; +use vars qw/ $table $primary_key /; +$table = 'Monitors'; +$primary_key = 'Id'; + +sub Server { + return new ZoneMinder::Server( $_[0]{ServerId} ); +} # end sub Server + +1; +__END__ + +=head1 NAME + +ZoneMinder::Monitor - Perl Class for Monitors + +=head1 SYNOPSIS + +use ZoneMinder::Monitor; + +=head1 DESCRIPTION + + + +=head1 AUTHOR + +Isaac Connor, Eisaac@zoneminder.comE + +=head1 COPYRIGHT AND LICENSE + +Copyright (C) 2001-2017 ZoneMinder LLC + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself, either Perl version 5.8.3 or, +at your option, any later version of Perl 5 you may have available. + + +=cut diff --git a/scripts/zmaudit.pl.in b/scripts/zmaudit.pl.in index eaf724366..6cf2af2e3 100644 --- a/scripts/zmaudit.pl.in +++ b/scripts/zmaudit.pl.in @@ -41,9 +41,9 @@ yet. =head1 OPTIONS - -r, --report - Just report don't actually do anything - -i, --interactive - Ask before applying any changes -c, --continuous - Run continuously + -i, --interactive - Ask before applying any changes + -r, --report - Just report don't actually do anything -v, --version - Print the installed version of ZoneMinder =cut @@ -57,8 +57,8 @@ use bytes; # ========================================================================== use constant MAX_AGED_DIRS => 10; # Number of event dirs to check age on -use constant RECOVER_TAG => "(r)"; # Tag to append to event name when recovered -use constant RECOVER_TEXT => "Recovered."; # Text to append to event notes when recovered +use constant RECOVER_TAG => '(r)'; # Tag to append to event name when recovered +use constant RECOVER_TEXT => 'Recovered.'; # Text to append to event notes when recovered # ========================================================================== # @@ -96,10 +96,10 @@ logInit(); logSetSignal(); GetOptions( - 'report' =>\$report, - 'interactive' =>\$interactive, - 'continuous' =>\$continuous, - 'version' =>\$version + continuous =>\$continuous, + interactive =>\$interactive, + report =>\$report, + version =>\$version ) or pod2usage(-exitstatus => -1); if ( $version ) { @@ -111,6 +111,10 @@ if ( ($report + $interactive + $continuous) > 1 ) { pod2usage(-exitstatus => -1); } +if ( ! exists $Config{ZM_AUDIT_MIN_AGE} ) { + Fatal('ZM_AUDIT_MIN_AGE is not set in config.'); +} + my $dbh = zmDbConnect(); chdir( EVENT_PATH ); @@ -126,13 +130,13 @@ MAIN: while( $loop ) { $dbh = zmDbConnect(); if ( $continuous ) { - Error("Unable to connect to database"); + Error('Unable to connect to database'); # if we are running continuously, then just skip to the next # interval, otherwise we are a one off run, so wait a second and # retry until someone kills us. sleep( $Config{ZM_AUDIT_CHECK_INTERVAL} ); } else { - Fatal("Unable to connect to database"); + Fatal('Unable to connect to database'); } # end if } # end while can't connect to the db @@ -145,16 +149,12 @@ MAIN: while( $loop ) { sleep 1; } # end if - if ( ! exists $Config{ZM_AUDIT_MIN_AGE} ) { - Fatal("ZM_AUDIT_MIN_AGE is not set in config."); - } - my $db_monitors; - my $monitorSelectSql = "select Id from Monitors order by Id"; + my $monitorSelectSql = 'select Id from Monitors order by Id'; my $monitorSelectSth = $dbh->prepare_cached( $monitorSelectSql ) or Fatal( "Can't prepare '$monitorSelectSql': ".$dbh->errstr() ); - my $eventSelectSql = "SELECT Id, (unix_timestamp() - unix_timestamp(StartTime)) as Age - FROM Events WHERE MonitorId = ? ORDER BY Id"; + my $eventSelectSql = 'SELECT Id, (unix_timestamp() - unix_timestamp(StartTime)) as Age + FROM Events WHERE MonitorId = ? ORDER BY Id'; my $eventSelectSth = $dbh->prepare_cached( $eventSelectSql ) or Fatal( "Can't prepare '$eventSelectSql': ".$dbh->errstr() ); @@ -169,11 +169,11 @@ MAIN: while( $loop ) { while ( my $event = $eventSelectSth->fetchrow_hashref() ) { $db_events->{$event->{Id}} = $event->{Age}; } - Debug( "Got ".int(keys(%$db_events))." events\n" ); + Debug( 'Got '.int(keys(%$db_events))." events\n" ); } my $fs_monitors; - foreach my $monitor ( glob("[0-9]*") ) { + foreach my $monitor ( glob('[0-9]*') ) { # Thie glob above gives all files starting with a digit. So a monitor with a name starting with a digit will be in this list. next if $monitor =~ /\D/; Debug( "Found filesystem monitor '$monitor'" ); @@ -182,11 +182,17 @@ MAIN: while( $loop ) { if ( $Config{ZM_USE_DEEP_STORAGE} ) { foreach my $day_dir ( glob("$monitor_dir/*/*/*") ) { - Debug( "Checking $day_dir" ); + Debug( "Checking day dir $day_dir" ); ( $day_dir ) = ( $day_dir =~ /^(.*)$/ ); # De-taint - chdir( $day_dir ); - opendir( DIR, "." ) - or Fatal( "Can't open directory '$day_dir': $!" ); + if ( ! chdir( $day_dir ) ) { + Error( "Can't chdir to '$day_dir': $!" ); + next; + } + if ( ! opendir( DIR, '.' ) ) { + Error( "Can't open directory '$day_dir': $!" ); + next; + } + my @event_links = sort { $b <=> $a } grep { -l $_ } readdir( DIR ); closedir( DIR ); my $count = 0; @@ -226,7 +232,7 @@ MAIN: while( $loop ) { } chdir( EVENT_PATH ); } - Debug( "Got ".int(keys(%$fs_events))." events\n" ); + Debug( 'Got '.int(keys(%$fs_events))." events\n" ); } # end foreach monitor Id redo MAIN if ( $cleaned ); @@ -255,7 +261,7 @@ MAIN: while( $loop ) { } my $monitor_links; - foreach my $link ( glob("*") ) { + foreach my $link ( glob('*') ) { next if ( !-l $link ); next if ( -e $link ); @@ -270,16 +276,16 @@ MAIN: while( $loop ) { redo MAIN if ( $cleaned ); $cleaned = 0; - my $deleteMonitorSql = "delete low_priority from Monitors where Id = ?"; + my $deleteMonitorSql = 'delete low_priority from Monitors where Id = ?'; my $deleteMonitorSth = $dbh->prepare_cached( $deleteMonitorSql ) or Fatal( "Can't prepare '$deleteMonitorSql': ".$dbh->errstr() ); - my $deleteEventSql = "delete low_priority from Events where Id = ?"; + my $deleteEventSql = 'delete low_priority from Events where Id = ?'; my $deleteEventSth = $dbh->prepare_cached( $deleteEventSql ) or Fatal( "Can't prepare '$deleteEventSql': ".$dbh->errstr() ); - my $deleteFramesSql = "delete low_priority from Frames where EventId = ?"; + my $deleteFramesSql = 'delete low_priority from Frames where EventId = ?'; my $deleteFramesSth = $dbh->prepare_cached( $deleteFramesSql ) or Fatal( "Can't prepare '$deleteFramesSql': ".$dbh->errstr() ); - my $deleteStatsSql = "delete low_priority from Stats where EventId = ?"; + my $deleteStatsSql = 'delete low_priority from Stats where EventId = ?'; my $deleteStatsSth = $dbh->prepare_cached( $deleteStatsSql ) or Fatal( "Can't prepare '$deleteStatsSql': ".$dbh->errstr() ); while ( my ( $db_monitor, $db_events ) = each(%$db_monitors) ) { @@ -319,9 +325,9 @@ MAIN: while( $loop ) { # Remove orphaned events (with no monitor) $cleaned = 0; - my $selectOrphanedEventsSql = "SELECT Events.Id, Events.Name + my $selectOrphanedEventsSql = 'SELECT Events.Id, Events.Name FROM Events LEFT JOIN Monitors ON (Events.MonitorId = Monitors.Id) - WHERE isnull(Monitors.Id)"; + WHERE isnull(Monitors.Id)'; my $selectOrphanedEventsSth = $dbh->prepare_cached( $selectOrphanedEventsSql ) or Fatal( "Can't prepare '$selectOrphanedEventsSql': ".$dbh->errstr() ); $res = $selectOrphanedEventsSth->execute() @@ -356,8 +362,8 @@ MAIN: while( $loop ) { # Remove orphaned frame records $cleaned = 0; - my $selectOrphanedFramesSql = "SELECT DISTINCT EventId FROM Frames - WHERE EventId NOT IN (SELECT Id FROM Events)"; + my $selectOrphanedFramesSql = 'SELECT DISTINCT EventId FROM Frames + WHERE EventId NOT IN (SELECT Id FROM Events)'; my $selectOrphanedFramesSth = $dbh->prepare_cached( $selectOrphanedFramesSql ) or Fatal( "Can't prepare '$selectOrphanedFramesSql': ".$dbh->errstr() ); $res = $selectOrphanedFramesSth->execute() @@ -374,8 +380,8 @@ MAIN: while( $loop ) { # Remove orphaned stats records $cleaned = 0; - my $selectOrphanedStatsSql = "SELECT DISTINCT EventId FROM Stats - WHERE EventId NOT IN (SELECT Id FROM Events)"; + my $selectOrphanedStatsSql = 'SELECT DISTINCT EventId FROM Stats + WHERE EventId NOT IN (SELECT Id FROM Events)'; my $selectOrphanedStatsSth = $dbh->prepare_cached( $selectOrphanedStatsSql ) or Fatal( "Can't prepare '$selectOrphanedStatsSql': ".$dbh->errstr() ); $res = $selectOrphanedStatsSth->execute() @@ -429,20 +435,13 @@ MAIN: while( $loop ) { aud_print( "Found open event '$event->{Id}'" ); if ( confirm( 'close', 'closing' ) ) { $res = $updateUnclosedEventsSth->execute( - sprintf("%s%d%s", - $event->{Prefix}, - $event->{Id}, - RECOVER_TAG - ), + sprintf('%s%d%s', $event->{Prefix}, $event->{Id}, RECOVER_TAG), $event->{EndTime}, $event->{Length}, $event->{Frames}, $event->{AlarmFrames}, $event->{TotScore}, - $event->{AlarmFrames} - ? int($event->{TotScore} / $event->{AlarmFrames}) - : 0 - , + $event->{AlarmFrames} ? int($event->{TotScore} / $event->{AlarmFrames}) : 0, $event->{MaxScore}, RECOVER_TEXT, $event->{Id} @@ -453,8 +452,8 @@ MAIN: while( $loop ) { # Now delete any old image files my @old_files = grep { -M > $max_image_age } <$image_path/*.{jpg,gif,wbmp}>; if ( @old_files ) { - aud_print( "Deleting ".int(@old_files)." old images\n" ); - my $untainted_old_files = join( ";", @old_files ); + aud_print( 'Deleting '.( scalar @old_files )." old images\n" ); + my $untainted_old_files = join( ';', @old_files ); ( $untainted_old_files ) = ( $untainted_old_files =~ /^(.*)$/ ); unlink( split( /;/, $untainted_old_files ) ); } @@ -467,7 +466,7 @@ MAIN: while( $loop ) { if ( $Config{ZM_LOG_DATABASE_LIMIT} ) { if ( $Config{ZM_LOG_DATABASE_LIMIT} =~ /^\d+$/ ) { # Number of rows - my $selectLogRowCountSql = "SELECT count(*) as Rows from Logs"; + my $selectLogRowCountSql = 'SELECT count(*) AS Rows FROM Logs'; my $selectLogRowCountSth = $dbh->prepare_cached( $selectLogRowCountSql ) or Fatal( "Can't prepare '$selectLogRowCountSql': ".$dbh->errstr() ); $res = $selectLogRowCountSth->execute() @@ -475,20 +474,20 @@ MAIN: while( $loop ) { my $row = $selectLogRowCountSth->fetchrow_hashref(); my $logRows = $row->{Rows}; if ( $logRows > $Config{ZM_LOG_DATABASE_LIMIT} ) { - my $deleteLogByRowsSql = "DELETE low_priority FROM Logs ORDER BY TimeKey ASC LIMIT ?"; + my $deleteLogByRowsSql = 'DELETE low_priority FROM Logs ORDER BY TimeKey ASC LIMIT ?'; my $deleteLogByRowsSth = $dbh->prepare_cached( $deleteLogByRowsSql ) or Fatal( "Can't prepare '$deleteLogByRowsSql': ".$dbh->errstr() ); $res = $deleteLogByRowsSth->execute( $logRows - $Config{ZM_LOG_DATABASE_LIMIT} ) or Fatal( "Can't execute: ".$deleteLogByRowsSth->errstr() ); if ( $deleteLogByRowsSth->rows() ) { - aud_print( "Deleted ".$deleteLogByRowsSth->rows() ." log table entries by count\n" ); + aud_print( 'Deleted '.$deleteLogByRowsSth->rows() ." log table entries by count\n" ); } } } else { # Time of record my $deleteLogByTimeSql = - "DELETE low_priority FROM Logs - WHERE TimeKey < unix_timestamp(now() - interval ".$Config{ZM_LOG_DATABASE_LIMIT}.")"; + 'DELETE low_priority FROM Logs + WHERE TimeKey < unix_timestamp(now() - interval '.$Config{ZM_LOG_DATABASE_LIMIT}.')'; my $deleteLogByTimeSth = $dbh->prepare_cached( $deleteLogByTimeSql ) or Fatal( "Can't prepare '$deleteLogByTimeSql': ".$dbh->errstr() ); $res = $deleteLogByTimeSth->execute() @@ -515,8 +514,8 @@ sub aud_print { } sub confirm { - my $prompt = shift || "delete"; - my $action = shift || "deleting"; + my $prompt = shift || 'delete'; + my $action = shift || 'deleting'; my $yesno = 0; if ( $report ) { diff --git a/scripts/zmfilter.pl.in b/scripts/zmfilter.pl.in index a60fc497e..aeb11b509 100644 --- a/scripts/zmfilter.pl.in +++ b/scripts/zmfilter.pl.in @@ -27,7 +27,7 @@ zmfilter.pl - ZoneMinder tool to filter events =head1 SYNOPSIS - zmfilter.pl [-f ,--filter=] | -v, --version +zmfilter.pl [-f ,--filter=] | -v, --version =head1 DESCRIPTION @@ -37,8 +37,8 @@ matching events. =head1 OPTIONS - -f{filter name}, --filter={filter name} - The name of a specific filter to run - -v, --version - Print ZoneMinder version +-f{filter name}, --filter={filter name} - The name of a specific filter to run +-v, --version - Print ZoneMinder version =cut use strict; @@ -60,7 +60,6 @@ use constant START_DELAY => 5; # How long to wait before starting @EXTRA_PERL_LIB@ use ZoneMinder; -require ZoneMinder::Filter; use DBI; use POSIX; use Time::HiRes qw/gettimeofday/; @@ -69,61 +68,61 @@ use Getopt::Long; use autouse 'Pod::Usage'=>qw(pod2usage); use autouse 'Data::Dumper'=>qw(Dumper); +my $filter_parm = ''; +my $version = 0; + +GetOptions( + 'filter=s' =>\$filter_parm, + 'version' =>\$version + ) or pod2usage(-exitstatus => -1); + +if ( $version ) { + print ZoneMinder::Base::ZM_VERSION . "\n"; + exit(0); +} + +require ZoneMinder::Filter; + use constant EVENT_PATH => ($Config{ZM_DIR_EVENTS}=~m|/|) - ? $Config{ZM_DIR_EVENTS} - : ($Config{ZM_PATH_WEB}.'/'.$Config{ZM_DIR_EVENTS}) -; + ? $Config{ZM_DIR_EVENTS} + : ($Config{ZM_PATH_WEB}.'/'.$Config{ZM_DIR_EVENTS}) + ; logInit(); logSetSignal(); -if ( $Config{ZM_OPT_UPLOAD} ) -{ - # Comment these out if you don't have them and don't want to upload - # or don't want to use that format - if ( $Config{ZM_UPLOAD_ARCH_FORMAT} eq "zip" ) - { - require Archive::Zip; - import Archive::Zip qw( :ERROR_CODES :CONSTANTS ); - } - else - { - require Archive::Tar; - } - if ( $Config{ZM_UPLOAD_PROTOCOL} eq "ftp" ) - { - require Net::FTP; - } - else - { - require Net::SFTP::Foreign; - } +if ( $Config{ZM_OPT_UPLOAD} ) { +# Comment these out if you don't have them and don't want to upload +# or don't want to use that format + if ( $Config{ZM_UPLOAD_ARCH_FORMAT} eq 'zip' ) { + require Archive::Zip; + import Archive::Zip qw( :ERROR_CODES :CONSTANTS ); + } else { + require Archive::Tar; + } + if ( $Config{ZM_UPLOAD_PROTOCOL} eq 'ftp' ) { + require Net::FTP; + } else { + require Net::SFTP::Foreign; + } } -if ( $Config{ZM_OPT_EMAIL} ) -{ - if ( $Config{ZM_NEW_MAIL_MODULES} ) - { - require MIME::Lite; - require Net::SMTP; - } - else - { - require MIME::Entity; - } +if ( $Config{ZM_OPT_EMAIL} ) { + if ( $Config{ZM_NEW_MAIL_MODULES} ) { + require MIME::Lite; + require Net::SMTP; + } else { + require MIME::Entity; + } } -if ( $Config{ZM_OPT_MESSAGE} ) -{ - if ( $Config{ZM_NEW_MAIL_MODULES} ) - { - require MIME::Lite; - require Net::SMTP; - } - else - { - require MIME::Entity; - } +if ( $Config{ZM_OPT_MESSAGE} ) { + if ( $Config{ZM_NEW_MAIL_MODULES} ) { + require MIME::Lite; + require Net::SMTP; + } else { + require MIME::Entity; + } } $| = 1; @@ -134,933 +133,792 @@ delete @ENV{qw(IFS CDPATH ENV BASH_ENV)}; my $delay = $Config{ZM_FILTER_EXECUTE_INTERVAL}; my $event_id = 0; -my $filter_parm = ""; -my $version = 0; - -# - -GetOptions( - 'filter=s' =>\$filter_parm, - 'version' =>\$version -) or pod2usage(-exitstatus => -1); - -if ( $version ) { - print ZoneMinder::Base::ZM_VERSION . "\n"; - exit(0); -} if ( ! EVENT_PATH ) { - Error( "No event path defined. Config was $Config{ZM_DIR_EVENTS}\n" ); - die; + Error( "No event path defined. Config was $Config{ZM_DIR_EVENTS}\n" ); + die; } chdir( EVENT_PATH ); my $dbh = zmDbConnect(); -if ( $filter_parm ) -{ - Info( "Scanning for events using filter '$filter_parm'\n" ); -} -else -{ - Info( "Scanning for events\n" ); +if ( $filter_parm ) { + Info( "Scanning for events using filter '$filter_parm'\n" ); +} else { + Info( "Scanning for events\n" ); } -if ( !$filter_parm ) -{ - sleep( START_DELAY ); +if ( !$filter_parm ) { + sleep( START_DELAY ); } -my $filters; +my @filters; my $last_action = 0; -while( 1 ) -{ - my $now = time; - if ( ($now - $last_action) > $Config{ZM_FILTER_RELOAD_DELAY} ) - { - Debug( "Reloading filters\n" ); - $last_action = $now; - $filters = getFilters( $filter_parm ); - } +while( 1 ) { + my $now = time; + if ( ($now - $last_action) > $Config{ZM_FILTER_RELOAD_DELAY} ) { + Debug( "Reloading filters\n" ); + $last_action = $now; + @filters = getFilters( $filter_parm ); + } - foreach my $filter ( @$filters ) - { - checkFilter( $filter ); - } + foreach my $filter ( @filters ) { + checkFilter( $filter ); + } - last if ( $filter_parm ); + last if $filter_parm; - Debug( "Sleeping for $delay seconds\n" ); - sleep( $delay ); + Debug( "Sleeping for $delay seconds\n" ); + sleep( $delay ); } -sub getFilters -{ - my $filter_name = shift; +sub getFilters { + my $filter_name = shift; - my @filters; - my $sql = "SELECT * FROM Filters WHERE"; - if ( $filter_name ) - { - $sql .= " Name = ? and"; + my @filters; + my $sql = 'SELECT * FROM Filters WHERE'; + if ( $filter_name ) { + $sql .= ' Name = ? and'; + } else { + $sql .= ' Background = 1 and'; + } + $sql .= '( AutoArchive = 1 + or AutoVideo = 1 + or AutoUpload = 1 + or AutoEmail = 1 + or AutoMessage = 1 + or AutoExecute = 1 + or AutoDelete = 1 + ) ORDER BY Name'; + my $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res; + if ( $filter_name ) { + $res = $sth->execute( $filter_name ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + } else { + $res = $sth->execute() + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + } +FILTER: while( my $db_filter = $sth->fetchrow_hashref() ) { + my $filter = new ZoneMinder::Filter( $$db_filter{Id}, $db_filter ); + Debug( "Found filter '$db_filter->{Name}'\n" ); + my $sql = $filter->Sql(); + + if ( ! $sql ) { + Error( "Error parsing Sql. skipping filter '$db_filter->{Name}'\n" ); + next FILTER; } - else - { - $sql .= " Background = 1 and"; - } - $sql .= "( AutoArchive = 1 - or AutoVideo = 1 - or AutoUpload = 1 - or AutoEmail = 1 - or AutoMessage = 1 - or AutoExecute = 1 - or AutoDelete = 1 - ) ORDER BY Name"; - my $sth = $dbh->prepare_cached( $sql ) + push( @filters, $filter ); + } + $sth->finish(); + Debug( 'Got ' . @filters . ' filters' ); + return( @filters ); +} + +sub checkFilter { + my $filter = shift; + + Debug( "Checking filter '$filter->{Name}'". + ($filter->{AutoDelete}?', delete':''). + ($filter->{AutoArchive}?', archive':''). + ($filter->{AutoVideo}?', video':''). + ($filter->{AutoUpload}?', upload':''). + ($filter->{AutoEmail}?', email':''). + ($filter->{AutoMessage}?', message':''). + ($filter->{AutoExecute}?', execute':''). + '\n' + ); + + foreach my $event ( $filter->Execute() ) { + Debug( "Checking event $event->{Id}\n" ); + my $delete_ok = !undef; + $dbh->ping(); + if ( $filter->{AutoArchive} ) { + Info( "Archiving event $event->{Id}\n" ); +# Do it individually to avoid locking up the table for new events + my $sql = 'update Events set Archived = 1 where Id = ?'; + my $sth = $dbh->prepare_cached( $sql ) or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res; - if ( $filter_name ) - { - $res = $sth->execute( $filter_name ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + my $res = $sth->execute( $event->{Id} ) + or Error( "Can't execute '$sql': ".$sth->errstr() ); } - else - { - $res = $sth->execute() - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + if ( $Config{ZM_OPT_FFMPEG} && $filter->{AutoVideo} ) { + if ( !$event->{Videoed} ) { + $delete_ok = undef if ( !generateVideo( $filter, $event ) ); + } } - FILTER: while( my $db_filter = $sth->fetchrow_hashref() ) - { - my $filter = new ZoneMinder::Filter( $$db_filter{Id}, $db_filter ); - Debug( "Found filter '$db_filter->{Name}'\n" ); - my $sql = $filter->Sql(); - - if ( ! $sql ) { - Error( "Error parsing Sql. skipping filter '$db_filter->{Name}'\n" ); - next FILTER; - } - push( @filters, $filter ); + if ( $Config{ZM_OPT_EMAIL} && $filter->{AutoEmail} ) { + if ( !$event->{Emailed} ) { + $delete_ok = undef if ( !sendEmail( $filter, $event ) ); + } } - $sth->finish(); - Debug( "Got " . @filters . " filters" ); - return( \@filters ); -} - -sub checkFilter -{ - my $filter = shift; - - Debug( "Checking filter '$filter->{Name}'". - ($filter->{AutoDelete}?", delete":""). - ($filter->{AutoArchive}?", archive":""). - ($filter->{AutoVideo}?", video":""). - ($filter->{AutoUpload}?", upload":""). - ($filter->{AutoEmail}?", email":""). - ($filter->{AutoMessage}?", message":""). - ($filter->{AutoExecute}?", execute":""). - "\n" - ); - - foreach my $event ( $filter->Execute() ) { - Debug( "Checking event $event->{Id}\n" ); - my $delete_ok = !undef; -$dbh->ping(); - if ( $filter->{AutoArchive} ) - { - Info( "Archiving event $event->{Id}\n" ); - # Do it individually to avoid locking up the table for new events - my $sql = "update Events set Archived = 1 where Id = ?"; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $event->{Id} ) - or Error( "Can't execute '$sql': ".$sth->errstr() ); - } - if ( $Config{ZM_OPT_FFMPEG} && $filter->{AutoVideo} ) - { - if ( !$event->{Videoed} ) - { - $delete_ok = undef if ( !generateVideo( $filter, $event ) ); - } - } - if ( $Config{ZM_OPT_EMAIL} && $filter->{AutoEmail} ) - { - if ( !$event->{Emailed} ) - { - $delete_ok = undef if ( !sendEmail( $filter, $event ) ); - } - } - if ( $Config{ZM_OPT_MESSAGE} && $filter->{AutoMessage} ) - { - if ( !$event->{Messaged} ) - { - $delete_ok = undef if ( !sendMessage( $filter, $event ) ); - } - } - if ( $Config{ZM_OPT_UPLOAD} && $filter->{AutoUpload} ) - { - if ( !$event->{Uploaded} ) - { - $delete_ok = undef if ( !uploadArchFile( $filter, $event ) ); - } - } - if ( $filter->{AutoExecute} ) - { - if ( !$event->{Executed} ) - { - $delete_ok = undef if ( !executeCommand( $filter, $event ) ); - } - } - if ( $filter->{AutoDelete} ) - { - if ( $delete_ok ) - { - Info( "Deleting event $event->{Id} from Monitor $event->{MonitorId}\n" ); - # Do it individually to avoid locking up the table for new events - my $sql = "delete from Events where Id = ?"; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $event->{Id} ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); - - if ( ! $Config{ZM_OPT_FAST_DELETE} ) - { - my $sql = "delete from Frames where EventId = ?"; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $event->{Id} ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); - - $sql = "delete from Stats where EventId = ?"; - $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - $res = $sth->execute( $event->{Id} ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); - - deleteEventFiles( $event->{Id}, $event->{MonitorId} ); - } - } - else - { - Error( "Unable to delete event $event->{Id} as previous operations failed\n" ); - } - } + if ( $Config{ZM_OPT_MESSAGE} && $filter->{AutoMessage} ) { + if ( !$event->{Messaged} ) { + $delete_ok = undef if ( !sendMessage( $filter, $event ) ); + } } -} - -sub generateVideo -{ - my $filter = shift; - my $event = shift; - my $phone = shift; - - my $rate = $event->{DefaultRate}/100; - my $scale = $event->{DefaultScale}/100; - my $format; - - my @ffmpeg_formats = split( /\s+/, $Config{ZM_FFMPEG_FORMATS} ); - my $default_video_format; - my $default_phone_format; - foreach my $ffmpeg_format( @ffmpeg_formats ) - { - if ( $ffmpeg_format =~ /^(.+)\*\*$/ ) - { - $default_phone_format = $1; - } - elsif ( $ffmpeg_format =~ /^(.+)\*$/ ) - { - $default_video_format = $1; - } + if ( $Config{ZM_OPT_UPLOAD} && $filter->{AutoUpload} ) { + if ( !$event->{Uploaded} ) { + $delete_ok = undef if ( !uploadArchFile( $filter, $event ) ); + } } - - if ( $phone && $default_phone_format ) - { - $format = $default_phone_format; + if ( $filter->{AutoExecute} ) { + if ( !$event->{Executed} ) { + $delete_ok = undef if ( !executeCommand( $filter, $event ) ); + } } - elsif ( $default_video_format ) - { - $format = $default_video_format; - } - else - { - $format = $ffmpeg_formats[0]; - } - - my $command = $Config{ZM_PATH_BIN}."/zmvideo.pl -e " - .$event->{Id}." -r ".$rate." -s ".$scale." -f ".$format; - my $output = qx($command); - chomp( $output ); - my $status = $? >> 8; - if ( $status || logDebugging() ) - { - Debug( "Output: $output\n" ); - } - if ( $status ) - { - Error( "Video generation '$command' failed with status: $status\n" ); - if ( wantarray() ) - { - return( undef, undef ); - } - return( 0 ); - } - else - { - my $sql = "update Events set Videoed = 1 where Id = ?"; + if ( $filter->{AutoDelete} ) { + if ( $delete_ok ) { + Info( "Deleting event $event->{Id} from Monitor $event->{MonitorId}\n" ); +# Do it individually to avoid locking up the table for new events + my $sql = 'delete from Events where Id = ?'; my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute( $event->{Id} ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + + if ( ! $Config{ZM_OPT_FAST_DELETE} ) { + my $sql = 'delete from Frames where EventId = ?'; + my $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $event->{Id} ) or Fatal( "Can't execute '$sql': ".$sth->errstr() ); - if ( wantarray() ) - { - return( $format, $output ); + + $sql = 'delete from Stats where EventId = ?'; + $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + $res = $sth->execute( $event->{Id} ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + + deleteEventFiles( $event->{Id}, $event->{MonitorId} ); } + } else { + Error( "Unable to delete event $event->{Id} as previous operations failed\n" ); + } } - return( 1 ); + } +} + +sub generateVideo { + my $filter = shift; + my $event = shift; + my $phone = shift; + + my $rate = $event->{DefaultRate}/100; + my $scale = $event->{DefaultScale}/100; + my $format; + + my @ffmpeg_formats = split( /\s+/, $Config{ZM_FFMPEG_FORMATS} ); + my $default_video_format; + my $default_phone_format; + foreach my $ffmpeg_format( @ffmpeg_formats ) { + if ( $ffmpeg_format =~ /^(.+)\*\*$/ ) { + $default_phone_format = $1; + } elsif ( $ffmpeg_format =~ /^(.+)\*$/ ) { + $default_video_format = $1; + } + } + + if ( $phone && $default_phone_format ) { + $format = $default_phone_format; + } elsif ( $default_video_format ) { + $format = $default_video_format; + } else { + $format = $ffmpeg_formats[0]; + } + + my $command = join( '', + $Config{ZM_PATH_BIN}, + '/zmvideo.pl -e ', + $event->{Id}, + ' -r ', + $rate, + ' -s ', + $scale, + ' -f ', + $format, + ); + my $output = qx($command); + chomp( $output ); + my $status = $? >> 8; + if ( $status || logDebugging() ) { + Debug( "Output: $output\n" ); + } + if ( $status ) { + Error( "Video generation '$command' failed with status: $status\n" ); + if ( wantarray() ) { + return( undef, undef ); + } + return( 0 ); + } else { + my $sql = 'update Events set Videoed = 1 where Id = ?'; + my $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $event->{Id} ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + if ( wantarray() ) { + return( $format, $output ); + } + } + return( 1 ); } # Returns an image absolute path for given event and frame # Optionally an analyse image path may be returned if an analyse image exists # If neither capture nor analyse image exists it will try to extract a frame from .mp4 file if exists # An empty string is returned if no one from methods above works -sub generateImage -{ - my $event = shift; - my $frame = shift; - my $analyse = do { @_ ? shift : 0 }; # don't return analyse image by default +sub generateImage { + my $event = shift; + my $frame = shift; + my $analyse = do { @_ ? shift : 0 }; # don't return analyse image by default - my $capture_image_path = sprintf("%s/%0".$Config{ZM_EVENT_IMAGE_DIGITS}."d-capture.jpg", getEventPath($event), $frame->{FrameId}); - my $analyse_image_path = sprintf("%s/%0".$Config{ZM_EVENT_IMAGE_DIGITS}."d-analyse.jpg", getEventPath($event), $frame->{FrameId}) if $analyse; - my $video_path = sprintf("%s/%d-video.mp4", getEventPath($event), $event->{Id}); - my $image_path = ""; + my $capture_image_path = sprintf('%s/%0'.$Config{ZM_EVENT_IMAGE_DIGITS}.'d-capture.jpg', getEventPath($event), $frame->{FrameId}); + my $analyse_image_path = sprintf('%s/%0'.$Config{ZM_EVENT_IMAGE_DIGITS}.'d-analyse.jpg', getEventPath($event), $frame->{FrameId}) if $analyse; + my $video_path = sprintf('%s/%d-video.mp4', getEventPath($event), $event->{Id}); + my $image_path = ''; - # check if the image file exists. If the file doesn't exist and we use H264 try to extract it from .mp4 video - if ( $analyse && -r $analyse_image_path ) { - $image_path = $analyse_image_path; - } elsif ( -r $capture_image_path ) { - $image_path = $capture_image_path; - } elsif ( -e $video_path ) { - if ( !system("ffmpeg -y -v 0 -i ".$video_path." -vf 'select=gte(n\\,".$frame->{FrameId}."),setpts=PTS-STARTPTS' -vframes 1 -f image2 ".$capture_image_path) ) { - $image_path = $capture_image_path; - } +# check if the image file exists. If the file doesn't exist and we use H264 try to extract it from .mp4 video + if ( $analyse && -r $analyse_image_path ) { + $image_path = $analyse_image_path; + } elsif ( -r $capture_image_path ) { + $image_path = $capture_image_path; + } elsif ( -e $video_path ) { + if ( !system('ffmpeg -y -v 0 -i '.$video_path." -vf 'select=gte(n\\,".$frame->{FrameId}."),setpts=PTS-STARTPTS' -vframes 1 -f image2 ".$capture_image_path) ) { + $image_path = $capture_image_path; } - return $image_path; + } + return $image_path; } -sub uploadArchFile -{ - my $filter = shift; - my $event = shift; +sub uploadArchFile { + my $filter = shift; + my $event = shift; - if ( ! $Config{ZM_UPLOAD_HOST} ) - { - Error( "Cannot upload archive as no upload host defined" ); - return( 0 ); - } + if ( ! $Config{ZM_UPLOAD_HOST} ) { + Error( 'Cannot upload archive as no upload host defined' ); + return( 0 ); + } - my $archFile = $event->{MonitorName}.'-'.$event->{Id}; - my $archImagePath = getEventPath( $event ) - ."/" - .( - ( $Config{ZM_UPLOAD_ARCH_ANALYSE} ) - ? '{*analyse,*capture}' - : '*capture' - ) - .".jpg" + my $archFile = $event->{MonitorName}.'-'.$event->{Id}; + my $archImagePath = getEventPath( $event ) + .'/' + .( + ( $Config{ZM_UPLOAD_ARCH_ANALYSE} ) + ? '{*analyse,*capture}' + : '*capture' + ) + .'.jpg' ; - my @archImageFiles = glob($archImagePath); - my $archLocPath; + my @archImageFiles = glob($archImagePath); + my $archLocPath; - my $archError = 0; - if ( $Config{ZM_UPLOAD_ARCH_FORMAT} eq "zip" ) - { - $archFile .= '.zip'; - $archLocPath = $Config{ZM_UPLOAD_LOC_DIR}.'/'.$archFile; - my $zip = Archive::Zip->new(); - Info( "Creating upload file '$archLocPath', ".int(@archImageFiles)." files\n" ); + my $archError = 0; + if ( $Config{ZM_UPLOAD_ARCH_FORMAT} eq 'zip' ) { + $archFile .= '.zip'; + $archLocPath = $Config{ZM_UPLOAD_LOC_DIR}.'/'.$archFile; + my $zip = Archive::Zip->new(); + Info( "Creating upload file '$archLocPath', ".int(@archImageFiles)." files\n" ); - my $status = &AZ_OK; - foreach my $imageFile ( @archImageFiles ) - { - Debug( "Adding $imageFile\n" ); - my $member = $zip->addFile( $imageFile ); - if ( !$member ) - { - Error( "Unable to add image file $imageFile to zip archive $archLocPath" ); - $archError = 1; - last; - } - $member->desiredCompressionMethod( $Config{ZM_UPLOAD_ARCH_COMPRESS} - ? &COMPRESSION_DEFLATED - : &COMPRESSION_STORED - ); - } - if ( !$archError ) - { - $status = $zip->writeToFileNamed( $archLocPath ); - - if ( $archError = ($status != &AZ_OK) ) - { - Error( "Zip error: $status\n " ); - } - } - else - { - Error( "Error adding images to zip archive $archLocPath, not writing" ); - } + my $status = &AZ_OK; + foreach my $imageFile ( @archImageFiles ) { + Debug( "Adding $imageFile\n" ); + my $member = $zip->addFile( $imageFile ); + if ( !$member ) { + Error( "Unable to add image file $imageFile to zip archive $archLocPath" ); + $archError = 1; + last; + } + $member->desiredCompressionMethod( $Config{ZM_UPLOAD_ARCH_COMPRESS} + ? &COMPRESSION_DEFLATED + : &COMPRESSION_STORED + ); } - elsif ( $Config{ZM_UPLOAD_ARCH_FORMAT} eq "tar" ) - { - if ( $Config{ZM_UPLOAD_ARCH_COMPRESS} ) - { - $archFile .= '.tar.gz'; - } - else - { - $archFile .= '.tar'; - } - $archLocPath = $Config{ZM_UPLOAD_LOC_DIR}.'/'.$archFile; - Info( "Creating upload file '$archLocPath', ".int(@archImageFiles)." files\n" ); + if ( !$archError ) { + $status = $zip->writeToFileNamed( $archLocPath ); - if ( $archError = !Archive::Tar->create_archive( - $archLocPath, - $Config{ZM_UPLOAD_ARCH_COMPRESS}, - @archImageFiles - ) - ) - { - Error( "Tar error: ".Archive::Tar->error()."\n " ); - } + if ( $archError = ($status != &AZ_OK) ) { + Error( "Zip error: $status\n " ); + } + } else { + Error( "Error adding images to zip archive $archLocPath, not writing" ); } + } elsif ( $Config{ZM_UPLOAD_ARCH_FORMAT} eq 'tar' ) { + if ( $Config{ZM_UPLOAD_ARCH_COMPRESS} ) { + $archFile .= '.tar.gz'; + } else { + $archFile .= '.tar'; + } + $archLocPath = $Config{ZM_UPLOAD_LOC_DIR}.'/'.$archFile; + Info( "Creating upload file '$archLocPath', ".int(@archImageFiles)." files\n" ); - if ( $archError ) - { + if ( $archError = !Archive::Tar->create_archive( + $archLocPath, + $Config{ZM_UPLOAD_ARCH_COMPRESS}, + @archImageFiles + ) + ) { + Error( 'Tar error: '.Archive::Tar->error()."\n " ); + } + } + + if ( $archError ) { + return( 0 ); + } else { + if ( $Config{ZM_UPLOAD_PROTOCOL} eq 'ftp' ) { + Info( 'Uploading to '.$Config{ZM_UPLOAD_HOST}." using FTP\n" ); + my $ftp = Net::FTP->new( + $Config{ZM_UPLOAD_HOST}, + Timeout=>$Config{ZM_UPLOAD_TIMEOUT}, + Passive=>$Config{ZM_UPLOAD_FTP_PASSIVE}, + Debug=>$Config{ZM_UPLOAD_DEBUG} + ); + if ( !$ftp ) { + Error( "Can't create FTP connection: $@" ); return( 0 ); - } - else - { - if ( $Config{ZM_UPLOAD_PROTOCOL} eq "ftp" ) - { - Info( "Uploading to ".$Config{ZM_UPLOAD_HOST}." using FTP\n" ); - my $ftp = Net::FTP->new( - $Config{ZM_UPLOAD_HOST}, - Timeout=>$Config{ZM_UPLOAD_TIMEOUT}, - Passive=>$Config{ZM_UPLOAD_FTP_PASSIVE}, - Debug=>$Config{ZM_UPLOAD_DEBUG} - ); - if ( !$ftp ) - { - Error( "Can't create FTP connection: $@" ); - return( 0 ); - } - $ftp->login( $Config{ZM_UPLOAD_USER}, $Config{ZM_UPLOAD_PASS} ) - or Error( "FTP - Can't login" ); - $ftp->binary() - or Error( "FTP - Can't go binary" ); - $ftp->cwd( $Config{ZM_UPLOAD_REM_DIR} ) - or Error( "FTP - Can't cwd" ) - if ( $Config{ZM_UPLOAD_REM_DIR} ); - $ftp->put( $archLocPath ) - or Error( "FTP - Can't upload '$archLocPath'" ); - $ftp->quit() - or Error( "FTP - Can't quit" ); - } - else - { - my $host = $Config{ZM_UPLOAD_HOST}; - $host .= ":".$Config{ZM_UPLOAD_PORT} - if $Config{ZM_UPLOAD_PORT}; - Info( "Uploading to ".$host." using SFTP\n" ); - my %sftpOptions = ( host=>$Config{ZM_UPLOAD_HOST}, user=>$Config{ZM_UPLOAD_USER} ); - $sftpOptions{password} = $Config{ZM_UPLOAD_PASS} - if $Config{ZM_UPLOAD_PASS}; - $sftpOptions{port} = $Config{ZM_UPLOAD_PORT} - if $Config{ZM_UPLOAD_PORT}; - $sftpOptions{timeout} = $Config{ZM_UPLOAD_TIMEOUT} - if $Config{ZM_UPLOAD_TIMEOUT}; - my @more_ssh_args; - push @more_ssh_args, '-o'=>'StrictHostKeyChecking=no' - if ! $Config{ZM_UPLOAD_STRICT}; - push @more_ssh_args, '-v' - if $Config{ZM_UPLOAD_DEBUG}; - $sftpOptions{more} = [@more_ssh_args]; - my $sftp = Net::SFTP::Foreign->new( $Config{ZM_UPLOAD_HOST}, %sftpOptions ); - if ( $sftp->error ) - { - Error( "Can't create SFTP connection: ".$sftp->error ); - return( 0 ); - } - $sftp->setcwd( $Config{ZM_UPLOAD_REM_DIR} ) - or Error( "SFTP - Can't setcwd: ".$sftp->error ) - if $Config{ZM_UPLOAD_REM_DIR}; - $sftp->put( $archLocPath, $archFile ) - or Error( "SFTP - Can't upload '$archLocPath': ".$sftp->error ); - } - unlink( $archLocPath ); - my $sql = "update Events set Uploaded = 1 where Id = ?"; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $event->{Id} ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); - } - return( 1 ); -} - -sub substituteTags -{ - my $text = shift; - my $filter = shift; - my $event = shift; - my $attachments_ref = shift; - - # First we'd better check what we need to get - # We have a filter and an event, do we need any more - # monitor information? - my $need_monitor = $text =~ /%(?:MET|MEH|MED|MEW|MEN|MEA)%/; - - my $monitor = {}; - if ( $need_monitor ) - { - my $db_now = strftime( "%Y-%m-%d %H:%M:%S", localtime() ); - my $sql = "SELECT - M.Id, - count(E.Id) as EventCount, - count(if(E.Archived,1,NULL)) - as ArchEventCount, - count(if(E.StartTime>'$db_now' - INTERVAL 1 HOUR && E.Archived = 0,1,NULL)) - as HourEventCount, - count(if(E.StartTime>'$db_now' - INTERVAL 1 DAY && E.Archived = 0,1,NULL)) - as DayEventCount, - count(if(E.StartTime>'$db_now' - INTERVAL 7 DAY && E.Archived = 0,1,NULL)) - as WeekEventCount, - count(if(E.StartTime>'$db_now' - INTERVAL 1 MONTH && E.Archived = 0,1,NULL)) - as MonthEventCount - FROM Monitors as M LEFT JOIN Events as E on E.MonitorId = M.Id - WHERE MonitorId = ? - GROUP BY E.MonitorId - ORDER BY Id" - ; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $event->{MonitorId} ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); - $monitor = $sth->fetchrow_hashref(); - $sth->finish(); - return() if ( !$monitor ); - } - - # Do we need the image information too? - my $need_images = $text =~ /%(?:EPI1|EPIM|EI1|EIM|EI1A|EIMA)%/; - my $first_alarm_frame; - my $max_alarm_frame; - my $max_alarm_score = 0; - if ( $need_images ) - { - my $sql = "SELECT * FROM Frames - WHERE EventId = ? AND Type = 'Alarm' - ORDER BY FrameId" - ; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $event->{Id} ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); - while( my $frame = $sth->fetchrow_hashref() ) - { - if ( !$first_alarm_frame ) - { - $first_alarm_frame = $frame; - } - if ( $frame->{Score} > $max_alarm_score ) - { - $max_alarm_frame = $frame; - $max_alarm_score = $frame->{Score}; - } - } - $sth->finish(); - } - - my $url = $Config{ZM_URL}; - $text =~ s/%ZP%/$url/g; - $text =~ s/%MN%/$event->{MonitorName}/g; - $text =~ s/%MET%/$monitor->{EventCount}/g; - $text =~ s/%MEH%/$monitor->{HourEventCount}/g; - $text =~ s/%MED%/$monitor->{DayEventCount}/g; - $text =~ s/%MEW%/$monitor->{WeekEventCount}/g; - $text =~ s/%MEM%/$monitor->{MonthEventCount}/g; - $text =~ s/%MEA%/$monitor->{ArchEventCount}/g; - $text =~ s/%MP%/$url?view=watch&mid=$event->{MonitorId}/g; - $text =~ s/%MPS%/$url?view=watchfeed&mid=$event->{MonitorId}&mode=stream/g; - $text =~ s/%MPI%/$url?view=watchfeed&mid=$event->{MonitorId}&mode=still/g; - $text =~ s/%EP%/$url?view=event&mid=$event->{MonitorId}&eid=$event->{Id}/g; - $text =~ s/%EPS%/$url?view=event&mode=stream&mid=$event->{MonitorId}&eid=$event->{Id}/g; - $text =~ s/%EPI%/$url?view=event&mode=still&mid=$event->{MonitorId}&eid=$event->{Id}/g; - $text =~ s/%EI%/$event->{Id}/g; - $text =~ s/%EN%/$event->{Name}/g; - $text =~ s/%EC%/$event->{Cause}/g; - $text =~ s/%ED%/$event->{Notes}/g; - $text =~ s/%ET%/$event->{StartTime}/g; - $text =~ s/%EL%/$event->{Length}/g; - $text =~ s/%EF%/$event->{Frames}/g; - $text =~ s/%EFA%/$event->{AlarmFrames}/g; - $text =~ s/%EST%/$event->{TotScore}/g; - $text =~ s/%ESA%/$event->{AvgScore}/g; - $text =~ s/%ESM%/$event->{MaxScore}/g; - if ( $first_alarm_frame ) - { - $text =~ s/%EPI1%/$url?view=frame&mid=$event->{MonitorId}&eid=$event->{Id}&fid=$first_alarm_frame->{FrameId}/g; - $text =~ s/%EPIM%/$url?view=frame&mid=$event->{MonitorId}&eid=$event->{Id}&fid=$max_alarm_frame->{FrameId}/g; - if ( $attachments_ref && $text =~ s/%EI1%//g ) - { - my $path = generateImage( $event, $first_alarm_frame); - if ( -e $path ) { - push( @$attachments_ref, - { - type=>"image/jpeg", - path=>$path - } - ); - } - } - if ( $attachments_ref && $text =~ s/%EIM%//g ) - { - # Don't attach the same image twice - if ( !@$attachments_ref - || ($first_alarm_frame->{FrameId} != $max_alarm_frame->{FrameId} ) - ) - { - my $path = generateImage( $event, $max_alarm_frame); - if ( -e $path ) { - push( @$attachments_ref, - { - type=>"image/jpeg", - path=>$path - } - ); - } - } - } - if ( $attachments_ref && $text =~ s/%EI1A%//g ) - { - my $path = generateImage( $event, $first_alarm_frame, "analyse" ); - if ( -e $path ) { - push( @$attachments_ref, - { - type=>"image/jpeg", - path=>$path - } - ); - } - } - if ( $attachments_ref && $text =~ s/%EIMA%//g ) - { - # Don't attach the same image twice - if ( !@$attachments_ref - || ($first_alarm_frame->{FrameId} != $max_alarm_frame->{FrameId} ) - ) - { - my $path = generateImage( $event, $max_alarm_frame, "analyse"); - if ( -e $path ) { - push( @$attachments_ref, - { - type=>"image/jpeg", - path=>$path - } - ); - } - } - } - } - if ( $attachments_ref && $Config{ZM_OPT_FFMPEG} ) - { - if ( $text =~ s/%EV%//g ) - { - my ( $format, $path ) = generateVideo( $filter, $event ); - if ( !$format ) - { - return( undef ); - } - push( @$attachments_ref, { type=>"video/$format", path=>$path } ); - } - if ( $text =~ s/%EVM%//g ) - { - my ( $format, $path ) = generateVideo( $filter, $event, 1 ); - if ( !$format ) - { - return( undef ); - } - push( @$attachments_ref, { type=>"video/$format", path=>$path } ); - } - } - $text =~ s/%FN%/$filter->{Name}/g; - ( my $filter_name = $filter->{Name} ) =~ s/ /+/g; - $text =~ s/%FP%/$url?view=filter&mid=$event->{MonitorId}&filter_name=$filter_name/g; - - return( $text ); -} - -sub sendEmail -{ - my $filter = shift; - my $event = shift; - - if ( ! $Config{ZM_FROM_EMAIL} ) - { - Error( "No 'from' email address defined, not sending email" ); + } + $ftp->login( $Config{ZM_UPLOAD_USER}, $Config{ZM_UPLOAD_PASS} ) + or Error( "FTP - Can't login" ); + $ftp->binary() + or Error( "FTP - Can't go binary" ); + $ftp->cwd( $Config{ZM_UPLOAD_REM_DIR} ) + or Error( "FTP - Can't cwd" ) + if ( $Config{ZM_UPLOAD_REM_DIR} ); + $ftp->put( $archLocPath ) + or Error( "FTP - Can't upload '$archLocPath'" ); + $ftp->quit() + or Error( "FTP - Can't quit" ); + } else { + my $host = $Config{ZM_UPLOAD_HOST}; + $host .= ':'.$Config{ZM_UPLOAD_PORT} + if $Config{ZM_UPLOAD_PORT}; + Info( 'Uploading to '.$host." using SFTP\n" ); + my %sftpOptions = ( host=>$Config{ZM_UPLOAD_HOST}, user=>$Config{ZM_UPLOAD_USER} ); + $sftpOptions{password} = $Config{ZM_UPLOAD_PASS} + if $Config{ZM_UPLOAD_PASS}; + $sftpOptions{port} = $Config{ZM_UPLOAD_PORT} + if $Config{ZM_UPLOAD_PORT}; + $sftpOptions{timeout} = $Config{ZM_UPLOAD_TIMEOUT} + if $Config{ZM_UPLOAD_TIMEOUT}; + my @more_ssh_args; + push @more_ssh_args, '-o'=>'StrictHostKeyChecking=no' + if ! $Config{ZM_UPLOAD_STRICT}; + push @more_ssh_args, '-v' + if $Config{ZM_UPLOAD_DEBUG}; + $sftpOptions{more} = [@more_ssh_args]; + my $sftp = Net::SFTP::Foreign->new( $Config{ZM_UPLOAD_HOST}, %sftpOptions ); + if ( $sftp->error ) { + Error( "Can't create SFTP connection: ".$sftp->error ); return( 0 ); + } + $sftp->setcwd( $Config{ZM_UPLOAD_REM_DIR} ) + or Error( "SFTP - Can't setcwd: ".$sftp->error ) + if $Config{ZM_UPLOAD_REM_DIR}; + $sftp->put( $archLocPath, $archFile ) + or Error( "SFTP - Can't upload '$archLocPath': ".$sftp->error ); } - if ( ! $Config{ZM_EMAIL_ADDRESS} ) - { - Error( "No email address defined, not sending email" ); - return( 0 ); - } - - Info( "Creating notification email\n" ); - - my $subject = substituteTags( $Config{ZM_EMAIL_SUBJECT}, $filter, $event ); - return( 0 ) if ( !$subject ); - my @attachments; - my $body = substituteTags( $Config{ZM_EMAIL_BODY}, $filter, $event, \@attachments ); - return( 0 ) if ( !$body ); - - Info( "Sending notification email '$subject'\n" ); - - eval - { - if ( $Config{ZM_NEW_MAIL_MODULES} ) - { - ### Create the multipart container - my $mail = MIME::Lite->new ( - From => $Config{ZM_FROM_EMAIL}, - To => $Config{ZM_EMAIL_ADDRESS}, - Subject => $subject, - Type => "multipart/mixed" - ); - ### Add the text message part - $mail->attach ( - Type => "TEXT", - Data => $body - ); - ### Add the attachments - foreach my $attachment ( @attachments ) - { - Info( "Attaching '$attachment->{path}\n" ); - $mail->attach( - Path => $attachment->{path}, - Type => $attachment->{type}, - Disposition => "attachment" - ); - } - ### Send the Message - if ( $Config{ZM_SSMTP_MAIL} ) { - my $ssmtp_location = $Config{ZM_SSMTP_PATH}; - if ( !$ssmtp_location ) { - if ( logDebugging() ) { - Debug( "which ssmtp: $ssmtp_location - set ssmtp path in options to suppress this message\n" ); - } - $ssmtp_location = qx('which ssmtp'); - } - if ( !$ssmtp_location ) { - Debug( "Can't find ssmtp, trying MIME::Lite->send" ); - MIME::Lite->send( "smtp", $Config{ZM_EMAIL_HOST}, Timeout=>60 ); - $mail->send(); - } else { - ### Send using SSMTP - $mail->send( 'sendmail', $ssmtp_location, $Config{ZM_EMAIL_ADDRESS} ); - } - } else { - MIME::Lite->send( "smtp", $Config{ZM_EMAIL_HOST}, Timeout=>60 ); - $mail->send(); - } - } - else - { - my $mail = MIME::Entity->build( - From => $Config{ZM_FROM_EMAIL}, - To => $Config{ZM_EMAIL_ADDRESS}, - Subject => $subject, - Type => (($body=~//)?'text/html':'text/plain'), - Data => $body - ); - - foreach my $attachment ( @attachments ) - { - Info( "Attaching '$attachment->{path}\n" ); - $mail->attach( - Path => $attachment->{path}, - Type => $attachment->{type}, - Encoding => "base64" - ); - } - $mail->smtpsend( Host => $Config{ZM_EMAIL_HOST}, MailFrom => $Config{ZM_FROM_EMAIL} ); - } - }; - if ( $@ ) - { - Error( "Can't send email: $@" ); - return( 0 ); - } - else - { - Info( "Notification email sent\n" ); - } - my $sql = "update Events set Emailed = 1 where Id = ?"; + unlink( $archLocPath ); + my $sql = 'update Events set Uploaded = 1 where Id = ?'; my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute( $event->{Id} ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); - - return( 1 ); + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + } + return( 1 ); } -sub sendMessage -{ - my $filter = shift; - my $event = shift; +sub substituteTags { + my $text = shift; + my $filter = shift; + my $event = shift; + my $attachments_ref = shift; - if ( ! $Config{ZM_FROM_EMAIL} ) - { - Error( "No 'from' email address defined, not sending message" ); - return( 0 ); - } - if ( ! $Config{ZM_MESSAGE_ADDRESS} ) - { - Error( "No message address defined, not sending message" ); - return( 0 ); - } +# First we'd better check what we need to get +# We have a filter and an event, do we need any more +# monitor information? + my $need_monitor = $text =~ /%(?:MET|MEH|MED|MEW|MEN|MEA)%/; - Info( "Creating notification message\n" ); - - my $subject = substituteTags( $Config{ZM_MESSAGE_SUBJECT}, $filter, $event ); - return( 0 ) if ( !$subject ); - my @attachments; - my $body = substituteTags( $Config{ZM_MESSAGE_BODY}, $filter, $event, \@attachments ); - return( 0 ) if ( !$body ); - - Info( "Sending notification message '$subject'\n" ); - - eval - { - if ( $Config{ZM_NEW_MAIL_MODULES} ) - { - ### Create the multipart container - my $mail = MIME::Lite->new ( - From => $Config{ZM_FROM_EMAIL}, - To => $Config{ZM_MESSAGE_ADDRESS}, - Subject => $subject, - Type => "multipart/mixed" - ); - ### Add the text message part - $mail->attach ( - Type => "TEXT", - Data => $body - ); - ### Add the attachments - foreach my $attachment ( @attachments ) - { - Info( "Attaching '$attachment->{path}\n" ); - $mail->attach( - Path => $attachment->{path}, - Type => $attachment->{type}, - Disposition => "attachment" - ); - } - ### Send the Message - if ( $Config{ZM_SSMTP_MAIL} ) { - my $ssmtp_location = $Config{ZM_SSMTP_PATH}; - if ( !$ssmtp_location ) { - if ( logDebugging() ) { - Debug( "which ssmtp: $ssmtp_location - set ssmtp path in options to suppress this message\n" ); - } - $ssmtp_location = qx('which ssmtp'); - } - if ( !$ssmtp_location ) { - Debug( "Can't find ssmtp, trying MIME::Lite->send" ); - MIME::Lite->send( "smtp", $Config{ZM_EMAIL_HOST}, Timeout=>60 ); - $mail->send(); - } else { - ### Send using SSMTP - $mail->send( 'sendmail', $ssmtp_location, $Config{ZM_MESSAGE_ADDRESS} ); - } - } else { - MIME::Lite->send( "smtp", $Config{ZM_EMAIL_HOST}, Timeout=>60 ); - $mail->send(); - } - } - else - { - my $mail = MIME::Entity->build( - From => $Config{ZM_FROM_EMAIL}, - To => $Config{ZM_MESSAGE_ADDRESS}, - Subject => $subject, - Type => (($body=~//)?'text/html':'text/plain'), - Data => $body - ); - - foreach my $attachment ( @attachments ) - { - Info( "Attaching '$attachment->{path}\n" ); - $mail->attach( - Path => $attachment->{path}, - Type => $attachment->{type}, - Encoding => "base64" - ); - } - $mail->smtpsend( Host => $Config{ZM_EMAIL_HOST}, - MailFrom => $Config{ZM_FROM_EMAIL} - ); - } - }; - if ( $@ ) - { - Error( "Can't send email: $@" ); - return( 0 ); - } - else - { - Info( "Notification message sent\n" ); - } - my $sql = "update Events set Messaged = 1 where Id = ?"; + my $monitor = {}; + if ( $need_monitor ) { + my $db_now = strftime( "%Y-%m-%d %H:%M:%S", localtime() ); + my $sql = "SELECT + M.Id, + count(E.Id) as EventCount, + count(if(E.Archived,1,NULL)) + as ArchEventCount, + count(if(E.StartTime>'$db_now' - INTERVAL 1 HOUR && E.Archived = 0,1,NULL)) + as HourEventCount, + count(if(E.StartTime>'$db_now' - INTERVAL 1 DAY && E.Archived = 0,1,NULL)) + as DayEventCount, + count(if(E.StartTime>'$db_now' - INTERVAL 7 DAY && E.Archived = 0,1,NULL)) + as WeekEventCount, + count(if(E.StartTime>'$db_now' - INTERVAL 1 MONTH && E.Archived = 0,1,NULL)) + as MonthEventCount + FROM Monitors as M LEFT JOIN Events as E on E.MonitorId = M.Id + WHERE MonitorId = ? + GROUP BY E.MonitorId + ORDER BY Id" + ; my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $event->{MonitorId} ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + $monitor = $sth->fetchrow_hashref(); + $sth->finish(); + return() if ( !$monitor ); + } + +# Do we need the image information too? + my $need_images = $text =~ /%(?:EPI1|EPIM|EI1|EIM|EI1A|EIMA)%/; + my $first_alarm_frame; + my $max_alarm_frame; + my $max_alarm_score = 0; + if ( $need_images ) { + my $sql = "SELECT * FROM Frames + WHERE EventId = ? AND Type = 'Alarm' + ORDER BY FrameId" + ; + my $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); my $res = $sth->execute( $event->{Id} ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + while( my $frame = $sth->fetchrow_hashref() ) { + if ( !$first_alarm_frame ) { + $first_alarm_frame = $frame; + } + if ( $frame->{Score} > $max_alarm_score ) { + $max_alarm_frame = $frame; + $max_alarm_score = $frame->{Score}; + } + } + $sth->finish(); + } - return( 1 ); + my $url = $Config{ZM_URL}; + $text =~ s/%ZP%/$url/g; + $text =~ s/%MN%/$event->{MonitorName}/g; + $text =~ s/%MET%/$monitor->{EventCount}/g; + $text =~ s/%MEH%/$monitor->{HourEventCount}/g; + $text =~ s/%MED%/$monitor->{DayEventCount}/g; + $text =~ s/%MEW%/$monitor->{WeekEventCount}/g; + $text =~ s/%MEM%/$monitor->{MonthEventCount}/g; + $text =~ s/%MEA%/$monitor->{ArchEventCount}/g; + $text =~ s/%MP%/$url?view=watch&mid=$event->{MonitorId}/g; + $text =~ s/%MPS%/$url?view=watchfeed&mid=$event->{MonitorId}&mode=stream/g; + $text =~ s/%MPI%/$url?view=watchfeed&mid=$event->{MonitorId}&mode=still/g; + $text =~ s/%EP%/$url?view=event&mid=$event->{MonitorId}&eid=$event->{Id}/g; + $text =~ s/%EPS%/$url?view=event&mode=stream&mid=$event->{MonitorId}&eid=$event->{Id}/g; + $text =~ s/%EPI%/$url?view=event&mode=still&mid=$event->{MonitorId}&eid=$event->{Id}/g; + $text =~ s/%EI%/$event->{Id}/g; + $text =~ s/%EN%/$event->{Name}/g; + $text =~ s/%EC%/$event->{Cause}/g; + $text =~ s/%ED%/$event->{Notes}/g; + $text =~ s/%ET%/$event->{StartTime}/g; + $text =~ s/%EL%/$event->{Length}/g; + $text =~ s/%EF%/$event->{Frames}/g; + $text =~ s/%EFA%/$event->{AlarmFrames}/g; + $text =~ s/%EST%/$event->{TotScore}/g; + $text =~ s/%ESA%/$event->{AvgScore}/g; + $text =~ s/%ESM%/$event->{MaxScore}/g; + + if ( $first_alarm_frame ) { + $text =~ s/%EPI1%/$url?view=frame&mid=$event->{MonitorId}&eid=$event->{Id}&fid=$first_alarm_frame->{FrameId}/g; + $text =~ s/%EPIM%/$url?view=frame&mid=$event->{MonitorId}&eid=$event->{Id}&fid=$max_alarm_frame->{FrameId}/g; + if ( $attachments_ref && $text =~ s/%EI1%//g ) { + my $path = generateImage( $event, $first_alarm_frame ); + if ( -e $path ) { + push( @$attachments_ref, { type=>'image/jpeg', path=>$path } ); + } + } + + if ( $attachments_ref && $text =~ s/%EIM%//g ) { + # Don't attach the same image twice + if ( !@$attachments_ref + || ($first_alarm_frame->{FrameId} != $max_alarm_frame->{FrameId} ) + ) { + my $path = generateImage( $event, $max_alarm_frame ); + if ( -e $path ) { + push( @$attachments_ref, { type=>'image/jpeg', path=>$path } ); + } + } + } + if ( $attachments_ref && $text =~ s/%EI1A%//g ) { + my $path = generateImage( $event, $first_alarm_frame, 'analyse' ); + if ( -e $path ) { + push( @$attachments_ref, { type=>'image/jpeg', path=>$path } ); + } + } + if ( $attachments_ref && $text =~ s/%EIMA%//g ) { + # Don't attach the same image twice + if ( !@$attachments_ref + || ($first_alarm_frame->{FrameId} != $max_alarm_frame->{FrameId} ) + ) { + my $path = generateImage( $event, $max_alarm_frame, 'analyse'); + if ( -e $path ) { + push( @$attachments_ref, { type=>'image/jpeg', path=>$path } ); + } + } + } + } # end if $first_alarm_frame + + if ( $attachments_ref && $Config{ZM_OPT_FFMPEG} ) { + if ( $text =~ s/%EV%//g ) { + my ( $format, $path ) = generateVideo( $filter, $event ); + if ( !$format ) { + return( undef ); + } + push( @$attachments_ref, { type=>"video/$format", path=>$path } ); + } + if ( $text =~ s/%EVM%//g ) { + my ( $format, $path ) = generateVideo( $filter, $event, 1 ); + if ( !$format ) { + return( undef ); + } + push( @$attachments_ref, { type=>"video/$format", path=>$path } ); + } + } + $text =~ s/%FN%/$filter->{Name}/g; + ( my $filter_name = $filter->{Name} ) =~ s/ /+/g; + $text =~ s/%FP%/$url?view=filter&mid=$event->{MonitorId}&filter_name=$filter_name/g; + + return( $text ); +} # end subsitituteTags + +sub sendEmail { + my $filter = shift; + my $event = shift; + + if ( ! $Config{ZM_FROM_EMAIL} ) { + Error( "No 'from' email address defined, not sending email" ); + return( 0 ); + } + if ( ! $Config{ZM_EMAIL_ADDRESS} ) { + Error( 'No email address defined, not sending email' ); + return( 0 ); + } + + Info( "Creating notification email\n" ); + + my $subject = substituteTags( $Config{ZM_EMAIL_SUBJECT}, $filter, $event ); + return( 0 ) if ( !$subject ); + my @attachments; + my $body = substituteTags( $Config{ZM_EMAIL_BODY}, $filter, $event, \@attachments ); + return( 0 ) if ( !$body ); + + Info( "Sending notification email '$subject'\n" ); + + eval { + if ( $Config{ZM_NEW_MAIL_MODULES} ) { +### Create the multipart container + my $mail = MIME::Lite->new ( + From => $Config{ZM_FROM_EMAIL}, + To => $Config{ZM_EMAIL_ADDRESS}, + Subject => $subject, + Type => 'multipart/mixed' + ); +### Add the text message part + $mail->attach ( + Type => 'TEXT', + Data => $body + ); +### Add the attachments + foreach my $attachment ( @attachments ) { + Info( "Attaching '$attachment->{path}\n" ); + $mail->attach( + Path => $attachment->{path}, + Type => $attachment->{type}, + Disposition => 'attachment' + ); + } +### Send the Message + if ( $Config{ZM_SSMTP_MAIL} ) { + my $ssmtp_location = $Config{ZM_SSMTP_PATH}; + if ( !$ssmtp_location ) { + if ( logDebugging() ) { + Debug( "which ssmtp: $ssmtp_location - set ssmtp path in options to suppress this message\n" ); + } + $ssmtp_location = qx('which ssmtp'); + } + if ( !$ssmtp_location ) { + Debug( "Can't find ssmtp, trying MIME::Lite->send" ); + MIME::Lite->send( 'smtp', $Config{ZM_EMAIL_HOST}, Timeout=>60 ); + $mail->send(); + } else { +### Send using SSMTP + $mail->send( 'sendmail', $ssmtp_location, $Config{ZM_EMAIL_ADDRESS} ); + } + } else { + MIME::Lite->send( 'smtp', $Config{ZM_EMAIL_HOST}, Timeout=>60 ); + $mail->send(); + } + } else { + my $mail = MIME::Entity->build( + From => $Config{ZM_FROM_EMAIL}, + To => $Config{ZM_EMAIL_ADDRESS}, + Subject => $subject, + Type => (($body=~//)?'text/html':'text/plain'), + Data => $body + ); + + foreach my $attachment ( @attachments ) { + Info( "Attaching '$attachment->{path}\n" ); + $mail->attach( + Path => $attachment->{path}, + Type => $attachment->{type}, + Encoding => 'base64' + ); + } + $mail->smtpsend( Host => $Config{ZM_EMAIL_HOST}, MailFrom => $Config{ZM_FROM_EMAIL} ); + } + }; + if ( $@ ) { + Error( "Can't send email: $@" ); + return( 0 ); + } else { + Info( "Notification email sent\n" ); + } + my $sql = 'update Events set Emailed = 1 where Id = ?'; + my $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $event->{Id} ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + + return( 1 ); } -sub executeCommand -{ - my $filter = shift; - my $event = shift; +sub sendMessage { + my $filter = shift; + my $event = shift; - my $event_path = getEventPath( $event ); + if ( ! $Config{ZM_FROM_EMAIL} ) { + Error( "No 'from' email address defined, not sending message" ); + return( 0 ); + } + if ( ! $Config{ZM_MESSAGE_ADDRESS} ) { + Error( 'No message address defined, not sending message' ); + return( 0 ); + } - my $command = $filter->{AutoExecuteCmd}; - $command .= " $event_path"; - $command = substituteTags( $command, $filter, $event ); + Info( "Creating notification message\n" ); - Info( "Executing '$command'\n" ); - my $output = qx($command); - my $status = $? >> 8; - if ( $status || logDebugging() ) - { - chomp( $output ); - Debug( "Output: $output\n" ); + my $subject = substituteTags( $Config{ZM_MESSAGE_SUBJECT}, $filter, $event ); + return( 0 ) if ( !$subject ); + my @attachments; + my $body = substituteTags( $Config{ZM_MESSAGE_BODY}, $filter, $event, \@attachments ); + return( 0 ) if ( !$body ); + + Info( "Sending notification message '$subject'\n" ); + + eval { + if ( $Config{ZM_NEW_MAIL_MODULES} ) { +### Create the multipart container + my $mail = MIME::Lite->new ( + From => $Config{ZM_FROM_EMAIL}, + To => $Config{ZM_MESSAGE_ADDRESS}, + Subject => $subject, + Type => 'multipart/mixed' + ); +### Add the text message part + $mail->attach ( + Type => 'TEXT', + Data => $body + ); +### Add the attachments + foreach my $attachment ( @attachments ) { + Info( "Attaching '$attachment->{path}\n" ); + $mail->attach( + Path => $attachment->{path}, + Type => $attachment->{type}, + Disposition => 'attachment' + ); + } +### Send the Message + if ( $Config{ZM_SSMTP_MAIL} ) { + my $ssmtp_location = $Config{ZM_SSMTP_PATH}; + if ( !$ssmtp_location ) { + if ( logDebugging() ) { + Debug( "which ssmtp: $ssmtp_location - set ssmtp path in options to suppress this message\n" ); + } + $ssmtp_location = qx('which ssmtp'); + } + if ( !$ssmtp_location ) { + Debug( "Can't find ssmtp, trying MIME::Lite->send" ); + MIME::Lite->send( 'smtp', $Config{ZM_EMAIL_HOST}, Timeout=>60 ); + $mail->send(); + } else { +### Send using SSMTP + $mail->send( 'sendmail', $ssmtp_location, $Config{ZM_MESSAGE_ADDRESS} ); + } + } else { + MIME::Lite->send( 'smtp', $Config{ZM_EMAIL_HOST}, Timeout=>60 ); + $mail->send(); + } + } else { + my $mail = MIME::Entity->build( + From => $Config{ZM_FROM_EMAIL}, + To => $Config{ZM_MESSAGE_ADDRESS}, + Subject => $subject, + Type => (($body=~//)?'text/html':'text/plain'), + Data => $body + ); + + foreach my $attachment ( @attachments ) { + Info( "Attaching '$attachment->{path}\n" ); + $mail->attach( + Path => $attachment->{path}, + Type => $attachment->{type}, + Encoding => 'base64' + ); + } + $mail->smtpsend( Host => $Config{ZM_EMAIL_HOST}, + MailFrom => $Config{ZM_FROM_EMAIL} + ); } - if ( $status ) - { - Error( "Command '$command' exited with status: $status\n" ); - return( 0 ); - } - else - { - my $sql = "update Events set Executed = 1 where Id = ?"; - my $sth = $dbh->prepare_cached( $sql ) - or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); - my $res = $sth->execute( $event->{Id} ) - or Fatal( "Can't execute '$sql': ".$sth->errstr() ); - } - return( 1 ); + }; + if ( $@ ) { + Error( "Can't send email: $@" ); + return( 0 ); + } else { + Info( "Notification message sent\n" ); + } + my $sql = 'update Events set Messaged = 1 where Id = ?'; + my $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $event->{Id} ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + + return( 1 ); +} + +sub executeCommand { + my $filter = shift; + my $event = shift; + + my $event_path = getEventPath( $event ); + + my $command = $filter->{AutoExecuteCmd}; + $command .= " $event_path"; + $command = substituteTags( $command, $filter, $event ); + + Info( "Executing '$command'\n" ); + my $output = qx($command); + my $status = $? >> 8; + if ( $status || logDebugging() ) { + chomp( $output ); + Debug( "Output: $output\n" ); + } + if ( $status ) { + Error( "Command '$command' exited with status: $status\n" ); + return( 0 ); + } else { + my $sql = 'update Events set Executed = 1 where Id = ?'; + my $sth = $dbh->prepare_cached( $sql ) + or Fatal( "Can't prepare '$sql': ".$dbh->errstr() ); + my $res = $sth->execute( $event->{Id} ) + or Fatal( "Can't execute '$sql': ".$sth->errstr() ); + } + return( 1 ); } diff --git a/src/zm_ffmpeg_camera.cpp b/src/zm_ffmpeg_camera.cpp index 4c7f4cff0..d11ea936e 100644 --- a/src/zm_ffmpeg_camera.cpp +++ b/src/zm_ffmpeg_camera.cpp @@ -167,11 +167,8 @@ int FfmpegCamera::Capture( Image &image ) Debug( 5, "Got packet from stream %d dts (%d) pts(%d)", packet.stream_index, packet.pts, packet.dts ); // What about audio stream? Maybe someday we could do sound detection... if ( packet.stream_index == mVideoStreamId ) { -#if LIBAVCODEC_VERSION_CHECK(52, 23, 0, 23, 0) - if (avcodec_decode_video2(mVideoCodecContext, mRawFrame, &frameComplete, &packet) < 0) -#else - if (avcodec_decode_video(mVideoCodecContext, mRawFrame, &frameComplete, packet.data, packet.size) < 0) -#endif + int ret = zm_avcodec_decode_video( mVideoCodecContext, mRawFrame, &frameComplete, &packet ); + if ( ret < 0 ) Fatal( "Unable to decode frame at frame %d", frameCount ); Debug( 4, "Decoded video packet at frame %d", frameCount ); @@ -364,7 +361,7 @@ int FfmpegCamera::OpenFfmpeg() { Fatal( "Unable to open codec for video stream from %s", mPath.c_str() ); } - if (mAudioStreamId >= 0) { + if ( mAudioStreamId >= 0 ) { mAudioCodecContext = mFormatContext->streams[mAudioStreamId]->codec; if ((mAudioCodec = avcodec_find_decoder(mAudioCodecContext->codec_id)) == NULL) { Debug(1, "Can't find codec for audio stream from %s", mPath.c_str()); @@ -437,7 +434,7 @@ int FfmpegCamera::OpenFfmpeg() { mCanCapture = true; return 0; -} +} // int FfmpegCamera::OpenFfmpeg() int FfmpegCamera::ReopenFfmpeg() { @@ -639,6 +636,7 @@ int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event unsigned int packet_count = 0; ZMPacket *queued_packet; + // Clear all packets that predate the moment when the recording began packetqueue.clear_unwanted_packets( &recording, mVideoStreamId ); while ( ( queued_packet = packetqueue.popPacket() ) ) { @@ -673,7 +671,7 @@ int FfmpegCamera::CaptureAndRecord( Image &image, timeval recording, char* event // Buffer video packets, since we are not recording. // All audio packets are keyframes, so only if it's a video keyframe - if ( packet.stream_index == mVideoStreamId) { + if ( packet.stream_index == mVideoStreamId ) { if ( key_frame ) { Debug(3, "Clearing queue"); packetqueue.clearQueue( monitor->GetPreEventCount(), mVideoStreamId ); @@ -688,18 +686,22 @@ else if ( packet.pts && video_last_pts > packet.pts ) { } #endif } - - if ( - ( packet.stream_index != mAudioStreamId || record_audio ) - && - ( key_frame || packetqueue.size() ) - ) { - packetqueue.queuePacket( &packet ); + + // The following lines should ensure that the queue always begins with a video keyframe + if ( packet.stream_index == mAudioStreamId ) { +Debug(2, "Have audio packet, reocrd_audio is (%d) and packetqueue.size is (%d)", record_audio, packetqueue.size() ); + if ( record_audio && packetqueue.size() ) { + // if it's audio, and we are doing audio, and there is already something in the queue + packetqueue.queuePacket( &packet ); + } + } else if ( packet.stream_index == mVideoStreamId ) { + if ( key_frame || packetqueue.size() ) // it's a keyframe or we already have something in the queue + packetqueue.queuePacket( &packet ); } } // end if recording or not if ( packet.stream_index == mVideoStreamId ) { - if ( videoStore ) { + if ( videoStore ) { //Write the packet to our video store int ret = videoStore->writeVideoFramePacket( &packet ); if ( ret < 0 ) { //Less than zero and we skipped a frame diff --git a/src/zm_packetqueue.cpp b/src/zm_packetqueue.cpp index f60b11df5..d4520caba 100644 --- a/src/zm_packetqueue.cpp +++ b/src/zm_packetqueue.cpp @@ -60,14 +60,12 @@ ZMPacket* zm_packetqueue::popPacket( ) { unsigned int zm_packetqueue::clearQueue( unsigned int frames_to_keep, int stream_id ) { - Debug(3, "Clearing all but %d frames", frames_to_keep ); + Debug(3, "Clearing all but %d frames, queue has %d", frames_to_keep, pktQueue.size() ); frames_to_keep += 1; if ( pktQueue.empty() ) { Debug(3, "Queue is empty"); return 0; - } else { - Debug(3, "Queue has (%d)", pktQueue.size() ); } list::reverse_iterator it; @@ -77,18 +75,19 @@ unsigned int zm_packetqueue::clearQueue( unsigned int frames_to_keep, int stream ZMPacket *zm_packet = *it; AVPacket *av_packet = &(zm_packet->packet); - Debug(3, "Looking at packet with stream index (%d) with keyframe (%d), frames_to_keep is (%d)", av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), frames_to_keep ); + Debug(4, "Looking at packet with stream index (%d) with keyframe (%d), frames_to_keep is (%d)", av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), frames_to_keep ); // Want frames_to_keep video keyframes. Otherwise, we may not have enough if ( ( av_packet->stream_index == stream_id) && ( av_packet->flags & AV_PKT_FLAG_KEY ) ) { - if (!frames_to_keep) - break; frames_to_keep --; } } + if ( frames_to_keep ) { + Debug(3, "Hit end of queue, still need (%d) video keyframes", frames_to_keep ); + } unsigned int delete_count = 0; while ( it != pktQueue.rend() ) { - Debug(3, "Deleting a packet from the front, count is (%d)", delete_count ); + Debug(4, "Deleting a packet from the front, count is (%d)", delete_count ); packet = pktQueue.front(); pktQueue.pop_front(); @@ -96,6 +95,7 @@ unsigned int zm_packetqueue::clearQueue( unsigned int frames_to_keep, int stream delete_count += 1; } + Debug(3, "Deleted (%d) packets", delete_count ); return delete_count; } // end unsigned int zm_packetqueue::clearQueue( unsigned int frames_to_keep, int stream_id ) @@ -123,10 +123,10 @@ void zm_packetqueue::clear_unwanted_packets( timeval *recording_started, int mVi // Step 2 - pop packets until we get to the packet in step 2 list::reverse_iterator it; + Debug(3, "Looking for keyframe after start recording stream id (%d)", mVideoStreamId ); for ( it = pktQueue.rbegin(); it != pktQueue.rend(); ++ it ) { ZMPacket *zm_packet = *it; AVPacket *av_packet = &(zm_packet->packet); -Debug(1, "Looking for keyframe after start" ); if ( ( av_packet->flags & AV_PKT_FLAG_KEY ) && @@ -134,7 +134,7 @@ Debug(1, "Looking for keyframe after start" ); && timercmp( &(zm_packet->timestamp), recording_started, < ) ) { -Debug(1, "Found keyframe before start" ); + Debug(3, "Found keyframe before start with stream index (%d) with keyframe (%d)", av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ) ); break; } } @@ -143,10 +143,29 @@ Debug(1, "Found keyframe before start" ); return; } + ZMPacket *zm_packet = *it; + AVPacket *av_packet = &(zm_packet->packet); + Debug(3, "Found packet before start with stream index (%d) with keyframe (%d), distance(%d), size(%d)", + av_packet->stream_index, + ( av_packet->flags & AV_PKT_FLAG_KEY ), + distance( it, pktQueue.rend() ), + pktQueue.size() ); + + unsigned int deleted_frames = 0; ZMPacket *packet = NULL; - while ( pktQueue.rend() != it ) { + while ( distance( it, pktQueue.rend() ) > 1 ) { + //while ( pktQueue.rend() != it ) { packet = pktQueue.front(); pktQueue.pop_front(); delete packet; + deleted_frames += 1; + } + + zm_packet = pktQueue.front(); + av_packet = &(zm_packet->packet); + if ( ( ! ( av_packet->flags & AV_PKT_FLAG_KEY ) ) || ( av_packet->stream_index != mVideoStreamId ) ) { + Error( "Done looking for keyframe. Deleted %d frames. Remaining frames in queue: %d stream of head packet is (%d), keyframe (%d), distance(%d), packets(%d)", deleted_frames, pktQueue.size(), av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), distance( it, pktQueue.rend() ), pktQueue.size() ); + } else { + Debug(1, "Done looking for keyframe. Deleted %d frames. Remaining frames in queue: %d stream of head packet is (%d), keyframe (%d), distance(%d), packets(%d)", deleted_frames, pktQueue.size(), av_packet->stream_index, ( av_packet->flags & AV_PKT_FLAG_KEY ), distance( it, pktQueue.rend() ), pktQueue.size() ); } } diff --git a/src/zm_videostore.cpp b/src/zm_videostore.cpp index 99b521c3c..0f7892f80 100644 --- a/src/zm_videostore.cpp +++ b/src/zm_videostore.cpp @@ -82,28 +82,36 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in, if (dsr < 0) Warning("%s:%d: title set failed", __FILE__, __LINE__ ); oc->metadata = pmetadata; - output_format = oc->oformat; + Debug(2, "setting parameters"); +#if LIBAVCODEC_VERSION_CHECK(58, 0, 0, 0, 0) + AVCodec *codec = avcodec_find_decoder( video_input_stream->codecpar->codec_id ); + video_output_context = avcodec_alloc_context3( codec ); + ret = avcodec_parameters_to_context( video_output_context, video_input_stream->codecpar ); + if ( ret < 0 ) { + Error( "Could not initialize stream parameteres"); + return; + } else { + Debug(2, "Success setting parameters"); + } + if ( avcodec_open2( video_output_context, codec, NULL ) < 0 ) { + Fatal("Unable to open video out codec\n"); + } + video_output_stream = avformat_new_stream( oc, codec ); + if (!video_output_stream) { + Fatal("Unable to create video out stream\n"); + } else { + Debug(2, "Success creating video out stream" ); + } +#else video_output_stream = avformat_new_stream(oc, (AVCodec*)video_input_context->codec); if (!video_output_stream) { Fatal("Unable to create video out stream\n"); } else { Debug(2, "Success creating video out stream" ); } - video_output_context = video_output_stream->codec; - -#if LIBAVCODEC_VERSION_CHECK(58, 0, 0, 0, 0) - Debug(2, "setting parameters"); - ret = avcodec_parameters_to_context( video_output_context, video_input_stream->codecpar ); - if ( ret < 0 ) { - Error( "Could not initialize stream parameteres"); - return; - } else { - Debug(2, "Success getting parameters"); - } -#else ret = avcodec_copy_context(video_output_context, video_input_context ); if (ret < 0) { Fatal("Unable to copy input video context to output video context %s\n", @@ -145,6 +153,7 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in, } Monitor::Orientation orientation = monitor->getOrientation(); + Debug(3, "Have orientation" ); if ( orientation ) { if ( orientation == Monitor::ROTATE_0 ) { @@ -170,6 +179,7 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in, #endif if (audio_input_stream) { + Debug(3, "Have audio stream" ); audio_input_context = audio_input_stream->codec; if ( audio_input_context->codec_id != AV_CODEC_ID_AAC ) { @@ -187,9 +197,13 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in, Error("Unable to create audio out stream\n"); audio_output_stream = NULL; } else { + Debug(2, "setting parameters"); audio_output_context = audio_output_stream->codec; - +#if LIBAVCODEC_VERSION_CHECK(57, 0, 0, 0, 0) + ret = avcodec_parameters_to_context( audio_output_context, audio_input_stream->codecpar ); +#else ret = avcodec_copy_context(audio_output_context, audio_input_context); +#endif if (ret < 0) { Error("Unable to copy audio context %s\n", av_make_error_string(ret).c_str()); audio_output_stream = NULL; @@ -233,7 +247,8 @@ VideoStore::VideoStore(const char *filename_in, const char *format_in, //av_dict_set(&opts, "movflags", "frag_custom+dash+delay_moov", 0); //av_dict_set(&opts, "movflags", "frag_custom+dash+delay_moov", 0); //av_dict_set(&opts, "movflags", "frag_keyframe+empty_moov+default_base_moof", 0); - if ((ret = avformat_write_header(oc, &opts)) < 0) { + if ((ret = avformat_write_header(oc, NULL)) < 0) { + //if ((ret = avformat_write_header(oc, &opts)) < 0) { Warning("Unable to set movflags to frag_custom+dash+delay_moov"); /* Write the stream header, if any. */ ret = avformat_write_header(oc, NULL); @@ -262,14 +277,14 @@ VideoStore::~VideoStore(){ if ( audio_output_codec ) { // Do we need to flush the outputs? I have no idea. AVPacket pkt; - int got_packet; + int got_packet = 0; av_init_packet(&pkt); pkt.data = NULL; pkt.size = 0; int64_t size; while(1) { -#if LIBAVCODEC_VERSION_CHECK(58, 0, 0, 0, 0) +#if LIBAVCODEC_VERSION_CHECK(57, 0, 0, 0, 0) ret = avcodec_receive_packet( audio_output_context, &pkt ); #else ret = avcodec_encode_audio2( audio_output_context, &pkt, NULL, &got_packet ); @@ -677,7 +692,7 @@ int VideoStore::writeAudioFramePacket( AVPacket *ipkt ) { if ( audio_output_codec ) { #ifdef HAVE_LIBAVRESAMPLE -#if 0 +#if LIBAVCODEC_VERSION_CHECK(57, 0, 0, 0, 0) ret = avcodec_send_packet( audio_input_context, ipkt ); if ( ret < 0 ) { Error("avcodec_send_packet fail %s", av_make_error_string(ret).c_str()); @@ -696,26 +711,7 @@ int VideoStore::writeAudioFramePacket( AVPacket *ipkt ) { input_frame->channel_layout, audio_output_context->refcounted_frames ); - - ret = avcodec_send_frame( audio_output_context, input_frame ); - if ( ret < 0 ) { - av_frame_unref( input_frame ); - Error("avcodec_send_frame fail(%d), %s codec is open(%d) is_encoder(%d)", ret, av_make_error_string(ret).c_str(), - avcodec_is_open( audio_output_context ), - av_codec_is_encoder( audio_output_context->codec) - ); - return 0; - } - ret = avcodec_receive_packet( audio_output_context, &opkt ); - if ( ret < 0 ) { - av_frame_unref( input_frame ); - Error("avcodec_receive_packet fail %s", av_make_error_string(ret).c_str()); - return 0; - } - av_frame_unref( input_frame ); #else - - /** * Decode the audio frame stored in the packet. * The input audio stream decoder is used to do this. @@ -736,7 +732,7 @@ int VideoStore::writeAudioFramePacket( AVPacket *ipkt ) { zm_av_packet_unref(&opkt); return 0; } - +#endif int frame_size = input_frame->nb_samples; Debug(4, "Frame size: %d", frame_size ); @@ -778,7 +774,7 @@ int VideoStore::writeAudioFramePacket( AVPacket *ipkt ) { * Encode the audio frame and store it in the temporary packet. * The output audio stream encoder is used to do this. */ -#if LIBAVCODEC_VERSION_CHECK(58, 0, 0, 0, 0) +#if LIBAVCODEC_VERSION_CHECK(57, 0, 0, 0, 0) if (( ret = avcodec_receive_packet( audio_output_context, &opkt )) < 0 ) { #else if (( ret = avcodec_encode_audio2( audio_output_context, &opkt, output_frame, &data_present )) < 0) { @@ -794,7 +790,6 @@ int VideoStore::writeAudioFramePacket( AVPacket *ipkt ) { return 0; } -#endif #endif } else { av_init_packet(&opkt); diff --git a/src/zma.cpp b/src/zma.cpp index 4479b4f78..e59a0e65d 100644 --- a/src/zma.cpp +++ b/src/zma.cpp @@ -163,7 +163,7 @@ int main( int argc, char *argv[] ) if ( analysis_update_delay ) { cur_time = time( 0 ); - if ( ( cur_time - last_analysis_update_time ) > analysis_update_delay ) + if ( (unsigned int)( cur_time - last_analysis_update_time ) > analysis_update_delay ) { analysis_rate = monitor->GetAnalysisRate(); monitor->UpdateAdaptiveSkip(); diff --git a/src/zmu.cpp b/src/zmu.cpp index 21b623c4b..c3a795ad4 100644 --- a/src/zmu.cpp +++ b/src/zmu.cpp @@ -95,8 +95,7 @@ Options for use with monitors: #include "zm_monitor.h" #include "zm_local_camera.h" -void Usage( int status=-1 ) -{ +void Usage( int status=-1 ) { fprintf( stderr, "zmu <-d device_path> [-v] [function] [-U -P]\n" ); fprintf( stderr, "zmu <-m monitor_id> [-v] [function] [-U -P]\n" ); fprintf( stderr, "General options:\n" ); @@ -167,48 +166,38 @@ typedef enum { ZMU_LIST = 0x10000000, } Function; -bool ValidateAccess( User *user, int mon_id, int function ) -{ +bool ValidateAccess( User *user, int mon_id, int function ) { bool allowed = true; - if ( function & (ZMU_STATE|ZMU_IMAGE|ZMU_TIME|ZMU_READ_IDX|ZMU_WRITE_IDX|ZMU_FPS) ) - { + if ( function & (ZMU_STATE|ZMU_IMAGE|ZMU_TIME|ZMU_READ_IDX|ZMU_WRITE_IDX|ZMU_FPS) ) { if ( user->getStream() < User::PERM_VIEW ) allowed = false; } - if ( function & ZMU_EVENT ) - { + if ( function & ZMU_EVENT ) { if ( user->getEvents() < User::PERM_VIEW ) allowed = false; } - if ( function & (ZMU_ZONES|ZMU_QUERY|ZMU_LIST) ) - { + if ( function & (ZMU_ZONES|ZMU_QUERY|ZMU_LIST) ) { if ( user->getMonitors() < User::PERM_VIEW ) allowed = false; } - if ( function & (ZMU_ALARM|ZMU_NOALARM|ZMU_CANCEL|ZMU_RELOAD|ZMU_ENABLE|ZMU_DISABLE|ZMU_SUSPEND|ZMU_RESUME|ZMU_BRIGHTNESS|ZMU_CONTRAST|ZMU_HUE|ZMU_COLOUR) ) - { + if ( function & (ZMU_ALARM|ZMU_NOALARM|ZMU_CANCEL|ZMU_RELOAD|ZMU_ENABLE|ZMU_DISABLE|ZMU_SUSPEND|ZMU_RESUME|ZMU_BRIGHTNESS|ZMU_CONTRAST|ZMU_HUE|ZMU_COLOUR) ) { if ( user->getMonitors() < User::PERM_EDIT ) allowed = false; } - if ( mon_id > 0 ) - { - if ( !user->canAccess( mon_id ) ) - { + if ( mon_id > 0 ) { + if ( !user->canAccess( mon_id ) ) { allowed = false; } } - if ( !allowed ) - { + if ( !allowed ) { fprintf( stderr, "Error, insufficient privileges for requested action\n" ); exit( -1 ); } return( allowed ); } -int main( int argc, char *argv[] ) -{ - if ( access(ZM_CONFIG, R_OK) != 0 ) - { +int main( int argc, char *argv[] ) { + if ( access(ZM_CONFIG, R_OK) != 0 ) { fprintf( stderr, "Can't open %s: %s\n", ZM_CONFIG, strerror(errno) ); exit( -1 ); } @@ -274,18 +263,15 @@ int main( int argc, char *argv[] ) int v4lVersion = 1; #endif // ZM_HAS_V4L2/1 #endif // ZM_HAS_V4L - while (1) - { + while (1) { int option_index = 0; int c = getopt_long (argc, argv, "d:m:vsEDLurwei::S:t::fz::ancqhlB::C::H::O::U:P:A:V:", long_options, &option_index); - if (c == -1) - { + if (c == -1) { break; } - switch (c) - { + switch (c) { case 'd': if ( optarg ) device = optarg; @@ -405,8 +391,7 @@ int main( int argc, char *argv[] ) } } - if (optind < argc) - { + if (optind < argc) { fprintf( stderr, "Extraneous options, " ); while (optind < argc) fprintf( stderr, "%s ", argv[optind++]); @@ -414,13 +399,11 @@ int main( int argc, char *argv[] ) Usage(); } - if ( device && !(function&ZMU_QUERY) ) - { + if ( device && !(function&ZMU_QUERY) ) { fprintf( stderr, "Error, -d option cannot be used with this option\n" ); Usage(); } - if ( scale != -1 && !(function&ZMU_IMAGE) ) - { + if ( scale != -1 && !(function&ZMU_IMAGE) ) { fprintf( stderr, "Error, -S option cannot be used with this option\n" ); Usage(); } @@ -435,46 +418,36 @@ int main( int argc, char *argv[] ) User *user = 0; - if ( config.opt_use_auth ) - { - if ( strcmp( config.auth_relay, "none" ) == 0 ) - { - if ( !username ) - { + if ( config.opt_use_auth ) { + if ( strcmp( config.auth_relay, "none" ) == 0 ) { + if ( !username ) { fprintf( stderr, "Error, username must be supplied\n" ); exit( -1 ); } - if ( username ) - { + if ( username ) { user = zmLoadUser( username ); } - } - else - { - if ( !(username && password) && !auth ) - { + } else { + if ( !(username && password) && !auth ) { fprintf( stderr, "Error, username and password or auth string must be supplied\n" ); exit( -1 ); } //if ( strcmp( config.auth_relay, "hashed" ) == 0 ) { - if ( auth ) - { + if ( auth ) { user = zmLoadAuthUser( auth, false ); } } //else if ( strcmp( config.auth_relay, "plain" ) == 0 ) { - if ( username && password ) - { + if ( username && password ) { user = zmLoadUser( username, password ); } } } - if ( !user ) - { + if ( !user ) { fprintf( stderr, "Error, unable to authenticate user\n" ); exit( -1 ); } @@ -482,13 +455,10 @@ int main( int argc, char *argv[] ) } - if ( mon_id > 0 ) - { + if ( mon_id > 0 ) { Monitor *monitor = Monitor::Load( mon_id, function&(ZMU_QUERY|ZMU_ZONES), Monitor::QUERY ); - if ( monitor ) - { - if ( verbose ) - { + if ( monitor ) { + if ( verbose ) { printf( "Monitor %d(%s)\n", monitor->Id(), monitor->Name() ); } if ( ! monitor->connect() ) { @@ -498,23 +468,19 @@ int main( int argc, char *argv[] ) char separator = ' '; bool have_output = false; - if ( function & ZMU_STATE ) - { + if ( function & ZMU_STATE ) { Monitor::State state = monitor->GetState(); if ( verbose ) printf( "Current state: %s\n", state==Monitor::ALARM?"Alarm":(state==Monitor::ALERT?"Alert":"Idle") ); - else - { + else { if ( have_output ) printf( "%c", separator ); printf( "%d", state ); have_output = true; } } - if ( function & ZMU_TIME ) - { + if ( function & ZMU_TIME ) { struct timeval timestamp = monitor->GetTimestamp( image_idx ); - if ( verbose ) - { + if ( verbose ) { char timestamp_str[64] = "None"; if ( timestamp.tv_sec ) strftime( timestamp_str, sizeof(timestamp_str), "%Y-%m-%d %H:%M:%S", localtime( ×tamp.tv_sec ) ); @@ -522,62 +488,50 @@ int main( int argc, char *argv[] ) printf( "Time of last image capture: %s.%02ld\n", timestamp_str, timestamp.tv_usec/10000 ); else 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 ); printf( "%ld.%02ld", timestamp.tv_sec, timestamp.tv_usec/10000 ); have_output = true; } } - if ( function & ZMU_READ_IDX ) - { + if ( function & ZMU_READ_IDX ) { if ( verbose ) printf( "Last read index: %d\n", monitor->GetLastReadIndex() ); - else - { + else { if ( have_output ) printf( "%c", separator ); printf( "%d", monitor->GetLastReadIndex() ); have_output = true; } } - if ( function & ZMU_WRITE_IDX ) - { + if ( function & ZMU_WRITE_IDX ) { if ( verbose ) printf( "Last write index: %d\n", monitor->GetLastWriteIndex() ); - else - { + else { if ( have_output ) printf( "%c", separator ); printf( "%d", monitor->GetLastWriteIndex() ); have_output = true; } } - if ( function & ZMU_EVENT ) - { + if ( function & ZMU_EVENT ) { if ( verbose ) printf( "Last event id: %d\n", monitor->GetLastEvent() ); - else - { + else { if ( have_output ) printf( "%c", separator ); printf( "%d", monitor->GetLastEvent() ); have_output = true; } } - if ( function & ZMU_FPS ) - { + if ( function & ZMU_FPS ) { if ( verbose ) printf( "Current capture rate: %.2f frames per second\n", monitor->GetFPS() ); - else - { + else { if ( have_output ) printf( "%c", separator ); printf( "%.2f", monitor->GetFPS() ); have_output = true; } } - if ( function & ZMU_IMAGE ) - { - if ( verbose ) - { + if ( function & ZMU_IMAGE ) { + if ( verbose ) { if ( image_idx == -1 ) printf( "Dumping last image captured to Monitor%d.jpg", monitor->Id() ); else @@ -588,77 +542,63 @@ int main( int argc, char *argv[] ) } monitor->GetImage( image_idx, scale>0?scale:100 ); } - if ( function & ZMU_ZONES ) - { + if ( function & ZMU_ZONES ) { if ( verbose ) printf( "Dumping zone image to Zones%d.jpg\n", monitor->Id() ); monitor->DumpZoneImage( zoneString ); } - if ( function & ZMU_ALARM ) - { + if ( function & ZMU_ALARM ) { if ( verbose ) printf( "Forcing alarm on\n" ); monitor->ForceAlarmOn( config.forced_alarm_score, "Forced Web" ); } - if ( function & ZMU_NOALARM ) - { + if ( function & ZMU_NOALARM ) { if ( verbose ) printf( "Forcing alarm off\n" ); monitor->ForceAlarmOff(); } - if ( function & ZMU_CANCEL ) - { + if ( function & ZMU_CANCEL ) { if ( verbose ) printf( "Cancelling forced alarm on/off\n" ); monitor->CancelForced(); } - if ( function & ZMU_RELOAD ) - { + if ( function & ZMU_RELOAD ) { if ( verbose ) printf( "Reloading monitor settings\n" ); monitor->actionReload(); } - if ( function & ZMU_ENABLE ) - { + if ( function & ZMU_ENABLE ) { if ( verbose ) printf( "Enabling event generation\n" ); monitor->actionEnable(); } - if ( function & ZMU_DISABLE ) - { + if ( function & ZMU_DISABLE ) { if ( verbose ) printf( "Disabling event generation\n" ); monitor->actionDisable(); } - if ( function & ZMU_SUSPEND ) - { + if ( function & ZMU_SUSPEND ) { if ( verbose ) printf( "Suspending event generation\n" ); monitor->actionSuspend(); } - if ( function & ZMU_RESUME ) - { + if ( function & ZMU_RESUME ) { if ( verbose ) printf( "Resuming event generation\n" ); monitor->actionResume(); } - if ( function & ZMU_QUERY ) - { + if ( function & ZMU_QUERY ) { char monString[16382] = ""; monitor->DumpSettings( monString, verbose ); printf( "%s\n", monString ); } - if ( function & ZMU_BRIGHTNESS ) - { - if ( verbose ) - { + if ( function & ZMU_BRIGHTNESS ) { + if ( verbose ) { if ( brightness >= 0 ) printf( "New brightness: %d\n", monitor->actionBrightness( brightness ) ); else printf( "Current brightness: %d\n", monitor->actionBrightness() ); - } - else - { + } else { if ( have_output ) printf( "%c", separator ); if ( brightness >= 0 ) printf( "%d", monitor->actionBrightness( brightness ) ); @@ -667,17 +607,13 @@ int main( int argc, char *argv[] ) have_output = true; } } - if ( function & ZMU_CONTRAST ) - { - if ( verbose ) - { + if ( function & ZMU_CONTRAST ) { + if ( verbose ) { if ( contrast >= 0 ) printf( "New brightness: %d\n", monitor->actionContrast( contrast ) ); else printf( "Current contrast: %d\n", monitor->actionContrast() ); - } - else - { + } else { if ( have_output ) printf( "%c", separator ); if ( contrast >= 0 ) printf( "%d", monitor->actionContrast( contrast ) ); @@ -686,17 +622,13 @@ int main( int argc, char *argv[] ) have_output = true; } } - if ( function & ZMU_HUE ) - { - if ( verbose ) - { + if ( function & ZMU_HUE ) { + if ( verbose ) { if ( hue >= 0 ) printf( "New hue: %d\n", monitor->actionHue( hue ) ); else printf( "Current hue: %d\n", monitor->actionHue() ); - } - else - { + } else { if ( have_output ) printf( "%c", separator ); if ( hue >= 0 ) printf( "%d", monitor->actionHue( hue ) ); @@ -705,17 +637,13 @@ int main( int argc, char *argv[] ) have_output = true; } } - if ( function & ZMU_COLOUR ) - { - if ( verbose ) - { + if ( function & ZMU_COLOUR ) { + if ( verbose ) { if ( colour >= 0 ) printf( "New colour: %d\n", monitor->actionColour( colour ) ); else printf( "Current colour: %d\n", monitor->actionColour() ); - } - else - { + } else { if ( have_output ) printf( "%c", separator ); if ( colour >= 0 ) printf( "%d", monitor->actionColour( colour ) ); @@ -724,26 +652,19 @@ int main( int argc, char *argv[] ) have_output = true; } } - if ( have_output ) - { + if ( have_output ) { printf( "\n" ); } - if ( !function ) - { + if ( !function ) { Usage(); } delete monitor; - } - else - { + } else { fprintf( stderr, "Error, invalid monitor id %d\n", mon_id ); exit( -1 ); } - } - else - { - if ( function & ZMU_QUERY ) - { + } else { + if ( function & ZMU_QUERY ) { #if ZM_HAS_V4L char vidString[0x10000] = ""; bool ok = LocalCamera::GetCurrentSettings( device, vidString, v4lVersion, verbose ); @@ -755,24 +676,20 @@ int main( int argc, char *argv[] ) #endif // ZM_HAS_V4L } - if ( function & ZMU_LIST ) - { + if ( function & ZMU_LIST ) { std::string sql = "select Id, Function+0 from Monitors"; - if ( !verbose ) - { + if ( !verbose ) { sql += "where Function != 'None'"; } sql += " order by Id asc"; - if ( mysql_query( &dbconn, sql.c_str() ) ) - { + if ( mysql_query( &dbconn, sql.c_str() ) ) { Error( "Can't run query: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } MYSQL_RES *result = mysql_store_result( &dbconn ); - if ( !result ) - { + if ( !result ) { Error( "Can't use query result: %s", mysql_error( &dbconn ) ); exit( mysql_errno( &dbconn ) ); } @@ -780,17 +697,13 @@ int main( int argc, char *argv[] ) Debug( 1, "Got %d monitors", n_monitors ); printf( "%4s%5s%6s%9s%14s%6s%6s%8s%8s\n", "Id", "Func", "State", "TrgState", "LastImgTim", "RdIdx", "WrIdx", "LastEvt", "FrmRate" ); - for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) - { + for( int i = 0; MYSQL_ROW dbrow = mysql_fetch_row( result ); i++ ) { int mon_id = atoi(dbrow[0]); int function = atoi(dbrow[1]); - if ( !user || user->canAccess( mon_id ) ) - { - if ( function > 1 ) - { + if ( !user || user->canAccess( mon_id ) ) { + if ( function > 1 ) { Monitor *monitor = Monitor::Load( mon_id, false, Monitor::QUERY ); - if ( monitor && monitor->connect() ) - { + if ( monitor && monitor->connect() ) { struct timeval tv = monitor->GetTimestamp(); printf( "%4d%5d%6d%9d%11ld.%02ld%6d%6d%8d%8.2f\n", monitor->Id(), @@ -805,9 +718,7 @@ int main( int argc, char *argv[] ) ); delete monitor; } - } - else - { + } else { struct timeval tv = { 0, 0 }; printf( "%4d%5d%6d%9d%11ld.%02ld%6d%6d%8d%8.2f\n", mon_id, diff --git a/utils/packpack/startpackpack.sh b/utils/packpack/startpackpack.sh index 51b6c4a58..d5ca7ff71 100755 --- a/utils/packpack/startpackpack.sh +++ b/utils/packpack/startpackpack.sh @@ -46,7 +46,7 @@ commonprep () { echo "Checking packpack github repo for changes..." git -C packpack pull origin master else - echo "Cloning pakcpack github repo..." + echo "Cloning packpack github repo..." git clone https://github.com/packpack/packpack.git packpack fi @@ -117,6 +117,11 @@ installtrusty () { # This sets the naming convention for the deb packages setdebpkgver () { + # DEBUG + git describe --long --always + git describe --long --always | sed -n 's/^\([0-9\.]*\)-\([0-9]*\)-\([a-z0-9]*\)/\1/p' + git describe --long --always | sed -n 's/^\([0-9\.]*\)-\([0-9]*\)-\([a-z0-9]*\)/\2/p' + # Set VERSION to x.xx.x+x e.g. 1.30.2+15 # the last x is number of commits since release # Creates zoneminder packages in the format: zoneminder-{version}-{release} diff --git a/version b/version index 593d72103..34aae156b 100644 --- a/version +++ b/version @@ -1 +1 @@ -1.30.4 +1.31.0 diff --git a/web/api/app/Config/bootstrap.php.in b/web/api/app/Config/bootstrap.php.in index ac3bcb62c..a2bfe9c26 100644 --- a/web/api/app/Config/bootstrap.php.in +++ b/web/api/app/Config/bootstrap.php.in @@ -100,12 +100,12 @@ App::uses('CakeLog', 'Log'); CakeLog::config('debug', array( 'engine' => 'File', 'types' => array('notice', 'info', 'debug'), - 'file' => 'cake_debug', + 'file' => '@ZM_LOGDIR@/cake_debug', )); CakeLog::config('error', array( 'engine' => 'File', 'types' => array('warning', 'error', 'critical', 'alert', 'emergency'), - 'file' => 'cake_error', + 'file' => '@ZM_LOGDIR@/cake_error', )); CakeLog::config('custom_path', array( 'engine' => 'File', diff --git a/web/includes/Event.php b/web/includes/Event.php index 0e072fb33..f4bf4a3da 100644 --- a/web/includes/Event.php +++ b/web/includes/Event.php @@ -29,12 +29,15 @@ class Event { Error('No row for Event ' . $IdOrRow ); } } // end function __construct + public function Storage() { return new Storage( isset($this->{'StorageId'}) ? $this->{'StorageId'} : NULL ); } + public function Monitor() { return new Monitor( isset($this->{'MonitorId'}) ? $this->{'MonitorId'} : NULL ); } + public function __call( $fn, array $args){ if ( array_key_exists( $fn, $this ) ) { return $this->{$fn}; @@ -54,6 +57,7 @@ class Event { $Storage = $this->Storage(); return $Storage->Path().'/'.$this->Relative_Path(); } + public function Relative_Path() { $event_path = ''; @@ -197,20 +201,26 @@ class Event { return( $thumbData ); } // end function createListThumbnail + // frame is an array representing the db row for a frame. function getImageSrc( $frame, $scale=SCALE_BASE, $captureOnly=false, $overwrite=false ) { $Storage = new Storage( isset($this->{'StorageId'}) ? $this->{'StorageId'} : NULL ); $Event = $this; $eventPath = $Event->Path(); - if ( !is_array($frame) ) + if ( $frame and ! is_array($frame) ) { + # Must be an Id + Debug("Assuming that $frame is an Id"); $frame = array( 'FrameId'=>$frame, 'Type'=>'' ); + } - if ( file_exists( $eventPath.'/snapshot.jpg' ) ) { - $captImage = "snapshot.jpg"; + if ( ( ! $frame ) and file_exists( $eventPath.'/snapshot.jpg' ) ) { + # No frame specified, so look for a snapshot to use + $captImage = 'snapshot.jpg'; + Debug("Frame not specified, using snapshot"); } else { $captImage = sprintf( '%0'.ZM_EVENT_IMAGE_DIGITS.'d-capture.jpg', $frame['FrameId'] ); if ( ! file_exists( $eventPath.'/'.$captImage ) ) { - # Generate the frame JPG + # Generate the frame JPG if ( $Event->DefaultVideo() ) { $videoPath = $eventPath.'/'.$Event->DefaultVideo(); @@ -275,8 +285,7 @@ class Event { } $thumbFile = $thumbPath; - if ( $overwrite || !file_exists( $thumbFile ) || !filesize( $thumbFile ) ) - { + if ( $overwrite || ! file_exists( $thumbFile ) || ! filesize( $thumbFile ) ) { // Get new dimensions list( $imageWidth, $imageHeight ) = getimagesize( $imagePath ); $thumbWidth = $imageWidth * $fraction; @@ -290,7 +299,7 @@ class Event { if ( !imagejpeg( $thumbImage, $thumbPath ) ) Error( "Can't create thumbnail '$thumbPath'" ); } - } + } # Create thumbnails $imageData = array( 'eventPath' => $eventPath, @@ -298,7 +307,7 @@ class Event { 'thumbPath' => $thumbPath, 'imageFile' => $imagePath, 'thumbFile' => $thumbFile, - 'imageClass' => $alarmFrame?"alarm":"normal", + 'imageClass' => $alarmFrame?'alarm':'normal', 'isAnalImage' => $isAnalImage, 'hasAnalImage' => $hasAnalImage, ); diff --git a/web/includes/Frame.php b/web/includes/Frame.php index eaec94c58..5a31b3dc3 100644 --- a/web/includes/Frame.php +++ b/web/includes/Frame.php @@ -27,9 +27,11 @@ class Frame { Error("No row for Frame " . $IdOrRow ); } } // end function __construct + public function Storage() { return $this->Event()->Storage(); } + public function Event() { return new Event( $this->{'EventId'} ); } @@ -70,7 +72,9 @@ class Frame { } public function getImageSrc( $show='capture' ) { - return $_SERVER['PHP_SELF'].'?view=image&fid='.$this->{'Id'}.'&show='.$show.'&filename='.$this->Event()->MonitorId().'_'.$this->{'EventId'}.'_'.$this->{'FrameId'}.'.jpg'; + + return $_SERVER['PHP_SELF'].'?view=image&fid='.$this->{'FrameId'}.'&eid='.$this->{'EventId'}.'&show='.$show; + #return $_SERVER['PHP_SELF'].'?view=image&fid='.$this->{'Id'}.'&show='.$show.'&filename='.$this->Event()->MonitorId().'_'.$this->{'EventId'}.'_'.$this->{'FrameId'}.'.jpg'; } // end function getImageSrc public static function find( $parameters = array(), $limit = NULL ) { diff --git a/web/includes/functions.php b/web/includes/functions.php index 06c3f6478..8e0004ea9 100644 --- a/web/includes/functions.php +++ b/web/includes/functions.php @@ -27,13 +27,14 @@ if ( version_compare( phpversion(), '4.3.0', '<') ) { } } +# We are requiring these because this file is getting included from the api, which hasn't already included them. require_once( 'logger.php' ); require_once( 'database.php' ); function userLogin( $username, $password='', $passwordHashed=false ) { global $user, $cookies; - $sql = 'select * from Users where Enabled = 1'; + $sql = 'SELECT * FROM Users WHERE Enabled = 1'; $sql_values = NULL; if ( ZM_AUTH_TYPE == 'builtin' ) { if ( $passwordHashed ) { @@ -323,9 +324,9 @@ function outputImageStream( $id, $src, $width, $height, $title='' ) { function getImageStream( $id, $src, $width, $height, $title='' ) { if ( canStreamIframe() ) { - return '