Compare commits

...

221 Commits

Author SHA1 Message Date
zhiyang7 35ca55e4eb Add hevc wasm player 2022-03-09 15:57:12 +08:00
Isaac Connor 3a8c740a69 Bump version to 1.36.12 2021-12-10 17:36:30 -05:00
Isaac Connor 15b01c2730 timestamp image before scaling. Fixes lack of scaling when TIMESTAMP_ON_CAPTURE
is off
2021-12-10 16:45:26 -05:00
pkubaj a577c3fe79 Fix build on FreeBSD/armv7
1. FreeBSD uses elf_aux_info instead of getauxval.
2. FreeBSD uses HWCAP_NEON macro for Neon.
2021-12-10 16:42:08 -05:00
Isaac Connor 5fdad04a9e Move Cleanup and framebuffer freeing into Close() so that we don't crash on Reload 2021-12-10 16:41:37 -05:00
Isaac Connor a0fd8d64d2 Wait for closeEvent thread to finish. Fixes unfinished event when zmc told to restart 2021-12-10 16:40:52 -05:00
Isaac Connor bfbba5474f Add auth relay to status ajax request. clear statusTimer instead of streamTimer 2021-12-10 16:37:46 -05:00
Isaac Connor b7b49437b9 spacing 2021-12-10 16:23:46 -05:00
Isaac Connor 327f481893 must clear the monitors array before terminating log. 2021-12-10 16:23:31 -05:00
Isaac Connor 756aa56710 Detect group hierarchy loops and break them. 2021-12-03 13:27:05 -05:00
Petter Reinholdtsen ea0e65d300 Make config file comment on unix socket option a bit clearer
Initially I took the comment for granted, and the 'unix_socket' string
as a magic string to tell zoneminder to use the mysql default socket.  Alas,
this do not work, as the setting really need to point to the path of the socket.
Rewrite the comment to make this more clear, and less confusing for the
future users.
2021-12-03 13:26:52 -05:00
Isaac Connor fb0f184d1d Revert "timestamp image before scaling. Fixes lack of scaling when TIMESTAMP_ON_CAPTURE"
This reverts commit 59e8bca3bc.
2021-11-30 12:03:21 -05:00
Isaac Connor 7f8195b248 Merge branch 'release-1.36' of github.com:ZoneMinder/zoneminder into release-1.36 2021-11-29 13:59:24 -05:00
Isaac Connor 59e8bca3bc timestamp image before scaling. Fixes lack of scaling when TIMESTAMP_ON_CAPTURE
is off
2021-11-29 13:57:39 -05:00
Isaac Connor 9764875449 Move init of ctx up before we setup the monitors. I think in some cases we can calls functions that assume ctx has a value. Uncaught%20TypeError%3A%20Cannot%20read%20properties%20of%20undefined%20(reading%20'getImageData') 2021-11-29 13:55:23 -05:00
Isaac Connor 0f476998d4 kill the background timer when switching to history so that we don't cause a javascript error. comment out debugging and use native javascript instead of jquery. 2021-11-29 13:54:42 -05:00
Isaac Connor afc21cd14d If we are starting a process that is waiting to term, mark it to get started by the reaper. Fixes case where zmdc thought the process was still running and so didn't start it. We never noticed because zmwatch would eventually notice. The result is instant restart. 2021-11-29 13:54:11 -05:00
Isaac Connor 5adf5dab50 Remove text-nowrap from cause/notes column 2021-11-29 13:53:50 -05:00
Isaac Connor 7d470fa059 Code comments and make warning when the first packet in queue is locked. 2021-11-29 13:53:37 -05:00
Isaac Connor 11c12f5d10 typo 2021-11-29 13:52:47 -05:00
Isaac Connor 209d45c5f0 include monitor dimensions when logging about zone mismatch 2021-11-29 13:52:31 -05:00
Isaac Connor 765886ae72 Handle bug where a value of '' will prevent special case handling. Allow '' to mean NULL when specifying Storage Area 2021-11-29 13:52:05 -05:00
Isaac Connor 4030fa8bc4 Fix NULL and add special 0 case for Storage area specification in filter 2021-11-29 13:51:50 -05:00
Isaac Connor eeb655fd77 Fix underline 2021-11-29 13:50:07 -05:00
Isaac Connor 4ef5056a91 fix by removing code block 2021-11-29 13:49:55 -05:00
Isaac Connor c14d7889a5 Fix task=>action so that deleting works. Pause streaming before delete to prevent errors being logged due to missing files 2021-11-29 13:48:44 -05:00
Isaac Connor 48c744caed Merge branch 'release-1.36' of github.com:ZoneMinder/zoneminder into release-1.36 2021-11-29 09:13:20 -05:00
Isaac Connor 56fdae0f19 timestamp image before scaling. Fixes lack of scaling when TIMESTAMP_ON_CAPTURE
is off
2021-11-19 12:02:31 -05:00
Isaac Connor 7a181fc082 Merge branch 'release-1.36' of github.com:ZoneMinder/zoneminder into release-1.36 2021-11-17 11:06:38 -05:00
Isaac Connor 2e62826e6c Bump version for release 2021-11-17 11:05:50 -05:00
Isaac Connor a0b60aa4f7 Restore inclusion of video files in export when not including images. Fixes #3324 2021-11-17 11:04:48 -05:00
Isaac Connor d8eb0b2350
Merge pull request #3383 from BlueMax/musl_build_fix
fix posix/musl build
2021-11-16 15:01:35 -05:00
Isaac Connor b7d8add5ad Merge branch 'release-1.36' of github.com:ZoneMinder/zoneminder into release-1.36 2021-11-16 15:00:34 -05:00
Isaac Connor cb8ccaaa49 Set to never timeout while generating video 2021-11-16 15:00:04 -05:00
Isaac Connor 498d565034 In multi-server when viewing an event it may be coming from a different server than the serverhost. Use monitorUrl instead of thisUrl in ajax calls and include auth data. Fixes failed ajax when viewing h264 using zms on a multi-server environment 2021-11-16 15:00:04 -05:00
Isaac Connor 51e7aa0983 Fix use of thisUrl instead of monitorUrl when getting stream status. Fix changing stream image due to use of jquery. 2021-11-16 15:00:04 -05:00
Isaac Connor f8e6fae013 spacing and check for permission to view the specific event instead of events in general 2021-11-16 15:00:04 -05:00
Isaac Connor 4b08b0ae84 Add title to Download button 2021-11-16 15:00:04 -05:00
Isaac Connor d5f9eb11c6 WHen saving v4l settings redirect back to watch instead of console. 2021-11-16 15:00:04 -05:00
Isaac Connor 1b3e0eda13 dbError is supposed to take the sql that caused the error. So pass something to satisfy php 2021-11-16 15:00:04 -05:00
Isaac Connor 90e3345440 implement Event::canEdit 2021-11-16 15:00:04 -05:00
Isaac Connor 29fe7f76a2 implement UrlToZMS in Monitor 2021-11-16 15:00:04 -05:00
Isaac Connor fa533d04ff Send all stats rows instead of just 1. Handle receiving all rows, and don't list event id and frame id 2021-11-16 15:00:04 -05:00
Isaac Connor bbf1269e6f remove extra , 2021-11-16 15:00:04 -05:00
Isaac Connor 601df481ed Add privacy to options tabs so we can get back to it. 2021-11-16 15:00:04 -05:00
Isaac Connor 92bc6ed552 Allow NOW or CURRENT for PACKAGE_VERSION similar to snapshot 2021-11-16 14:59:37 -05:00
BlueMax 8a4a530cf3
fix posix/musl build 2021-11-16 19:50:17 +00:00
Isaac Connor e3d2b14b69 Don't exit(0) on QUIT. Instead set zm_terminate=true so that all the cleanup routines run. 2021-11-09 15:49:25 -05:00
Isaac Connor 9b723d73a1 Set zm_terminate on crash so that other threads exit instead of continuing 2021-11-09 10:51:39 -05:00
Isaac Connor 00919314e7 Report error if sql fails. Add check for access to specific event. 2021-11-09 10:51:29 -05:00
Isaac Connor 55e739d4ea Whitespace 2021-11-09 10:51:16 -05:00
Isaac Connor dd542d5b30 alert error message is an error is returned instead of rows 2021-11-09 10:50:55 -05:00
Isaac Connor 5d93e9a957 av_write_trailer can return a positive value which is not an error 2021-11-02 17:24:54 -04:00
Isaac Connor 2aa7293326 Fix event listing when not paginated. 2021-11-02 17:23:46 -04:00
Isaac Connor 84b8e43034 Only start a transaction if we are not already in a transaction 2021-10-28 15:28:55 -04:00
Isaac Connor 15a7c22b94 layout->layer 2021-10-28 15:26:12 -04:00
ColorfullyZhang d7abdb1505 Set character set as utf8 when connect to mysql to avoid mistakes when there are Chinese characters in storage path. 2021-10-28 15:18:55 -04:00
Isaac Connor 7fd8efeaef is no longer in existence 2021-10-28 15:17:33 -04:00
Isaac Connor 94752e0cf3 Make delete dialog disappear on success. Fixes #3377 2021-10-28 15:15:24 -04:00
Isaac Connor bd8ed71ffc Remove debugging 2021-10-28 15:11:42 -04:00
Isaac Connor e344141222 merge fix from master for filters not deleting 2021-10-28 15:10:55 -04:00
Steve Gilvarry 8061b4f71d Define Date formats
Set Locale for time to en_GB.utf8, changed STRF_FMT_DATETIME_SHORTER to %x which is locale aware short date, but does include year. Makes event table wider, not sure if that is a problem for others
2021-10-28 14:41:19 -04:00
Isaac Connor 198d9c0f5a Restore the download button's behaviour. It is a simple link to the mp4, not an export. Also add a handler for the video.js rate control to sync up our non video.js rate dropdown and stored cookie. 2021-10-28 14:37:47 -04:00
Isaac Connor bf7c13558c Always include the download button so that we can assume that it exists in the js. So avoid console errors when no mp4. 2021-10-28 14:37:19 -04:00
Isaac Connor 615ce5d4c2 Set rows on email textarea 2021-10-28 14:36:06 -04:00
Andrea Vezzali 649eec4dae Update italian (it_it) translation (#3357) 2021-10-28 14:35:29 -04:00
Andrea Vezzali 0dbcd680df Update it_it.php 2021-10-28 14:35:16 -04:00
Andrea Vezzali 64b60613d8 Update it_it.php 2021-10-28 14:35:08 -04:00
Isaac Connor 8600a0af87 Add some extra parenthesis to make sure the logic works right 2021-10-28 13:21:50 -04:00
Isaac Connor 06f1ab4d46 Fix Event count subsitutions in emails because they are no longer in Monitor_Status. They are in Event_Summaries. So create a new object for them and use it. 2021-10-28 13:19:43 -04:00
Isaac Connor 42ab65f7a0 Merge pull request #3361 from criadoperez/debian-install
Added Debian 11 (bullseye) installation instructions
2021-10-28 13:17:38 -04:00
Isaac Connor 67504b4f39 Put back generate video button 2021-10-28 13:16:59 -04:00
Isaac Connor 7568dd63ed add js to manage the generate video button 2021-10-28 13:15:27 -04:00
Isaac Connor 2de36f01db Handle auth to mysql problems more gracefully 2021-10-28 13:15:18 -04:00
Isaac Connor 33a067c085 fix validInt to take negative integers. Introduce validCardinal to handle positive integers 2021-10-28 13:11:44 -04:00
Isaac Connor 65418abf98 Add sort_asc, sort_field and limit to the filter QueryString 2021-10-28 13:10:37 -04:00
SzymekCRX 9da9d1840c Critical bug in events.php
Two extra brackets causes fatal error in Ajax request causing 500 HTTP error and problems with listing / deleting events
2021-10-28 13:09:41 -04:00
Isaac Connor d03365c9e7 Use filter->sort_asc and sort_field which will use either the value specified in query, or defaults set in system. url params order and sort will override. 2021-10-28 13:09:31 -04:00
Isaac Connor 48494a22db Add sort-name and sort-order to bootstrap table options. Also set data-remember-order=false. This allows orderings set by filters to work. Fixes #3348 2021-10-28 13:09:00 -04:00
Isaac Connor 1f75af7534 add licensing info for fontawesome. 2021-10-28 13:08:29 -04:00
Isaac Connor 0dc9017472 reset starttime when changing events. Fixes super fast playback after switch to next event. Also, skip some unneeded calculations and logging. 2021-10-28 13:02:58 -04:00
Isaac Connor ee30c0f05e add a comment about rollbacks 2021-10-28 12:03:58 -04:00
Isaac Connor a6795cd026 Just return the error 2021-10-28 11:59:36 -04:00
Isaac Connor 6e68a35861 Remove dead code, remove locking from CopyTo, put locking into MoveTo. 2021-10-28 11:59:23 -04:00
Isaac Connor dd758aacac Fix Event count subsitutions in emails because they are no longer in Monitor_Status. They are in Event_Summaries. So create a new object for them and use it. 2021-10-27 12:21:30 -04:00
Isaac Connor 8893a29b51 Merge branch 'release-1.36' of github.com:ZoneMinder/zoneminder into release-1.36 2021-10-25 17:06:40 -04:00
Isaac Connor 1ab58aabc6 Bump version to 1.36.10 2021-10-25 17:05:06 -04:00
Isaac Connor 4b5bc09c41 Merge new logic from master. We now delete a non-keyframe from head instead of waiting in capture. 2021-10-25 17:03:36 -04:00
Isaac Connor 22f398dd6f set vertical-align:top on monitor edit labels 2021-10-25 16:55:42 -04:00
Isaac Connor d4467dba36 Fix logic ordering in 1.35.14 updated that moves columns from Monitors table to Monitor_Status 2021-10-25 16:55:42 -04:00
Isaac Connor c600725a1a Fix monitor type labels by adding an Unknown for entry 0. Implement Function_Strings. Fix decoding_enabled not being recalculated correctly because we havn't loaded savejpegs or videowriter yet. 2021-10-25 16:44:09 -04:00
Isaac Connor 7517bdc6ec Merge branch 'release-1.36' of github.com:ZoneMinder/zoneminder into release-1.36 2021-10-25 16:44:01 -04:00
Isaac Connor 522f8dd5ba Do no commit on error as it releases locks. Add better storage loading error handling 2021-10-25 09:44:27 -04:00
Isaac Connor a258567c16 You cannot commit on error because it releases the locks. 2021-10-25 09:44:27 -04:00
Isaac Connor 501aa3fa4a enforce default action 2021-10-19 15:27:37 -04:00
Isaac Connor 6245a4df6a Correct logic so that RECORD and MOCORD also get events. 2021-10-19 15:24:01 -04:00
Isaac Connor 5f317b3651 Merge pull request #3373 from havardAasen/minor_fixes
Update man-pages and typos.
2021-10-19 14:55:59 -04:00
Isaac Connor 7cd8f3d887 Only record when in modect or nodect. Linked monitors would cause a monitor in monitor mode to record 2021-10-19 14:55:59 -04:00
Isaac Connor 1a2665d8e5 Bump version to 1.36.9 2021-10-19 14:24:59 -04:00
Isaac Connor b2e1f7ed56 Put actions and options in a div, remove hr's and style the resulting div have the borders and clearing required. Make email options 100% 2021-10-19 13:17:00 -04:00
Isaac Connor b5bb5e67ff Change commands used to set and goto presets. Fixes #3371 2021-10-19 13:16:21 -04:00
Isaac Connor 1dd19d425a always correct decoding_enable, as zms needs to know it's correct value 2021-10-19 13:15:54 -04:00
Isaac Connor ba38e71b29 Correct definition of Importance. Want less importance to have a hgiher value. Make not null. 2021-10-19 13:14:36 -04:00
Isaac Connor f2e537f177 Fix loading importance. Needs to be -1 not -2. The first value is 1, but we want zero based. 2021-10-19 13:13:18 -04:00
Isaac Connor a1a8f4d09b remove debug hello 2021-10-19 13:11:50 -04:00
Isaac Connor f5b54caa61 Improvements to export. Fix tar -v, should be tar --version. make table width:100% and iframe height 100%. Always show thumbnail of video. Show Id of event if no other links. generate Images frame content event if no jpegs but there is an mp4. Set timeout to infinity for generating export. Provide more feedback if it breaks. Fix ticker. 2021-10-19 13:11:31 -04:00
Isaac Connor 41d193afe3 Fix removing uneeded checked 2021-10-17 19:07:55 -04:00
Isaac Connor 7b5ab0adae fix index -> image_index 2021-10-17 15:13:14 -04:00
Isaac Connor ed901b0235 Improve debug logging of packetqueue cleaning 2021-10-17 15:01:55 -04:00
Isaac Connor ca1b7ebdc7 Test for existence of AutoEmail and AutoMessage. Fixes #3369 issue 2. 2021-10-13 11:57:58 -04:00
Isaac Connor de5ad5fc61 confirm before tar-gzipping orig. Fix default behaviour in dput prompt 2021-10-12 14:10:08 -04:00
Isaac Connor 0a7e0c4781 Bump version to 1.36.8 2021-10-06 10:40:20 -04:00
Isaac Connor aa44bb0d12 spacing. Use a separate boolean to tell if we have specified a new value for controls. This allows negative settings. Fix zmy outputting brightness when contrast is specified. 2021-10-06 10:37:54 -04:00
Isaac Connor ff87856951 Merge pull request #3366 from haade-administrator/patch-1
Update translation
2021-10-06 10:36:52 -04:00
Isaac Connor 1a27ac9ab3 Handle when SERVER['HTTP_HOST'] is not set 2021-10-06 10:36:35 -04:00
Isaac Connor d622ae9251 fix width=0px causing empty looking montage 2021-10-06 10:35:52 -04:00
Isaac Connor 7905f11be6 remove useless commit. 2021-09-25 14:27:26 -04:00
Isaac Connor 369e0d74b7 Add open collective username 2021-09-20 13:48:13 -04:00
Isaac Connor cab4c24d06 Remove redundant notify_all, spelling mistake 2021-09-15 14:11:32 -04:00
Mike Dussault b670bf98e0 Fixed a bug in Image::Buffer that would return the wrong location in the image if the image had > 1 channels (and if the request were for x > 0). 2021-09-15 14:11:16 -04:00
Isaac Connor 2b18dd5978 Don't crash when unable to create source. erase will call the desctructor. Fixes #3344 2021-09-15 14:11:03 -04:00
Isaac Connor 6b8dc07018 wait won't wake up other threads, so notify first. Since we have the lock, this should be ok 2021-09-15 14:10:52 -04:00
Isaac Connor bbe90dad6e Add support for package version 2021-09-15 14:10:36 -04:00
Isaac Connor ef74294d32 More properly fix the threading lock. Instead of waiting on a packet, release it and wait on the packetqueue. 2021-09-15 14:10:25 -04:00
Isaac Connor fbe0aa7401 Revert "use get_packet_and_increment_it instead of the two step to improve locking"
This reverts commit a44bbf8e34.
2021-09-15 14:10:06 -04:00
Isaac Connor 31a6ab2224 notify anyone waiting in packetqueue before waiting on a packet in motion detection. Should fix decode lockup 2021-09-15 14:09:31 -04:00
Isaac Connor 4bc522e273 change to use our self hosted runner 2021-09-15 10:46:55 -04:00
Isaac Connor f299d57a39 Fix js error in montage review when using scaled mode. Fixes #3351 2021-09-14 10:27:05 -04:00
Isaac Connor 4553592caa Merge pull request #3352 from vezza/patch-3
Update it_it.php
2021-09-14 10:26:52 -04:00
Isaac Connor 77a4601d71 Bump version to 1.36.7 2021-09-13 15:05:09 -04:00
Isaac Connor 0162ec2c39 Add missing update_function_pointers so that we use SSE blend functions. Significantly reduces cpu use in motion detection. 2021-09-13 15:04:06 -04:00
Andrea Vezzali ded2c86858 Update it_it translation (#3350) 2021-09-13 09:31:00 -04:00
Isaac Connor c47a66559a Need to increase frame_count or else frame_count%frame_mod will never == 0 2021-09-10 18:01:32 -04:00
Isaac Connor 5475b44852 Fix html emails when using ZM_NEW_MAIL_MODULES. Improve debugging and reduce Info logging 2021-09-10 18:01:04 -04:00
Isaac Connor b75cc07028 Add an example fail2ban rule as provided by Pedulla 2021-09-10 18:00:52 -04:00
Isaac Connor 8c92fa5dc3 Fix version of Crud 2021-09-09 13:26:59 -04:00
Isaac Connor f5b4fbeb89 Bump version for release 2021-09-08 19:22:13 -04:00
Isaac Connor 12fcae7574 canView takes a user object, not a string 2021-09-08 19:08:08 -04:00
Isaac Connor 6b095a17aa Fix std::string to const char * 2021-09-08 18:46:58 -04:00
Isaac Connor ad1db2c960 Only suspend/resume motion detection if the monitor is doing motion detection 2021-09-08 13:59:34 -04:00
Isaac Connor a42786afd1 Add missing Importance member 2021-09-08 13:59:21 -04:00
criadoperez 4d09e5f93f Fixed broken wiki links 2021-09-08 13:59:07 -04:00
criadoperez 8e051ca5f2 Fixed issue #3338 2021-09-08 13:58:51 -04:00
Peter Keresztes Schmidt 9cc4bbbe86 LocalCamera: Add a missing include to fix FreeBSD build
Fixes #3330
2021-09-08 13:58:34 -04:00
Isaac Connor 1f5ae94c20 Fix errors due to not stopping the dbQueue. Fix crash when querying v4l devices 2021-09-04 12:33:23 -04:00
Isaac Connor 0e24b03785 Make failure to resolve non fatal 2021-09-04 12:17:50 -04:00
Isaac Connor ae90ebf74f Return early if packetqueue is empty instead of getting the lock. Return early in clear() if we are not initialised 2021-09-04 12:16:18 -04:00
Isaac Connor 09d4f0f9c2 Add missing fields. defaults need to be quoted because they are evalled 2021-09-04 10:44:12 -04:00
Isaac Connor 29e8d39c74 defaults need to be quoted because they are evalled 2021-09-04 10:44:04 -04:00
Isaac Connor ae0f7acd56 add support for building impish 2021-09-04 10:33:33 -04:00
Isaac Connor 3e3ce151d8 Add impish to possible distros to build 2021-09-04 10:18:44 -04:00
anon8675309 4cbda5e645 Simplified branch detection in build scripts for GitLab CI 2021-09-04 10:14:37 -04:00
anon8675309 f5272c99b3 Attempting to build the lastest tag instead of a hard-coded one 2021-09-04 10:14:27 -04:00
anon8675309 74641731bb Attempting to build the lastest tag instead of a hard-coded one 2021-09-04 10:14:15 -04:00
anon8675309 a03b505324 Attempting to build the lastest tag instead of a hard-coded one 2021-09-04 10:14:02 -04:00
anon8675309 85504a8d01 Attempting to build the lastest tag instead of a hard-coded one 2021-09-04 10:13:13 -04:00
anon8675309 2f32098f46 Attempting to build the lastest tag instead of a hard-coded one 2021-09-04 10:13:02 -04:00
anon8675309 022e9ad014 Attempting to build the lastest tag instead of a hard-coded one 2021-09-04 10:12:49 -04:00
anon8675309 6e32de6a91 Attempting to build the lastest tag instead of a hard-coded one 2021-09-04 10:12:08 -04:00
anon8675309 15ed9bf06d Attempting to build the lastest tag instead of a hard-coded one 2021-09-04 10:11:57 -04:00
anon8675309 3cf48fc51c Attempting to build the lastest tag instead of a hard-coded one 2021-09-04 10:11:47 -04:00
anon8675309 ffb0e0dd8c Attempting to build the lastest tag instead of a hard-coded one 2021-09-04 10:11:35 -04:00
anon8675309 ddecc3800b Attempting to build the lastest tag instead of a hard-coded one 2021-09-04 10:11:17 -04:00
anon8675309 d4c2c99de4 Attempting to build the lastest tag instead of a hard-coded one 2021-09-04 10:11:06 -04:00
anon8675309 456290ca19 Enable gitlab CI to build debian packages 2021-09-04 10:10:50 -04:00
Peter Keresztes Schmidt bc7079a0be utils/startpackpack: Add support for Debian Bullseye 2021-09-04 10:08:10 -04:00
gmanproxtreme 75b16c9084 Updated WEB_TITLE section from ToDo.
The Web_Title's use was unknown. I have seen the changed title appear on the login screen. Documentation updated to reflect this.
2021-09-04 10:04:53 -04:00
Peter Keresztes Schmidt 53cbe524c1 docs: Reference current 1.36 version in installation instructions 2021-09-04 10:04:33 -04:00
Isaac Connor b0df3fd2f6 Fix user summary, as there must be a space between -u and dbuser, etc 2021-09-04 10:02:43 -04:00
Isaac Connor 583c259951 Spacing 2021-09-04 10:02:16 -04:00
Isaac Connor 8da5a1d2c9 If we fail to suspend/resume, assume we need to disconnect/reconnect to the mmap 2021-09-04 10:00:55 -04:00
Isaac Connor 9ebff52c44 Add a 5 second timeout when setting suspending/resuming motion detection.Log errors appropriately 2021-09-04 10:00:42 -04:00
Isaac Connor e9dab48834 Fix building SQL for ExistsInFileSystem PostCondition. Fix how we turn the rows into Event objects. Fix value handling in ExistsInFileSystem post condition. 2021-09-04 09:59:38 -04:00
Isaac Connor 4612f4ae75 add getMonitorStatuses function to return string values for status numbers 2021-09-04 09:57:56 -04:00
Isaac Connor f319d02373 Fix PTZ Diagonal cmds. Fixes #3300 2021-09-03 22:39:44 -04:00
Isaac Connor e529d8fcd1 add autoplay tag. Fixes #3343 2021-09-03 22:29:45 -04:00
Isaac Connor 55080da9dc Don't use AUTH_HASH_IPS when talking to zmu as it doesn't support that at this time. 2021-09-03 22:29:16 -04:00
Isaac Connor be46b063c8 spacing 2021-09-03 22:28:59 -04:00
r01k 2d965e7d50 Fixed bug that caused 'Call to undefined function Error()' in control_functions.php. Exception was being raised due to logger.php not being included in control_functions.php. 2021-09-03 22:28:37 -04:00
Isaac Connor 4c213ab453 Only show thumbnail if Function is != None 2021-09-03 22:28:08 -04:00
Isaac Connor 706e2ff536 spacing. When the selected layout is not freeform, calculate the ratio of computed size to stream source size to calculate a value for scale. 2021-09-03 22:27:53 -04:00
Isaac Connor dab9bce8f4 spacing remove dead code 2021-09-03 22:27:26 -04:00
Isaac Connor 25f6935280 Allow snapshot downloading 2021-09-03 22:26:51 -04:00
Isaac Connor 4c261eb413 Use event->canView so that events in snapshots can be exported 2021-09-03 22:26:26 -04:00
Isaac Connor ca4ec91ef3 Move CSP stuff down to view parsing. ajax requests only output json, so CSP shouldn't be relevant. Only end output buffer if there is one. archive view for example clears all output buffers. 2021-09-03 22:26:09 -04:00
Isaac Connor 30aad6ab9a Spacing and quotes 2021-09-03 22:25:43 -04:00
Isaac Connor bed79039f3 implement Event->canView 2021-09-03 22:25:14 -04:00
Isaac Connor d884d86b38 Can export events if canView Snapshots. Event->canView will filter events that are not in a snapshot 2021-09-03 22:23:30 -04:00
Isaac Connor 391fc1fec8 Use canView.Snapshots for snapshot permissions instead of canView.Events 2021-09-03 22:23:09 -04:00
Isaac Connor a1fe4e2638 Change monitor->canView semantics so that a specified monitorId trumps the Monitors:None setting. This is so that the console can be hidden, but the group dropdown still gets populated. 2021-09-03 22:22:47 -04:00
Isaac Connor a2f3583481 turn Save, SaveAs and Execute into submit buttons. Put Id into the form action so that it shows up in the url bar. Disable putting the form contents into the url bar in parse_rows. 2021-09-03 22:22:16 -04:00
Isaac Connor 809183716a clean up code logic so that if we are executing an unchanged filter we don't do the unecessary save. 2021-09-03 22:21:40 -04:00
Isaac Connor 3ca920f1a9 spacing. Add defaults for AutoMoveTo and AutoCopyTo so that we don't get false changes. redirect is a global, so make it so. Re-null the Id of the filter object after temp execute so that we don't reference a no longer existing filter. 2021-09-03 22:21:21 -04:00
Isaac Connor 2e09334b9c Fix url to ajax endpoint for deleting snapshots. 2021-09-03 22:20:51 -04:00
Isaac Connor a69882ffaf Cleanup, split export and download functions in snapshot. 2021-09-03 22:20:27 -04:00
Isaac Connor b306f92116 event may have changed (have endtime set) between load and saving Archived bit. Lock the event which now also reloads it. 2021-09-03 22:19:57 -04:00
Isaac Connor ae1e3d88b2 When locking, use the results to reload the object fields fresh as they may have changed since the object was loaded 2021-09-03 22:19:39 -04:00
Isaac Connor cf87f2cc40 Snapshot exports are now flat, without frame images 2021-09-03 22:19:19 -04:00
Isaac Connor c089702fab Pass exportStructure around so that it can be used to put the right filename to the jpg in the html. Fix flat zips. Fix video files being included if exportMisc is true. 2021-09-03 22:19:02 -04:00
Isaac Connor 2c7af3886c Actually delete the snapshot record, not just the event connection 2021-09-03 22:18:38 -04:00
Isaac Connor 2f7c44dce2 Fix deleting snapshots 2021-09-03 22:18:24 -04:00
Isaac Connor ad9ce720fd Allow specifying export Structure to get a flat zip 2021-09-03 22:18:06 -04:00
Isaac Connor 50326cf80c zmu may still output results even if it encounters errors, so continue even if we have an error return status from zmu. 2021-09-03 22:17:15 -04:00
Isaac Connor c280279cf7 Continue to work even if opener isn't defined, which it isn't anymore because we got rid of popups. Fixes save button on monitorprobe. 2021-09-03 22:16:30 -04:00
Isaac Connor 6a6d6935e8 Merge script sections just to remove bytes per view. 2021-09-03 22:15:56 -04:00
Isaac Connor 814e8559aa output an error message image when we can't load a jpeg 2021-09-03 22:15:29 -04:00
Isaac Connor 8d5207636a Turn on export functionality for snapshots 2021-09-03 22:15:08 -04:00
Isaac Connor 2273deaf17 Support specifying the export filename by passing the export_root 2021-09-03 22:14:42 -04:00
Isaac Connor 06ff94de2f Fix styling of the shutdown button 2021-09-03 22:13:55 -04:00
Steven Gilvarry 59a03d6d59 Update Dark Skin to fix modal being light and some other issues
Fixed Modal style as per post to forums, then also fixed text area and select being white. Adjusted some other colours to work better. Console hover could be better still
2021-09-03 22:13:26 -04:00
Isaac Connor 17ec2f922e Remove some debug logging 2021-09-03 22:11:51 -04:00
Isaac Connor 6d9c582e13 Merge an old stash allowing passing an alternate buttonconfig parameter to show/get Modal 2021-09-03 22:11:14 -04:00
Isaac Connor cb58b70078 Leave the zoneminder source dir during build-deps step so that we don't pollute it. Seems to be an issue with newer ubuntu builds. 2021-09-03 21:59:36 -04:00
Isaac Connor 431417ea8b Use old time code as this is 1.36 2021-09-03 12:19:53 -04:00
Isaac Connor e0e81a3769 Fix frame_count fps when paused 2021-09-03 11:39:34 -04:00
Isaac Connor 94662dc170 Fix viewing fps display by keeping track of last update time, last frame count and actually calculate it based on frames sent over a period of time. 2021-09-03 11:38:09 -04:00
Isaac Connor 132fc84c31 Spacing and fix the bogus setting shared_data->valid to false on disconnect. Other processes call disconnect. Only the capturing thread should set it to false. 2021-09-03 11:35:54 -04:00
Isaac Connor c00651e826 Spacing 2021-09-03 11:34:32 -04:00
gmanproxtreme 6a0e27db4c Updated WEB_TITLE section from ToDo.
The Web_Title's use was unknown. I have seen the changed title appear on the login screen. Documentation updated to reflect this.
2021-08-29 09:31:37 -04:00
Isaac Connor 199e86e92a spacing 2021-08-29 09:30:52 -04:00
Isaac Connor 85ade02cba Set shm->valid to false on disconnect. 2021-08-29 09:30:30 -04:00
149 changed files with 3145 additions and 1768 deletions

2
.github/FUNDING.yml vendored
View File

@ -2,7 +2,7 @@
github: [connortechnology,pliablepixels] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] github: [connortechnology,pliablepixels] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: zoneminder # Replace with a single Patreon username patreon: zoneminder # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username open_collective: zoneminder # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry

View File

@ -9,7 +9,7 @@ on:
jobs: jobs:
build: build:
runs-on: ubuntu-16.04 runs-on: zm-xenial-ci
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2

17
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,17 @@
default:
image:
name: ubuntu:latest
before_script:
- apt-get update -yq
- DEBIAN_FRONTEND=noninteractive apt-get install -yq devscripts sudo
deb:
stage: build
tags:
- docker
script:
- yes "" | ./utils/do_debian_package.sh --snapshot=stable --type=binary --interactive=no --dput=no --debbuild-extra=--no-sign || true
artifacts:
paths:
- '*.deb'
expire_in: 1 week

View File

@ -30,7 +30,7 @@ This is the recommended method to install ZoneMinder onto your system. ZoneMinde
- Debian from their [default repository](https://packages.debian.org/search?searchon=names&keywords=zoneminder) - Debian from their [default repository](https://packages.debian.org/search?searchon=names&keywords=zoneminder)
- RHEL/CentOS and clones via [RPM Fusion](http://rpmfusion.org) - RHEL/CentOS and clones via [RPM Fusion](http://rpmfusion.org)
- Fedora via [RPM Fusion](http://rpmfusion.org) - Fedora via [RPM Fusion](http://rpmfusion.org)
- OpenSuse via [third party repository](http://www.zoneminder.com/wiki/index.php/Installing_using_ZoneMinder_RPMs_for_SuSE) - OpenSuse via [third party repository](https://wiki.zoneminder.com/Installing_using_ZoneMinder_RPMs_for_SuSE)
- Mageia from their default repository - Mageia from their default repository
- Arch via the [AUR](https://aur.archlinux.org/packages/zoneminder/) - Arch via the [AUR](https://aur.archlinux.org/packages/zoneminder/)
- Gentoo via [Portage Overlays](http://gpo.zugaina.org/www-misc/zoneminder) - Gentoo via [Portage Overlays](http://gpo.zugaina.org/www-misc/zoneminder)

View File

@ -539,7 +539,7 @@ CREATE TABLE `Monitors` (
`Longitude` DECIMAL(11,8), `Longitude` DECIMAL(11,8),
`RTSPServer` BOOLEAN NOT NULL DEFAULT FALSE, `RTSPServer` BOOLEAN NOT NULL DEFAULT FALSE,
`RTSPStreamName` varchar(255) NOT NULL default '', `RTSPStreamName` varchar(255) NOT NULL default '',
`Importance` enum('Not','Less','Normal'), `Importance` enum('Normal','Less','Not') NOT NULL default 'Normal',
PRIMARY KEY (`Id`) PRIMARY KEY (`Id`)
) ENGINE=@ZM_MYSQL_ENGINE@; ) ENGINE=@ZM_MYSQL_ENGINE@;

View File

@ -28,8 +28,8 @@ SET @s = (SELECT IF(
AND table_name = 'Monitors' AND table_name = 'Monitors'
AND column_name = 'TotalEvents' AND column_name = 'TotalEvents'
) > 0, ) > 0,
"SELECT 'Column TotalEvents is already removed from Monitors'", "ALTER TABLE `Monitors` DROP `TotalEvents`",
"ALTER TABLE `Monitors` DROP `TotalEvents`" "SELECT 'Column TotalEvents is already removed from Monitors'"
)); ));
PREPARE stmt FROM @s; PREPARE stmt FROM @s;
EXECUTE stmt; EXECUTE stmt;
@ -50,8 +50,8 @@ SET @s = (SELECT IF(
AND table_name = 'Monitors' AND table_name = 'Monitors'
AND column_name = 'TotalEventDiskSpace' AND column_name = 'TotalEventDiskSpace'
) > 0, ) > 0,
"SELECT 'Column TotalEventDiskSpace is already removed from Monitors'", "ALTER TABLE `Monitors` DROP `TotalEventDiskSpace`",
"ALTER TABLE `Monitors` DROP `TotalEventDiskSpace`" "SELECT 'Column TotalEventDiskSpace is already removed from Monitors'"
)); ));
PREPARE stmt FROM @s; PREPARE stmt FROM @s;
EXECUTE stmt; EXECUTE stmt;

2
db/zm_update-1.36.9.sql Normal file
View File

@ -0,0 +1,2 @@
UPDATE Monitors set Importance = 'Normal' where Importance IS NULL;
ALTER TABLE `Monitors` MODIFY `Importance` enum('Normal','Less','Not') NOT NULL default 'Normal';

View File

@ -36,7 +36,7 @@
%global _hardened_build 1 %global _hardened_build 1
Name: zoneminder Name: zoneminder
Version: 1.36.5 Version: 1.36.12
Release: 1%{?dist} Release: 1%{?dist}
Summary: A camera monitoring and analysis tool Summary: A camera monitoring and analysis tool
Group: System Environment/Daemons Group: System Environment/Daemons
@ -430,6 +430,27 @@ ln -sf %{_sysconfdir}/zm/www/zoneminder.nginx.conf %{_sysconfdir}/zm/www/zonemin
%dir %attr(755,nginx,nginx) %{_localstatedir}/log/zoneminder %dir %attr(755,nginx,nginx) %{_localstatedir}/log/zoneminder
%changelog %changelog
* Fri Dec 10 2021 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.36.12-1
- 1.36.12 release
* Wed Nov 17 2021 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.36.11-1
- 1.36.11 release
* Mon Oct 25 2021 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.36.10-1
- 1.36.10 release
* Tue Oct 19 2021 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.36.9-1
- 1.36.9 release
* Wed Oct 06 2021 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.36.8-1
- 1.36.8 release
* Mon Sep 13 2021 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.36.7-1
- 1.36.7 release
* Wed Sep 08 2021 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.36.6-1
- 1.36.6 release
* Tue Jun 22 2021 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.36.5-1 * Tue Jun 22 2021 Andrew Bauer <zonexpertconsulting@outlook.com> - 1.36.5-1
- 1.36.5 release - 1.36.5 release

View File

@ -5,6 +5,12 @@ set +e
create_db () { create_db () {
echo "Checking for db" echo "Checking for db"
mysqladmin --defaults-file=/etc/mysql/debian.cnf -f reload mysqladmin --defaults-file=/etc/mysql/debian.cnf -f reload
if [ $? -ne 0 ]; then
echo "Cannot talk to database. You will have to create the db manually with something like:";
echo "cat /usr/share/zoneminder/db/zm_create.sql | mysql -u root";
return;
fi
# test if database if already present... # test if database if already present...
if ! $(echo quit | mysql --defaults-file=/etc/mysql/debian.cnf zm > /dev/null 2> /dev/null) ; then if ! $(echo quit | mysql --defaults-file=/etc/mysql/debian.cnf zm > /dev/null 2> /dev/null) ; then
echo "Creating zm db" echo "Creating zm db"

View File

@ -225,7 +225,7 @@ change the 3 to a 1
I can't see more than 6 monitors in montage on my browser I can't see more than 6 monitors in montage on my browser
--------------------------------------------------------- ---------------------------------------------------------
Browsers such a Chrome and Safari only support upto 6 streams from the same domain. To work around that, take a look at the multi-port configuration discussed in the ``MIN_STREAMING_PORT`` configuration in :doc:`/userguide/options/options_network` Browsers such a Chrome and Safari only support up to 6 streams from the same domain. To work around that, take a look at the multi-port configuration discussed in the ``MIN_STREAMING_PORT`` configuration in :doc:`/userguide/options/options_network`
Why is ZoneMinder using so much CPU? Why is ZoneMinder using so much CPU?
--------------------------------------- ---------------------------------------

View File

@ -3,6 +3,50 @@ Debian
.. contents:: .. contents::
Easy Way: Debian 11 (Bullseye)
------------------------------
This procedure will guide you through the installation of ZoneMinder on Debian 11 (Bullseye).
**Step 1:** Setup Sudo (optional but recommended)
By default Debian does not come with sudo, so you have to install it and configure it manually.
This step is optional but recommended and the following instructions assume that you have setup sudo.
If you prefer to setup ZoneMinder as root, do it at your own risk and adapt the following instructions accordingly.
::
apt install sudo
usermod -a -G sudo <username>
exit
Now your terminal session is back under your normal user. You can check that
you are now part of the sudo group with the command ``groups``, "sudo" should
appear in the list. If not, run ``newgrp sudo`` and check again with ``groups``.
**Step 2:** Update system and install zoneminder
Run the following commands.
::
sudo apt update
sudo apt upgrade
sudo apt install mariadb-server
sudo apt install zoneminder
When mariadb is installed for the first time, it doesn't add a password to the root user. Therefore, for security, it is recommended to run ``mysql secure installation``.
**Step 3:** Setup permissions for zm.conf
To make sure zoneminder can read the configuration file, run the following command.
::
sudo chgrp -c www-data /etc/zm/zm.conf
Congratulations! You should now be able to access zoneminder at ``http://yourhostname/zm``
Easy Way: Debian Buster Easy Way: Debian Buster
------------------------ ------------------------
@ -56,13 +100,13 @@ Add the following to the /etc/apt/sources.list.d/zoneminder.list file
:: ::
# ZoneMinder repository # ZoneMinder repository
deb https://zmrepo.zoneminder.com/debian/release-1.34 buster/ deb https://zmrepo.zoneminder.com/debian/release-1.36 buster/
You can do this using: You can do this using:
.. code-block:: ::
echo "deb https://zmrepo.zoneminder.com/debian/release-1.34 buster/" | sudo tee /etc/apt/sources.list.d/zoneminder.list echo "deb https://zmrepo.zoneminder.com/debian/release-1.36 buster/" | sudo tee /etc/apt/sources.list.d/zoneminder.list
Because ZoneMinder's package repository provides a secure connection through HTTPS, apt must be enabled for HTTPS. Because ZoneMinder's package repository provides a secure connection through HTTPS, apt must be enabled for HTTPS.
:: ::
@ -158,7 +202,7 @@ You are now ready to go with ZoneMinder. Open a browser and type either ``localh
Easy Way: Debian Stretch Easy Way: Debian Stretch
------------------------ ------------------------
This procedure will guide you through the installation of ZoneMinder on Debian 9 (Stretch). This section has been tested with ZoneMinder 1.34 on Debian 9.8. This procedure will guide you through the installation of ZoneMinder on Debian 9 (Stretch). This section has been tested with ZoneMinder 1.36 on Debian 9.8.
**Step 1:** Make sure your system is up to date **Step 1:** Make sure your system is up to date
@ -204,7 +248,7 @@ Add the following to the bottom of the file
:: ::
# ZoneMinder repository # ZoneMinder repository
deb https://zmrepo.zoneminder.com/debian/release-1.34 stretch/ deb https://zmrepo.zoneminder.com/debian/release-1.36 stretch/
CTRL+o and <Enter> to save CTRL+o and <Enter> to save
CTRL+x to exit CTRL+x to exit

View File

@ -2,7 +2,7 @@ An Easy To Use Docker Image
=========================== ===========================
If you are interested in trying out ZoneMinder quickly, user Dan Landon maintains an easy to use docker image for ZoneMinder. With a few simple configuration changes, it also provides complete Event Notification Server and Machine Learning hook support. Please follow instructions in his repostory. He maintains two repositories: If you are interested in trying out ZoneMinder quickly, user Dan Landon maintains an easy to use docker image for ZoneMinder. With a few simple configuration changes, it also provides complete Event Notification Server and Machine Learning hook support. Please follow instructions in his repostory. He maintains two repositories:
* If you want to run the latest stable release, please use his `zoneminder repository <https://github.com/dlandon/zoneminder>`__. * If you want to run the latest stable release, please use his `zoneminder machine learning repository <https://github.com/dlandon/zoneminder.machine.learning>`__.
* If you want to run the latest zoneminder master, please use his `zoneminder master repository <https://github.com/dlandon/zoneminder.master-docker>`__. * If you want to run the latest zoneminder master, please use his `zoneminder master repository <https://github.com/dlandon/zoneminder.master-docker>`__.
In both cases, instructions are provided in the repo README files. In both cases, instructions are provided in the repo README files.

View File

@ -41,7 +41,7 @@ guide you with a quick search.
:: ::
add-apt-repository ppa:iconnor/zoneminder-1.34 add-apt-repository ppa:iconnor/zoneminder-1.36
Update repo and upgrade. Update repo and upgrade.

View File

@ -86,7 +86,7 @@ Source Path
Use this field to enter the full URL of the stream or file your camera supports. This is usually an RTSP url. There are several methods to learn this: Use this field to enter the full URL of the stream or file your camera supports. This is usually an RTSP url. There are several methods to learn this:
* Check the documentation that came with your camera * Check the documentation that came with your camera
* Look for your camera in the hardware compatibilty list in the `hardware compatibility wiki <https://wiki.zoneminder.com/Hardware_Compatibility_List>`__ * Look for your camera in the hardware compatibility list in the `hardware compatibility wiki <https://wiki.zoneminder.com/Hardware_Compatibility_List>`__
* Try ZoneMinder's new ONVIF probe feature * Try ZoneMinder's new ONVIF probe feature
* Download and install the `ONVIF Device Manager <https://sourceforge.net/projects/onvifdm/>`__ onto a Windows machine * Download and install the `ONVIF Device Manager <https://sourceforge.net/projects/onvifdm/>`__ onto a Windows machine
* Use Google to find third party sites, such as ispy, which document this information * Use Google to find third party sites, such as ispy, which document this information
@ -179,12 +179,12 @@ Storage Tab
The storage section allows for each monitor to configure if and how video and audio are recorded. The storage section allows for each monitor to configure if and how video and audio are recorded.
Save JPEGs Save JPEGs
Records video in individual JPEG frames. Storing JPEG frames requires more storage space than h264 but it allows to view an event anytime while it is being recorded. Records video in individual JPEG frames. Storing JPEG frames requires more storage space than h264 but it allows one to view an event anytime while it is being recorded.
* Disabled video is not recorded as JPEG frames. If this setting is selected, then "Video Writer" should be enabled otherwise there is no video recording at all. * Disabled video is not recorded as JPEG frames. If this setting is selected, then "Video Writer" should be enabled otherwise there is no video recording at all.
* Frames only video is recorded in individual JPEG frames. * Frames only video is recorded in individual JPEG frames.
* Analysis images only (if available) video is recorded in invidual JPEG frames with an overlay of the motion detection analysis information. Note that this overlay remains permanently visible in the frames. * Analysis images only (if available) video is recorded in individual JPEG frames with an overlay of the motion detection analysis information. Note that this overlay remains permanently visible in the frames.
* Frames + Analysis images (if available) video is recorded twice, once as normal individual JPEG frames and once in invidual JPEG frames with analysis information overlaid. * Frames + Analysis images (if available) video is recorded twice, once as normal individual JPEG frames and once in individual JPEG frames with analysis information overlaid.
Video Writer Video Writer
Records video in real video format. It provides much better compression results than saving JPEGs, thus longer video history can be stored. Records video in real video format. It provides much better compression results than saving JPEGs, thus longer video history can be stored.

View File

@ -34,7 +34,7 @@ Here is what the filter window looks like
* Update used disk space: calculates how much disk space is currently taken by the event and updates the db record. * Update used disk space: calculates how much disk space is currently taken by the event and updates the db record.
* Create video for all matches: creates a video file of all the events that match * Create video for all matches: creates a video file of all the events that match
* Create video for all matches: ffmpeg will be used to create a video file (mp4) out of all the stored jpgs if using jpeg storage. * Create video for all matches: ffmpeg will be used to create a video file (mp4) out of all the stored jpgs if using jpeg storage.
* Execute command on all matches: Allows you to execute any arbitrary command on the matched events. You can use replacement tokens as subsequent arguents to the command, the last argument will be the absolute path to the event, preceeded by replacement arguents. eg: /usr/bin/script.sh %MN% will excecute as /usr/bin/script.sh MonitorName /path/to/event. Please note that urls may contain characters like & that need quoting. So you may need to put quotes around them like /usr/bin/scrupt.sh "%MN%". * Execute command on all matches: Allows you to execute any arbitrary command on the matched events. You can use replacement tokens as subsequent arguents to the command, the last argument will be the absolute path to the event, preceded by replacement arguents. eg: /usr/bin/script.sh %MN% will execute as /usr/bin/script.sh MonitorName /path/to/event. Please note that urls may contain characters like & that need quoting. So you may need to put quotes around them like /usr/bin/scrupt.sh "%MN%".
* Delete all matches: Deletes all the matched events. * Delete all matches: Deletes all the matched events.
* Email details of all matches: Sends an email to the configured address with details about the event. * Email details of all matches: Sends an email to the configured address with details about the event.
* Copy all matches: copies the event files to another location, specified in the Copy To dropdown. The other location must be setup in the Storage Tab under options. * Copy all matches: copies the event files to another location, specified in the Copy To dropdown. The other location must be setup in the Storage Tab under options.

View File

@ -53,7 +53,7 @@ This screen is called the "console" screen in ZoneMinder and shows a summary of
* **B**: This brings up a color coded log window that shows various system and component level logs. This window is useful if you are trying to diagnose issues. Refer to :doc:`logging`. * **B**: This brings up a color coded log window that shows various system and component level logs. This window is useful if you are trying to diagnose issues. Refer to :doc:`logging`.
* **C**: ZoneMinder allows you to group monitors for logical separation. This option lets you create new groups, associate monitors to them and edit/delete existing groups. * **C**: ZoneMinder allows you to group monitors for logical separation. This option lets you create new groups, associate monitors to them and edit/delete existing groups.
* **D**: Filters are a powerful mechanism to perform actions when certain conditions are met. ZoneMinder comes with some preset filters that keep a tab of disk space and others. Many users create their own filters for more advanced actions like sending emails when certain events occur and more. Refer to :doc:`filterevents`. * **D**: Filters are a powerful mechanism to perform actions when certain conditions are met. ZoneMinder comes with some preset filters that keep a tab of disk space and others. Many users create their own filters for more advanced actions like sending emails when certain events occur and more. Refer to :doc:`filterevents`.
* **E**: The Cycle option allows you to rotate between live views of each cofigured monitor. * **E**: The Cycle option allows you to rotate between live views of each configured monitor.
* **F**: The Montage option shows a collage of your monitors. You can customize them including moving them around. * **F**: The Montage option shows a collage of your monitors. You can customize them including moving them around.
* **G**: Montage Review allows you to simultaneously view past events for different monitors. Note that this is a very resource intensive page and its performance will vary based on your system capabilities. * **G**: Montage Review allows you to simultaneously view past events for different monitors. Note that this is a very resource intensive page and its performance will vary based on your system capabilities.
* **H**: Audit Events Report is more of a power user feature. This option looks for recording gaps in events and recording issues in mp4 files. * **H**: Audit Events Report is more of a power user feature. This option looks for recording gaps in events and recording issues in mp4 files.

View File

@ -101,6 +101,6 @@ FROM_EMAIL - The emails or messages that will be sent to you informing you of ev
URL - The emails or messages that will be sent to you informing you of events can include a link to the events themselves for easy viewing. If you intend to use this feature then set this option to the url of your installation as it would appear from where you read your email, e.g. ``http://host.your.domain/zm/index.php``. URL - The emails or messages that will be sent to you informing you of events can include a link to the events themselves for easy viewing. If you intend to use this feature then set this option to the url of your installation as it would appear from where you read your email, e.g. ``http://host.your.domain/zm/index.php``.
SSMTP_MAIL - SSMTP is a lightweight and efficient method to send email. The SSMTP application is not installed by default. NEW_MAIL_MODULES must also be enabled. Please visit the ZoneMinder `SSMTP Wiki page <http://www.zoneminder.com/wiki/index.php/How_to_get_ssmtp_working_with_Zoneminder>`__ for setup and configuration help. SSMTP_MAIL - SSMTP is a lightweight and efficient method to send email. The SSMTP application is not installed by default. NEW_MAIL_MODULES must also be enabled. Please visit the ZoneMinder `SSMTP Wiki page <https://wiki.zoneminder.com/How_to_get_ssmtp_working_with_Zoneminder>`__ for setup and configuration help.
SSMTP_PATH - The path to the SSMTP application. If path is not defined. Zoneminder will try to determine the path via shell command. Example path: /usr/sbin/ssmtp. SSMTP_PATH - The path to the SSMTP application. If path is not defined. Zoneminder will try to determine the path via shell command. Example path: /usr/sbin/ssmtp.

View File

@ -30,7 +30,7 @@ This screen allows you to configure various permissions on a per user basis. The
.. note:: if you are using zmNinja, users are required to have 'View' access to system because multi-server information is only available as part of this permission .. note:: if you are using zmNinja, users are required to have 'View' access to system because multi-server information is only available as part of this permission
- Bandwidth - Bandwidth
- Specifies the maximum bandwith that this user can configure (Low, Medium or High) - Specifies the maximum bandwidth that this user can configure (Low, Medium or High)
- API enabled - API enabled
- Specifies if the ZoneMinder API is enabled for this user (needs to be on, if you are using a mobile app such as zmNinja) - Specifies if the ZoneMinder API is enabled for this user (needs to be on, if you are using a mobile app such as zmNinja)
@ -42,4 +42,4 @@ Here is an example of a restricted user, for example:
.. image:: images/Options_Users_Example.png .. image:: images/Options_Users_Example.png
This user "home" is enabled, can view live streams and events, but only from "DoorBell" and "DeckCamera". This user also cannot control PTZ. This user "home" is enabled, can view live streams and events, but only from "DoorBell" and "DeckCamera". This user also cannot control PTZ.

View File

@ -5,10 +5,7 @@ This screen lets you customize several aspects of the web interface of ZoneMinde
.. image:: images/Options_web.png .. image:: images/Options_web.png
WEB_TITLE - WEB_TITLE - The actual text that is shown on the login screen. It is possible that it also appears in other areas.
.. todo ::
not quite sure what this does. Seems to change the "target" name - not sure what effect it is supposed to have.
WEB_TITLE_PREFIX - If you have more than one installation of ZoneMinder it can be helpful to display different titles for each one. Changing this option allows you to customise the window titles to include further information to aid identification. WEB_TITLE_PREFIX - If you have more than one installation of ZoneMinder it can be helpful to display different titles for each one. Changing this option allows you to customise the window titles to include further information to aid identification.
@ -48,4 +45,4 @@ WEB_USE_OBJECT_TAGS - There are two methods of including media content in web pa
WEB_XFRAME_WARN - When creating a Web Site monitor, if the target web site has X-Frame-Options set to sameorigin in the header, the site will not display in ZoneMinder. This is a design feature in most modern browsers. When this condition occurs, ZoneMinder will write a warning to the log file. To get around this, one can install a browser plugin or extension to ignore X-Frame headers, and then the page will display properly. Once the plugin or extension has ben installed, the end user may choose to turn this warning off WEB_XFRAME_WARN - When creating a Web Site monitor, if the target web site has X-Frame-Options set to sameorigin in the header, the site will not display in ZoneMinder. This is a design feature in most modern browsers. When this condition occurs, ZoneMinder will write a warning to the log file. To get around this, one can install a browser plugin or extension to ignore X-Frame headers, and then the page will display properly. Once the plugin or extension has ben installed, the end user may choose to turn this warning off
WEB_FILTER_SOURCE - This option only affects monitors with a source type of Ffmpeg, Libvlc, or WebSite. This setting controls what information is displayed in the Source column on the console. Selecting 'None' will not filter anything. The entire source string will be displayed, which may contain sensitive information. Selecting 'NoCredentials' will strip out usernames and passwords from the string. If there are any port numbers in the string and they are common (80, 554, etc) then those will be removed as well. Selecting 'Hostname' will filter out all information except for the hostname or ip address. When in doubt, stay with the default 'Hostname'. This feature uses the php function 'url_parts' to identify the various pieces of the url. If the url in question is unusual or not standard in some way, then filtering may not produce the desired results. WEB_FILTER_SOURCE - This option only affects monitors with a source type of Ffmpeg, Libvlc, or WebSite. This setting controls what information is displayed in the Source column on the console. Selecting 'None' will not filter anything. The entire source string will be displayed, which may contain sensitive information. Selecting 'NoCredentials' will strip out usernames and passwords from the string. If there are any port numbers in the string and they are common (80, 554, etc) then those will be removed as well. Selecting 'Hostname' will filter out all information except for the hostname or ip address. When in doubt, stay with the default 'Hostname'. This feature uses the php function 'url_parts' to identify the various pieces of the url. If the url in question is unusual or not standard in some way, then filtering may not produce the desired results.

2
misc/fail2ban.rules Normal file
View File

@ -0,0 +1,2 @@
failregex = ^\s*web_php\[\d+\]\.ERR \[<HOST>\].*includes/auth.php
datepattern = ^%%m/%%d/%%y %%H:%%M:%%S(?:\.%%f)

View File

@ -147,7 +147,7 @@ This attribute is of type L<SOAP::WSDL::XSD::Typelib::Builtin::integer|SOAP::WSD
=item * Cells =item * Cells
A 1 denotes a cell where motion is detected and a 0 an empty cell. The first cell is in the upper left corner. Then the cell order goes first from left to right and then from up to down. If the number of cells is not a multiple of 8 the last byte is filled with zeros. The information is run length encoded according to Packbit coding in ISO 12369 (TIFF, Revision 6.0). A "1" denotes a cell where motion is detected and a "0" an empty cell. The first cell is in the upper left corner. Then the cell order goes first from left to right and then from up to down. If the number of cells is not a multiple of 8 the last byte is filled with zeros. The information is run length encoded according to Packbit coding in ISO 12369 (TIFF, Revision 6.0).

View File

@ -2709,7 +2709,7 @@ Returns a L<ONVIF::Device::Elements::GetNetworkInterfacesResponse|ONVIF::Device:
=head3 SetNetworkInterfaces =head3 SetNetworkInterfaces
For interoperability with a client unaware of the IEEE 802.11 extension a device shall retain its IEEE 802.11 configuration if the IEEE 802.11 configuration element isnt present in the request. For interoperability with a client unaware of the IEEE 802.11 extension a device shall retain its IEEE 802.11 configuration if the IEEE 802.11 configuration element isn't present in the request.
Returns a L<ONVIF::Device::Elements::SetNetworkInterfacesResponse|ONVIF::Device::Elements::SetNetworkInterfacesResponse> object. Returns a L<ONVIF::Device::Elements::SetNetworkInterfacesResponse|ONVIF::Device::Elements::SetNetworkInterfacesResponse> object.
@ -3093,7 +3093,7 @@ Returns a L<ONVIF::Device::Elements::SetRelayOutputStateResponse|ONVIF::Device::
=head3 SendAuxiliaryCommand =head3 SendAuxiliaryCommand
tt:IRLamp|Auto Request to configure an IR illuminator attached to the unit so that it automatically turns ON and OFF. A device that indicates auxiliary service capability shall support this command. tt:IRLamp|Auto - Request to configure an IR illuminator attached to the unit so that it automatically turns ON and OFF. A device that indicates auxiliary service capability shall support this command.
Returns a L<ONVIF::Device::Elements::SendAuxiliaryCommandResponse|ONVIF::Device::Elements::SendAuxiliaryCommandResponse> object. Returns a L<ONVIF::Device::Elements::SendAuxiliaryCommandResponse|ONVIF::Device::Elements::SendAuxiliaryCommandResponse> object.
@ -3288,7 +3288,7 @@ Returns a L<ONVIF::Device::Elements::GetSystemUrisResponse|ONVIF::Device::Elemen
=head3 StartFirmwareUpgrade =head3 StartFirmwareUpgrade
The value of the Content-Type header in the HTTP POST request shall be application/octetstream. The value of the Content-Type header in the HTTP POST request shall be "application/octetstream".
Returns a L<ONVIF::Device::Elements::StartFirmwareUpgradeResponse|ONVIF::Device::Elements::StartFirmwareUpgradeResponse> object. Returns a L<ONVIF::Device::Elements::StartFirmwareUpgradeResponse|ONVIF::Device::Elements::StartFirmwareUpgradeResponse> object.
@ -3298,7 +3298,7 @@ Returns a L<ONVIF::Device::Elements::StartFirmwareUpgradeResponse|ONVIF::Device:
=head3 StartSystemRestore =head3 StartSystemRestore
The value of the Content-Type header in the HTTP POST request shall be application/octetstream. The value of the Content-Type header in the HTTP POST request shall be "application/octetstream".
Returns a L<ONVIF::Device::Elements::StartSystemRestoreResponse|ONVIF::Device::Elements::StartSystemRestoreResponse> object. Returns a L<ONVIF::Device::Elements::StartSystemRestoreResponse|ONVIF::Device::Elements::StartSystemRestoreResponse> object.

View File

@ -147,7 +147,7 @@ This attribute is of type L<SOAP::WSDL::XSD::Typelib::Builtin::integer|SOAP::WSD
=item * Cells =item * Cells
A 1 denotes a cell where motion is detected and a 0 an empty cell. The first cell is in the upper left corner. Then the cell order goes first from left to right and then from up to down. If the number of cells is not a multiple of 8 the last byte is filled with zeros. The information is run length encoded according to Packbit coding in ISO 12369 (TIFF, Revision 6.0). A "1" denotes a cell where motion is detected and a "0" an empty cell. The first cell is in the upper left corner. Then the cell order goes first from left to right and then from up to down. If the number of cells is not a multiple of 8 the last byte is filled with zeros. The information is run length encoded according to Packbit coding in ISO 12369 (TIFF, Revision 6.0).

View File

@ -2147,7 +2147,7 @@ Returns a L<ONVIF::Media::Elements::GetAudioOutputsResponse|ONVIF::Media::Elemen
=head3 CreateProfile =head3 CreateProfile
This operation creates a new empty media profile. The media profile shall be created in the device and shall be persistent (remain after reboot). A created profile shall be deletable and a device shall set the fixed attribute to false in the returned Profile. This operation creates a new empty media profile. The media profile shall be created in the device and shall be persistent (remain after reboot). A created profile shall be deletable and a device shall set the "fixed" attribute to false in the returned Profile.
Returns a L<ONVIF::Media::Elements::CreateProfileResponse|ONVIF::Media::Elements::CreateProfileResponse> object. Returns a L<ONVIF::Media::Elements::CreateProfileResponse|ONVIF::Media::Elements::CreateProfileResponse> object.

View File

@ -44,7 +44,7 @@ not checked yet.
The current implementation of union resorts to inheriting from the base type, The current implementation of union resorts to inheriting from the base type,
which means (quoted from the XML Schema specs): "If the <list> or <union> which means (quoted from the XML Schema specs): "If the <list> or <union>
alternative is chosen, then the simple ur-type definition·." alternative is chosen, then the simple ur-type definition."

View File

@ -147,7 +147,7 @@ This attribute is of type L<SOAP::WSDL::XSD::Typelib::Builtin::integer|SOAP::WSD
=item * Cells =item * Cells
A 1 denotes a cell where motion is detected and a 0 an empty cell. The first cell is in the upper left corner. Then the cell order goes first from left to right and then from up to down. If the number of cells is not a multiple of 8 the last byte is filled with zeros. The information is run length encoded according to Packbit coding in ISO 12369 (TIFF, Revision 6.0). A "1" denotes a cell where motion is detected and a "0" an empty cell. The first cell is in the upper left corner. Then the cell order goes first from left to right and then from up to down. If the number of cells is not a multiple of 8 the last byte is filled with zeros. The information is run length encoded according to Packbit coding in ISO 12369 (TIFF, Revision 6.0).

View File

@ -44,7 +44,7 @@ not checked yet.
The current implementation of union resorts to inheriting from the base type, The current implementation of union resorts to inheriting from the base type,
which means (quoted from the XML Schema specs): "If the <list> or <union> which means (quoted from the XML Schema specs): "If the <list> or <union>
alternative is chosen, then the simple ur-type definition·." alternative is chosen, then the simple ur-type definition."

View File

@ -987,7 +987,7 @@ Returns a L<ONVIF::PTZ::Elements::GotoHomePositionResponse|ONVIF::PTZ::Elements:
=head3 SetHomePosition =head3 SetHomePosition
Operation to save current position as the home position. The SetHomePosition command returns with a failure if the home position is fixed and cannot be overwritten. If the SetHomePosition is successful, it is possible to recall the Home Position with the GotoHomePosition command. Operation to save current position as the home position. The SetHomePosition command returns with a failure if the "home" position is fixed and cannot be overwritten. If the SetHomePosition is successful, it is possible to recall the Home Position with the GotoHomePosition command.
Returns a L<ONVIF::PTZ::Elements::SetHomePositionResponse|ONVIF::PTZ::Elements::SetHomePositionResponse> object. Returns a L<ONVIF::PTZ::Elements::SetHomePositionResponse|ONVIF::PTZ::Elements::SetHomePositionResponse> object.

View File

@ -44,7 +44,7 @@ not checked yet.
The current implementation of union resorts to inheriting from the base type, The current implementation of union resorts to inheriting from the base type,
which means (quoted from the XML Schema specs): "If the <list> or <union> which means (quoted from the XML Schema specs): "If the <list> or <union>
alternative is chosen, then the simple ur-type definition·." alternative is chosen, then the simple ur-type definition."

View File

@ -147,7 +147,7 @@ This attribute is of type L<SOAP::WSDL::XSD::Typelib::Builtin::integer|SOAP::WSD
=item * Cells =item * Cells
A 1 denotes a cell where motion is detected and a 0 an empty cell. The first cell is in the upper left corner. Then the cell order goes first from left to right and then from up to down. If the number of cells is not a multiple of 8 the last byte is filled with zeros. The information is run length encoded according to Packbit coding in ISO 12369 (TIFF, Revision 6.0). A "1" denotes a cell where motion is detected and a "0" an empty cell. The first cell is in the upper left corner. Then the cell order goes first from left to right and then from up to down. If the number of cells is not a multiple of 8 the last byte is filled with zeros. The information is run length encoded according to Packbit coding in ISO 12369 (TIFF, Revision 6.0).

View File

@ -44,7 +44,7 @@ not checked yet.
The current implementation of union resorts to inheriting from the base type, The current implementation of union resorts to inheriting from the base type,
which means (quoted from the XML Schema specs): "If the <list> or <union> which means (quoted from the XML Schema specs): "If the <list> or <union>
alternative is chosen, then the simple ur-type definition·." alternative is chosen, then the simple ur-type definition."

View File

@ -44,7 +44,7 @@ not checked yet.
The current implementation of union resorts to inheriting from the base type, The current implementation of union resorts to inheriting from the base type,
which means (quoted from the XML Schema specs): "If the <list> or <union> which means (quoted from the XML Schema specs): "If the <list> or <union>
alternative is chosen, then the simple ur-type definition·." alternative is chosen, then the simple ur-type definition."

View File

@ -44,7 +44,7 @@ not checked yet.
The current implementation of union resorts to inheriting from the base type, The current implementation of union resorts to inheriting from the base type,
which means (quoted from the XML Schema specs): "If the <list> or <union> which means (quoted from the XML Schema specs): "If the <list> or <union>
alternative is chosen, then the simple ur-type definition·." alternative is chosen, then the simple ur-type definition."

View File

@ -100,7 +100,7 @@ of the corresponding class can be passed instead of the marked hash ref.
You may pass any combination of objects, hash and list refs to these You may pass any combination of objects, hash and list refs to these
methods, as long as you meet the structure. methods, as long as you meet the structure.
List items (i.e. multiple occurences) are not displayed in the synopsis. List items (i.e. multiple occurrences) are not displayed in the synopsis.
You may generally pass a list ref of hash refs (or objects) instead of a hash You may generally pass a list ref of hash refs (or objects) instead of a hash
ref - this may result in invalid XML if used improperly, though. Note that ref - this may result in invalid XML if used improperly, though. Note that
SOAP::WSDL always expects list references at maximum depth position. SOAP::WSDL always expects list references at maximum depth position.

View File

@ -44,7 +44,7 @@ not checked yet.
The current implementation of union resorts to inheriting from the base type, The current implementation of union resorts to inheriting from the base type,
which means (quoted from the XML Schema specs): "If the <list> or <union> which means (quoted from the XML Schema specs): "If the <list> or <union>
alternative is chosen, then the simple ur-type definition·." alternative is chosen, then the simple ur-type definition."

View File

@ -44,7 +44,7 @@ not checked yet.
The current implementation of union resorts to inheriting from the base type, The current implementation of union resorts to inheriting from the base type,
which means (quoted from the XML Schema specs): "If the <list> or <union> which means (quoted from the XML Schema specs): "If the <list> or <union>
alternative is chosen, then the simple ur-type definition·." alternative is chosen, then the simple ur-type definition."

View File

@ -44,7 +44,7 @@ not checked yet.
The current implementation of union resorts to inheriting from the base type, The current implementation of union resorts to inheriting from the base type,
which means (quoted from the XML Schema specs): "If the <list> or <union> which means (quoted from the XML Schema specs): "If the <list> or <union>
alternative is chosen, then the simple ur-type definition·." alternative is chosen, then the simple ur-type definition."

View File

@ -44,7 +44,7 @@ not checked yet.
The current implementation of union resorts to inheriting from the base type, The current implementation of union resorts to inheriting from the base type,
which means (quoted from the XML Schema specs): "If the <list> or <union> which means (quoted from the XML Schema specs): "If the <list> or <union>
alternative is chosen, then the simple ur-type definition·." alternative is chosen, then the simple ur-type definition."

View File

@ -44,7 +44,7 @@ not checked yet.
The current implementation of union resorts to inheriting from the base type, The current implementation of union resorts to inheriting from the base type,
which means (quoted from the XML Schema specs): "If the <list> or <union> which means (quoted from the XML Schema specs): "If the <list> or <union>
alternative is chosen, then the simple ur-type definition·." alternative is chosen, then the simple ur-type definition."

View File

@ -44,7 +44,7 @@ not checked yet.
The current implementation of union resorts to inheriting from the base type, The current implementation of union resorts to inheriting from the base type,
which means (quoted from the XML Schema specs): "If the <list> or <union> which means (quoted from the XML Schema specs): "If the <list> or <union>
alternative is chosen, then the simple ur-type definition·." alternative is chosen, then the simple ur-type definition."

View File

@ -3766,7 +3766,7 @@ our @options = (
SSMTP is a lightweight and efficient method to send email. SSMTP is a lightweight and efficient method to send email.
The SSMTP application is not installed by default. The SSMTP application is not installed by default.
NEW_MAIL_MODULES must also be enabled. NEW_MAIL_MODULES must also be enabled.
Please visit the ZoneMinder [SSMTP Wiki page](http://www.zoneminder.com/wiki/index.php/How_to_get_ssmtp_working_with_Zoneminder) Please visit the ZoneMinder [SSMTP Wiki page](https://wiki.zoneminder.com/How_to_get_ssmtp_working_with_Zoneminder)
for setup and configuration help. for setup and configuration help.
`, `,
type => $types{boolean}, type => $types{boolean},

View File

@ -220,14 +220,14 @@ sub moveConUpRight {
my $self = shift; my $self = shift;
Debug('Move Diagonally Up Right'); Debug('Move Diagonally Up Right');
$$self{Monitor}->suspendMotionDetection() if !$self->{Monitor}->{ModectDuringPTZ}; $$self{Monitor}->suspendMotionDetection() if !$self->{Monitor}->{ModectDuringPTZ};
$$self{LastCmd} = 'code=RightUp&channel=0&arg1=0&arg2=1&arg3=0'; $$self{LastCmd} = 'code=RightUp&channel=0&arg1=1&arg2=1&arg3=0';
$self->sendCmd('cgi-bin/ptz.cgi?action=start&'.$$self{LastCmd}); $self->sendCmd('cgi-bin/ptz.cgi?action=start&'.$$self{LastCmd});
} }
sub moveConDownRight { sub moveConDownRight {
my $self = shift; my $self = shift;
Debug('Move Diagonally Down Right'); Debug('Move Diagonally Down Right');
$$self{LastCmd} = 'code=RightDown&channel=0&arg1=0&arg2=1&arg3=0'; $$self{LastCmd} = 'code=RightDown&channel=0&arg1=1&arg2=1&arg3=0';
$$self{Monitor}->suspendMotionDetection() if !$self->{Monitor}->{ModectDuringPTZ}; $$self{Monitor}->suspendMotionDetection() if !$self->{Monitor}->{ModectDuringPTZ};
$self->sendCmd('cgi-bin/ptz.cgi?action=start&'.$$self{LastCmd}); $self->sendCmd('cgi-bin/ptz.cgi?action=start&'.$$self{LastCmd});
} }
@ -236,7 +236,7 @@ sub moveConUpLeft {
my $self = shift; my $self = shift;
Debug('Move Diagonally Up Left'); Debug('Move Diagonally Up Left');
$$self{Monitor}->suspendMotionDetection() if !$self->{Monitor}->{ModectDuringPTZ}; $$self{Monitor}->suspendMotionDetection() if !$self->{Monitor}->{ModectDuringPTZ};
$$self{LastCmd} = 'code=LeftUp&channel=0&arg1=0&arg2=1&arg3=0'; $$self{LastCmd} = 'code=LeftUp&channel=0&arg1=1&arg2=1&arg3=0';
$self->sendCmd('cgi-bin/ptz.cgi?action=start&'.$$self{LastCmd}); $self->sendCmd('cgi-bin/ptz.cgi?action=start&'.$$self{LastCmd});
} }
@ -244,7 +244,7 @@ sub moveConDownLeft {
my $self = shift; my $self = shift;
Debug('Move Diagonally Down Left'); Debug('Move Diagonally Down Left');
$$self{Monitor}->suspendMotionDetection() if !$self->{Monitor}->{ModectDuringPTZ}; $$self{Monitor}->suspendMotionDetection() if !$self->{Monitor}->{ModectDuringPTZ};
$$self{LastCmd} = 'code=LeftDown&channel=0&arg1=0&arg2=1&arg3=0'; $$self{LastCmd} = 'code=LeftDown&channel=0&arg1=1&arg2=1&arg3=0';
$self->sendCmd('cgi-bin/ptz.cgi?action=start&'.$$self{LastCmd}); $self->sendCmd('cgi-bin/ptz.cgi?action=start&'.$$self{LastCmd});
} }

View File

@ -283,7 +283,7 @@ None by default.
=head1 SEE ALSO =head1 SEE ALSO
See if there are better instructions for the DCS-5020L at See if there are better instructions for the DCS-5020L at
http://www.zoneminder.com/wiki/index.php/Dlink https://wiki.zoneminder.com/Dlink
=head1 AUTHOR =head1 AUTHOR

View File

@ -283,7 +283,7 @@ sub presetSet
my $self = shift; my $self = shift;
my $params = shift; my $params = shift;
my $preset = $self->getParam( $params, 'preset' ); my $preset = $self->getParam( $params, 'preset' );
my $cmd = "cgi/ptz_set?Channel=1&Group=PTZCtrlInfo&PresetNumber=1&Preset=0"; my $cmd = 'form/presetSet?flag=3&existFlag=1&language=cn&presetNum='.$preset;
$self->sendCmd( $cmd ); $self->sendCmd( $cmd );
} }
@ -294,7 +294,7 @@ sub presetGoto
my $self = shift; my $self = shift;
my $params = shift; my $params = shift;
my $preset = $self->getParam( $params, 'preset' ); my $preset = $self->getParam( $params, 'preset' );
my $cmd = "cgi/ptz_set?Channel=1&Group=PTZCtrlInfo&PresetNumber=1&Preset=1"; my $cmd = 'form/presetSet?flag=4&existFlag=1&language=cn&presetNum='.$preset;
$self->sendCmd( $cmd ); $self->sendCmd( $cmd );
} }

View File

@ -107,6 +107,7 @@ sub zmDbConnect {
.$socket . $sslOptions . ($options?join(';', '', map { $_.'='.$$options{$_} } keys %{$options} ) : '') .$socket . $sslOptions . ($options?join(';', '', map { $_.'='.$$options{$_} } keys %{$options} ) : '')
, $ZoneMinder::Config::Config{ZM_DB_USER} , $ZoneMinder::Config::Config{ZM_DB_USER}
, $ZoneMinder::Config::Config{ZM_DB_PASS} , $ZoneMinder::Config::Config{ZM_DB_PASS}
, { mysql_enable_utf8 => 1, }
); );
}; };
if ( !$dbh or $@ ) { if ( !$dbh or $@ ) {

View File

@ -584,6 +584,7 @@ sub DiskSpace {
return $_[0]{DiskSpace}; return $_[0]{DiskSpace};
} }
# Icon: I removed the locking from this. So we now have an assumption that the Event object is up to date.
sub CopyTo { sub CopyTo {
my ( $self, $NewStorage ) = @_; my ( $self, $NewStorage ) = @_;
@ -614,16 +615,12 @@ sub CopyTo {
Debug("$NewPath is good"); Debug("$NewPath is good");
} }
$ZoneMinder::Database::dbh->begin_work();
$self->lock_and_load();
# data is reloaded, so need to check that the move hasn't already happened. # data is reloaded, so need to check that the move hasn't already happened.
if ( $$self{StorageId} == $$NewStorage{Id} ) { if ( $$self{StorageId} == $$NewStorage{Id} ) {
$ZoneMinder::Database::dbh->commit();
return 'Event has already been moved by someone else.'; return 'Event has already been moved by someone else.';
} }
if ( $$OldStorage{Id} != $$self{StorageId} ) { if ( $$OldStorage{Id} != $$self{StorageId} ) {
$ZoneMinder::Database::dbh->commit();
return 'Old Storage path changed, Event has moved somewhere else.'; return 'Old Storage path changed, Event has moved somewhere else.';
} }
@ -661,39 +658,21 @@ sub CopyTo {
} }
my $event_path = $subpath.$self->RelativePath(); my $event_path = $subpath.$self->RelativePath();
if ( 0 ) { # Not neccessary
Debug("Making directory $event_path/");
if ( !$bucket->add_key($event_path.'/', '') ) {
Warning("Unable to add key for $event_path/ :". $s3->err . ': '. $s3->errstr());
}
}
my @files = glob("$OldPath/*"); my @files = glob("$OldPath/*");
Debug("Files to move @files"); Debug("Files to move @files");
foreach my $file ( @files ) { foreach my $file (@files) {
next if $file =~ /^\./; next if $file =~ /^\./;
( $file ) = ( $file =~ /^(.*)$/ ); # De-taint ($file) = ($file =~ /^(.*)$/); # De-taint
my $starttime = [gettimeofday]; my $starttime = [gettimeofday];
Debug("Moving file $file to $NewPath"); Debug("Moving file $file to $NewPath");
my $size = -s $file; my $size = -s $file;
if ( ! $size ) { if (!$size) {
Info('Not moving file with 0 size'); Info('Not moving file with 0 size');
} }
if ( 0 ) { my $filename = $event_path.'/'.File::Basename::basename($file);
my $file_contents = File::Slurp::read_file($file); if (!$bucket->add_key_filename($filename, $file)) {
if ( ! $file_contents ) { die "Unable to add key for $filename " . $s3->err . ': '. $s3->errstr;
die 'Loaded empty file, but it had a size. Giving up';
}
my $filename = $event_path.'/'.File::Basename::basename($file);
if ( ! $bucket->add_key($filename, $file_contents) ) {
die "Unable to add key for $filename : ".$s3->err . ': ' . $s3->errstr;
}
} else {
my $filename = $event_path.'/'.File::Basename::basename($file);
if ( ! $bucket->add_key_filename($filename, $file) ) {
die "Unable to add key for $filename " . $s3->err . ': '. $s3->errstr;
}
} }
my $duration = tv_interval($starttime); my $duration = tv_interval($starttime);
@ -704,16 +683,15 @@ sub CopyTo {
}; };
Error($@) if $@; Error($@) if $@;
} else { } else {
Error("Unable to parse S3 Url into it's component parts."); Error('Unable to parse S3 Url into it\'s component parts.');
} }
#die $@ if $@;
} # end if Url } # end if Url
} # end if s3 } # end if s3
my $error = ''; my $error = '';
if ( !$moved ) { if (!$moved) {
File::Path::make_path($NewPath, {error => \my $err}); File::Path::make_path($NewPath, {error => \my $err});
if ( @$err ) { if (@$err) {
for my $diag (@$err) { for my $diag (@$err) {
my ($file, $message) = %$diag; my ($file, $message) = %$diag;
next if $message eq 'File exists'; next if $message eq 'File exists';
@ -724,23 +702,16 @@ sub CopyTo {
} }
} }
} }
if ( $error ) { return $error if $error;
$ZoneMinder::Database::dbh->commit();
return $error;
}
my @files = glob("$OldPath/*"); my @files = glob("$OldPath/*");
if ( ! @files ) { return 'No files to move.' if !@files;
$ZoneMinder::Database::dbh->commit();
return 'No files to move.';
}
for my $file (@files) { for my $file (@files) {
next if $file =~ /^\./; next if $file =~ /^\./;
( $file ) = ( $file =~ /^(.*)$/ ); # De-taint ($file) = ($file =~ /^(.*)$/); # De-taint
my $starttime = [gettimeofday]; my $starttime = [gettimeofday];
Debug("Moving file $file to $NewPath");
my $size = -s $file; my $size = -s $file;
if ( ! File::Copy::copy( $file, $NewPath ) ) { if (!File::Copy::copy($file, $NewPath)) {
$error .= "Copy failed: for $file to $NewPath: $!"; $error .= "Copy failed: for $file to $NewPath: $!";
last; last;
} }
@ -749,20 +720,21 @@ sub CopyTo {
} # end foreach file. } # end foreach file.
} # end if ! moved } # end if ! moved
if ( $error ) { return $error;
$ZoneMinder::Database::dbh->commit();
return $error;
}
} # end sub CopyTo } # end sub CopyTo
sub MoveTo { sub MoveTo {
my ( $self, $NewStorage ) = @_; my ($self, $NewStorage) = @_;
if ( !$self->canEdit() ) { if (!$self->canEdit()) {
Warning('No permission to move event.'); Warning('No permission to move event.');
return 'No permission to move event.'; return 'No permission to move event.';
} }
my $was_in_transaction = !$ZoneMinder::Database::dbh->{AutoCommit};
$ZoneMinder::Database::dbh->begin_work() if !$was_in_transaction;
$self->lock_and_load(); # The fact that we are in a transaction might not imply locking
my $OldStorage = $self->Storage(undef); my $OldStorage = $self->Storage(undef);
my $error = $self->CopyTo($NewStorage); my $error = $self->CopyTo($NewStorage);
@ -772,11 +744,11 @@ sub MoveTo {
$$self{StorageId} = $$NewStorage{Id}; $$self{StorageId} = $$NewStorage{Id};
$self->Storage($NewStorage); $self->Storage($NewStorage);
$error .= $self->save(); $error .= $self->save();
if ( $error ) {
$ZoneMinder::Database::dbh->commit(); # Going to leave it to upper layer as to whether we rollback or not
return $error; $ZoneMinder::Database::dbh->commit() if !$was_in_transaction;
} return $error if $error;
$ZoneMinder::Database::dbh->commit();
$self->delete_files($OldStorage); $self->delete_files($OldStorage);
return $error; return $error;
} # end sub MoveTo } # end sub MoveTo

View File

@ -0,0 +1,99 @@
# ==========================================================================
#
# ZoneMinder Event_Summary Module
# Copyright (C) 2020 ZoneMinder
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# ==========================================================================
#
# This module contains the common definitions and functions used by the rest
# of the ZoneMinder scripts
#
package ZoneMinder::Event_Summary;
use 5.006;
use strict;
use warnings;
require ZoneMinder::Base;
require ZoneMinder::Object;
#our @ISA = qw(Exporter ZoneMinder::Base);
use parent qw(ZoneMinder::Object);
use vars qw/ $table $primary_key %fields $serial %defaults $debug/;
$table = 'Event_Summaries';
$serial = $primary_key = 'MonitorId';
%fields = map { $_ => $_ } qw(
MonitorId
TotalEvents
TotalEventDiskSpace
HourEvents
HourEventDiskSpace
DayEvents
DayEventDiskSpace
WeekEvents
WeekEventDiskSpace
MonthEvents
MonthEventDiskSpace
ArchivedEvents
ArchivedEventDiskSpace
);
%defaults = (
TotalEvents => undef,
TotalEventDiskSpace => undef,
HourEvents => undef,
HourEventDiskSpace => undef,
DayEvents => undef,
DayEventDiskSpace => undef,
WeekEvents => undef,
WeekEventDiskSpace => undef,
MonthEvents => undef,
MonthEventDiskSpace => undef,
ArchivedEvents => undef,
ArchivedEventDiskSpace => undef,
);
sub Monitor {
return new ZoneMinder::Monitor( $_[0]{MonitorId} );
} # end sub Monitor
1;
__END__
=head1 NAME
ZoneMinder::Event_Summary - Perl Class for Event Summaries
=head1 SYNOPSIS
use ZoneMinder::Event_Summary;
=head1 AUTHOR
Isaac Connor, E<lt>isaac@zoneminder.comE<gt>
=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

View File

@ -127,9 +127,11 @@ sub Execute {
foreach my $term ( @{$$self{PostSQLConditions}} ) { foreach my $term ( @{$$self{PostSQLConditions}} ) {
if ( $$term{attr} eq 'ExistsInFileSystem' ) { if ( $$term{attr} eq 'ExistsInFileSystem' ) {
foreach my $row ( @results ) { foreach my $row ( @results ) {
my $event = new ZoneMinder::Event($row); my $event = new ZoneMinder::Event($$row{Id}, $row);
if ( -e $event->Path() ) { if ( -e $event->Path() ) {
push @filtered_events, $row; push @filtered_events, $row if $$term{val} eq 'true';
} else {
push @filtered_events, $row if $$term{val} eq 'false';
} }
} }
} }
@ -164,138 +166,143 @@ sub Sql {
if ( exists($term->{obr}) ) { if ( exists($term->{obr}) ) {
$self->{Sql} .= str_repeat('(', $term->{obr}).' '; $self->{Sql} .= str_repeat('(', $term->{obr}).' ';
} }
if (!$term->{attr}) {
Error("Invalid term in filter $$self{Id}. Empty attr");
next;
}
my $value = $term->{val}; my $value = $term->{val};
my @value_list; my @value_list;
if ( $term->{attr} ) {
if ( $term->{attr} eq 'AlarmedZoneId' ) {
$term->{op} = 'EXISTS';
} elsif ( $term->{attr} =~ /^Monitor/ ) {
$sql = 'SELECT E.*, unix_timestamp(E.StartDateTime) as Time, M.Name as MonitorName
FROM Events as E INNER JOIN Monitors as M on M.Id = E.MonitorId';
my ( $temp_attr_name ) = $term->{attr} =~ /^Monitor(.+)$/;
$self->{Sql} .= 'M.'.$temp_attr_name;
} elsif ( $term->{attr} eq 'ServerId' or $term->{attr} eq 'MonitorServerId' ) {
$sql = 'SELECT E.*, unix_timestamp(E.StartDateTime) as Time, M.Name as MonitorName
FROM Events as E INNER JOIN Monitors as M on M.Id = E.MonitorId';
$self->{Sql} .= 'M.ServerId';
} elsif ( $term->{attr} eq 'StorageServerId' ) {
$self->{Sql} .= '(SELECT Storage.ServerId FROM Storage WHERE Storage.Id=E.StorageId)';
} elsif ( $term->{attr} eq 'FilterServerId' ) {
$self->{Sql} .= $Config{ZM_SERVER_ID};
# StartTime options
} elsif ( $term->{attr} eq 'DateTime' ) {
$self->{Sql} .= 'E.StartDateTime';
} elsif ( $term->{attr} eq 'Date' ) {
$self->{Sql} .= 'to_days( E.StartDateTime )';
} elsif ( $term->{attr} eq 'StartDate' ) {
$self->{Sql} .= 'to_days( E.StartDateTime )';
} elsif ( $term->{attr} eq 'Time' or $term->{attr} eq 'StartTime' ) {
$self->{Sql} .= 'extract( hour_second from E.StartDateTime )';
} elsif ( $term->{attr} eq 'Weekday' or $term->{attr} eq 'StartWeekday' ) {
$self->{Sql} .= 'weekday( E.StartDateTime )';
# EndTIme options if ( $term->{attr} eq 'AlarmedZoneId' ) {
} elsif ( $term->{attr} eq 'EndDateTime' ) { $term->{op} = 'EXISTS';
$self->{Sql} .= 'E.EndDateTime'; } elsif ( $term->{attr} =~ /^Monitor/ ) {
} elsif ( $term->{attr} eq 'EndDate' ) { $sql = 'SELECT E.*, unix_timestamp(E.StartDateTime) as Time, M.Name as MonitorName
$self->{Sql} .= 'to_days( E.EndDateTime )'; FROM Events as E INNER JOIN Monitors as M on M.Id = E.MonitorId';
} elsif ( $term->{attr} eq 'EndTime' ) { my ( $temp_attr_name ) = $term->{attr} =~ /^Monitor(.+)$/;
$self->{Sql} .= 'extract( hour_second from E.EndDateTime )'; $self->{Sql} .= 'M.'.$temp_attr_name;
} elsif ( $term->{attr} eq 'EndWeekday' ) { } elsif ( $term->{attr} eq 'ServerId' or $term->{attr} eq 'MonitorServerId' ) {
$self->{Sql} .= 'weekday( E.EndDateTime )'; $sql = 'SELECT E.*, unix_timestamp(E.StartDateTime) as Time, M.Name as MonitorName
} elsif ( $term->{attr} eq 'ExistsInFileSystem' ) { FROM Events as E INNER JOIN Monitors as M on M.Id = E.MonitorId';
push @{$self->{PostSQLConditions}}, $term; $self->{Sql} .= 'M.ServerId';
$self->{Sql} .= 'TRUE /* ExistsInFileSystem */'; } elsif ( $term->{attr} eq 'StorageServerId' ) {
} elsif ( $term->{attr} eq 'DiskPercent' ) { $self->{Sql} .= '(SELECT Storage.ServerId FROM Storage WHERE Storage.Id=E.StorageId)';
$self->{Sql} .= 'zmDiskPercent'; } elsif ( $term->{attr} eq 'FilterServerId' ) {
$self->{HasDiskPercent} = !undef; $self->{Sql} .= $Config{ZM_SERVER_ID};
} elsif ( $term->{attr} eq 'DiskBlocks' ) { # StartTime options
$self->{Sql} .= 'zmDiskBlocks'; } elsif ( $term->{attr} eq 'DateTime' ) {
$self->{HasDiskBlocks} = !undef; $self->{Sql} .= 'E.StartDateTime';
} elsif ( $term->{attr} eq 'SystemLoad' ) { } elsif ( $term->{attr} eq 'Date' ) {
$self->{Sql} .= 'zmSystemLoad'; $self->{Sql} .= 'to_days( E.StartDateTime )';
$self->{HasSystemLoad} = !undef; } elsif ( $term->{attr} eq 'StartDate' ) {
} else { $self->{Sql} .= 'to_days( E.StartDateTime )';
$self->{Sql} .= 'E.'.$term->{attr}; } elsif ( $term->{attr} eq 'Time' or $term->{attr} eq 'StartTime' ) {
} $self->{Sql} .= 'extract( hour_second from E.StartDateTime )';
} elsif ( $term->{attr} eq 'Weekday' or $term->{attr} eq 'StartWeekday' ) {
$self->{Sql} .= 'weekday( E.StartDateTime )';
if ( $term->{attr} eq 'ExistsInFileSystem' ) { # EndTIme options
# PostCondition, so no further SQL } elsif ( $term->{attr} eq 'EndDateTime' ) {
} else { $self->{Sql} .= 'E.EndDateTime';
( my $stripped_value = $value ) =~ s/^["\']+?(.+)["\']+?$/$1/; } elsif ( $term->{attr} eq 'EndDate' ) {
foreach my $temp_value ( split( /["'\s]*?,["'\s]*?/, $stripped_value ) ) { $self->{Sql} .= 'to_days( E.EndDateTime )';
} elsif ( $term->{attr} eq 'EndTime' ) {
$self->{Sql} .= 'extract( hour_second from E.EndDateTime )';
} elsif ( $term->{attr} eq 'EndWeekday' ) {
$self->{Sql} .= 'weekday( E.EndDateTime )';
} elsif ( $term->{attr} eq 'ExistsInFileSystem' ) {
push @{$self->{PostSQLConditions}}, $term;
$self->{Sql} .= 'TRUE /* ExistsInFileSystem */';
} elsif ( $term->{attr} eq 'DiskPercent' ) {
$self->{Sql} .= 'zmDiskPercent';
$self->{HasDiskPercent} = !undef;
} elsif ( $term->{attr} eq 'DiskBlocks' ) {
$self->{Sql} .= 'zmDiskBlocks';
$self->{HasDiskBlocks} = !undef;
} elsif ( $term->{attr} eq 'SystemLoad' ) {
$self->{Sql} .= 'zmSystemLoad';
$self->{HasSystemLoad} = !undef;
} else {
$self->{Sql} .= 'E.'.$term->{attr};
}
if ( $term->{attr} eq 'AlarmedZoneId' ) { if ( $term->{attr} eq 'ExistsInFileSystem' ) {
$value = '(SELECT * FROM Stats WHERE EventId=E.Id AND Score > 0 AND ZoneId='.$value.')'; # PostCondition, so no further SQL
} elsif ( $term->{attr} =~ /^MonitorName/ ) { } else {
$value = "'$temp_value'"; ( my $stripped_value = $value ) =~ s/^["\']+?(.+)["\']+?$/$1/;
} elsif ( $term->{attr} =~ /ServerId/) { # Empty value will result in () from split
Debug("ServerId, temp_value is ($temp_value) ($ZoneMinder::Config::Config{ZM_SERVER_ID})"); foreach my $temp_value ( $stripped_value ? split( /["'\s]*?,["'\s]*?/, $stripped_value ) : $stripped_value ) {
if ( $temp_value eq 'ZM_SERVER_ID' ) { if ( $term->{attr} eq 'AlarmedZoneId' ) {
$value = "'$ZoneMinder::Config::Config{ZM_SERVER_ID}'"; $value = '(SELECT * FROM Stats WHERE EventId=E.Id AND Score > 0 AND ZoneId='.$value.')';
# This gets used later, I forget for what } elsif ( $term->{attr} =~ /^MonitorName/ ) {
$$self{Server} = new ZoneMinder::Server($ZoneMinder::Config::Config{ZM_SERVER_ID}); $value = "'$temp_value'";
} elsif ( $temp_value eq 'NULL' ) { } elsif ( $term->{attr} =~ /ServerId/) {
$value = $temp_value; Debug("ServerId, temp_value is ($temp_value) ($ZoneMinder::Config::Config{ZM_SERVER_ID})");
} else { if ( $temp_value eq 'ZM_SERVER_ID' ) {
$value = "'$temp_value'"; $value = "'$ZoneMinder::Config::Config{ZM_SERVER_ID}'";
# This gets used later, I forget for what # This gets used later, I forget for what
$$self{Server} = new ZoneMinder::Server($temp_value); $$self{Server} = new ZoneMinder::Server($ZoneMinder::Config::Config{ZM_SERVER_ID});
} } elsif ( $temp_value eq 'NULL' ) {
} elsif ( $term->{attr} eq 'StorageId' ) {
$value = "'$temp_value'";
$$self{Storage} = new ZoneMinder::Storage($temp_value);
} elsif ( $term->{attr} eq 'Name'
|| $term->{attr} eq 'Cause'
|| $term->{attr} eq 'Notes'
) {
if ( $term->{op} eq 'LIKE'
|| $term->{op} eq 'NOT LIKE'
) {
$temp_value = '%'.$temp_value.'%' if $temp_value !~ /%/;
}
$value = "'$temp_value'";
} elsif ( $term->{attr} eq 'DateTime' or $term->{attr} eq 'StartDateTime' or $term->{attr} eq 'EndDateTime' ) {
if ( $temp_value eq 'NULL' ) {
$value = $temp_value;
} else {
$value = DateTimeToSQL($temp_value);
if ( !$value ) {
Error("Error parsing date/time '$temp_value', skipping filter '$self->{Name}'");
return;
}
$value = "'$value'";
}
} elsif ( $term->{attr} eq 'Date' or $term->{attr} eq 'StartDate' or $term->{attr} eq 'EndDate' ) {
if ( $temp_value eq 'NULL' ) {
$value = $temp_value;
} elsif ( $temp_value eq 'CURDATE()' or $temp_value eq 'NOW()' ) {
$value = 'to_days('.$temp_value.')';
} else {
$value = DateTimeToSQL($temp_value);
if ( !$value ) {
Error("Error parsing date/time '$temp_value', skipping filter '$self->{Name}'");
return;
}
$value = "to_days( '$value' )";
}
} elsif ( $term->{attr} eq 'Time' or $term->{attr} eq 'StartTime' or $term->{attr} eq 'EndTime' ) {
if ( $temp_value eq 'NULL' ) {
$value = $temp_value;
} else {
$value = DateTimeToSQL($temp_value);
if ( !$value ) {
Error("Error parsing date/time '$temp_value', skipping filter '$self->{Name}'");
return;
}
$value = "extract( hour_second from '$value' )";
}
} else {
$value = $temp_value; $value = $temp_value;
} else {
$value = "'$temp_value'";
# This gets used later, I forget for what
$$self{Server} = new ZoneMinder::Server($temp_value);
} }
push @value_list, $value; } elsif ( $term->{attr} eq 'StorageId' ) {
} # end foreach temp_value # Empty means NULL, otherwise must be an integer
} # end if has an attr $value = $temp_value ne '' ? int($temp_value) : 'NULL';
$$self{Storage} = new ZoneMinder::Storage($temp_value);
} elsif ( $term->{attr} eq 'Name'
|| $term->{attr} eq 'Cause'
|| $term->{attr} eq 'Notes'
) {
if ( $term->{op} eq 'LIKE'
|| $term->{op} eq 'NOT LIKE'
) {
$temp_value = '%'.$temp_value.'%' if $temp_value !~ /%/;
}
$value = "'$temp_value'";
} elsif ( $term->{attr} eq 'DateTime' or $term->{attr} eq 'StartDateTime' or $term->{attr} eq 'EndDateTime' ) {
if ( $temp_value eq 'NULL' ) {
$value = $temp_value;
} else {
$value = DateTimeToSQL($temp_value);
if ( !$value ) {
Error("Error parsing date/time '$temp_value', skipping filter '$self->{Name}'");
return;
}
$value = "'$value'";
}
} elsif ( $term->{attr} eq 'Date' or $term->{attr} eq 'StartDate' or $term->{attr} eq 'EndDate' ) {
if ( $temp_value eq 'NULL' ) {
$value = $temp_value;
} elsif ( $temp_value eq 'CURDATE()' or $temp_value eq 'NOW()' ) {
$value = 'to_days('.$temp_value.')';
} else {
$value = DateTimeToSQL($temp_value);
if ( !$value ) {
Error("Error parsing date/time '$temp_value', skipping filter '$self->{Name}'");
return;
}
$value = "to_days( '$value' )";
}
} elsif ( $term->{attr} eq 'Time' or $term->{attr} eq 'StartTime' or $term->{attr} eq 'EndTime' ) {
if ( $temp_value eq 'NULL' ) {
$value = $temp_value;
} else {
$value = DateTimeToSQL($temp_value);
if ( !$value ) {
Error("Error parsing date/time '$temp_value', skipping filter '$self->{Name}'");
return;
}
$value = "extract( hour_second from '$value' )";
}
} else {
$value = $temp_value;
}
push @value_list, $value;
} # end foreach temp_value
if ( $term->{op} ) { if ( $term->{op} ) {
if ( $term->{op} eq '=~' ) { if ( $term->{op} eq '=~' ) {

View File

@ -35,6 +35,7 @@ require ZoneMinder::Storage;
require ZoneMinder::Server; require ZoneMinder::Server;
require ZoneMinder::Memory; require ZoneMinder::Memory;
require ZoneMinder::Monitor_Status; require ZoneMinder::Monitor_Status;
require ZoneMinder::Event_Summary;
require ZoneMinder::Zone; require ZoneMinder::Zone;
#our @ISA = qw(Exporter ZoneMinder::Base); #our @ISA = qw(Exporter ZoneMinder::Base);
@ -136,8 +137,8 @@ $serial = $primary_key = 'Id';
%defaults = ( %defaults = (
ServerId => 0, ServerId => 0,
StorageId => 0, StorageId => 0,
Type => 'Ffmpeg', Type => q`'Ffmpeg'`,
Function => 'Mocord', Function => q`'Mocord'`,
Enabled => 1, Enabled => 1,
LinkedMonitors => undef, LinkedMonitors => undef,
Device => '', Device => '',
@ -166,15 +167,15 @@ $serial = $primary_key = 'Id';
VideoWriter => 0, VideoWriter => 0,
OutputCodec => undef, OutputCodec => undef,
OutputContainer => undef, OutputContainer => undef,
EncoderParameters => "# Lines beginning with # are a comment \n# For changing quality, use the crf option\n# 1 is best, 51 is worst quality\n#crf=23\n", EncoderParameters => '',
RecordAudio=>0, RecordAudio=>0,
RTSPDescribe=>0, RTSPDescribe=>0,
Brightness => -1, Brightness => -1,
Contrast => -1, Contrast => -1,
Hue => -1, Hue => -1,
Colour => -1, Colour => -1,
EventPrefix => 'Event-', EventPrefix => q`'Event-'`,
LabelFormat => '%N - %d/%m/%y %H:%M:%S', LabelFormat => '',
LabelX => 0, LabelX => 0,
LabelY => 0, LabelY => 0,
LabelSize => 1, LabelSize => 1,
@ -208,13 +209,13 @@ $serial = $primary_key = 'Id';
DefaultRate => 100, DefaultRate => 100,
DefaultScale => 100, DefaultScale => 100,
SignalCheckPoints => 0, SignalCheckPoints => 0,
SignalCheckColour => '#0000BE', SignalCheckColour => q`'#0000BE'`,
WebColour => '#ff0000', WebColour => q`'#ff0000'`,
Exif => 0, Exif => 0,
Sequence => undef, Sequence => undef,
ZoneCount => 0, ZoneCount => 0,
Refresh => undef, Refresh => undef,
DefaultCodec => 'auto', DefaultCodec => q`'auto'`,
Latitude => undef, Latitude => undef,
Longitude => undef, Longitude => undef,
); );
@ -266,6 +267,15 @@ sub Status {
return $$self{Status}; return $$self{Status};
} }
sub Event_Summary {
my $self = shift;
$$self{Event_Summary} = shift if @_;
if ( ! $$self{Event_Summary} ) {
$$self{Event_Summary} = ZoneMinder::Event_Summary->find_one(MonitorId=>$$self{Id});
}
return $$self{Event_Summary};
}
sub connect { sub connect {
my $self = shift; my $self = shift;
return ZoneMinder::Memory::zmMemVerify($self); return ZoneMinder::Memory::zmMemVerify($self);
@ -279,21 +289,37 @@ sub disconnect {
sub suspendMotionDetection { sub suspendMotionDetection {
my $self = shift; my $self = shift;
return 0 if ! ZoneMinder::Memory::zmMemVerify($self); return 0 if ! ZoneMinder::Memory::zmMemVerify($self);
while (ZoneMinder::Memory::zmMemRead($self, 'shared_data:active', 1)) { return if $$self{Function} eq 'Nodect' or $$self{Function} eq 'Monitor' or $$self{Function} eq 'None';
my $count = 50;
while ($count and ZoneMinder::Memory::zmMemRead($self, 'shared_data:active', 1)) {
ZoneMinder::Logger::Debug(1, 'Suspending motion detection'); ZoneMinder::Logger::Debug(1, 'Suspending motion detection');
ZoneMinder::Memory::zmMonitorSuspend($self); ZoneMinder::Memory::zmMonitorSuspend($self);
usleep(100000); usleep(100000);
$count -= 1;
}
if (!$count) {
ZoneMinder::Logger::Error('Unable to suspend motion detection after 5 seconds.');
ZoneMinder::Memory::zmMemInvalidate($self); # Close our file handle to the zmc process we are about to end
} else {
ZoneMinder::Logger::Debug(1, 'shared_data:active='.ZoneMinder::Memory::zmMemRead($self, 'shared_data:active', 1));
} }
ZoneMinder::Logger::Debug(1,ZoneMinder::Memory::zmMemRead($self, 'shared_data:active', 1));
} }
sub resumeMotionDetection { sub resumeMotionDetection {
my $self = shift; my $self = shift;
return 0 if ! ZoneMinder::Memory::zmMemVerify($self); return 0 if ! ZoneMinder::Memory::zmMemVerify($self);
#while (zmMemRead($self, 'shared_data:active', 1)) { return if $$self{Function} eq 'Nodect' or $$self{Function} eq 'Monitor' or $$self{Function} eq 'None';
my $count = 50;
while ($count and !ZoneMinder::Memory::zmMemRead($self, 'shared_data:active', 1)) {
ZoneMinder::Logger::Debug(1, 'Resuming motion detection'); ZoneMinder::Logger::Debug(1, 'Resuming motion detection');
ZoneMinder::Memory::zmMonitorResume($self); ZoneMinder::Memory::zmMonitorResume($self);
#} usleep(100000);
$count -= 1;
}
if (!$count) {
ZoneMinder::Logger::Error('Unable to resume motion detection after 5 seconds.');
ZoneMinder::Memory::zmMemInvalidate($self); # Close our file handle to the zmc process we are about to end
}
return 1; return 1;
} }

View File

@ -43,18 +43,6 @@ $serial = $primary_key = 'MonitorId';
CaptureFPS CaptureFPS
AnalysisFPS AnalysisFPS
CaptureBandwidth CaptureBandwidth
TotalEvents
TotalEventDiskSpace
HourEvents
HourEventDiskSpace
DayEvents
DayEventDiskSpace
WeekEvents
WeekEventDiskSpace
MonthEvents
MonthEventDiskSpace
ArchivedEvents
ArchivedEventDiskSpace
); );
%defaults = ( %defaults = (
@ -62,18 +50,6 @@ $serial = $primary_key = 'MonitorId';
CaptureFPS => undef, CaptureFPS => undef,
AnalysisFPS => undef, AnalysisFPS => undef,
CaptureBandwidth => undef, CaptureBandwidth => undef,
TotalEvents => undef,
TotalEventDiskSpace => undef,
HourEvents => undef,
HourEventDiskSpace => undef,
DayEvents => undef,
DayEventDiskSpace => undef,
WeekEvents => undef,
WeekEventDiskSpace => undef,
MonthEvents => undef,
MonthEventDiskSpace => undef,
ArchivedEvents => undef,
ArchivedEventDiskSpace => undef,
); );
sub Monitor { sub Monitor {

View File

@ -218,7 +218,7 @@ sub save {
my $serial = eval '$'.$type.'::serial'; my $serial = eval '$'.$type.'::serial';
my @identified_by = eval '@'.$type.'::identified_by'; my @identified_by = eval '@'.$type.'::identified_by';
my $ac = ZoneMinder::Database::start_transaction( $local_dbh ); my $ac = ZoneMinder::Database::start_transaction( $local_dbh ) if $local_dbh->{AutoCommit};
if ( ! $serial ) { if ( ! $serial ) {
my $insert = $force_insert; my $insert = $force_insert;
my %serial = eval '%'.$type.'::serial'; my %serial = eval '%'.$type.'::serial';
@ -234,8 +234,8 @@ $log->debug("No serial") if $debug;
if ( ! ( ( $_ = $local_dbh->prepare("DELETE FROM `$table` WHERE $where") ) and $_->execute( @$self{@identified_by} ) ) ) { if ( ! ( ( $_ = $local_dbh->prepare("DELETE FROM `$table` WHERE $where") ) and $_->execute( @$self{@identified_by} ) ) ) {
$where =~ s/\?/\%s/g; $where =~ s/\?/\%s/g;
$log->error("Error deleting: DELETE FROM $table WHERE " . sprintf($where, map { defined $_ ? $_ : 'undef' } ( @$self{@identified_by}) ).'):' . $local_dbh->errstr); $log->error("Error deleting: DELETE FROM $table WHERE " . sprintf($where, map { defined $_ ? $_ : 'undef' } ( @$self{@identified_by}) ).'):' . $local_dbh->errstr);
$local_dbh->rollback(); $local_dbh->rollback() if $ac;
ZoneMinder::Database::end_transaction( $local_dbh, $ac ); ZoneMinder::Database::end_transaction( $local_dbh, $ac ) if $ac;
return $local_dbh->errstr; return $local_dbh->errstr;
} elsif ( $debug ) { } elsif ( $debug ) {
$log->debug("SQL succesful DELETE FROM $table WHERE $where"); $log->debug("SQL succesful DELETE FROM $table WHERE $where");
@ -267,8 +267,8 @@ $log->debug("No serial") if $debug;
my $error = $local_dbh->errstr; my $error = $local_dbh->errstr;
$command =~ s/\?/\%s/g; $command =~ s/\?/\%s/g;
$log->error('SQL statement execution failed: ('.sprintf($command, , map { defined $_ ? $_ : 'undef' } ( @sql{@keys}) ).'):' . $local_dbh->errstr); $log->error('SQL statement execution failed: ('.sprintf($command, , map { defined $_ ? $_ : 'undef' } ( @sql{@keys}) ).'):' . $local_dbh->errstr);
$local_dbh->rollback(); $local_dbh->rollback() if $ac;
ZoneMinder::Database::end_transaction( $local_dbh, $ac ); ZoneMinder::Database::end_transaction( $local_dbh, $ac ) if $ac;
return $error; return $error;
} # end if } # end if
if ( $debug or DEBUG_ALL ) { if ( $debug or DEBUG_ALL ) {
@ -282,8 +282,8 @@ $log->debug("No serial") if $debug;
my $error = $local_dbh->errstr; my $error = $local_dbh->errstr;
$command =~ s/\?/\%s/g; $command =~ s/\?/\%s/g;
$log->error('SQL failed: ('.sprintf($command, , map { defined $_ ? $_ : 'undef' } ( @sql{@keys, @$fields{@identified_by}}) ).'):' . $local_dbh->errstr); $log->error('SQL failed: ('.sprintf($command, , map { defined $_ ? $_ : 'undef' } ( @sql{@keys, @$fields{@identified_by}}) ).'):' . $local_dbh->errstr);
$local_dbh->rollback(); $local_dbh->rollback() if $ac;
ZoneMinder::Database::end_transaction( $local_dbh, $ac ); ZoneMinder::Database::end_transaction( $local_dbh, $ac ) if $ac;
return $error; return $error;
} # end if } # end if
if ( $debug or DEBUG_ALL ) { if ( $debug or DEBUG_ALL ) {
@ -321,8 +321,8 @@ $log->debug("No serial") if $debug;
$command =~ s/\?/\%s/g; $command =~ s/\?/\%s/g;
my $error = $local_dbh->errstr; my $error = $local_dbh->errstr;
$log->error('SQL failed: ('.sprintf($command, map { defined $_ ? $_ : 'undef' } ( @sql{@keys}) ).'):' . $error); $log->error('SQL failed: ('.sprintf($command, map { defined $_ ? $_ : 'undef' } ( @sql{@keys}) ).'):' . $error);
$local_dbh->rollback(); $local_dbh->rollback() if $ac;
ZoneMinder::Database::end_transaction( $local_dbh, $ac ); ZoneMinder::Database::end_transaction( $local_dbh, $ac ) if $ac;
return $error; return $error;
} # end if } # end if
if ( $debug or DEBUG_ALL ) { if ( $debug or DEBUG_ALL ) {
@ -340,8 +340,8 @@ $log->debug("No serial") if $debug;
my $error = $local_dbh->errstr; my $error = $local_dbh->errstr;
$command =~ s/\?/\%s/g; $command =~ s/\?/\%s/g;
$log->error('SQL failed: ('.sprintf($command, map { defined $_ ? $_ : 'undef' } ( @sql{@keys}, @sql{@$fields{@identified_by}} ) ).'):' . $error) if $log; $log->error('SQL failed: ('.sprintf($command, map { defined $_ ? $_ : 'undef' } ( @sql{@keys}, @sql{@$fields{@identified_by}} ) ).'):' . $error) if $log;
$local_dbh->rollback(); $local_dbh->rollback() if $ac;
ZoneMinder::Database::end_transaction( $local_dbh, $ac ); ZoneMinder::Database::end_transaction( $local_dbh, $ac ) if $ac;
return $error; return $error;
} # end if } # end if
if ( $debug or DEBUG_ALL ) { if ( $debug or DEBUG_ALL ) {
@ -350,7 +350,7 @@ $log->debug("No serial") if $debug;
} # end if } # end if
} # end if } # end if
} # end if } # end if
ZoneMinder::Database::end_transaction( $local_dbh, $ac ); ZoneMinder::Database::end_transaction( $local_dbh, $ac ) if $ac;
#$self->load(); #$self->load();
#if ( $$fields{id} ) { #if ( $$fields{id} ) {
#if ( ! $ZoneMinder::Object::cache{$type}{$$self{id}} ) { #if ( ! $ZoneMinder::Object::cache{$type}{$$self{id}} ) {

View File

@ -40,6 +40,10 @@ $serial = $primary_key = 'Id';
MonitorId MonitorId
Type Type
Units Units
NumCoords
Coords
Area
AlarmRGB
CheckMethod CheckMethod
MinPixelThreshold MinPixelThreshold
MaxPixelThreshold MaxPixelThreshold
@ -59,9 +63,13 @@ $serial = $primary_key = 'Id';
%defaults = ( %defaults = (
Name => '', Name => '',
Type => 'Active', Type => q`'Active'`,
Units => 'Pixels', Units => q`'Pixels'`,
CheckMethod => 'Blobs', NumCoords => 0,
Coords => '',
Area => 0,
AlarmRGB => 0,
CheckMethod => q`'Blobs'`,
MinPixelThreshold => undef, MinPixelThreshold => undef,
MaxPixelThreshold => undef, MaxPixelThreshold => undef,
MinAlarmPixels => undef, MinAlarmPixels => undef,

View File

@ -61,12 +61,12 @@ GetOptions(
'autostop' =>\$options{autostop}, 'autostop' =>\$options{autostop},
) or pod2usage(-exitstatus => -1); ) or pod2usage(-exitstatus => -1);
if ( !$id ) { if (!$id) {
print(STDERR "Please give a valid monitor id\n"); print(STDERR "Please give a valid monitor id\n");
pod2usage(-exitstatus => -1); pod2usage(-exitstatus => -1);
} }
( $id ) = $id =~ /^(\w+)$/; ($id) = $id =~ /^(\w+)$/;
logInit($id?(id=>'zmcontrol_'.$id):()); logInit($id?(id=>'zmcontrol_'.$id):());
my $sock_file = $Config{ZM_PATH_SOCKS}.'/zmcontrol-'.$id.'.sock'; my $sock_file = $Config{ZM_PATH_SOCKS}.'/zmcontrol-'.$id.'.sock';
@ -76,7 +76,7 @@ socket(CLIENT, PF_UNIX, SOCK_STREAM, 0) or Fatal("Can't open socket: $!");
my $saddr = sockaddr_un($sock_file); my $saddr = sockaddr_un($sock_file);
if ( $options{command} ) { if ($options{command}) {
# Have a command, so we are the client, connect to the server and send it. # Have a command, so we are the client, connect to the server and send it.
my $tries = 10; my $tries = 10;
@ -101,18 +101,16 @@ if ( $options{command} ) {
Error("Unable to connect to zmcontrol server at $sock_file"); Error("Unable to connect to zmcontrol server at $sock_file");
} }
} else { } else {
# The server isn't there # The server isn't there
my $monitor = zmDbGetMonitorAndControl($id); my $monitor = zmDbGetMonitorAndControl($id);
if ( !$monitor ) { Fatal("Unable to load control data for monitor $id") if !$monitor;
Fatal("Unable to load control data for monitor $id");
}
my $protocol = $monitor->{Protocol}; my $protocol = $monitor->{Protocol};
if ( !$protocol ) { if (!$protocol) {
Fatal('No protocol is set in monitor. Please edit the monitor, edit control type, select the control capability and fill in the Protocol field'); Fatal('No protocol is set in monitor. Please edit the monitor, edit control type, select the control capability and fill in the Protocol field');
} }
if ( -x $protocol ) { if (-x $protocol) {
# Protocol is actually a script! # Protocol is actually a script!
# Holdover from previous versions # Holdover from previous versions
my $command .= $protocol.' '.$arg_string; my $command .= $protocol.' '.$arg_string;
@ -120,11 +118,11 @@ if ( $options{command} ) {
my $output = qx($command); my $output = qx($command);
my $status = $? >> 8; my $status = $? >> 8;
if ( $status || logDebugging() ) { if ($status || logDebugging()) {
chomp($output); chomp($output);
Debug("Output: $output"); Debug("Output: $output");
} }
if ( $status ) { if ($status) {
Error("Command '$command' exited with status: $status"); Error("Command '$command' exited with status: $status");
exit($status); exit($status);
} }
@ -134,7 +132,7 @@ if ( $options{command} ) {
Info("Starting control server $id/$protocol"); Info("Starting control server $id/$protocol");
close(CLIENT); close(CLIENT);
if ( ! can_load( modules => { "ZoneMinder::Control::$protocol" => undef } ) ) { if (!can_load(modules => {'ZoneMinder::Control::'.$protocol => undef})) {
Fatal("Can't load ZoneMinder::Control::$protocol\n$Module::Load::Conditional::ERROR"); Fatal("Can't load ZoneMinder::Control::$protocol\n$Module::Load::Conditional::ERROR");
} }
@ -159,7 +157,7 @@ if ( $options{command} ) {
$control->open(); $control->open();
# If we have a command when starting up, then do it. # If we have a command when starting up, then do it.
if ( $options{command} ) { if ($options{command}) {
my $command = $options{command}; my $command = $options{command};
$control->$command(\%options); $control->$command(\%options);
} }

View File

@ -429,10 +429,20 @@ sub start {
# It's not running, or at least it's not been started by us # It's not running, or at least it's not been started by us
$process = { daemon=>$daemon, args=>\@args, command=>$command, keepalive=>!undef }; $process = { daemon=>$daemon, args=>\@args, command=>$command, keepalive=>!undef };
} elsif ( $process->{pid} && $pid_hash{$process->{pid}} ) { } elsif ( $process->{pid} && $pid_hash{$process->{pid}} ) {
dPrint(ZoneMinder::Logger::INFO, "'$process->{command}' already running at " if ($process->{term_sent_at}) {
dPrint(ZoneMinder::Logger::INFO, "'$process->{command}' was told to term at "
.strftime('%y/%m/%d %H:%M:%S', localtime($process->{term_sent_at}))
.", pid = $process->{pid}\n"
);
$process->{keepalive} = !undef;
$process->{delay} = 0;
delete $terminating_processes{$command};
} else {
dPrint(ZoneMinder::Logger::INFO, "'$process->{command}' already running at "
.strftime('%y/%m/%d %H:%M:%S', localtime($process->{started})) .strftime('%y/%m/%d %H:%M:%S', localtime($process->{started}))
.", pid = $process->{pid}\n" .", pid = $process->{pid}\n"
); );
}
return; return;
} }
@ -523,7 +533,7 @@ sub send_stop {
."\n" ."\n"
); );
sigprocmask(SIG_UNBLOCK, $blockset) or die "dying at unblock...\n"; sigprocmask(SIG_UNBLOCK, $blockset) or die "dying at unblock...\n";
return(); return ();
} }
my $pid = $process->{pid}; my $pid = $process->{pid};
@ -586,7 +596,7 @@ sub check_for_processes_to_kill {
sub stop { sub stop {
my ( $daemon, @args ) = @_; my ( $daemon, @args ) = @_;
my $command = join(' ', $daemon, @args ); my $command = join(' ', $daemon, @args);
my $process = $cmd_hash{$command}; my $process = $cmd_hash{$command};
if ( !$process ) { if ( !$process ) {
dPrint(ZoneMinder::Logger::WARNING, "Can't find process with command of '$command'"); dPrint(ZoneMinder::Logger::WARNING, "Can't find process with command of '$command'");

View File

@ -358,25 +358,27 @@ sub checkFilter {
} }
} # end if AutoDelete } # end if AutoDelete
if ( $filter->{AutoMove} ) { if ($filter->{AutoMove}) {
my $NewStorage = new ZoneMinder::Storage($filter->{AutoMoveTo}); my $NewStorage = ZoneMinder::Storage->find_one(Id=>$filter->{AutoMoveTo});
Info("Moving event $Event->{Id} to datastore $filter->{AutoMoveTo}"); if ($NewStorage) {
$_ = $Event->MoveTo($NewStorage); Info("Moving event $Event->{Id} to datastore $filter->{AutoMoveTo}");
Error($_) if $_; $_ = $Event->MoveTo($NewStorage);
Error($_) if $_;
} else {
Error("No storage area found for move to operation. AutoMoveTo was $$filter{AutoMoveTo}");
}
} }
if ( $filter->{AutoCopy} ) { if ($filter->{AutoCopy}) {
# Copy To is different from MoveTo in that it JUST copies the files # Copy To is different from MoveTo in that it JUST copies the files
# So we still need to update the Event object with the new SecondaryStorageId # So we still need to update the Event object with the new SecondaryStorageId
my $NewStorage = ZoneMinder::Storage->find_one(Id=>$filter->{AutoCopyTo}); my $NewStorage = ZoneMinder::Storage->find_one(Id=>$filter->{AutoCopyTo});
if ( $NewStorage ) { if ( $NewStorage ) {
Info("Copying event $Event->{Id} to datastore $filter->{AutoCopyTo}"); Info("Copying event $Event->{Id} to datastore $filter->{AutoCopyTo}");
$_ = $Event->CopyTo($NewStorage); $_ = $Event->CopyTo($NewStorage);
if ( $_ ) { if ($_) {
$ZoneMinder::Database::dbh->commit();
Error($_); Error($_);
} else { } else {
$Event->save({SecondaryStorageId=>$$NewStorage{Id}}); $Event->save({SecondaryStorageId=>$$NewStorage{Id}});
$ZoneMinder::Database::dbh->commit();
} }
} else { } else {
Error("No storage area found for copy to operation. AutoCopyTo was $$filter{AutoCopyTo}"); Error("No storage area found for copy to operation. AutoCopyTo was $$filter{AutoCopyTo}");
@ -399,7 +401,6 @@ sub checkFilter {
) { ) {
$Event->save(); $Event->save();
} }
$ZoneMinder::Database::dbh->commit() if !$$filter{LockRows};
} # end if UpdateDiskSpace } # end if UpdateDiskSpace
} # end foreach event } # end foreach event
ZoneMinder::Database::end_transaction($dbh, $in_transaction) if $$filter{LockRows}; ZoneMinder::Database::end_transaction($dbh, $in_transaction) if $$filter{LockRows};
@ -666,10 +667,10 @@ sub substituteTags {
# We have a filter and an event, do we need any more # We have a filter and an event, do we need any more
# monitor information? # monitor information?
my $need_monitor = $text =~ /%(?:MN|MET|MEH|MED|MEW|MEN|MEA)%/; my $need_monitor = $text =~ /%(?:MN|MET|MEH|MED|MEW|MEN|MEA)%/;
my $need_status = $text =~ /%(?:MET|MEH|MED|MEW|MEN|MEA)%/; my $need_summary = $text =~ /%(?:MET|MEH|MED|MEW|MEN|MEA)%/;
my $Monitor = $Event->Monitor() if $need_monitor; my $Monitor = $Event->Monitor() if $need_monitor;
my $Status = $Monitor->Status() if $need_status; my $Summary = $Monitor->Event_Summary() if $need_summary;
# Do we need the image information too? # Do we need the image information too?
my $need_images = $text =~ /%(?:EPI1|EPIM|EI1|EIM|EI1A|EIMA|EIMOD|EIMODG)%/; my $need_images = $text =~ /%(?:EPI1|EPIM|EI1|EIM|EI1A|EIMA|EIMOD|EIMODG)%/;
@ -693,19 +694,19 @@ sub substituteTags {
} }
$rows ++; $rows ++;
} }
Debug("Frames: rows: $rows first alarm frame: $first_alarm_frame max_alaarm_frame: $max_alarm_frame, score: $max_alarm_score"); Debug("Frames: rows: $rows first alarm frame: $first_alarm_frame max_alarm_frame: $max_alarm_frame, score: $max_alarm_score");
$sth->finish(); $sth->finish();
} }
my $url = $Config{ZM_URL}; my $url = $Config{ZM_URL};
$text =~ s/%ZP%/$url/g; $text =~ s/%ZP%/$url/g;
$text =~ s/%MN%/$Monitor->{Name}/g; $text =~ s/%MN%/$Monitor->{Name}/g;
$text =~ s/%MET%/$Status->{TotalEvents}/g; $text =~ s/%MET%/$Summary->{TotalEvents}/g;
$text =~ s/%MEH%/$Status->{HourEvents}/g; $text =~ s/%MEH%/$Summary->{HourEvents}/g;
$text =~ s/%MED%/$Status->{DayEvents}/g; $text =~ s/%MED%/$Summary->{DayEvents}/g;
$text =~ s/%MEW%/$Status->{WeekEvents}/g; $text =~ s/%MEW%/$Summary->{WeekEvents}/g;
$text =~ s/%MEM%/$Status->{MonthEvents}/g; $text =~ s/%MEM%/$Summary->{MonthEvents}/g;
$text =~ s/%MEA%/$Status->{ArchivedEvents}/g; $text =~ s/%MEA%/$Summary->{ArchivedEvents}/g;
$text =~ s/%MP%/$url?view=watch&mid=$Event->{MonitorId}/g; $text =~ s/%MP%/$url?view=watch&mid=$Event->{MonitorId}/g;
$text =~ s/%MPS%/$url?view=watch&mid=$Event->{MonitorId}&mode=stream/g; $text =~ s/%MPS%/$url?view=watch&mid=$Event->{MonitorId}&mode=stream/g;
$text =~ s/%MPI%/$url?view=watch&mid=$Event->{MonitorId}&mode=still/g; $text =~ s/%MPI%/$url?view=watch&mid=$Event->{MonitorId}&mode=still/g;
@ -843,7 +844,7 @@ sub sendEmail {
return 0; return 0;
} }
Info('Creating notification email'); Debug('Creating notification email');
my $subject = substituteTags($$filter{EmailSubject}, $filter, $Event); my $subject = substituteTags($$filter{EmailSubject}, $filter, $Event);
return 0 if !$subject; return 0 if !$subject;
@ -851,7 +852,7 @@ sub sendEmail {
my $body = substituteTags($$filter{EmailBody}, $filter, $Event, \@attachments); my $body = substituteTags($$filter{EmailBody}, $filter, $Event, \@attachments);
return 0 if !$body; return 0 if !$body;
Info("Sending notification email '$subject'"); Debug("Sending notification email '$subject'");
eval { eval {
if ( $Config{ZM_NEW_MAIL_MODULES} ) { if ( $Config{ZM_NEW_MAIL_MODULES} ) {
@ -864,7 +865,7 @@ sub sendEmail {
); );
### Add the text message part ### Add the text message part
$mail->attach ( $mail->attach (
Type => 'TEXT', Type => (($body=~/<html/)?'text/html':'text/plain'),
Data => $body Data => $body
); );
### Add the attachments ### Add the attachments
@ -886,9 +887,7 @@ sub sendEmail {
if ( $Config{ZM_SSMTP_MAIL} ) { if ( $Config{ZM_SSMTP_MAIL} ) {
my $ssmtp_location = $Config{ZM_SSMTP_PATH}; my $ssmtp_location = $Config{ZM_SSMTP_PATH};
if ( !$ssmtp_location ) { if ( !$ssmtp_location ) {
if ( logDebugging() ) { Debug("which ssmtp: $ssmtp_location - set ssmtp path in options to suppress this message");
Debug("which ssmtp: $ssmtp_location - set ssmtp path in options to suppress this message");
}
$ssmtp_location = qx('which ssmtp'); $ssmtp_location = qx('which ssmtp');
} }
if ( !$ssmtp_location ) { if ( !$ssmtp_location ) {
@ -916,7 +915,7 @@ sub sendEmail {
foreach my $attachment ( @attachments ) { foreach my $attachment ( @attachments ) {
my $size = -s $attachment->{path}; my $size = -s $attachment->{path};
$total_size += $size; $total_size += $size;
Info("Attaching '$attachment->{path}' which is $size bytes"); Debug("Attaching '$attachment->{path}' which is $size bytes");
$mail->attach( $mail->attach(
Path => $attachment->{path}, Path => $attachment->{path},
@ -934,7 +933,7 @@ sub sendEmail {
Error("Unable to send email: $@"); Error("Unable to send email: $@");
return 0; return 0;
} else { } else {
Info('Notification email sent'); Info("Notification email sent to $$filter{EmailTo}");
} }
my $sql = 'UPDATE `Events` SET `Emailed` = 1 WHERE `Id` = ?'; my $sql = 'UPDATE `Events` SET `Emailed` = 1 WHERE `Id` = ?';
my $sth = $dbh->prepare_cached($sql) my $sth = $dbh->prepare_cached($sql)

View File

@ -27,7 +27,7 @@ zmupdate.pl - check and upgrade ZoneMinder database
=head1 SYNOPSIS =head1 SYNOPSIS
zmupdate.pl -c,--check | -f,--freshen | -v<version>,--version=<version> [-u<dbuser> -p<dbpass>] zmupdate.pl -c,--check | -f,--freshen | -v<version>,--version=<version> [-u <dbuser> -p <dbpass>]
=head1 DESCRIPTION =head1 DESCRIPTION

View File

@ -98,19 +98,19 @@ while (!$zm_terminate) {
next if $monitor->{Type} eq 'WebSite'; next if $monitor->{Type} eq 'WebSite';
my $now = time(); my $now = time();
my $restart = 0; my $restart = 0;
if ( zmMemVerify($monitor) ) { if (zmMemVerify($monitor)) {
# Check we have got an image recently # Check we have got an image recently
my $capture_time = zmGetLastWriteTime($monitor); my $capture_time = zmGetLastWriteTime($monitor);
if ( !defined($capture_time) ) { if (!defined($capture_time)) {
# Can't read from shared data # Can't read from shared data
Debug('LastWriteTime is not defined.'); Debug('LastWriteTime is not defined.');
zmMemInvalidate($monitor); zmMemInvalidate($monitor);
next; next;
} }
Debug("Monitor $$monitor{Id} LastWriteTime is $capture_time."); Debug("Monitor $$monitor{Id} LastWriteTime is $capture_time.");
if ( !$capture_time ) { if (!$capture_time) {
my $startup_time = zmGetStartupTime($monitor); my $startup_time = zmGetStartupTime($monitor);
if ( ( $now - $startup_time ) > $Config{ZM_WATCH_MAX_DELAY} ) { if (($now - $startup_time) > $Config{ZM_WATCH_MAX_DELAY}) {
Warning( Warning(
"Restarting capture daemon for $$monitor{Name}, no image since startup. ". "Restarting capture daemon for $$monitor{Name}, no image since startup. ".
"Startup time was $startup_time - now $now > $Config{ZM_WATCH_MAX_DELAY}" "Startup time was $startup_time - now $now > $Config{ZM_WATCH_MAX_DELAY}"
@ -122,7 +122,7 @@ while (!$zm_terminate) {
next; next;
} }
} }
if ( ! $restart ) { if (!$restart) {
my $max_image_delay = ( my $max_image_delay = (
$monitor->{MaxFPS} $monitor->{MaxFPS}
&&($monitor->{MaxFPS}>0) &&($monitor->{MaxFPS}>0)
@ -144,29 +144,28 @@ while (!$zm_terminate) {
$restart = 1; $restart = 1;
} }
if ( $restart ) { if ($restart) {
my $command; my $command;
if ( $monitor->{Type} eq 'Local' ) { if ($monitor->{Type} eq 'Local') {
$command = "zmdc.pl restart zmc -d $monitor->{Device}"; $command = 'zmdc.pl restart zmc -d '.$monitor->{Device};
} else { } else {
$command = "zmdc.pl restart zmc -m $monitor->{Id}"; $command = 'zmdc.pl restart zmc -m '.$monitor->{Id};
} }
runCommand($command); runCommand($command);
} elsif ( $monitor->{Function} ne 'Monitor' ) { } elsif ($monitor->{Function} ne 'Monitor') {
# Now check analysis daemon # Now check analysis daemon
$restart = 0; $restart = 0;
# Check we have got an image recently # Check we have got an image recently
my $image_time = zmGetLastReadTime($monitor); my $image_time = zmGetLastReadTime($monitor);
if ( !defined($image_time) ) { if (!defined($image_time)) {
# Can't read from shared data # Can't read from shared data
$restart = 1; $restart = 1;
Error("Error reading shared data for $$monitor{Id} $$monitor{Name}"); Error("Error reading shared data for $$monitor{Id} $$monitor{Name}");
} elsif ( !$image_time ) { } elsif (!$image_time) {
# We can't get the last capture time so can't be sure it's died. # We can't get the last capture time so can't be sure it's died.
#$restart = 1; #$restart = 1;
Error("Last analyse time for $$monitor{Id} $$monitor{Name} was zero."); Error("Last analyse time for $$monitor{Id} $$monitor{Name} was zero.");
} else { } else {
my $max_image_delay = ( $monitor->{MaxFPS} my $max_image_delay = ( $monitor->{MaxFPS}
&&($monitor->{MaxFPS}>0) &&($monitor->{MaxFPS}>0)
&&($monitor->{MaxFPS}<1) &&($monitor->{MaxFPS}<1)
@ -175,7 +174,7 @@ while (!$zm_terminate) {
; ;
my $image_delay = $now-$image_time; my $image_delay = $now-$image_time;
Debug("Monitor $monitor->{Id} last analysed $image_delay seconds ago, max is $max_image_delay"); Debug("Monitor $monitor->{Id} last analysed $image_delay seconds ago, max is $max_image_delay");
if ( $image_delay > $max_image_delay ) { if ($image_delay > $max_image_delay) {
Warning("Analysis daemon for $$monitor{Id} $$monitor{Name} needs restarting," Warning("Analysis daemon for $$monitor{Id} $$monitor{Name} needs restarting,"
." time since last analysis $image_delay seconds ($now-$image_time)" ." time since last analysis $image_delay seconds ($now-$image_time)"
); );
@ -183,13 +182,13 @@ while (!$zm_terminate) {
} }
} }
if ( $restart ) { if ($restart) {
Info("Restarting analysis daemon for $$monitor{Id} $$monitor{Name}\n"); Info("Restarting analysis daemon for $$monitor{Id} $$monitor{Name}");
my $command; my $command;
if ( $monitor->{Type} eq 'Local' ) { if ( $monitor->{Type} eq 'Local' ) {
$command = "zmdc.pl restart zmc -d $monitor->{Device}"; $command = 'zmdc.pl restart zmc -d '.$monitor->{Device};
} else { } else {
$command = "zmdc.pl restart zmc -m $monitor->{Id}"; $command = 'zmdc.pl restart zmc -m '.$monitor->{Id};
} }
runCommand($command); runCommand($command);
} # end if restart } # end if restart
@ -201,7 +200,7 @@ while (!$zm_terminate) {
sleep($Config{ZM_WATCH_CHECK_INTERVAL}); sleep($Config{ZM_WATCH_CHECK_INTERVAL});
} # end while (!$zm_terminate) } # end while (!$zm_terminate)
Info("Watchdog exiting"); Info('Watchdog exiting');
exit(); exit();
1; 1;

View File

@ -642,6 +642,7 @@ bool EventStream::checkEventLoaded() {
else else
curr_frame_id = 1; curr_frame_id = 1;
Debug(2, "New frame id = %ld", curr_frame_id); Debug(2, "New frame id = %ld", curr_frame_id);
gettimeofday(&start, nullptr);
return true; return true;
} else { } else {
Debug(2, "No next event loaded using %s. Pausing", sql.c_str()); Debug(2, "No next event loaded using %s. Pausing", sql.c_str());
@ -927,11 +928,13 @@ void EventStream::runStream() {
delta_us = (unsigned int)(frame_data->delta * 1000000); delta_us = (unsigned int)(frame_data->delta * 1000000);
Debug(3, "frame delta %uus ", delta_us); Debug(3, "frame delta %uus ", delta_us);
// if effective > base we should speed up frame delivery // if effective > base we should speed up frame delivery
delta_us = (unsigned int)((delta_us * base_fps)/effective_fps); if (base_fps < effective_fps) {
Debug(3, "delta %u = base_fps(%f)/effective fps(%f)", delta_us, base_fps, effective_fps); delta_us = (unsigned int)((delta_us * base_fps)/effective_fps);
// but must not exceed maxfps Debug(3, "delta %u = base_fps(%f)/effective fps(%f)", delta_us, base_fps, effective_fps);
delta_us = std::max(delta_us, int(1000000/maxfps)); // but must not exceed maxfps
Debug(3, "delta %u = base_fps(%f)/effective fps(%f) from 30fps", delta_us, base_fps, effective_fps); delta_us = std::max(delta_us, int(1000000/maxfps));
Debug(3, "delta %u = base_fps(%f)/effective fps(%f) from 30fps", delta_us, base_fps, effective_fps);
}
// +/- 1? What if we are skipping frames? // +/- 1? What if we are skipping frames?
curr_frame_id += (replay_rate>0) ? frame_mod : -1*frame_mod; curr_frame_id += (replay_rate>0) ? frame_mod : -1*frame_mod;

View File

@ -321,6 +321,7 @@ bool Image::Assign(const AVFrame *frame, SwsContext *convert_context, AVFrame *t
return false; return false;
} }
zm_dump_video_frame(temp_frame, "dest frame after convert"); zm_dump_video_frame(temp_frame, "dest frame after convert");
update_function_pointers();
return true; return true;
} // end Image::Assign(const AVFrame *frame, SwsContext *convert_context, AVFrame *temp_frame) } // end Image::Assign(const AVFrame *frame, SwsContext *convert_context, AVFrame *temp_frame)
@ -695,6 +696,7 @@ void Image::AssignDirect(
subpixelorder = p_subpixelorder; subpixelorder = p_subpixelorder;
pixels = width * height; pixels = width * height;
size = new_buffer_size; size = new_buffer_size;
update_function_pointers();
} // end void Image::AssignDirect } // end void Image::AssignDirect
void Image::Assign( void Image::Assign(
@ -796,6 +798,7 @@ void Image::Assign(const Image &image) {
linesize = image.linesize; linesize = image.linesize;
} }
update_function_pointers();
if ( image.buffer != buffer ) if ( image.buffer != buffer )
(*fptr_imgbufcpy)(buffer, image.buffer, size); (*fptr_imgbufcpy)(buffer, image.buffer, size);
} }

View File

@ -180,7 +180,7 @@ class Image {
/* Internal buffer should not be modified from functions outside of this class */ /* Internal buffer should not be modified from functions outside of this class */
inline const uint8_t* Buffer() const { return buffer; } inline const uint8_t* Buffer() const { return buffer; }
inline const uint8_t* Buffer(unsigned int x, unsigned int y=0) const { return &buffer[(y*linesize)+x]; } inline const uint8_t* Buffer(unsigned int x, unsigned int y=0) const { return &buffer[(y*linesize) + x*colours]; }
/* Request writeable buffer */ /* Request writeable buffer */
uint8_t* WriteBuffer(const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder); uint8_t* WriteBuffer(const unsigned int p_width, const unsigned int p_height, const unsigned int p_colours, const unsigned int p_subpixelorder);
// Is only acceptable on a pre-allocated buffer // Is only acceptable on a pre-allocated buffer

View File

@ -23,7 +23,7 @@ void bind_libvnc_symbols() {
libvnc_lib = dlopen("libvncclient.so", RTLD_LAZY | RTLD_GLOBAL); libvnc_lib = dlopen("libvncclient.so", RTLD_LAZY | RTLD_GLOBAL);
if (!libvnc_lib) { if (!libvnc_lib) {
Error("Error loading libvncclient: %s", dlerror()); Error("Error loading libvncclient.so: %s", dlerror());
return; return;
} }
@ -135,11 +135,6 @@ VncCamera::VncCamera(
} }
VncCamera::~VncCamera() { VncCamera::~VncCamera() {
if (capture and mRfb) {
if (mRfb->frameBuffer)
free(mRfb->frameBuffer);
(*rfbClientCleanup_f)(mRfb);
}
if (libvnc_lib) { if (libvnc_lib) {
dlclose(libvnc_lib); dlclose(libvnc_lib);
libvnc_lib = nullptr; libvnc_lib = nullptr;
@ -253,6 +248,12 @@ int VncCamera::PostCapture() {
} }
int VncCamera::Close() { int VncCamera::Close() {
if (capture and mRfb) {
if (mRfb->frameBuffer)
free(mRfb->frameBuffer);
(*rfbClientCleanup_f)(mRfb);
mRfb = nullptr;
}
return 1; return 1;
} }
#endif #endif

View File

@ -24,6 +24,7 @@
#include <fcntl.h> #include <fcntl.h>
#include <sys/mman.h> #include <sys/mman.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <unistd.h>
#if ZM_HAS_V4L #if ZM_HAS_V4L

View File

@ -89,9 +89,10 @@ std::string load_monitor_sql =
"`SectionLength`, `MinSectionLength`, `FrameSkip`, `MotionFrameSkip`, " "`SectionLength`, `MinSectionLength`, `FrameSkip`, `MotionFrameSkip`, "
"`FPSReportInterval`, `RefBlendPerc`, `AlarmRefBlendPerc`, `TrackMotion`, `Exif`," "`FPSReportInterval`, `RefBlendPerc`, `AlarmRefBlendPerc`, `TrackMotion`, `Exif`,"
"`RTSPServer`, `RTSPStreamName`," "`RTSPServer`, `RTSPStreamName`,"
"`SignalCheckPoints`, `SignalCheckColour`, `Importance`-2 FROM `Monitors`"; "`SignalCheckPoints`, `SignalCheckColour`, `Importance`-1 FROM `Monitors`";
std::string CameraType_Strings[] = { std::string CameraType_Strings[] = {
"Unknown",
"Local", "Local",
"Remote", "Remote",
"File", "File",
@ -99,10 +100,21 @@ std::string CameraType_Strings[] = {
"LibVLC", "LibVLC",
"NVSOCKET", "NVSOCKET",
"CURL", "CURL",
"VNC", "VNC"
};
std::string Function_Strings[] = {
"Unknown",
"None",
"Monitor",
"Modect",
"Record",
"Mocord",
"Nodect"
}; };
std::string State_Strings[] = { std::string State_Strings[] = {
"Unknown",
"IDLE", "IDLE",
"PREALARM", "PREALARM",
"ALARM", "ALARM",
@ -151,12 +163,12 @@ bool Monitor::MonitorLink::connect() {
Debug(1, "link.mem.size=%jd", mem_size); Debug(1, "link.mem.size=%jd", mem_size);
#if ZM_MEM_MAPPED #if ZM_MEM_MAPPED
map_fd = open(mem_file.c_str(), O_RDWR, (mode_t)0600); map_fd = open(mem_file.c_str(), O_RDWR, (mode_t)0600);
if ( map_fd < 0 ) { if (map_fd < 0) {
Debug(3, "Can't open linked memory map file %s: %s", mem_file.c_str(), strerror(errno)); Debug(3, "Can't open linked memory map file %s: %s", mem_file.c_str(), strerror(errno));
disconnect(); disconnect();
return false; return false;
} }
while ( map_fd <= 2 ) { while (map_fd <= 2) {
int new_map_fd = dup(map_fd); int new_map_fd = dup(map_fd);
Warning("Got one of the stdio fds for our mmap handle. map_fd was %d, new one is %d", map_fd, new_map_fd); Warning("Got one of the stdio fds for our mmap handle. map_fd was %d, new one is %d", map_fd, new_map_fd);
close(map_fd); close(map_fd);
@ -164,31 +176,31 @@ bool Monitor::MonitorLink::connect() {
} }
struct stat map_stat; struct stat map_stat;
if ( fstat(map_fd, &map_stat) < 0 ) { if (fstat(map_fd, &map_stat) < 0) {
Error("Can't stat linked memory map file %s: %s", mem_file.c_str(), strerror(errno)); Error("Can't stat linked memory map file %s: %s", mem_file.c_str(), strerror(errno));
disconnect(); disconnect();
return false; return false;
} }
if ( map_stat.st_size == 0 ) { if (map_stat.st_size == 0) {
Error("Linked memory map file %s is empty: %s", mem_file.c_str(), strerror(errno)); Error("Linked memory map file %s is empty: %s", mem_file.c_str(), strerror(errno));
disconnect(); disconnect();
return false; return false;
} else if ( map_stat.st_size < mem_size ) { } else if (map_stat.st_size < mem_size) {
Error("Got unexpected memory map file size %ld, expected %jd", map_stat.st_size, mem_size); Error("Got unexpected memory map file size %ld, expected %jd", map_stat.st_size, mem_size);
disconnect(); disconnect();
return false; return false;
} }
mem_ptr = (unsigned char *)mmap(nullptr, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED, map_fd, 0); mem_ptr = (unsigned char *)mmap(nullptr, mem_size, PROT_READ|PROT_WRITE, MAP_SHARED, map_fd, 0);
if ( mem_ptr == MAP_FAILED ) { if (mem_ptr == MAP_FAILED) {
Error("Can't map file %s (%jd bytes) to memory: %s", mem_file.c_str(), mem_size, strerror(errno)); Error("Can't map file %s (%jd bytes) to memory: %s", mem_file.c_str(), mem_size, strerror(errno));
disconnect(); disconnect();
return false; return false;
} }
#else // ZM_MEM_MAPPED #else // ZM_MEM_MAPPED
shm_id = shmget((config.shm_key&0xffff0000)|id, mem_size, 0700); shm_id = shmget((config.shm_key&0xffff0000)|id, mem_size, 0700);
if ( shm_id < 0 ) { if (shm_id < 0) {
Debug(3, "Can't shmget link memory: %s", strerror(errno)); Debug(3, "Can't shmget link memory: %s", strerror(errno));
connected = false; connected = false;
return false; return false;
@ -204,7 +216,7 @@ bool Monitor::MonitorLink::connect() {
shared_data = (SharedData *)mem_ptr; shared_data = (SharedData *)mem_ptr;
trigger_data = (TriggerData *)((char *)shared_data + sizeof(SharedData)); trigger_data = (TriggerData *)((char *)shared_data + sizeof(SharedData));
if ( !shared_data->valid ) { if (!shared_data->valid) {
Debug(3, "Linked memory not initialised by capture daemon"); Debug(3, "Linked memory not initialised by capture daemon");
disconnect(); disconnect();
return false; return false;
@ -220,23 +232,23 @@ bool Monitor::MonitorLink::connect() {
} // end bool Monitor::MonitorLink::connect() } // end bool Monitor::MonitorLink::connect()
bool Monitor::MonitorLink::disconnect() { bool Monitor::MonitorLink::disconnect() {
if ( connected ) { if (connected) {
connected = false; connected = false;
#if ZM_MEM_MAPPED #if ZM_MEM_MAPPED
if ( mem_ptr > (void *)0 ) { if (mem_ptr > (void *)0) {
msync( mem_ptr, mem_size, MS_ASYNC ); msync(mem_ptr, mem_size, MS_ASYNC);
munmap( mem_ptr, mem_size ); munmap(mem_ptr, mem_size);
} }
if ( map_fd >= 0 ) if (map_fd >= 0)
close( map_fd ); close(map_fd);
map_fd = -1; map_fd = -1;
#else // ZM_MEM_MAPPED #else // ZM_MEM_MAPPED
struct shmid_ds shm_data; struct shmid_ds shm_data;
if ( shmctl( shm_id, IPC_STAT, &shm_data ) < 0 ) { if (shmctl(shm_id, IPC_STAT, &shm_data) < 0) {
Debug( 3, "Can't shmctl: %s", strerror(errno) ); Debug(3, "Can't shmctl: %s", strerror(errno));
return( false ); return false;
} }
shm_id = 0; shm_id = 0;
@ -252,7 +264,6 @@ bool Monitor::MonitorLink::disconnect() {
Debug(3, "Can't shmdt: %s", strerror(errno)); Debug(3, "Can't shmdt: %s", strerror(errno));
return false; return false;
} }
#endif // ZM_MEM_MAPPED #endif // ZM_MEM_MAPPED
mem_size = 0; mem_size = 0;
mem_ptr = nullptr; mem_ptr = nullptr;
@ -445,7 +456,7 @@ Monitor::Monitor()
"SectionLength, MinSectionLength, FrameSkip, MotionFrameSkip, " "SectionLength, MinSectionLength, FrameSkip, MotionFrameSkip, "
"FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif," "FPSReportInterval, RefBlendPerc, AlarmRefBlendPerc, TrackMotion, Exif,"
"`RTSPServer`,`RTSPStreamName`, "`RTSPServer`,`RTSPStreamName`,
"SignalCheckPoints, SignalCheckColour, Importance-2 FROM Monitors"; "SignalCheckPoints, SignalCheckColour, Importance-1 FROM Monitors";
*/ */
void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) { void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) {
@ -482,8 +493,9 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) {
Debug(1, "Have camera type %s", CameraType_Strings[type].c_str()); Debug(1, "Have camera type %s", CameraType_Strings[type].c_str());
col++; col++;
function = (Function)atoi(dbrow[col]); col++; function = (Function)atoi(dbrow[col]); col++;
enabled = dbrow[col] ? atoi(dbrow[col]) : 0; col++; enabled = dbrow[col] ? atoi(dbrow[col]) : false; col++;
decoding_enabled = dbrow[col] ? atoi(dbrow[col]) : 0; col++; decoding_enabled = dbrow[col] ? atoi(dbrow[col]) : false; col++;
// See below after save_jpegs for a recalculation of decoding_enabled
ReloadLinkedMonitors(dbrow[col]); col++; ReloadLinkedMonitors(dbrow[col]); col++;
@ -546,6 +558,17 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) {
videowriter = (VideoWriter)atoi(dbrow[col]); col++; videowriter = (VideoWriter)atoi(dbrow[col]); col++;
encoderparams = dbrow[col] ? dbrow[col] : ""; col++; encoderparams = dbrow[col] ? dbrow[col] : ""; col++;
decoding_enabled = !(
( function == RECORD or function == NODECT )
and
( savejpegs == 0 )
and
( videowriter == PASSTHROUGH )
and
!decoding_enabled
);
Debug(3, "Decoding enabled: %d function %d %s savejpegs %d videowriter %d", decoding_enabled, function, Function_Strings[function].c_str(), savejpegs, videowriter);
/*"`OutputCodec`, `Encoder`, `OutputContainer`, " */ /*"`OutputCodec`, `Encoder`, `OutputContainer`, " */
output_codec = dbrow[col] ? atoi(dbrow[col]) : 0; col++; output_codec = dbrow[col] ? atoi(dbrow[col]) : 0; col++;
encoder = dbrow[col] ? dbrow[col] : ""; col++; encoder = dbrow[col] ? dbrow[col] : ""; col++;
@ -595,7 +618,7 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) {
rtsp_server = (*dbrow[col] != '0'); col++; rtsp_server = (*dbrow[col] != '0'); col++;
rtsp_streamname = dbrow[col]; col++; rtsp_streamname = dbrow[col]; col++;
/*"SignalCheckPoints, SignalCheckColour, Importance-2 FROM Monitors"; */ /*"SignalCheckPoints, SignalCheckColour, Importance-1 FROM Monitors"; */
signal_check_points = atoi(dbrow[col]); col++; signal_check_points = atoi(dbrow[col]); col++;
signal_check_colour = strtol(dbrow[col][0] == '#' ? dbrow[col]+1 : dbrow[col], 0, 16); col++; signal_check_colour = strtol(dbrow[col][0] == '#' ? dbrow[col]+1 : dbrow[col], 0, 16); col++;
@ -607,6 +630,7 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) {
grayscale_val = signal_check_colour & 0xff; /* Clear all bytes but lowest byte */ grayscale_val = signal_check_colour & 0xff; /* Clear all bytes but lowest byte */
importance = dbrow[col] ? atoi(dbrow[col]) : 0;// col++; importance = dbrow[col] ? atoi(dbrow[col]) : 0;// col++;
if (importance < 0) importance = 0; // Should only be >= 0
// How many frames we need to have before we start analysing // How many frames we need to have before we start analysing
ready_count = std::max(warmup_count, pre_event_count); ready_count = std::max(warmup_count, pre_event_count);
@ -654,18 +678,6 @@ void Monitor::Load(MYSQL_ROW dbrow, bool load_zones=true, Purpose p = QUERY) {
Error("Can't mkdir %s: %s", monitor_dir.c_str(), strerror(errno)); Error("Can't mkdir %s: %s", monitor_dir.c_str(), strerror(errno));
} }
// Do this here to save a few cycles with all the comparisons
decoding_enabled = !(
( function == RECORD or function == NODECT )
and
( savejpegs == 0 )
and
( videowriter == PASSTHROUGH )
and
!decoding_enabled
);
Debug(1, "Decoding enabled: %d", decoding_enabled);
if ( config.record_diag_images ) { if ( config.record_diag_images ) {
if ( config.record_diag_images_fifo ) { if ( config.record_diag_images_fifo ) {
diag_path_ref = stringtf("%s/diagpipe-r-%d.jpg", staticConfig.PATH_SOCKS.c_str(), id); diag_path_ref = stringtf("%s/diagpipe-r-%d.jpg", staticConfig.PATH_SOCKS.c_str(), id);
@ -899,7 +911,6 @@ std::shared_ptr<Monitor> Monitor::Load(unsigned int p_id, bool load_zones, Purpo
} }
bool Monitor::connect() { bool Monitor::connect() {
if (mem_ptr != nullptr) { if (mem_ptr != nullptr) {
Warning("Already connected. Please call disconnect first."); Warning("Already connected. Please call disconnect first.");
} }
@ -1041,7 +1052,7 @@ bool Monitor::connect() {
video_store_data->size = sizeof(VideoStoreData); video_store_data->size = sizeof(VideoStoreData);
usedsubpixorder = camera->SubpixelOrder(); // Used in CheckSignal usedsubpixorder = camera->SubpixelOrder(); // Used in CheckSignal
shared_data->valid = true; shared_data->valid = true;
} else if ( !shared_data->valid ) { } else if (!shared_data->valid) {
Error("Shared data not initialised by capture daemon for monitor %s", name.c_str()); Error("Shared data not initialised by capture daemon for monitor %s", name.c_str());
return false; return false;
} }
@ -1063,6 +1074,13 @@ bool Monitor::disconnect() {
return true; return true;
} }
if (purpose == CAPTURE) {
if (unlink(mem_file.c_str()) < 0) {
Warning("Can't unlink '%s': %s", mem_file.c_str(), strerror(errno));
}
Debug(1, "Setting shared_data->valid = false");
shared_data->valid = false;
}
#if ZM_MEM_MAPPED #if ZM_MEM_MAPPED
msync(mem_ptr, mem_size, MS_ASYNC); msync(mem_ptr, mem_size, MS_ASYNC);
munmap(mem_ptr, mem_size); munmap(mem_ptr, mem_size);
@ -1072,9 +1090,6 @@ bool Monitor::disconnect() {
mem_ptr = nullptr; mem_ptr = nullptr;
shared_data = nullptr; shared_data = nullptr;
if (purpose == CAPTURE and (unlink(mem_file.c_str()) < 0) ) {
Warning("Can't unlink '%s': %s", mem_file.c_str(), strerror(errno));
}
#else // ZM_MEM_MAPPED #else // ZM_MEM_MAPPED
struct shmid_ds shm_data; struct shmid_ds shm_data;
if (shmctl(shm_id, IPC_STAT, &shm_data) < 0) { if (shmctl(shm_id, IPC_STAT, &shm_data) < 0) {
@ -1095,7 +1110,7 @@ bool Monitor::disconnect() {
} }
#endif // ZM_MEM_MAPPED #endif // ZM_MEM_MAPPED
for ( int32_t i = 0; i < image_buffer_count; i++ ) { for (int32_t i = 0; i < image_buffer_count; i++) {
// We delete the image because it is an object pointing to space that won't be free'd. // We delete the image because it is an object pointing to space that won't be free'd.
delete image_buffer[i]; delete image_buffer[i];
image_buffer[i] = nullptr; image_buffer[i] = nullptr;
@ -1109,10 +1124,6 @@ Monitor::~Monitor() {
if (mem_ptr != nullptr) { if (mem_ptr != nullptr) {
if (purpose != QUERY) { if (purpose != QUERY) {
shared_data->state = state = IDLE;
shared_data->last_read_index = image_buffer_count;
shared_data->last_read_time = 0;
shared_data->valid = false;
memset(mem_ptr, 0, mem_size); memset(mem_ptr, 0, mem_size);
} // end if purpose != query } // end if purpose != query
disconnect(); disconnect();
@ -1745,10 +1756,6 @@ bool Monitor::Analyse() {
return false; return false;
} }
// Store the it that points to our snap we will need it later
packetqueue_iterator snap_it = *analysis_it;
packetqueue.increment_it(analysis_it);
// signal is set by capture // signal is set by capture
bool signal = shared_data->signal; bool signal = shared_data->signal;
bool signal_change = (signal != last_signal); bool signal_change = (signal != last_signal);
@ -1858,7 +1865,14 @@ bool Monitor::Analyse() {
while (!snap->decoded and !zm_terminate and !analysis_thread->Stopped()) { while (!snap->decoded and !zm_terminate and !analysis_thread->Stopped()) {
// Need to wait for the decoder thread. // Need to wait for the decoder thread.
Debug(1, "Waiting for decode"); Debug(1, "Waiting for decode");
packet_lock->wait(); packetqueue.unlock(packet_lock); // This will delete packet_lock and notify_all
packetqueue.wait();
// Another thread may have moved our it. Unlikely but possible
packet_lock = packetqueue.get_packet(analysis_it);
if (!packet_lock) return false;
snap = packet_lock->packet_;
if (!snap->image and snap->decoded) { if (!snap->image and snap->decoded) {
Debug(1, "No image but was decoded, giving up"); Debug(1, "No image but was decoded, giving up");
delete packet_lock; delete packet_lock;
@ -1890,7 +1904,7 @@ bool Monitor::Analyse() {
if (snap->image) { if (snap->image) {
// decoder may not have been able to provide an image // decoder may not have been able to provide an image
if (!ref_image.Buffer()) { if (!ref_image.Buffer()) {
Debug(1, "Assigning instead of Dectecting"); Debug(1, "Assigning instead of Detecting");
ref_image.Assign(*(snap->image)); ref_image.Assign(*(snap->image));
} else { } else {
Debug(1, "Detecting motion on image %d, image %p", snap->image_index, snap->image); Debug(1, "Detecting motion on image %d, image %p", snap->image_index, snap->image);
@ -1961,14 +1975,14 @@ bool Monitor::Analyse() {
// Must start on a keyframe so rewind. Only for passthrough though I guess. // Must start on a keyframe so rewind. Only for passthrough though I guess.
// FIXME this iterator is not protected from invalidation // FIXME this iterator is not protected from invalidation
packetqueue_iterator *start_it = packetqueue.get_event_start_packet_it( packetqueue_iterator *start_it = packetqueue.get_event_start_packet_it(
snap_it, 0 /* pre_event_count */ *analysis_it, 0 /* pre_event_count */
); );
// This gets a lock on the starting packet // This gets a lock on the starting packet
ZMLockedPacket *starting_packet_lock = nullptr; ZMLockedPacket *starting_packet_lock = nullptr;
std::shared_ptr<ZMPacket> starting_packet = nullptr; std::shared_ptr<ZMPacket> starting_packet = nullptr;
if (*start_it != snap_it) { if (*start_it != *analysis_it) {
starting_packet_lock = packetqueue.get_packet(start_it); starting_packet_lock = packetqueue.get_packet(start_it);
if (!starting_packet_lock) { if (!starting_packet_lock) {
Warning("Unable to get starting packet lock"); Warning("Unable to get starting packet lock");
@ -1982,12 +1996,12 @@ bool Monitor::Analyse() {
event = new Event(this, starting_packet->timestamp, "Continuous", noteSetMap); event = new Event(this, starting_packet->timestamp, "Continuous", noteSetMap);
// Write out starting packets, do not modify packetqueue it will garbage collect itself // Write out starting packets, do not modify packetqueue it will garbage collect itself
while (starting_packet and ((*start_it) != snap_it)) { while (starting_packet and ((*start_it) != *analysis_it)) {
event->AddPacket(starting_packet); event->AddPacket(starting_packet);
// Have added the packet, don't want to unlock it until we have locked the next // Have added the packet, don't want to unlock it until we have locked the next
packetqueue.increment_it(start_it); packetqueue.increment_it(start_it);
if ((*start_it) == snap_it) { if ((*start_it) == *analysis_it) {
if (starting_packet_lock) delete starting_packet_lock; if (starting_packet_lock) delete starting_packet_lock;
break; break;
} }
@ -2026,8 +2040,7 @@ bool Monitor::Analyse() {
} // end if ! event } // end if ! event
} // end if RECORDING } // end if RECORDING
if (score) { if (score and (function != MONITOR)) {
if ((state == IDLE) || (state == TAPE) || (state == PREALARM)) { if ((state == IDLE) || (state == TAPE) || (state == PREALARM)) {
// If we should end then previous continuous event and start a new non-continuous event // If we should end then previous continuous event and start a new non-continuous event
if (event && event->Frames() if (event && event->Frames()
@ -2065,12 +2078,12 @@ bool Monitor::Analyse() {
if (!event) { if (!event) {
packetqueue_iterator *start_it = packetqueue.get_event_start_packet_it( packetqueue_iterator *start_it = packetqueue.get_event_start_packet_it(
snap_it, *analysis_it,
(pre_event_count > alarm_frame_count ? pre_event_count : alarm_frame_count) (pre_event_count > alarm_frame_count ? pre_event_count : alarm_frame_count)
); );
ZMLockedPacket *starting_packet_lock = nullptr; ZMLockedPacket *starting_packet_lock = nullptr;
std::shared_ptr<ZMPacket> starting_packet = nullptr; std::shared_ptr<ZMPacket> starting_packet = nullptr;
if (*start_it != snap_it) { if (*start_it != *analysis_it) {
starting_packet_lock = packetqueue.get_packet(start_it); starting_packet_lock = packetqueue.get_packet(start_it);
if (!starting_packet_lock) return false; if (!starting_packet_lock) return false;
starting_packet = starting_packet_lock->packet_; starting_packet = starting_packet_lock->packet_;
@ -2085,11 +2098,11 @@ bool Monitor::Analyse() {
shared_data->state = state = ALARM; shared_data->state = state = ALARM;
// Write out starting packets, do not modify packetqueue it will garbage collect itself // Write out starting packets, do not modify packetqueue it will garbage collect itself
while (*start_it != snap_it) { while (*start_it != *analysis_it) {
event->AddPacket(starting_packet); event->AddPacket(starting_packet);
packetqueue.increment_it(start_it); packetqueue.increment_it(start_it);
if ( (*start_it) == snap_it ) { if ( (*start_it) == (*analysis_it) ) {
if (starting_packet_lock) delete starting_packet_lock; if (starting_packet_lock) delete starting_packet_lock;
break; break;
} }
@ -2280,6 +2293,7 @@ bool Monitor::Analyse() {
if (function == MODECT or function == MOCORD) if (function == MODECT or function == MOCORD)
UpdateAnalysisFPS(); UpdateAnalysisFPS();
} }
packetqueue.increment_it(analysis_it);
packetqueue.unlock(packet_lock); packetqueue.unlock(packet_lock);
shared_data->last_read_time = time(nullptr); shared_data->last_read_time = time(nullptr);
@ -3058,7 +3072,7 @@ int Monitor::PrimeCapture() {
Debug(1, "Creating decoder thread"); Debug(1, "Creating decoder thread");
decoder = ZM::make_unique<DecoderThread>(this); decoder = ZM::make_unique<DecoderThread>(this);
} else { } else {
Debug(1, "Restartg decoder thread"); Debug(1, "Restarting decoder thread");
decoder->Start(); decoder->Start();
} }
} }
@ -3082,9 +3096,6 @@ int Monitor::PrimeCapture() {
int Monitor::PreCapture() const { return camera->PreCapture(); } int Monitor::PreCapture() const { return camera->PreCapture(); }
int Monitor::PostCapture() const { return camera->PostCapture(); } int Monitor::PostCapture() const { return camera->PostCapture(); }
int Monitor::Close() { int Monitor::Close() {
if (close_event_thread.joinable()) {
close_event_thread.join();
}
// Because the stream indexes may change we have to clear out the packetqueue // Because the stream indexes may change we have to clear out the packetqueue
if (decoder) { if (decoder) {
decoder->Stop(); decoder->Stop();
@ -3102,10 +3113,14 @@ int Monitor::Close() {
video_fifo = nullptr; video_fifo = nullptr;
} }
if (close_event_thread.joinable()) {
close_event_thread.join();
}
std::lock_guard<std::mutex> lck(event_mutex); std::lock_guard<std::mutex> lck(event_mutex);
if (event) { if (event) {
Info("%s: image_count:%d - Closing event %" PRIu64 ", shutting down", name.c_str(), image_count, event->Id()); Info("%s: image_count:%d - Closing event %" PRIu64 ", shutting down", name.c_str(), image_count, event->Id());
closeEvent(); closeEvent();
close_event_thread.join();
} }
if (camera) camera->Close(); if (camera) camera->Close();
return 1; return 1;

View File

@ -89,7 +89,7 @@ void MonitorStream::processCommand(const CmdMsg *msg) {
break; break;
case CMD_PLAY : case CMD_PLAY :
Debug(1, "Got PLAY command"); Debug(1, "Got PLAY command");
if ( paused ) { if (paused) {
paused = false; paused = false;
delayed = true; delayed = true;
} }
@ -97,7 +97,7 @@ void MonitorStream::processCommand(const CmdMsg *msg) {
break; break;
case CMD_VARPLAY : case CMD_VARPLAY :
Debug(1, "Got VARPLAY command"); Debug(1, "Got VARPLAY command");
if ( paused ) { if (paused) {
paused = false; paused = false;
delayed = true; delayed = true;
} }
@ -110,7 +110,7 @@ void MonitorStream::processCommand(const CmdMsg *msg) {
break; break;
case CMD_FASTFWD : case CMD_FASTFWD :
Debug(1, "Got FAST FWD command"); Debug(1, "Got FAST FWD command");
if ( paused ) { if (paused) {
paused = false; paused = false;
delayed = true; delayed = true;
} }
@ -135,27 +135,27 @@ void MonitorStream::processCommand(const CmdMsg *msg) {
} }
break; break;
case CMD_SLOWFWD : case CMD_SLOWFWD :
Debug( 1, "Got SLOW FWD command" ); Debug(1, "Got SLOW FWD command");
paused = true; paused = true;
delayed = true; delayed = true;
replay_rate = ZM_RATE_BASE; replay_rate = ZM_RATE_BASE;
step = 1; step = 1;
break; break;
case CMD_SLOWREV : case CMD_SLOWREV :
Debug( 1, "Got SLOW REV command" ); Debug(1, "Got SLOW REV command");
paused = true; paused = true;
delayed = true; delayed = true;
replay_rate = ZM_RATE_BASE; replay_rate = ZM_RATE_BASE;
step = -1; step = -1;
break; break;
case CMD_FASTREV : case CMD_FASTREV :
Debug( 1, "Got FAST REV command" ); Debug(1, "Got FAST REV command");
if ( paused ) { if (paused) {
paused = false; paused = false;
delayed = true; delayed = true;
} }
// Set play rate // Set play rate
switch ( replay_rate ) { switch (replay_rate) {
case -2 * ZM_RATE_BASE : case -2 * ZM_RATE_BASE :
replay_rate = -5 * ZM_RATE_BASE; replay_rate = -5 * ZM_RATE_BASE;
break; break;
@ -229,6 +229,7 @@ void MonitorStream::processCommand(const CmdMsg *msg) {
break; break;
case CMD_QUIT : case CMD_QUIT :
Info("User initiated exit - CMD_QUIT"); Info("User initiated exit - CMD_QUIT");
zm_terminate = true;
break; break;
case CMD_QUERY : case CMD_QUERY :
Debug(1, "Got QUERY command, sending STATUS"); Debug(1, "Got QUERY command, sending STATUS");
@ -255,7 +256,7 @@ void MonitorStream::processCommand(const CmdMsg *msg) {
} status_data; } status_data;
status_data.id = monitor->Id(); status_data.id = monitor->Id();
if ( ! monitor->ShmValid() ) { if (!monitor->ShmValid()) {
status_data.fps = 0.0; status_data.fps = 0.0;
status_data.capture_fps = 0.0; status_data.capture_fps = 0.0;
status_data.analysis_fps = 0.0; status_data.analysis_fps = 0.0;
@ -265,7 +266,14 @@ void MonitorStream::processCommand(const CmdMsg *msg) {
status_data.forced = false; status_data.forced = false;
status_data.buffer_level = 0; status_data.buffer_level = 0;
} else { } else {
status_data.fps = monitor->GetFPS(); int elapsed = now.tv_sec - last_fps_update.tv_sec;
if (elapsed) {
actual_fps = (frame_count - last_frame_count) / elapsed;
last_frame_count = frame_count;
last_fps_update = now;
}
status_data.fps = actual_fps;
status_data.capture_fps = monitor->get_capture_fps(); status_data.capture_fps = monitor->get_capture_fps();
status_data.analysis_fps = monitor->get_analysis_fps(); status_data.analysis_fps = monitor->get_analysis_fps();
status_data.state = monitor->shared_data->state; status_data.state = monitor->shared_data->state;
@ -308,16 +316,6 @@ void MonitorStream::processCommand(const CmdMsg *msg) {
} }
} }
Debug(2, "Number of bytes sent to (%s): (%d)", rem_addr.sun_path, nbytes); Debug(2, "Number of bytes sent to (%s): (%d)", rem_addr.sun_path, nbytes);
// quit after sending a status, if this was a quit request
if ( (MsgCommand)msg->msg_data[0] == CMD_QUIT ) {
zm_terminate = true;
Debug(2, "Quitting");
return;
}
//Debug(2,"Updating framerate");
//updateFrameRate(monitor->GetFPS());
} // end void MonitorStream::processCommand(const CmdMsg *msg) } // end void MonitorStream::processCommand(const CmdMsg *msg)
bool MonitorStream::sendFrame(const char *filepath, const timeval &timestamp) { bool MonitorStream::sendFrame(const char *filepath, const timeval &timestamp) {
@ -380,9 +378,10 @@ bool MonitorStream::sendFrame(const char *filepath, const timeval &timestamp) {
} // end bool MonitorStream::sendFrame(const char *filepath, struct timeval *timestamp) } // end bool MonitorStream::sendFrame(const char *filepath, struct timeval *timestamp)
bool MonitorStream::sendFrame(Image *image, const timeval &timestamp) { bool MonitorStream::sendFrame(Image *image, const timeval &timestamp) {
if (!config.timestamp_on_capture) {
monitor->TimestampImage(image, timestamp);
}
Image *send_image = prepareImage(image); Image *send_image = prepareImage(image);
if (!config.timestamp_on_capture)
monitor->TimestampImage(send_image, timestamp);
fputs("--" BOUNDARY "\r\n", stdout); fputs("--" BOUNDARY "\r\n", stdout);
#if HAVE_LIBAVCODEC #if HAVE_LIBAVCODEC
@ -520,7 +519,7 @@ void MonitorStream::runStream() {
Image *paused_image = nullptr; Image *paused_image = nullptr;
struct timeval paused_timestamp; struct timeval paused_timestamp;
if ( connkey && ( playback_buffer > 0 ) ) { if (connkey && (playback_buffer > 0)) {
// 15 is the max length for the swap path suffix, /zmswap-whatever, assuming max 6 digits for monitor id // 15 is the max length for the swap path suffix, /zmswap-whatever, assuming max 6 digits for monitor id
const int max_swap_len_suffix = 15; const int max_swap_len_suffix = 15;
@ -529,27 +528,27 @@ void MonitorStream::runStream() {
int subfolder2_length = snprintf(nullptr, 0, "/zmswap-q%06d", connkey) + 1; int subfolder2_length = snprintf(nullptr, 0, "/zmswap-q%06d", connkey) + 1;
int total_swap_path_length = swap_path_length + subfolder1_length + subfolder2_length; int total_swap_path_length = swap_path_length + subfolder1_length + subfolder2_length;
if ( total_swap_path_length + max_swap_len_suffix > PATH_MAX ) { if (total_swap_path_length + max_swap_len_suffix > PATH_MAX) {
Error("Swap Path is too long. %d > %d ", total_swap_path_length+max_swap_len_suffix, PATH_MAX); Error("Swap Path is too long. %d > %d ", total_swap_path_length+max_swap_len_suffix, PATH_MAX);
} else { } else {
swap_path = staticConfig.PATH_SWAP; swap_path = staticConfig.PATH_SWAP;
Debug(3, "Checking swap path folder: %s", swap_path.c_str()); Debug(3, "Checking swap path folder: %s", swap_path.c_str());
if ( checkSwapPath(swap_path.c_str(), true) ) { if (checkSwapPath(swap_path.c_str(), true)) {
swap_path += stringtf("/zmswap-m%d", monitor->Id()); swap_path += stringtf("/zmswap-m%d", monitor->Id());
Debug(4, "Checking swap path subfolder: %s", swap_path.c_str()); Debug(4, "Checking swap path subfolder: %s", swap_path.c_str());
if ( checkSwapPath(swap_path.c_str(), true) ) { if (checkSwapPath(swap_path.c_str(), true)) {
swap_path += stringtf("/zmswap-q%06d", connkey); swap_path += stringtf("/zmswap-q%06d", connkey);
Debug(4, "Checking swap path subfolder: %s", swap_path.c_str()); Debug(4, "Checking swap path subfolder: %s", swap_path.c_str());
if ( checkSwapPath(swap_path.c_str(), true) ) { if (checkSwapPath(swap_path.c_str(), true)) {
buffered_playback = true; buffered_playback = true;
} }
} }
} }
if ( !buffered_playback ) { if (!buffered_playback) {
Error("Unable to validate swap image path, disabling buffered playback"); Error("Unable to validate swap image path, disabling buffered playback");
} else { } else {
Debug(2, "Assigning temporary buffer"); Debug(2, "Assigning temporary buffer");
@ -560,14 +559,13 @@ void MonitorStream::runStream() {
} }
} else { } else {
Debug(2, "Not using playback_buffer"); Debug(2, "Not using playback_buffer");
} // end if connkey & playback_buffer } // end if connkey && playback_buffer
while (!zm_terminate) { while (!zm_terminate) {
bool got_command = false; if (feof(stdout)) {
if ( feof(stdout) ) {
Debug(2, "feof stdout"); Debug(2, "feof stdout");
break; break;
} else if ( ferror(stdout) ) { } else if (ferror(stdout)) {
Debug(2, "ferror stdout"); Debug(2, "ferror stdout");
break; break;
} else if (!monitor->ShmValid()) { } else if (!monitor->ShmValid()) {
@ -578,8 +576,9 @@ void MonitorStream::runStream() {
gettimeofday(&now, nullptr); gettimeofday(&now, nullptr);
bool was_paused = paused; bool was_paused = paused;
if ( connkey ) { bool got_command = false; // commands like zoom should output a frame even if paused
while ( checkCommandQueue() && !zm_terminate ) { if (connkey) {
while (checkCommandQueue() && !zm_terminate) {
// Loop in here until all commands are processed. // Loop in here until all commands are processed.
Debug(2, "Have checking command Queue for connkey: %d", connkey); Debug(2, "Have checking command Queue for connkey: %d", connkey);
got_command = true; got_command = true;
@ -591,35 +590,31 @@ void MonitorStream::runStream() {
} }
} // end if connkey } // end if connkey
if ( paused ) { if (paused) {
if ( !was_paused ) { if (!was_paused) {
int index = monitor->shared_data->last_write_index % monitor->image_buffer_count; int index = monitor->shared_data->last_write_index % monitor->image_buffer_count;
Debug(1, "Saving paused image from index %d",index); Debug(1, "Saving paused image from index %d",index);
paused_image = new Image(*monitor->image_buffer[index]); paused_image = new Image(*monitor->image_buffer[index]);
paused_timestamp = monitor->shared_timestamps[index]; paused_timestamp = monitor->shared_timestamps[index];
} }
} else if ( paused_image ) { } else if (paused_image) {
Debug(1, "Clearing paused_image");
delete paused_image; delete paused_image;
paused_image = nullptr; paused_image = nullptr;
} }
if ( buffered_playback && delayed ) { if (buffered_playback && delayed) {
if ( temp_read_index == temp_write_index ) { if (temp_read_index == temp_write_index) {
// Go back to live viewing // Go back to live viewing
Debug(1, "Exceeded temporary streaming buffer"); Debug(1, "Exceeded temporary streaming buffer");
// Clear paused flag
paused = false; paused = false;
// Clear delayed_play flag
delayed = false; delayed = false;
replay_rate = ZM_RATE_BASE; replay_rate = ZM_RATE_BASE;
} else { } else {
if ( !paused ) { if (!paused) {
int temp_index = MOD_ADD(temp_read_index, 0, temp_image_buffer_count); int temp_index = MOD_ADD(temp_read_index, 0, temp_image_buffer_count);
// Debug( 3, "tri: %d, ti: %d", temp_read_index, temp_index );
SwapImage *swap_image = &temp_image_buffer[temp_index]; SwapImage *swap_image = &temp_image_buffer[temp_index];
if ( !swap_image->valid ) { if (!swap_image->valid) {
paused = true; paused = true;
delayed = true; delayed = true;
temp_read_index = MOD_ADD(temp_read_index, (replay_rate>=0?-1:1), temp_image_buffer_count); temp_read_index = MOD_ADD(temp_read_index, (replay_rate>=0?-1:1), temp_image_buffer_count);
@ -632,13 +627,14 @@ void MonitorStream::runStream() {
// If the next frame is due // If the next frame is due
if ( actual_delta_time > expected_delta_time ) { if ( actual_delta_time > expected_delta_time ) {
// Debug( 2, "eDT: %.3lf, aDT: %.3f", expected_delta_time, actual_delta_time ); // Debug( 2, "eDT: %.3lf, aDT: %.3f", expected_delta_time, actual_delta_time );
if ( temp_index%frame_mod == 0 ) { if ((temp_index % frame_mod) == 0) {
Debug(2, "Sending delayed frame %d", temp_index); Debug(2, "Sending delayed frame %d", temp_index);
// Send the next frame // Send the next frame
if (!sendFrame(temp_image_buffer[temp_index].file_name, temp_image_buffer[temp_index].timestamp)) { if (!sendFrame(temp_image_buffer[temp_index].file_name, temp_image_buffer[temp_index].timestamp)) {
zm_terminate = true; zm_terminate = true;
} }
memcpy(&last_frame_timestamp, &(swap_image->timestamp), sizeof(last_frame_timestamp)); frame_count++;
last_frame_timestamp = swap_image->timestamp;
// frame_sent = true; // frame_sent = true;
} }
temp_read_index = MOD_ADD(temp_read_index, (replay_rate>0?1:-1), temp_image_buffer_count); temp_read_index = MOD_ADD(temp_read_index, (replay_rate>0?1:-1), temp_image_buffer_count);
@ -650,17 +646,15 @@ void MonitorStream::runStream() {
SwapImage *swap_image = &temp_image_buffer[temp_read_index]; SwapImage *swap_image = &temp_image_buffer[temp_read_index];
// Send the next frame // Send the next frame
if ( !sendFrame( if (!sendFrame(
temp_image_buffer[temp_read_index].file_name, temp_image_buffer[temp_read_index].file_name,
temp_image_buffer[temp_read_index].timestamp temp_image_buffer[temp_read_index].timestamp)
) ) { ) {
zm_terminate = true; zm_terminate = true;
} }
memcpy( frame_count++;
&last_frame_timestamp,
&(swap_image->timestamp), last_frame_timestamp = swap_image->timestamp;
sizeof(last_frame_timestamp)
);
// frame_sent = true; // frame_sent = true;
step = 0; step = 0;
} else { } else {
@ -675,12 +669,13 @@ void MonitorStream::runStream() {
if ( !sendFrame(temp_image_buffer[temp_index].file_name, temp_image_buffer[temp_index].timestamp) ) { if ( !sendFrame(temp_image_buffer[temp_index].file_name, temp_image_buffer[temp_index].timestamp) ) {
zm_terminate = true; zm_terminate = true;
} }
frame_count++;
// frame_sent = true; // frame_sent = true;
} }
} // end if (!paused) or step or paused } // end if (!paused) or step or paused
} // end if have exceeded buffer or not } // end if have exceeded buffer or not
if ( temp_read_index == temp_write_index ) { if (temp_read_index == temp_write_index) {
// Go back to live viewing // Go back to live viewing
Warning("Rewound over write index, resuming live play"); Warning("Rewound over write index, resuming live play");
// Clear paused flag // Clear paused flag
@ -689,13 +684,13 @@ void MonitorStream::runStream() {
delayed = false; delayed = false;
replay_rate = ZM_RATE_BASE; replay_rate = ZM_RATE_BASE;
} }
} // end if ( buffered_playback && delayed ) } // end if (buffered_playback && delayed)
if ( last_read_index != monitor->shared_data->last_write_index ) { if (last_read_index != monitor->shared_data->last_write_index) {
// have a new image to send // have a new image to send
int index = monitor->shared_data->last_write_index % monitor->image_buffer_count; // % shouldn't be neccessary int index = monitor->shared_data->last_write_index % monitor->image_buffer_count;
if ( (frame_mod == 1) || ((frame_count%frame_mod) == 0) ) { if ((frame_mod == 1) || ((frame_count%frame_mod) == 0)) {
if ( !paused && !delayed ) { if (!paused && !delayed) {
last_read_index = monitor->shared_data->last_write_index; last_read_index = monitor->shared_data->last_write_index;
Debug(2, "Sending frame index: %d: frame_mod: %d frame count: %d paused(%d) delayed(%d)", Debug(2, "Sending frame index: %d: frame_mod: %d frame count: %d paused(%d) delayed(%d)",
index, frame_mod, frame_count, paused, delayed); index, frame_mod, frame_count, paused, delayed);
@ -710,9 +705,8 @@ void MonitorStream::runStream() {
zm_terminate = true; zm_terminate = true;
break; break;
} }
//frame_sent = true; frame_count++;
// if (frame_count == 0) {
if ( frame_count == 0 ) {
// Chrome will not display the first frame until it receives another. // Chrome will not display the first frame until it receives another.
// Firefox is fine. So just send the first frame twice. // Firefox is fine. So just send the first frame twice.
if ( !sendFrame(image, last_frame_timestamp) ) { if ( !sendFrame(image, last_frame_timestamp) ) {
@ -724,16 +718,18 @@ void MonitorStream::runStream() {
temp_read_index = temp_write_index; temp_read_index = temp_write_index;
} else { } else {
if ( delayed && !buffered_playback ) { if (delayed && !buffered_playback) {
Debug(2, "Can't delay when not buffering."); Debug(2, "Can't delay when not buffering.");
delayed = false; delayed = false;
} }
if ( last_zoom != zoom ) { if (last_zoom != zoom) {
Debug(2, "Sending 2 frames because change in zoom %d ?= %d", last_zoom, zoom); Debug(2, "Sending 2 frames because change in zoom %d ?= %d", last_zoom, zoom);
if (!sendFrame(paused_image, paused_timestamp)) if (!sendFrame(paused_image, paused_timestamp))
zm_terminate = true; zm_terminate = true;
if (!sendFrame(paused_image, paused_timestamp)) if (!sendFrame(paused_image, paused_timestamp))
zm_terminate = true; zm_terminate = true;
frame_count++;
frame_count++;
} else { } else {
double actual_delta_time = TV_2_FLOAT(now) - last_frame_sent; double actual_delta_time = TV_2_FLOAT(now) - last_frame_sent;
if ( actual_delta_time > 5 ) { if ( actual_delta_time > 5 ) {
@ -744,17 +740,20 @@ void MonitorStream::runStream() {
// Send the next frame // Send the next frame
if (!sendFrame(paused_image, paused_timestamp)) if (!sendFrame(paused_image, paused_timestamp))
zm_terminate = true; zm_terminate = true;
frame_count++;
} else { } else {
Debug(2, "Would have sent keepalive frame, but had no paused_image"); Debug(2, "Would have sent keepalive frame, but had no paused_image");
} }
} // end if actual_delta_time > 5 } // end if actual_delta_time > 5
} // end if change in zoom } // end if change in zoom
} // end if paused or not } // end if paused or not
} else {
frame_count++;
} // end if should send frame } // end if should send frame
if ( buffered_playback && !paused ) { if (buffered_playback && !paused) {
if ( monitor->shared_data->valid ) { if (monitor->shared_data->valid) {
if ( monitor->shared_timestamps[index].tv_sec ) { if (monitor->shared_timestamps[index].tv_sec) {
int temp_index = temp_write_index%temp_image_buffer_count; int temp_index = temp_write_index%temp_image_buffer_count;
Debug(2, "Storing frame %d", temp_index); Debug(2, "Storing frame %d", temp_index);
if ( !temp_image_buffer[temp_index].valid ) { if ( !temp_image_buffer[temp_index].valid ) {
@ -772,7 +771,7 @@ void MonitorStream::runStream() {
config.jpeg_file_quality config.jpeg_file_quality
); );
temp_write_index = MOD_ADD(temp_write_index, 1, temp_image_buffer_count); temp_write_index = MOD_ADD(temp_write_index, 1, temp_image_buffer_count);
if ( temp_write_index == temp_read_index ) { if (temp_write_index == temp_read_index) {
// Go back to live viewing // Go back to live viewing
Warning("Exceeded temporary buffer, resuming live play"); Warning("Exceeded temporary buffer, resuming live play");
paused = false; paused = false;
@ -786,7 +785,6 @@ void MonitorStream::runStream() {
Warning("Unable to store frame as shared memory invalid"); Warning("Unable to store frame as shared memory invalid");
} }
} // end if buffered playback } // end if buffered playback
frame_count++;
} else { } else {
Debug(3, "Waiting for capture last_write_index=%u", monitor->shared_data->last_write_index); Debug(3, "Waiting for capture last_write_index=%u", monitor->shared_data->last_write_index);
} // end if ( (unsigned int)last_read_index != monitor->shared_data->last_write_index ) } // end if ( (unsigned int)last_read_index != monitor->shared_data->last_write_index )
@ -815,16 +813,16 @@ void MonitorStream::runStream() {
Warning("no last_frame_sent. Shouldn't happen. frame_mod was (%d) frame_count (%d)", Warning("no last_frame_sent. Shouldn't happen. frame_mod was (%d) frame_count (%d)",
frame_mod, frame_count); frame_mod, frame_count);
} }
} // end while } // end while ! zm_terminate
if ( buffered_playback ) { if (buffered_playback) {
Debug(1, "Cleaning swap files from %s", swap_path.c_str()); Debug(1, "Cleaning swap files from %s", swap_path.c_str());
struct stat stat_buf; struct stat stat_buf = {};
if ( stat(swap_path.c_str(), &stat_buf) < 0 ) { if (stat(swap_path.c_str(), &stat_buf) < 0) {
if ( errno != ENOENT ) { if (errno != ENOENT) {
Error("Can't stat '%s': %s", swap_path.c_str(), strerror(errno)); Error("Can't stat '%s': %s", swap_path.c_str(), strerror(errno));
} }
} else if ( !S_ISDIR(stat_buf.st_mode) ) { } else if (!S_ISDIR(stat_buf.st_mode)) {
Error("Swap image path '%s' is not a directory", swap_path.c_str()); Error("Swap image path '%s' is not a directory", swap_path.c_str());
} else { } else {
char glob_pattern[PATH_MAX] = ""; char glob_pattern[PATH_MAX] = "";
@ -832,21 +830,21 @@ void MonitorStream::runStream() {
snprintf(glob_pattern, sizeof(glob_pattern), "%s/*.*", swap_path.c_str()); snprintf(glob_pattern, sizeof(glob_pattern), "%s/*.*", swap_path.c_str());
glob_t pglob; glob_t pglob;
int glob_status = glob(glob_pattern, 0, 0, &pglob); int glob_status = glob(glob_pattern, 0, 0, &pglob);
if ( glob_status != 0 ) { if (glob_status != 0) {
if ( glob_status < 0 ) { if (glob_status < 0) {
Error("Can't glob '%s': %s", glob_pattern, strerror(errno)); Error("Can't glob '%s': %s", glob_pattern, strerror(errno));
} else { } else {
Debug(1, "Can't glob '%s': %d", glob_pattern, glob_status); Debug(1, "Can't glob '%s': %d", glob_pattern, glob_status);
} }
} else { } else {
for ( unsigned int i = 0; i < pglob.gl_pathc; i++ ) { for (unsigned int i = 0; i < pglob.gl_pathc; i++) {
if ( unlink(pglob.gl_pathv[i]) < 0 ) { if (unlink(pglob.gl_pathv[i]) < 0) {
Error("Can't unlink '%s': %s", pglob.gl_pathv[i], strerror(errno)); Error("Can't unlink '%s': %s", pglob.gl_pathv[i], strerror(errno));
} }
} }
} }
globfree(&pglob); globfree(&pglob);
if ( rmdir(swap_path.c_str()) < 0 ) { if (rmdir(swap_path.c_str()) < 0) {
Error("Can't rmdir '%s': %s", swap_path.c_str(), strerror(errno)); Error("Can't rmdir '%s': %s", swap_path.c_str(), strerror(errno));
} }
} // end if checking for swap_path } // end if checking for swap_path
@ -867,14 +865,16 @@ void MonitorStream::SingleImage(int scale) {
Debug(1, "write index: %d %d", monitor->shared_data->last_write_index, index); Debug(1, "write index: %d %d", monitor->shared_data->last_write_index, index);
Image *snap_image = monitor->image_buffer[index]; Image *snap_image = monitor->image_buffer[index];
if (!config.timestamp_on_capture) {
monitor->TimestampImage(snap_image, monitor->shared_timestamps[index]);
}
if ( scale != ZM_SCALE_BASE ) { if ( scale != ZM_SCALE_BASE ) {
scaled_image.Assign(*snap_image); scaled_image.Assign(*snap_image);
scaled_image.Scale(scale); scaled_image.Scale(scale);
snap_image = &scaled_image; snap_image = &scaled_image;
} }
if ( !config.timestamp_on_capture ) {
monitor->TimestampImage(snap_image, monitor->shared_timestamps[index]);
}
snap_image->EncodeJpeg(img_buffer, &img_buffer_size); snap_image->EncodeJpeg(img_buffer, &img_buffer_size);
fprintf(stdout, fprintf(stdout,

View File

@ -41,7 +41,6 @@ class MonitorStream : public StreamBase {
time_t ttl; time_t ttl;
int playback_buffer; int playback_buffer;
bool delayed; bool delayed;
int frame_count;
protected: protected:
bool checkSwapPath(const char *path, bool create_path); bool checkSwapPath(const char *path, bool create_path);
@ -62,9 +61,9 @@ class MonitorStream : public StreamBase {
temp_write_index(0), temp_write_index(0),
ttl(0), ttl(0),
playback_buffer(0), playback_buffer(0),
delayed(false), delayed(false)
frame_count(0) { {}
}
void setStreamBuffer(int p_playback_buffer) { void setStreamBuffer(int p_playback_buffer) {
playback_buffer = p_playback_buffer; playback_buffer = p_playback_buffer;
} }

View File

@ -24,7 +24,6 @@
#include "zm_ffmpeg.h" #include "zm_ffmpeg.h"
#include "zm_packet.h" #include "zm_packet.h"
#include "zm_signal.h" #include "zm_signal.h"
#include <sys/time.h>
PacketQueue::PacketQueue(): PacketQueue::PacketQueue():
video_stream_id(-1), video_stream_id(-1),
@ -87,60 +86,6 @@ bool PacketQueue::queuePacket(std::shared_ptr<ZMPacket> add_packet) {
{ {
std::unique_lock<std::mutex> lck(mutex); std::unique_lock<std::mutex> lck(mutex);
if (add_packet->packet.stream_index == video_stream_id) {
if ((max_video_packet_count > 0) and (packet_counts[video_stream_id] > max_video_packet_count)) {
Warning("You have set the max video packets in the queue to %u."
" The queue is full. Either Analysis is not keeping up or"
" your camera's keyframe interval is larger than this setting."
" We are dropping packets.", max_video_packet_count);
if (add_packet->keyframe) {
// Have a new keyframe, so delete everything
while ((*pktQueue.begin() != add_packet) and (packet_counts[video_stream_id] > max_video_packet_count)) {
std::shared_ptr <ZMPacket>zm_packet = *pktQueue.begin();
ZMLockedPacket *lp = new ZMLockedPacket(zm_packet);
if (!lp->trylock()) {
Debug(1, "Found locked packet when trying to free up video packets. Can't continue");
delete lp;
break;
}
delete lp;
for (
std::list<packetqueue_iterator *>::iterator iterators_it = iterators.begin();
iterators_it != iterators.end();
++iterators_it
) {
packetqueue_iterator *iterator_it = *iterators_it;
// Have to check each iterator and make sure it doesn't point to the packet we are about to delete
if ( *(*iterator_it) == zm_packet ) {
Debug(1, "Bumping IT because it is at the front that we are deleting");
++(*iterators_it);
}
} // end foreach iterator
pktQueue.pop_front();
packet_counts[zm_packet->packet.stream_index] -= 1;
Debug(1,
"Deleting a packet with stream index:%d image_index:%d with keyframe:%d, video frames in queue:%d max: %d, queuesize:%zu",
zm_packet->packet.stream_index,
zm_packet->image_index,
zm_packet->keyframe,
packet_counts[video_stream_id],
max_video_packet_count,
pktQueue.size());
} // end while
}
} // end if too many video packets
if (max_video_packet_count > 0) {
while (packet_counts[video_stream_id] > max_video_packet_count) {
Error("Unable to free up older packets. Waiting.");
condition.wait(lck);
if (deleting or zm_terminate)
return false;
}
}
} // end if this packet is a video packet
pktQueue.push_back(add_packet); pktQueue.push_back(add_packet);
packet_counts[add_packet->packet.stream_index] += 1; packet_counts[add_packet->packet.stream_index] += 1;
Debug(2, "packet counts for %d is %d", Debug(2, "packet counts for %d is %d",
@ -148,25 +93,80 @@ bool PacketQueue::queuePacket(std::shared_ptr<ZMPacket> add_packet) {
packet_counts[add_packet->packet.stream_index]); packet_counts[add_packet->packet.stream_index]);
for ( for (
std::list<packetqueue_iterator *>::iterator iterators_it = iterators.begin(); auto iterators_it = iterators.begin();
iterators_it != iterators.end(); iterators_it != iterators.end();
++iterators_it ++iterators_it
) { ) {
packetqueue_iterator *iterator_it = *iterators_it; packetqueue_iterator *iterator_it = *iterators_it;
if (*iterator_it == pktQueue.end()) { if (*iterator_it == pktQueue.end()) {
Debug(4, "pointing it %p to back", iterator_it);
--(*iterator_it); --(*iterator_it);
} else {
Debug(4, "it %p not at end", iterator_it);
} }
} // end foreach iterator } // end foreach iterator
if (
(add_packet->packet.stream_index == video_stream_id)
and
(max_video_packet_count > 0)
and
(packet_counts[video_stream_id] > max_video_packet_count)
) {
Warning("You have set the max video packets in the queue to %u."
" The queue is full. Either Analysis is not keeping up or"
" your camera's keyframe interval is larger than this setting."
, max_video_packet_count);
for (
auto it = ++pktQueue.begin();
it != pktQueue.end() and *it != add_packet;
// iterator is incremented by erase
) {
std::shared_ptr <ZMPacket>zm_packet = *it;
ZMLockedPacket *lp = new ZMLockedPacket(zm_packet);
if (!lp->trylock()) {
Warning("Found locked packet when trying to free up video packets. This basically means that decoding is not keeping up.");
delete lp;
++it;
continue;
}
for (
auto iterators_it = iterators.begin();
iterators_it != iterators.end();
++iterators_it
) {
auto iterator_it = *iterators_it;
// Have to check each iterator and make sure it doesn't point to the packet we are about to delete
if ((*iterator_it!=pktQueue.end()) and (*(*iterator_it) == zm_packet)) {
Debug(1, "Bumping IT because it is at the front that we are deleting");
++(*iterator_it);
}
} // end foreach iterator
it = pktQueue.erase(it);
packet_counts[zm_packet->packet.stream_index] -= 1;
Debug(1,
"Deleting a packet with stream index:%d image_index:%d with keyframe:%d, video frames in queue:%d max: %d, queuesize:%zu",
zm_packet->packet.stream_index,
zm_packet->image_index,
zm_packet->keyframe,
packet_counts[video_stream_id],
max_video_packet_count,
pktQueue.size());
delete lp;
if (zm_packet->packet.stream_index == video_stream_id)
break;
} // end while
} // end if not able catch up
} // end lock scope } // end lock scope
// We signal on every packet because someday we may analyze sound // We signal on every packet because someday we may analyze sound
Debug(4, "packetqueue queuepacket, unlocked signalling"); Debug(4, "packetqueue queuepacket, unlocked signalling");
condition.notify_all(); condition.notify_all();
return true; return true;
} // end bool PacketQueue::queuePacket(ZMPacket* zm_packet) } // end bool PacketQueue::queuePacket(ZMPacket* zm_packet)
void PacketQueue::clearPackets(const std::shared_ptr<ZMPacket> &add_packet) { void PacketQueue::clearPackets(const std::shared_ptr<ZMPacket> &add_packet) {
// Only do queueCleaning if we are adding a video keyframe, so that we guarantee that there is one. // Only do queueCleaning if we are adding a video keyframe, so that we guarantee that there is one.
@ -179,6 +179,7 @@ void PacketQueue::clearPackets(const std::shared_ptr<ZMPacket> &add_packet) {
// So start at the beginning, counting video packets until the next keyframe. // So start at the beginning, counting video packets until the next keyframe.
// Then if deleting those packets doesn't break 1 and 2, then go ahead and delete them. // Then if deleting those packets doesn't break 1 and 2, then go ahead and delete them.
if (deleting) return; if (deleting) return;
if (!pktQueue.size()) return;
if (keep_keyframes and ! ( if (keep_keyframes and ! (
add_packet->packet.stream_index == video_stream_id add_packet->packet.stream_index == video_stream_id
@ -197,7 +198,6 @@ void PacketQueue::clearPackets(const std::shared_ptr<ZMPacket> &add_packet) {
return; return;
} }
std::unique_lock<std::mutex> lck(mutex); std::unique_lock<std::mutex> lck(mutex);
if (!pktQueue.size()) return;
// If analysis_it isn't at the end, we need to keep that many additional packets // If analysis_it isn't at the end, we need to keep that many additional packets
int tail_count = 0; int tail_count = 0;
@ -240,8 +240,8 @@ void PacketQueue::clearPackets(const std::shared_ptr<ZMPacket> &add_packet) {
return; return;
} }
packetqueue_iterator it = pktQueue.begin(); auto it = pktQueue.begin();
packetqueue_iterator next_front = pktQueue.begin(); auto next_front = pktQueue.begin();
// First packet is special because we know it is a video keyframe and only need to check for lock // First packet is special because we know it is a video keyframe and only need to check for lock
std::shared_ptr<ZMPacket> zm_packet = *it; std::shared_ptr<ZMPacket> zm_packet = *it;
@ -249,32 +249,32 @@ void PacketQueue::clearPackets(const std::shared_ptr<ZMPacket> &add_packet) {
return; return;
} }
Debug(1, "trying lock on first packet");
ZMLockedPacket *lp = new ZMLockedPacket(zm_packet); ZMLockedPacket *lp = new ZMLockedPacket(zm_packet);
if (lp->trylock()) { if (lp->trylock()) {
int video_packets_to_delete = 0; // This is a count of how many packets we will delete so we know when to stop looking int video_packets_to_delete = 0; // This is a count of how many packets we will delete so we know when to stop looking
Debug(1, "Have lock on first packet"); Debug(4, "Have lock on first packet");
++it; ++it;
delete lp; delete lp;
if (it == pktQueue.end()) {
Debug(1, "Hit end already");
it = pktQueue.begin();
} else {
// Since we have many packets in the queue, we should NOT be pointing at end so don't need to test for that // Since we have many packets in the queue, we should NOT be pointing at end so don't need to test for that
while (*it != add_packet) { while (*it != add_packet) {
zm_packet = *it; zm_packet = *it;
lp = new ZMLockedPacket(zm_packet); lp = new ZMLockedPacket(zm_packet);
if (!lp->trylock()) { if (!lp->trylock()) {
Debug(3, "Failed locking packet %d", zm_packet->image_index);
delete lp; delete lp;
break; break;
} }
delete lp; delete lp;
if (is_there_an_iterator_pointing_to_packet(zm_packet) and (pktQueue.begin() == next_front)) { #if 0
Warning("Found iterator at beginning of queue. Some thread isn't keeping up"); // There are no threads that follow analysis thread. So there cannot be an it pointing here
if (is_there_an_iterator_pointing_to_packet(zm_packet)) {
if (pktQueue.begin() == next_front)
Warning("Found iterator at beginning of queue. Some thread isn't keeping up");
break; break;
} }
#endif
if (zm_packet->packet.stream_index == video_stream_id) { if (zm_packet->packet.stream_index == video_stream_id) {
if (zm_packet->keyframe) { if (zm_packet->keyframe) {
@ -282,17 +282,16 @@ void PacketQueue::clearPackets(const std::shared_ptr<ZMPacket> &add_packet) {
next_front = it; next_front = it;
} }
++video_packets_to_delete; ++video_packets_to_delete;
Debug(4, "Counted %d video packets. Which would leave %d in packetqueue tail count is %d", Debug(3, "Counted %d video packets. Which would leave %d in packetqueue tail count is %d",
video_packets_to_delete, packet_counts[video_stream_id]-video_packets_to_delete, tail_count); video_packets_to_delete, packet_counts[video_stream_id]-video_packets_to_delete, tail_count);
if (packet_counts[video_stream_id] - video_packets_to_delete <= pre_event_video_packet_count + tail_count) { if (packet_counts[video_stream_id] - video_packets_to_delete <= pre_event_video_packet_count + tail_count) {
break; break;
} }
} }
++it; ++it;
} // end while } // end while
}
} // end if first packet not locked } // end if first packet not locked
Debug(1, "Resulting pointing at latest packet? %d, next front points to begin? %d", Debug(1, "Resulting it pointing at latest packet? %d, next front points to begin? %d",
( *it == add_packet ), ( *it == add_packet ),
( next_front == pktQueue.begin() ) ( next_front == pktQueue.begin() )
); );
@ -314,7 +313,6 @@ void PacketQueue::clearPackets(const std::shared_ptr<ZMPacket> &add_packet) {
pktQueue.size()); pktQueue.size());
pktQueue.pop_front(); pktQueue.pop_front();
packet_counts[zm_packet->packet.stream_index] -= 1; packet_counts[zm_packet->packet.stream_index] -= 1;
//delete zm_packet;
} }
} // end if have at least max_video_packet_count video packets remaining } // end if have at least max_video_packet_count video packets remaining
// We signal on every packet because someday we may analyze sound // We signal on every packet because someday we may analyze sound
@ -325,6 +323,8 @@ void PacketQueue::clearPackets(const std::shared_ptr<ZMPacket> &add_packet) {
void PacketQueue::clear() { void PacketQueue::clear() {
deleting = true; deleting = true;
condition.notify_all(); condition.notify_all();
if (!packet_counts) // special case, not initialised
return;
Debug(1, "Clearing packetqueue"); Debug(1, "Clearing packetqueue");
std::unique_lock<std::mutex> lck(mutex); std::unique_lock<std::mutex> lck(mutex);
@ -662,3 +662,12 @@ void PacketQueue::setPreEventVideoPackets(int p) {
pre_event_video_packet_count = 1; pre_event_video_packet_count = 1;
// We can simplify a lot of logic in queuePacket if we can assume at least 1 packet in queue // We can simplify a lot of logic in queuePacket if we can assume at least 1 packet in queue
} }
void PacketQueue::notify_all() {
condition.notify_all();
};
void PacketQueue::wait() {
std::unique_lock<std::mutex> lck(mutex);
condition.wait(lck);
}

View File

@ -81,6 +81,8 @@ class PacketQueue {
); );
bool is_there_an_iterator_pointing_to_packet(const std::shared_ptr<ZMPacket> &zm_packet); bool is_there_an_iterator_pointing_to_packet(const std::shared_ptr<ZMPacket> &zm_packet);
void unlock(ZMLockedPacket *lp); void unlock(ZMLockedPacket *lp);
void notify_all();
void wait();
}; };
#endif /* ZM_PACKETQUEUE_H */ #endif /* ZM_PACKETQUEUE_H */

View File

@ -100,7 +100,8 @@ void RemoteCamera::Initialise() {
int ret = getaddrinfo(host.c_str(), port.c_str(), &hints, &hp); int ret = getaddrinfo(host.c_str(), port.c_str(), &hints, &hp);
if ( ret != 0 ) { if ( ret != 0 ) {
Fatal( "Can't getaddrinfo(%s port %s): %s", host.c_str(), port.c_str(), gai_strerror(ret) ); Error( "Can't getaddrinfo(%s port %s): %s", host.c_str(), port.c_str(), gai_strerror(ret) );
return;
} }
struct addrinfo *p = nullptr; struct addrinfo *p = nullptr;
int addr_count = 0; int addr_count = 0;

View File

@ -144,6 +144,14 @@ void RemoteCameraHttp::Initialise() {
int RemoteCameraHttp::Connect() { int RemoteCameraHttp::Connect() {
struct addrinfo *p = nullptr; struct addrinfo *p = nullptr;
if (!hp) {
RemoteCamera::Initialise();
if (!hp) {
Error("Unable to resolve address for remote camera, aborting");
return -1;
}
}
for ( p = hp; p != nullptr; p = p->ai_next ) { for ( p = hp; p != nullptr; p = p->ai_next ) {
sd = socket( p->ai_family, p->ai_socktype, p->ai_protocol ); sd = socket( p->ai_family, p->ai_socktype, p->ai_protocol );
if ( sd < 0 ) { if ( sd < 0 ) {

View File

@ -118,41 +118,34 @@ constexpr Rgb kRGBTransparent = 0x01000000;
/* Convert RGB colour value into BGR\ARGB\ABGR */ /* Convert RGB colour value into BGR\ARGB\ABGR */
inline Rgb rgb_convert(Rgb p_col, int p_subpixorder) { inline Rgb rgb_convert(Rgb p_col, int p_subpixorder) {
Rgb result; Rgb result = 0;
switch(p_subpixorder) { switch (p_subpixorder) {
case ZM_SUBPIX_ORDER_BGR: case ZM_SUBPIX_ORDER_BGR:
case ZM_SUBPIX_ORDER_BGRA: case ZM_SUBPIX_ORDER_BGRA:
{ BLUE_PTR_BGRA(&result) = BLUE_VAL_RGBA(p_col);
BLUE_PTR_BGRA(&result) = BLUE_VAL_RGBA(p_col); GREEN_PTR_BGRA(&result) = GREEN_VAL_RGBA(p_col);
GREEN_PTR_BGRA(&result) = GREEN_VAL_RGBA(p_col); RED_PTR_BGRA(&result) = RED_VAL_RGBA(p_col);
RED_PTR_BGRA(&result) = RED_VAL_RGBA(p_col); break;
}
break;
case ZM_SUBPIX_ORDER_ARGB: case ZM_SUBPIX_ORDER_ARGB:
{ BLUE_PTR_ARGB(&result) = BLUE_VAL_RGBA(p_col);
BLUE_PTR_ARGB(&result) = BLUE_VAL_RGBA(p_col); GREEN_PTR_ARGB(&result) = GREEN_VAL_RGBA(p_col);
GREEN_PTR_ARGB(&result) = GREEN_VAL_RGBA(p_col); RED_PTR_ARGB(&result) = RED_VAL_RGBA(p_col);
RED_PTR_ARGB(&result) = RED_VAL_RGBA(p_col); break;
}
break;
case ZM_SUBPIX_ORDER_ABGR: case ZM_SUBPIX_ORDER_ABGR:
{ BLUE_PTR_ABGR(&result) = BLUE_VAL_RGBA(p_col);
BLUE_PTR_ABGR(&result) = BLUE_VAL_RGBA(p_col); GREEN_PTR_ABGR(&result) = GREEN_VAL_RGBA(p_col);
GREEN_PTR_ABGR(&result) = GREEN_VAL_RGBA(p_col); RED_PTR_ABGR(&result) = RED_VAL_RGBA(p_col);
RED_PTR_ABGR(&result) = RED_VAL_RGBA(p_col); break;
} /* Grayscale */
break;
/* Grayscale */
case ZM_SUBPIX_ORDER_NONE: case ZM_SUBPIX_ORDER_NONE:
result = p_col & 0xff; result = p_col & 0xff;
break; break;
default: default:
return p_col; result = p_col;
break; break;
} }
return result; return result;
} }

View File

@ -279,9 +279,8 @@ int main(int argc, char *argv[]) {
Warning("Unknown format in %s", videoFifoPath.c_str()); Warning("Unknown format in %s", videoFifoPath.c_str());
} }
if (videoSource == nullptr) { if (videoSource == nullptr) {
Error("Unable to create source"); Error("Unable to create source for %s", videoFifoPath.c_str());
rtspServer->RemoveSession(sessions[monitor->Id()]->GetMediaSessionId()); rtspServer->RemoveSession(sessions[monitor->Id()]->GetMediaSessionId());
delete sessions[monitor->Id()];
sessions.erase(monitor->Id()); sessions.erase(monitor->Id());
continue; continue;
} }

View File

@ -46,6 +46,7 @@ RETSIGTYPE zm_die_handler(int signal, siginfo_t * info, void *context)
RETSIGTYPE zm_die_handler(int signal) RETSIGTYPE zm_die_handler(int signal)
#endif #endif
{ {
zm_terminate = true;
Error("Got signal %d (%s), crashing", signal, strsignal(signal)); Error("Got signal %d (%s), crashing", signal, strsignal(signal));
#if (defined(__i386__) || defined(__x86_64__)) #if (defined(__i386__) || defined(__x86_64__))
// Get more information if available // Get more information if available

View File

@ -26,6 +26,7 @@
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <unistd.h> #include <unistd.h>
#include <fcntl.h>
StreamBase::~StreamBase() { StreamBase::~StreamBase() {
#if HAVE_LIBAVCODEC #if HAVE_LIBAVCODEC

View File

@ -102,7 +102,6 @@ protected:
int last_scale; int last_scale;
int zoom; int zoom;
int last_zoom; int last_zoom;
double maxfps;
int bitrate; int bitrate;
unsigned short last_x, last_y; unsigned short last_x, last_y;
unsigned short x, y; unsigned short x, y;
@ -122,8 +121,14 @@ protected:
struct timeval now; struct timeval now;
struct timeval last_comm_update; struct timeval last_comm_update;
double base_fps; double maxfps;
double effective_fps; double base_fps; // Should be capturing fps, hence a rough target
double effective_fps; // Target fps after taking max_fps into account
double actual_fps; // sliding calculated actual streaming fps achieved
struct timeval last_fps_update;
int frame_count; // Count of frames sent
int last_frame_count; // Used in calculating actual_fps from frame_count - last_frame_count
int frame_mod; int frame_mod;
double last_frame_sent; double last_frame_sent;
@ -154,7 +159,6 @@ public:
last_scale(DEFAULT_SCALE), last_scale(DEFAULT_SCALE),
zoom(DEFAULT_ZOOM), zoom(DEFAULT_ZOOM),
last_zoom(DEFAULT_ZOOM), last_zoom(DEFAULT_ZOOM),
maxfps(DEFAULT_MAXFPS),
bitrate(DEFAULT_BITRATE), bitrate(DEFAULT_BITRATE),
last_x(0), last_x(0),
last_y(0), last_y(0),
@ -166,7 +170,15 @@ public:
sd(-1), sd(-1),
lock_fd(0), lock_fd(0),
paused(false), paused(false),
step(0) step(0),
maxfps(DEFAULT_MAXFPS),
base_fps(0.0),
effective_fps(0.0),
actual_fps(0.0),
last_fps_update({}),
frame_count(0),
last_frame_count(0),
frame_mod(1)
{ {
memset(&loc_sock_path, 0, sizeof(loc_sock_path)); memset(&loc_sock_path, 0, sizeof(loc_sock_path));
memset(&loc_addr, 0, sizeof(loc_addr)); memset(&loc_addr, 0, sizeof(loc_addr));
@ -174,12 +186,8 @@ public:
memset(&rem_addr, 0, sizeof(rem_addr)); memset(&rem_addr, 0, sizeof(rem_addr));
memset(&sock_path_lock, 0, sizeof(sock_path_lock)); memset(&sock_path_lock, 0, sizeof(sock_path_lock));
base_fps = 0.0;
effective_fps = 0.0;
frame_mod = 1;
#if HAVE_LIBAVCODEC #if HAVE_LIBAVCODEC
vid_stream = 0; vid_stream = nullptr;
#endif // HAVE_LIBAVCODEC #endif // HAVE_LIBAVCODEC
last_frame_sent = 0.0; last_frame_sent = 0.0;
last_frame_timestamp = {}; last_frame_timestamp = {};

View File

@ -224,8 +224,15 @@ void HwCapsDetect() {
#elif defined(__arm__) #elif defined(__arm__)
// ARM processor in 32bit mode // ARM processor in 32bit mode
// To see if it supports NEON, we need to get that information from the kernel // To see if it supports NEON, we need to get that information from the kernel
#ifdef __linux__
unsigned long auxval = getauxval(AT_HWCAP); unsigned long auxval = getauxval(AT_HWCAP);
if (auxval & HWCAP_ARM_NEON) { if (auxval & HWCAP_ARM_NEON) {
#elif defined(__FreeBSD__)
unsigned long auxval = 0;
elf_aux_info(AT_HWCAP, &auxval, sizeof(auxval));
if (auxval & HWCAP_NEON) {
#error Unsupported OS.
#endif
Debug(1,"Detected ARM (AArch32) processor with Neon"); Debug(1,"Detected ARM (AArch32) processor with Neon");
neonversion = 1; neonversion = 1;
} else { } else {

View File

@ -638,7 +638,8 @@ VideoStore::~VideoStore() {
Debug(1, "Writing trailer"); Debug(1, "Writing trailer");
/* Write the trailer before close */ /* Write the trailer before close */
if ( int rc = av_write_trailer(oc) ) { int rc;
if ((rc = av_write_trailer(oc)) < 0) {
Error("Error writing trailer %s", av_err2str(rc)); Error("Error writing trailer %s", av_err2str(rc));
} else { } else {
Debug(3, "Success Writing trailer"); Debug(3, "Success Writing trailer");
@ -648,7 +649,7 @@ VideoStore::~VideoStore() {
if (!(out_format->flags & AVFMT_NOFILE)) { if (!(out_format->flags & AVFMT_NOFILE)) {
/* Close the out file. */ /* Close the out file. */
Debug(4, "Closing"); Debug(4, "Closing");
if (int rc = avio_close(oc->pb)) { if ((rc = avio_close(oc->pb)) < 0) {
Error("Error closing avio %s", av_err2str(rc)); Error("Error closing avio %s", av_err2str(rc));
} }
} else { } else {

View File

@ -874,16 +874,23 @@ std::vector<Zone> Zone::Load(Monitor *monitor) {
continue; continue;
} }
if (polygon.Extent().Lo().x_ < 0 || polygon.Extent().Hi().x_ > static_cast<int32>(monitor->Width()) if (polygon.Extent().Lo().x_ < 0
|| polygon.Extent().Lo().y_ < 0 || polygon.Extent().Hi().y_ > static_cast<int32>(monitor->Height())) { ||
Error("Zone %d/%s for monitor %s extends outside of image dimensions, (%d,%d), (%d,%d), fixing", polygon.Extent().Hi().x_ > static_cast<int32>(monitor->Width())
||
polygon.Extent().Lo().y_ < 0
||
polygon.Extent().Hi().y_ > static_cast<int32>(monitor->Height())) {
Error("Zone %d/%s for monitor %s extends outside of image dimensions, (%d,%d), (%d,%d) != (%d,%d), fixing",
Id, Id,
Name, Name,
monitor->Name(), monitor->Name(),
polygon.Extent().Lo().x_, polygon.Extent().Lo().x_,
polygon.Extent().Lo().y_, polygon.Extent().Lo().y_,
polygon.Extent().Hi().x_, polygon.Extent().Hi().x_,
polygon.Extent().Hi().y_); polygon.Extent().Hi().y_,
monitor->Width(),
monitor->Height());
polygon.Clip(Box( polygon.Clip(Box(
{0, 0}, {0, 0},

View File

@ -376,6 +376,7 @@ int main(int argc, char *argv[]) {
monitor->Id()); monitor->Id());
zmDbDo(sql); zmDbDo(sql);
} }
monitors.clear();
Image::Deinitialise(); Image::Deinitialise();
Debug(1, "terminating"); Debug(1, "terminating");

View File

@ -197,6 +197,7 @@ bool ValidateAccess(User *user, int mon_id, int function) {
void exit_zmu(int exit_code) { void exit_zmu(int exit_code) {
logTerm(); logTerm();
dbQueue.stop();
zmDbClose(); zmDbClose();
exit(exit_code); exit(exit_code);
@ -248,7 +249,7 @@ int main(int argc, char *argv[]) {
{nullptr, 0, nullptr, 0} {nullptr, 0, nullptr, 0}
}; };
const char *device = nullptr; std::string device;
int mon_id = 0; int mon_id = 0;
bool verbose = false; bool verbose = false;
int function = ZMU_BOGUS; int function = ZMU_BOGUS;
@ -256,9 +257,16 @@ int main(int argc, char *argv[]) {
int image_idx = -1; int image_idx = -1;
int scale = -1; int scale = -1;
int brightness = -1; int brightness = -1;
bool have_brightness = false;
int contrast = -1; int contrast = -1;
bool have_contrast = false;
int hue = -1; int hue = -1;
bool have_hue = false;
int colour = -1; int colour = -1;
bool have_colour = false;
char *zoneString = nullptr; char *zoneString = nullptr;
char *username = nullptr; char *username = nullptr;
char *password = nullptr; char *password = nullptr;
@ -275,13 +283,13 @@ int main(int argc, char *argv[]) {
int option_index = 0; int option_index = 0;
int c = getopt_long(argc, argv, "d:m:vsEDLurwei::S:t::fz::ancqhlB::C::H::O::RWU:P:A:V:T:", long_options, &option_index); int c = getopt_long(argc, argv, "d:m:vsEDLurwei::S:t::fz::ancqhlB::C::H::O::RWU:P:A:V:T:", long_options, &option_index);
if ( c == -1 ) { if (c == -1) {
break; break;
} }
switch (c) { switch (c) {
case 'd': case 'd':
if ( optarg ) if (optarg)
device = optarg; device = optarg;
break; break;
case 'm': case 'm':
@ -295,7 +303,7 @@ int main(int argc, char *argv[]) {
break; break;
case 'i': case 'i':
function |= ZMU_IMAGE; function |= ZMU_IMAGE;
if ( optarg ) if (optarg)
image_idx = atoi(optarg); image_idx = atoi(optarg);
break; break;
case 'S': case 'S':
@ -303,7 +311,7 @@ int main(int argc, char *argv[]) {
break; break;
case 't': case 't':
function |= ZMU_TIME; function |= ZMU_TIME;
if ( optarg ) if (optarg)
image_idx = atoi(optarg); image_idx = atoi(optarg);
break; break;
case 'R': case 'R':
@ -320,7 +328,7 @@ int main(int argc, char *argv[]) {
break; break;
case 'z': case 'z':
function |= ZMU_ZONES; function |= ZMU_ZONES;
if ( optarg ) if (optarg)
zoneString = optarg; zoneString = optarg;
break; break;
case 'a': case 'a':
@ -352,23 +360,31 @@ int main(int argc, char *argv[]) {
break; break;
case 'B': case 'B':
function |= ZMU_BRIGHTNESS; function |= ZMU_BRIGHTNESS;
if ( optarg ) if (optarg) {
have_brightness = true;
brightness = atoi(optarg); brightness = atoi(optarg);
}
break; break;
case 'C': case 'C':
function |= ZMU_CONTRAST; function |= ZMU_CONTRAST;
if ( optarg ) if (optarg) {
have_contrast = true;
contrast = atoi(optarg); contrast = atoi(optarg);
}
break; break;
case 'H': case 'H':
function |= ZMU_HUE; function |= ZMU_HUE;
if ( optarg ) if (optarg) {
have_hue = true;
hue = atoi(optarg); hue = atoi(optarg);
}
break; break;
case 'O': case 'O':
function |= ZMU_COLOUR; function |= ZMU_COLOUR;
if ( optarg ) if (optarg) {
have_colour = true;
colour = atoi(optarg); colour = atoi(optarg);
}
break; break;
case 'U': case 'U':
username = optarg; username = optarg;
@ -408,7 +424,7 @@ int main(int argc, char *argv[]) {
Usage(); Usage();
} }
if ( device && !(function&ZMU_QUERY) ) { if ( !device.empty() && !(function&ZMU_QUERY) ) {
fprintf(stderr, "Error, -d option cannot be used with this option\n"); fprintf(stderr, "Error, -d option cannot be used with this option\n");
Usage(); Usage();
} }
@ -643,60 +659,60 @@ int main(int argc, char *argv[]) {
monitor->DumpSettings(monString, verbose); monitor->DumpSettings(monString, verbose);
printf("%s\n", monString); printf("%s\n", monString);
} }
if ( function & ZMU_BRIGHTNESS ) { if (function & ZMU_BRIGHTNESS) {
if ( verbose ) { if (verbose) {
if ( brightness >= 0 ) if (have_brightness)
printf("New brightness: %d\n", monitor->actionBrightness(brightness)); printf("New brightness: %d\n", monitor->actionBrightness(brightness));
else else
printf("Current brightness: %d\n", monitor->actionBrightness()); printf("Current brightness: %d\n", monitor->actionBrightness());
} else { } else {
if ( have_output ) fputc(separator, stdout); if (have_output) fputc(separator, stdout);
if ( brightness >= 0 ) if (have_brightness)
printf("%d", monitor->actionBrightness(brightness)); printf("%d", monitor->actionBrightness(brightness));
else else
printf("%d", monitor->actionBrightness()); printf("%d", monitor->actionBrightness());
have_output = true; have_output = true;
} }
} }
if ( function & ZMU_CONTRAST ) { if (function & ZMU_CONTRAST) {
if ( verbose ) { if (verbose) {
if ( contrast >= 0 ) if (have_contrast)
printf("New brightness: %d\n", monitor->actionContrast(contrast)); printf("New contrast: %d\n", monitor->actionContrast(contrast));
else else
printf("Current contrast: %d\n", monitor->actionContrast()); printf("Current contrast: %d\n", monitor->actionContrast());
} else { } else {
if ( have_output ) fputc(separator, stdout); if (have_output) fputc(separator, stdout);
if ( contrast >= 0 ) if (have_contrast)
printf("%d", monitor->actionContrast(contrast)); printf("%d", monitor->actionContrast(contrast));
else else
printf("%d", monitor->actionContrast()); printf("%d", monitor->actionContrast());
have_output = true; have_output = true;
} }
} }
if ( function & ZMU_HUE ) { if (function & ZMU_HUE) {
if ( verbose ) { if (verbose) {
if ( hue >= 0 ) if (have_hue)
printf("New hue: %d\n", monitor->actionHue(hue)); printf("New hue: %d\n", monitor->actionHue(hue));
else else
printf("Current hue: %d\n", monitor->actionHue()); printf("Current hue: %d\n", monitor->actionHue());
} else { } else {
if ( have_output ) fputc(separator, stdout); if (have_output) fputc(separator, stdout);
if ( hue >= 0 ) if (have_hue)
printf("%d", monitor->actionHue(hue)); printf("%d", monitor->actionHue(hue));
else else
printf("%d", monitor->actionHue()); printf("%d", monitor->actionHue());
have_output = true; have_output = true;
} }
} }
if ( function & ZMU_COLOUR ) { if (function & ZMU_COLOUR) {
if ( verbose ) { if (verbose) {
if ( colour >= 0 ) if (have_colour)
printf("New colour: %d\n", monitor->actionColour(colour)); printf("New colour: %d\n", monitor->actionColour(colour));
else else
printf("Current colour: %d\n", monitor->actionColour()); printf("Current colour: %d\n", monitor->actionColour());
} else { } else {
if ( have_output ) fputc(separator, stdout); if (have_output) fputc(separator, stdout);
if ( colour >= 0 ) if (have_colour)
printf("%d", monitor->actionColour(colour)); printf("%d", monitor->actionColour(colour));
else else
printf("%d", monitor->actionColour()); printf("%d", monitor->actionColour());
@ -704,7 +720,7 @@ int main(int argc, char *argv[]) {
} }
} }
if ( have_output ) { if (have_output) {
printf("\n"); printf("\n");
} }
if ( !function ) { if ( !function ) {
@ -714,7 +730,7 @@ int main(int argc, char *argv[]) {
if ( function & ZMU_QUERY ) { if ( function & ZMU_QUERY ) {
#if ZM_HAS_V4L #if ZM_HAS_V4L
char vidString[0x10000] = ""; char vidString[0x10000] = "";
bool ok = LocalCamera::GetCurrentSettings(device, vidString, v4lVersion, verbose); bool ok = LocalCamera::GetCurrentSettings(device.c_str(), vidString, v4lVersion, verbose);
printf("%s", vidString); printf("%s", vidString);
exit_zmu(ok ? 0 : -1); exit_zmu(ok ? 0 : -1);
#else // ZM_HAS_V4L #else // ZM_HAS_V4L

View File

@ -58,6 +58,14 @@ case $i in
PACKAGE_VERSION="${i#*=}" PACKAGE_VERSION="${i#*=}"
shift shift
;; ;;
-x=*|--debbuild-extra=*)
DEBBUILD_EXTRA="${i#*=}"
shift
;;
--dput=*)
DPUT="${i#*=}"
shift
;;
--default) --default)
DEFAULT=YES DEFAULT=YES
shift # past argument with no value shift # past argument with no value
@ -80,7 +88,7 @@ fi;
if [ "$DISTROS" == "" ]; then if [ "$DISTROS" == "" ]; then
if [ "$RELEASE" != "" ]; then if [ "$RELEASE" != "" ]; then
DISTROS="xenial,bionic,focal,groovy,hirsute" DISTROS="xenial,bionic,focal,hirsute,impish"
else else
DISTROS=`lsb_release -a 2>/dev/null | grep Codename | awk '{print $2}'`; DISTROS=`lsb_release -a 2>/dev/null | grep Codename | awk '{print $2}'`;
fi; fi;
@ -112,6 +120,11 @@ else
if [ "$BRANCH" == "" ]; then if [ "$BRANCH" == "" ]; then
#REV=$(git rev-list --tags --max-count=1) #REV=$(git rev-list --tags --max-count=1)
BRANCH=`git describe --tags $(git rev-list --tags --max-count=1)`; BRANCH=`git describe --tags $(git rev-list --tags --max-count=1)`;
if [ -z "$BRANCH" ]; then
# This should only happen in CI environments where tag info isn't available
BRANCH=`cat version`
echo "Building branch $BRANCH"
fi
if [ "$BRANCH" == "" ]; then if [ "$BRANCH" == "" ]; then
echo "Unable to determine latest stable branch!" echo "Unable to determine latest stable branch!"
exit 0; exit 0;
@ -133,6 +146,14 @@ else
fi; fi;
fi fi
if [ "$PACKAGE_VERSION" == "NOW" ]; then
PACKAGE_VERSION=`date +%Y%m%d%H%M%S`;
else
if [ "$PACKAGE_VERSION" == "CURRENT" ]; then
PACKAGE_VERSION="`date +%Y%m%d.`$(git rev-list ${versionhash}..HEAD --count)"
fi;
fi;
IFS='.' read -r -a VERSION_PARTS <<< "$RELEASE" IFS='.' read -r -a VERSION_PARTS <<< "$RELEASE"
if [ "$PPA" == "" ]; then if [ "$PPA" == "" ]; then
if [ "$RELEASE" != "" ]; then if [ "$RELEASE" != "" ]; then
@ -216,8 +237,12 @@ rm -rf .git
rm .gitignore rm .gitignore
cd ../ cd ../
if [ ! -e "$DIRECTORY.orig.tar.gz" ]; then if [ ! -e "$DIRECTORY.orig.tar.gz" ]; then
tar zcf $DIRECTORY.orig.tar.gz $DIRECTORY.orig read -p "$DIRECTORY.orig.tar.gz does not exist, create it? [Y/n]"
if [[ "$REPLY" == "" || "$REPLY" == [yY] ]]; then
tar zcf $DIRECTORY.orig.tar.gz $DIRECTORY.orig
fi;
fi; fi;
IFS=',' ;for DISTRO in `echo "$DISTROS"`; do IFS=',' ;for DISTRO in `echo "$DISTROS"`; do
@ -229,7 +254,7 @@ IFS=',' ;for DISTRO in `echo "$DISTROS"`; do
fi; fi;
# Generate Changlog # Generate Changlog
if [ "$DISTRO" == "focal" ] || [ "$DISTRO" == "buster" ] || [ "$DISTRO" == "hirsute" ]; then if [ "$DISTRO" == "focal" ] || [ "$DISTRO" == "buster" ] || [ "$DISTRO" == "hirsute" ] || [ "$DISTRO" == "impish" ]; then
cp -Rpd distros/ubuntu2004 debian cp -Rpd distros/ubuntu2004 debian
elif [ "$DISTRO" == "beowulf" ] elif [ "$DISTRO" == "beowulf" ]
then then
@ -285,30 +310,37 @@ zoneminder ($VERSION-$DISTRO${PACKAGE_VERSION}) $DISTRO; urgency=$URGENCY
EOF EOF
fi; fi;
# Leave the .orig so that we don't pollute it when building deps
cd ..
if [ $TYPE == "binary" ]; then if [ $TYPE == "binary" ]; then
# Auto-install all ZoneMinder's depedencies using the Debian control file # Auto-install all ZoneMinder's depedencies using the Debian control file
sudo apt-get install devscripts equivs sudo apt-get install devscripts equivs
sudo mk-build-deps -ir ./debian/control sudo mk-build-deps -ir $DIRECTORY.orig/debian/control
echo "Status: $?" echo "Status: $?"
DEBUILD=debuild DEBUILD=debuild
else else
if [ $TYPE == "local" ]; then if [ $TYPE == "local" ]; then
# Auto-install all ZoneMinder's depedencies using the Debian control file # Auto-install all ZoneMinder's depedencies using the Debian control file
sudo apt-get install devscripts equivs sudo apt-get install devscripts equivs
sudo mk-build-deps -ir ./debian/control sudo mk-build-deps -ir $DIRECTORY.orig/debian/control
echo "Status: $?" echo "Status: $?"
DEBUILD="debuild -i -us -uc -b" DEBUILD="debuild -i -us -uc -b"
else else
# Source build, don't need build depends. # Source build, don't need build depends.
DEBUILD="debuild -S -sa" DEBUILD="debuild -S -sa"
fi; fi;
fi; fi;
cd $DIRECTORY.orig
if [ "$DEBSIGN_KEYID" != "" ]; then if [ "$DEBSIGN_KEYID" != "" ]; then
DEBUILD="$DEBUILD -k$DEBSIGN_KEYID" DEBUILD="$DEBUILD -k$DEBSIGN_KEYID"
fi fi
# Add any extra options specified on the CLI
DEBUILD="$DEBUILD $DEBBUILD_EXTRA"
eval $DEBUILD eval $DEBUILD
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
echo "Error status code is: $?" echo "Error status code is: $?"
echo "Build failed."; echo "Build failed.";
exit $?; exit $?;
fi; fi;
@ -340,12 +372,14 @@ EOF
dput="Y"; dput="Y";
if [ "$INTERACTIVE" != "no" ]; then if [ "$INTERACTIVE" != "no" ]; then
read -p "Ready to dput $SC to $PPA ? Y/n..."; read -p "Ready to dput $SC to $PPA ? Y/n...";
if [[ "$REPLY" == [yY] ]]; then if [[ "$REPLY" == "" || "$REPLY" == [yY] ]]; then
dput $PPA $SC dput $PPA $SC
fi; fi;
else else
echo "dputting to $PPA"; if [ "$DPUT" != "no" ]; then
dput $PPA $SC echo "dputting to $PPA";
dput $PPA $SC
fi;
fi; fi;
fi; fi;
done; # foreach distro done; # foreach distro

View File

@ -223,7 +223,7 @@ setdebpkgname () {
if [ "" == "$VERSION" ]; then if [ "" == "$VERSION" ]; then
export VERSION="${versionfile}~${thedate}.${numcommits}" export VERSION="${versionfile}~${thedate}.${numcommits}"
fi fi
export RELEASE="${DIST}" export RELEASE="${DIST}${PACKAGE_VERSION}"
checkvars checkvars
@ -369,7 +369,7 @@ elif [ "${OS}" == "debian" ] || [ "${OS}" == "ubuntu" ] || [ "${OS}" == "raspbia
setdebpkgname setdebpkgname
movecrud movecrud
if [ "${DIST}" == "focal" ] || [ "${DIST}" == "groovy" ] || [ "${DIST}" == "hirsuit" ] || [ "${DIST}" == "buster" ]; then if [ "${DIST}" == "bionic" ] || [ "${DIST}" == "focal" ] || [ "${DIST}" == "hirsute" ] || [ "${DIST}" == "impish" ] || [ "${DIST}" == "buster" ] || [ "${DIST}" == "bullseye" ]; then
ln -sfT distros/ubuntu2004 debian ln -sfT distros/ubuntu2004 debian
elif [ "${DIST}" == "beowulf" ]; then elif [ "${DIST}" == "beowulf" ]; then
ln -sfT distros/beowulf debian ln -sfT distros/beowulf debian

View File

@ -1 +1 @@
1.36.5 1.36.12

View File

@ -5,7 +5,7 @@ if ( empty($_REQUEST['id']) && empty($_REQUEST['eids']) ) {
ajaxError('No event id(s) supplied'); ajaxError('No event id(s) supplied');
} }
if ( canView('Events') ) { if ( canView('Events') or canView('Snapshots') ) {
switch ( $_REQUEST['action'] ) { switch ( $_REQUEST['action'] ) {
case 'video' : case 'video' :
if ( empty($_REQUEST['videoFormat']) ) { if ( empty($_REQUEST['videoFormat']) ) {
@ -74,10 +74,15 @@ if ( canView('Events') ) {
else else
$exportCompress = false; $exportCompress = false;
if ( !empty($_REQUEST['exportStructure']) )
$exportStructure = $_SESSION['export']['structure'] = $_REQUEST['exportStructure'];
else
$exportStructure = false;
session_write_close(); session_write_close();
$exportIds = !empty($_REQUEST['eids']) ? $_REQUEST['eids'] : $_REQUEST['id']; $exportIds = !empty($_REQUEST['eids']) ? $_REQUEST['eids'] : $_REQUEST['id'];
if ( $exportFile = exportEvents( if ($exportFile = exportEvents(
$exportIds, $exportIds,
(isset($_REQUEST['connkey'])?$_REQUEST['connkey']:''), (isset($_REQUEST['connkey'])?$_REQUEST['connkey']:''),
$exportDetail, $exportDetail,
@ -86,11 +91,14 @@ if ( canView('Events') ) {
$exportVideo, $exportVideo,
$exportMisc, $exportMisc,
$exportFormat, $exportFormat,
$exportCompress $exportCompress,
) ) $exportStructure,
ajaxResponse(array('exportFile'=>$exportFile)); (!empty($_REQUEST['exportFile'])?$_REQUEST['exportFile']:'zmExport')
else )) {
ajaxResponse(array('exportFile'=>$exportFile));
} else {
ajaxError('Export Failed'); ajaxError('Export Failed');
}
break; break;
case 'download' : case 'download' :
require_once(ZM_SKIN_PATH.'/includes/export_functions.php'); require_once(ZM_SKIN_PATH.'/includes/export_functions.php');
@ -104,7 +112,7 @@ if ( canView('Events') ) {
false,#detail false,#detail
false,#frames false,#frames
false,#images false,#images
$exportVideo, true, #$exportVideo,
false,#Misc false,#Misc
$exportFormat, $exportFormat,
false#,#Compress false#,#Compress

View File

@ -6,28 +6,30 @@ $data = array();
// INITIALIZE AND CHECK SANITY // INITIALIZE AND CHECK SANITY
// //
if ( !canView('Events') ) $message = 'Insufficient permissions for user '.$user['Username']; if (!canView('Events'))
$message = 'Insufficient permissions for user '.$user['Username'].'<br/>';
if ( empty($_REQUEST['task']) ) { if (empty($_REQUEST['task'])) {
$message = 'Must specify a task'; $message = 'Must specify a task<br/>';
} else { } else {
$task = $_REQUEST['task']; $task = $_REQUEST['task'];
} }
if ( empty($_REQUEST['eids']) ) { if (empty($_REQUEST['eids'])) {
if ( isset($_REQUEST['task']) && $_REQUEST['task'] != 'query' ) $message = 'No event id(s) supplied'; if (isset($_REQUEST['task']) && $_REQUEST['task'] != 'query')
$message = 'No event id(s) supplied<br/>';
} else { } else {
$eids = $_REQUEST['eids']; $eids = $_REQUEST['eids'];
} }
if ( $message ) { if ($message) {
ajaxError($message); ajaxError($message);
return; return;
} }
require_once('includes/Filter.php'); require_once('includes/Filter.php');
$filter = isset($_REQUEST['filter']) ? ZM\Filter::parse($_REQUEST['filter']) : new ZM\Filter(); $filter = isset($_REQUEST['filter']) ? ZM\Filter::parse($_REQUEST['filter']) : new ZM\Filter();
if ( $user['MonitorIds'] ) { if ($user['MonitorIds']) {
$filter = $filter->addTerm(array('cnj'=>'and', 'attr'=>'MonitorId', 'op'=>'IN', 'val'=>$user['MonitorIds'])); $filter = $filter->addTerm(array('cnj'=>'and', 'attr'=>'MonitorId', 'op'=>'IN', 'val'=>$user['MonitorIds']));
} }
@ -39,10 +41,19 @@ $search = isset($_REQUEST['search']) ? $_REQUEST['search'] : '';
$advsearch = isset($_REQUEST['advsearch']) ? json_decode($_REQUEST['advsearch'], JSON_OBJECT_AS_ARRAY) : array(); $advsearch = isset($_REQUEST['advsearch']) ? json_decode($_REQUEST['advsearch'], JSON_OBJECT_AS_ARRAY) : array();
// Order specifies the sort direction, either asc or desc // Order specifies the sort direction, either asc or desc
$order = (isset($_REQUEST['order']) and (strtolower($_REQUEST['order']) == 'asc')) ? 'ASC' : 'DESC'; $order = $filter->sort_asc() ? 'ASC' : 'DESC';
if (isset($_REQUEST['order'])) {
if (strtolower($_REQUEST['order']) == 'asc') {
$order = 'ASC';
} else if (strtolower($_REQUEST['order']) == 'desc') {
$order = 'DESC';
} else {
Warning("Invalid value for order " . $_REQUEST['order']);
}
}
// Sort specifies the name of the column to sort on // Sort specifies the name of the column to sort on
$sort = 'StartDateTime'; $sort = $filter->sort_field();
if (isset($_REQUEST['sort'])) { if (isset($_REQUEST['sort'])) {
$sort = $_REQUEST['sort']; $sort = $_REQUEST['sort'];
if ($sort == 'EndDateTime') { if ($sort == 'EndDateTime') {
@ -56,20 +67,19 @@ if (isset($_REQUEST['sort'])) {
// Offset specifies the starting row to return, used for pagination // Offset specifies the starting row to return, used for pagination
$offset = 0; $offset = 0;
if ( isset($_REQUEST['offset']) ) { if (isset($_REQUEST['offset'])) {
if ( ( !is_int($_REQUEST['offset']) and !ctype_digit($_REQUEST['offset']) ) ) { if ((!is_int($_REQUEST['offset']) and !ctype_digit($_REQUEST['offset']))) {
ZM\Error('Invalid value for offset: ' . $_REQUEST['offset']); ZM\Error('Invalid value for offset: ' . $_REQUEST['offset']);
} else { } else {
$offset = $_REQUEST['offset']; $offset = $_REQUEST['offset'];
} }
} }
// Limit specifies the number of rows to return // Limit specifies the number of rows to return
// Set the default to 0 for events view, to prevent an issue with ALL pagination // Set the default to 0 for events view, to prevent an issue with ALL pagination
$limit = 0; $limit = 0;
if ( isset($_REQUEST['limit']) ) { if (isset($_REQUEST['limit'])) {
if ( ( !is_int($_REQUEST['limit']) and !ctype_digit($_REQUEST['limit']) ) ) { if ((!is_int($_REQUEST['limit']) and !ctype_digit($_REQUEST['limit']))) {
ZM\Error('Invalid value for limit: ' . $_REQUEST['limit']); ZM\Error('Invalid value for limit: ' . $_REQUEST['limit']);
} else { } else {
$limit = $_REQUEST['limit']; $limit = $_REQUEST['limit'];
@ -80,25 +90,24 @@ if ( isset($_REQUEST['limit']) ) {
// MAIN LOOP // MAIN LOOP
// //
switch ( $task ) { switch ($task) {
case 'archive' : case 'archive' :
foreach ( $eids as $eid ) archiveRequest($task, $eid); foreach ($eids as $eid) archiveRequest($task, $eid);
break; break;
case 'unarchive' : case 'unarchive' :
# The idea is that anyone can archive, but only people with Event Edit permission can unarchive.. # The idea is that anyone can archive, but only people with Event Edit permission can unarchive..
if ( !canEdit('Events') ) { if (!canEdit('Events')) {
ajaxError('Insufficient permissions for user '.$user['Username']); ajaxError('Insufficient permissions for user '.$user['Username']);
return; return;
} }
foreach ( $eids as $eid ) archiveRequest($task, $eid); foreach ($eids as $eid) archiveRequest($task, $eid);
break; break;
case 'delete' : case 'delete' :
if ( !canEdit('Events') ) { if (!canEdit('Events')) {
ajaxError('Insufficient permissions for user '.$user['Username']); ajaxError('Insufficient permissions for user '.$user['Username']);
return; return;
} }
foreach ($eids as $eid) $data[] = deleteRequest($eid);
foreach ( $eids as $eid ) $data[] = deleteRequest($eid);
break; break;
case 'query' : case 'query' :
$data = queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $limit); $data = queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $limit);
@ -128,6 +137,8 @@ function deleteRequest($eid) {
$message[] = array($eid=>'Event not found.'); $message[] = array($eid=>'Event not found.');
} else if ( $event->Archived() ) { } else if ( $event->Archived() ) {
$message[] = array($eid=>'Event is archived, cannot delete it.'); $message[] = array($eid=>'Event is archived, cannot delete it.');
} else if (!$event->canEdit()) {
$message[] = array($eid=>'You do not have permission to delete event '.$event->Id());
} else { } else {
$event->delete(); $event->delete();
} }
@ -136,7 +147,6 @@ function deleteRequest($eid) {
} }
function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $limit) { function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $limit) {
$data = array( $data = array(
'total' => 0, 'total' => 0,
'totalNotFiltered' => 0, 'totalNotFiltered' => 0,
@ -145,7 +155,7 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim
); );
$failed = !$filter->test_pre_sql_conditions(); $failed = !$filter->test_pre_sql_conditions();
if ( $failed ) { if ($failed) {
ZM\Debug('Pre conditions failed, not doing sql'); ZM\Debug('Pre conditions failed, not doing sql');
return $data; return $data;
} }
@ -160,7 +170,7 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim
// The names of columns shown in the event view that are NOT dB columns in the database // The names of columns shown in the event view that are NOT dB columns in the database
$col_alt = array('Monitor', 'Storage'); $col_alt = array('Monitor', 'Storage');
if ( !in_array($sort, array_merge($columns, $col_alt)) ) { if (!in_array($sort, array_merge($columns, $col_alt))) {
ZM\Error('Invalid sort field: ' . $sort); ZM\Error('Invalid sort field: ' . $sort);
$sort = 'Id'; $sort = 'Id';
} }
@ -175,7 +185,7 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim
$storage_areas = ZM\Storage::find(); $storage_areas = ZM\Storage::find();
$StorageById = array(); $StorageById = array();
foreach ( $storage_areas as $S ) { foreach ($storage_areas as $S) {
$StorageById[$S->Id()] = $S; $StorageById[$S->Id()] = $S;
} }
@ -184,41 +194,43 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim
ZM\Debug('Calling the following sql query: ' .$sql); ZM\Debug('Calling the following sql query: ' .$sql);
$query = dbQuery($sql, $values); $query = dbQuery($sql, $values);
if ( $query ) { if (!$query) {
while ( $row = dbFetchNext($query) ) { ajaxError(dbError($sql));
$event = new ZM\Event($row); return;
$event->remove_from_cache();
if ( !$filter->test_post_sql_conditions($event) ) {
continue;
}
$event_ids[] = $event->Id();
$unfiltered_rows[] = $row;
} # end foreach row
} }
while ($row = dbFetchNext($query)) {
$event = new ZM\Event($row);
$event->remove_from_cache();
if (!$filter->test_post_sql_conditions($event)) {
continue;
}
$event_ids[] = $event->Id();
$unfiltered_rows[] = $row;
} # end foreach row
ZM\Debug('Have ' . count($unfiltered_rows) . ' events matching base filter.'); ZM\Debug('Have ' . count($unfiltered_rows) . ' events matching base filter.');
$filtered_rows = null; $filtered_rows = null;
if ( count($advsearch) or $search != '' ) { if (count($advsearch) or $search != '') {
$search_filter = new ZM\Filter(); $search_filter = new ZM\Filter();
$search_filter = $search_filter->addTerm(array('cnj'=>'and', 'attr'=>'Id', 'op'=>'IN', 'val'=>$event_ids)); $search_filter = $search_filter->addTerm(array('cnj'=>'and', 'attr'=>'Id', 'op'=>'IN', 'val'=>$event_ids));
// There are two search bars in the log view, normal and advanced // There are two search bars in the log view, normal and advanced
// Making an exuctive decision to ignore the normal search, when advanced search is in use // Making an exuctive decision to ignore the normal search, when advanced search is in use
// Alternatively we could try to do both // Alternatively we could try to do both
if ( count($advsearch) ) { if (count($advsearch)) {
$terms = array(); $terms = array();
foreach ( $advsearch as $col=>$text ) { foreach ($advsearch as $col=>$text) {
$terms[] = array('cnj'=>'and', 'attr'=>$col, 'op'=>'LIKE', 'val'=>$text); $terms[] = array('cnj'=>'and', 'attr'=>$col, 'op'=>'LIKE', 'val'=>$text);
} # end foreach col in advsearch } # end foreach col in advsearch
$terms[0]['obr'] = 1; $terms[0]['obr'] = 1;
$terms[count($terms)-1]['cbr'] = 1; $terms[count($terms)-1]['cbr'] = 1;
$search_filter->addTerms($terms); $search_filter->addTerms($terms);
} else if ( $search != '' ) { } else if ($search != '') {
$search = '%' .$search. '%'; $search = '%' .$search. '%';
$terms = array(); $terms = array();
foreach ( $columns as $col ) { foreach ($columns as $col) {
$terms[] = array('cnj'=>'or', 'attr'=>$col, 'op'=>'LIKE', 'val'=>$search); $terms[] = array('cnj'=>'or', 'attr'=>$col, 'op'=>'LIKE', 'val'=>$search);
} }
$terms[0]['obr'] = 1; $terms[0]['obr'] = 1;
@ -228,15 +240,17 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim
} # end if search } # end if search
$sql = 'SELECT ' .$col_str. ' FROM `Events` AS E INNER JOIN Monitors AS M ON E.MonitorId = M.Id WHERE '.$search_filter->sql().' ORDER BY ' .$sort. ' ' .$order; $sql = 'SELECT ' .$col_str. ' FROM `Events` AS E INNER JOIN Monitors AS M ON E.MonitorId = M.Id WHERE '.$search_filter->sql().' ORDER BY ' .$sort. ' ' .$order;
ZM\Debug('Calling the following sql query: ' .$sql);
$filtered_rows = dbFetchAll($sql); $filtered_rows = dbFetchAll($sql);
ZM\Debug('Have ' . count($filtered_rows) . ' events matching search filter.'); ZM\Debug('Have ' . count($filtered_rows) . ' events matching search filter: '.$sql);
} else { } else {
$filtered_rows = $unfiltered_rows; $filtered_rows = $unfiltered_rows;
} # end if search_filter->terms() > 1 } # end if search_filter->terms() > 1
if ($limit)
$filtered_rows = array_slice($filtered_rows, $offset, $limit);
$returned_rows = array(); $returned_rows = array();
foreach ( array_slice($filtered_rows, $offset, $limit) as $row ) { foreach ($filtered_rows as $row) {
$event = new ZM\Event($row); $event = new ZM\Event($row);
$scale = intval(5*100*ZM_WEB_LIST_THUMB_WIDTH / $event->Width()); $scale = intval(5*100*ZM_WEB_LIST_THUMB_WIDTH / $event->Width());

View File

@ -8,21 +8,23 @@ $data = array();
// INITIALIZE AND CHECK SANITY // INITIALIZE AND CHECK SANITY
// //
if ( !canView('Snapshots') ) $message = 'Insufficient permissions for user '.$user['Username']; if (!canView('Snapshots'))
$message = 'Insufficient permissions for user '.$user['Username'];
if ( empty($_REQUEST['task']) ) { $task = '';
if (empty($_REQUEST['task'])) {
$message = 'Must specify a task'; $message = 'Must specify a task';
} else { } else {
$task = $_REQUEST['task']; $task = $_REQUEST['task'];
} }
if ( empty($_REQUEST['ids']) ) { if (empty($_REQUEST['ids'])) {
if ( isset($_REQUEST['task']) && $_REQUEST['task'] != 'query' ) $message = 'No snapshot id(s) supplied'; if ($task != 'query') $message = 'No snapshot id(s) supplied';
} else { } else {
$eids = $_REQUEST['ids']; $ids = $_REQUEST['ids'];
} }
if ( $message ) { if ($message) {
ajaxError($message); ajaxError($message);
return; return;
} }
@ -36,15 +38,15 @@ $advsearch = isset($_REQUEST['advsearch']) ? json_decode($_REQUEST['advsearch'],
// Sort specifies the name of the column to sort on // Sort specifies the name of the column to sort on
$sort = 'Id'; $sort = 'Id';
if ( isset($_REQUEST['sort']) ) { if (isset($_REQUEST['sort'])) {
$sort = $_REQUEST['sort']; $sort = $_REQUEST['sort'];
} }
// Offset specifies the starting row to return, used for pagination // Offset specifies the starting row to return, used for pagination
$offset = 0; $offset = 0;
if ( isset($_REQUEST['offset']) ) { if (isset($_REQUEST['offset'])) {
if ( ( !is_int($_REQUEST['offset']) and !ctype_digit($_REQUEST['offset']) ) ) { if ((!is_int($_REQUEST['offset']) and !ctype_digit($_REQUEST['offset']))) {
ZM\Error('Invalid value for offset: ' . $_REQUEST['offset']); ZM\Error('Invalid value for offset: '.$_REQUEST['offset']);
} else { } else {
$offset = $_REQUEST['offset']; $offset = $_REQUEST['offset'];
} }
@ -56,8 +58,8 @@ $order = (isset($_REQUEST['order']) and (strtolower($_REQUEST['order']) == 'asc'
// Limit specifies the number of rows to return // Limit specifies the number of rows to return
// Set the default to 0 for events view, to prevent an issue with ALL pagination // Set the default to 0 for events view, to prevent an issue with ALL pagination
$limit = 0; $limit = 0;
if ( isset($_REQUEST['limit']) ) { if (isset($_REQUEST['limit'])) {
if ( ( !is_int($_REQUEST['limit']) and !ctype_digit($_REQUEST['limit']) ) ) { if ((!is_int($_REQUEST['limit']) and !ctype_digit($_REQUEST['limit']))) {
ZM\Error('Invalid value for limit: ' . $_REQUEST['limit']); ZM\Error('Invalid value for limit: ' . $_REQUEST['limit']);
} else { } else {
$limit = $_REQUEST['limit']; $limit = $_REQUEST['limit'];
@ -68,14 +70,14 @@ if ( isset($_REQUEST['limit']) ) {
// MAIN LOOP // MAIN LOOP
// //
switch ( $task ) { switch ($task) {
case 'delete' : case 'delete' :
if ( !canEdit('Snapshots') ) { if (!canEdit('Snapshots')) {
ajaxError('Insufficient permissions for user '.$user['Username']); ajaxError('Insufficient permissions for user '.$user['Username']);
return; return;
} }
foreach ( $ids as $id ) $data[] = deleteRequest($id); foreach ($ids as $id) $data[] = deleteRequest($id);
break; break;
case 'query' : case 'query' :
$data = queryRequest($search, $advsearch, $sort, $offset, $order, $limit); $data = queryRequest($search, $advsearch, $sort, $offset, $order, $limit);
@ -99,6 +101,7 @@ function deleteRequest($id) {
//$message[] = array($id=>'Event is archived, cannot delete it.'); //$message[] = array($id=>'Event is archived, cannot delete it.');
} else { } else {
$snapshot->delete(); $snapshot->delete();
$message[] = array($id=>'Snapshot deleted.');
} }
return $message; return $message;

View File

@ -1,7 +1,6 @@
<?php <?php
if (empty($_REQUEST['eid'])) ajaxError('Event Id Not Provided');
if ( empty($_REQUEST['eid']) ) ajaxError('Event Id Not Provided'); if (empty($_REQUEST['fid'])) ajaxError('Frame Id Not Provided');
if ( empty($_REQUEST['fid']) ) ajaxError('Frame Id Not Provided');
$eid = $_REQUEST['eid']; $eid = $_REQUEST['eid'];
$fid = $_REQUEST['fid']; $fid = $_REQUEST['fid'];
@ -9,32 +8,26 @@ $row = ( isset($_REQUEST['row']) ) ? $_REQUEST['row'] : '';
$raw = isset($_REQUEST['raw']); $raw = isset($_REQUEST['raw']);
$data = array(); $data = array();
// Not sure if this is required if ($raw) {
if ( ZM_OPT_USE_AUTH && (ZM_AUTH_RELAY == 'hashed') ) { $sql = 'SELECT S.*,E.*,Z.Name AS ZoneName,Z.Units,Z.Area,M.Name AS MonitorName
$auth_hash = generateAuthHash(ZM_AUTH_HASH_IPS); FROM Stats AS S LEFT JOIN Events AS E ON S.EventId = E.Id LEFT JOIN Zones AS Z ON S.ZoneId = Z.Id LEFT JOIN Monitors AS M ON E.MonitorId = M.Id
if ( isset($_REQUEST['auth']) and ($_REQUEST['auth'] != $auth_hash) ) { WHERE S.EventId = ? AND S.FrameId = ? ORDER BY S.ZoneId';
$data['auth'] = $auth_hash; $stats = dbFetchAll($sql, NULL, array($eid, $fid));
} foreach ($stats as $stat) {
}
if ( $raw ) {
$sql = 'SELECT S.*,E.*,Z.Name AS ZoneName,Z.Units,Z.Area,M.Name AS MonitorName FROM Stats AS S LEFT JOIN Events AS E ON S.EventId = E.Id LEFT JOIN Zones AS Z ON S.ZoneId = Z.Id LEFT JOIN Monitors AS M ON E.MonitorId = M.Id WHERE S.EventId = ? AND S.FrameId = ? ORDER BY S.ZoneId';
$stat = dbFetchOne( $sql, NULL, array( $eid, $fid ) );
if ( $stat ) {
$stat['ZoneName'] = validHtmlStr($stat['ZoneName']); $stat['ZoneName'] = validHtmlStr($stat['ZoneName']);
$stat['PixelDiff'] = validHtmlStr($stat['PixelDiff']); $stat['PixelDiff'] = validHtmlStr($stat['PixelDiff']);
$stat['AlarmPixels'] = sprintf( "%d (%d%%)", $stat['AlarmPixels'], (100*$stat['AlarmPixels']/$stat['Area']) ); $stat['AlarmPixels'] = sprintf('%d (%d%%)', $stat['AlarmPixels'], (100*$stat['AlarmPixels']/$stat['Area']));
$stat['FilterPixels'] = sprintf( "%d (%d%%)", $stat['FilterPixels'], (100*$stat['FilterPixels']/$stat['Area']) ); $stat['FilterPixels'] = sprintf('%d (%d%%)', $stat['FilterPixels'], (100*$stat['FilterPixels']/$stat['Area']));
$stat['BlobPixels'] = sprintf( "%d (%d%%)", $stat['BlobPixels'], (100*$stat['BlobPixels']/$stat['Area']) ); $stat['BlobPixels'] = sprintf('%d (%d%%)', $stat['BlobPixels'], (100*$stat['BlobPixels']/$stat['Area']));
$stat['Blobs'] = validHtmlStr($stat['Blobs']); $stat['Blobs'] = validHtmlStr($stat['Blobs']);
if ( $stat['Blobs'] > 1 ) { if ($stat['Blobs'] > 1) {
$stat['BlobSizes'] = sprintf( "%d-%d (%d%%-%d%%)", $stat['MinBlobSize'], $stat['MaxBlobSize'], (100*$stat['MinBlobSize']/$stat['Area']), (100*$stat['MaxBlobSize']/$stat['Area']) ); $stat['BlobSizes'] = sprintf('%d-%d (%d%%-%d%%)', $stat['MinBlobSize'], $stat['MaxBlobSize'], (100*$stat['MinBlobSize']/$stat['Area']), (100*$stat['MaxBlobSize']/$stat['Area']));
} else { } else {
$stat['BlobSizes'] = sprintf( "%d (%d%%)", $stat['MinBlobSize'], 100*$stat['MinBlobSize']/$stat['Area'] ); $stat['BlobSizes'] = sprintf('%d (%d%%)', $stat['MinBlobSize'], 100*$stat['MinBlobSize']/$stat['Area']);
} }
$stat['AlarmLimits'] = validHtmlStr($stat['MinX'].",".$stat['MinY']."-".$stat['MaxX'].",".$stat['MaxY']); $stat['AlarmLimits'] = validHtmlStr($stat['MinX'].','.$stat['MinY'].'-'.$stat['MaxX'].','.$stat['MaxY']);
} $data['raw'][] = $stat;
$data['raw'] = $stat; } # end foreach stat/zone
} else { } else {
$data['html'] = getStatsTableHTML($eid, $fid, $row); $data['html'] = getStatsTableHTML($eid, $fid, $row);
$data['id'] = '#contentStatsTable' .$row; $data['id'] = '#contentStatsTable' .$row;

View File

@ -266,7 +266,7 @@ class MonitorsController extends AppController {
if ($mToken) { if ($mToken) {
$auth = ' -T '.$mToken; $auth = ' -T '.$mToken;
} else if (ZM_AUTH_RELAY == 'hashed') { } else if (ZM_AUTH_RELAY == 'hashed') {
$auth = ' -A '.calculateAuthHash(ZM_AUTH_HASH_IPS?$_SERVER['REMOTE_ADDR']:''); $auth = ' -A '.calculateAuthHash(''); # Can't do REMOTE_IP because zmu doesn't normally have access to it.
} else if (ZM_AUTH_RELAY == 'plain') { } else if (ZM_AUTH_RELAY == 'plain') {
# Plain requires the plain text password which must either be in request or stored in session # Plain requires the plain text password which must either be in request or stored in session
$password = $this->request->query('pass') ? $this->request->query('pass') : $this->request->data('pass');; $password = $this->request->query('pass') ? $this->request->query('pass') : $this->request->data('pass');;

@ -1 +1 @@
Subproject commit 0bd63fb464957080ead342db58ca9e01532cf1ef Subproject commit 14292374ccf1328f2d5db20897bd06f99ba4d938

View File

@ -1,6 +1,12 @@
ZoneMinder uses certain 3rd party media assets/libraries for UI display purposes. Their licenses are listed in this file ZoneMinder uses certain 3rd party media assets/libraries for UI display purposes. Their licenses are listed in this file
### Font Awesome icons
Origin: http://fontawesome.io
License: Font: SIL OFL 1.1, CSS: MIT License (http://fontawesome.io/license)
### Material Design icons ### Material Design icons
Origin: https://github.com/google/material-design-icons Origin: https://github.com/google/material-design-icons

View File

@ -627,6 +627,46 @@ class Event extends ZM_Object {
return 'Unknown reason'; return 'Unknown reason';
} }
function canView($u=null) {
global $user;
if (!$u) $u=$user;
if (!$u) {
# auth turned on and not logged in
return false;
}
if (!empty($u['MonitorIds']) ) {
if (in_array($this->{'MonitorId'}, explode(',', $u['MonitorIds']))) {
return true;
}
return false;
}
if ($u['Events'] != 'None') {
return true;
}
if ($u['Snapshots'] != 'None') {
# If the event is contained in a snapshot, then we can still view it.
if (dbFetchOne('SELECT * FROM Snapshot_Events WHERE EventId=?', $this->Id()))
return true;
}
return false;
}
function canEdit($u=null) {
global $user;
if (!$u) $u=$user;
if (!$u) {
# auth turned on and not logged in
return false;
}
if (!empty($u['MonitorIds']) ) {
if (!in_array($this->{'MonitorId'}, explode(',', $u['MonitorIds']))) {
return false;
}
}
if ($u['Events'] != 'Edit') {
return false;
}
return true;
}
} # end class } # end class
?> ?>

View File

@ -60,6 +60,9 @@ class Filter extends ZM_Object {
foreach ( $this->FilterTerms() as $term ) { foreach ( $this->FilterTerms() as $term ) {
$this->_querystring .= $term->querystring($objectname, $separator); $this->_querystring .= $term->querystring($objectname, $separator);
} # end foreach term } # end foreach term
$this->_querystring .= $separator.urlencode($objectname.'[Query][sort_asc]').'='.$this->sort_asc();
$this->_querystring .= $separator.urlencode($objectname.'[Query][sort_field]').'='.$this->sort_field();
$this->_querystring .= $separator.urlencode($objectname.'[Query][limit]').'='.$this->limit();
if ( $this->Id() ) { if ( $this->Id() ) {
$this->_querystring .= $separator.$objectname.urlencode('[Id]').'='.$this->Id(); $this->_querystring .= $separator.$objectname.urlencode('[Id]').'='.$this->Id();
} }

View File

@ -167,9 +167,15 @@ class Group extends ZM_Object {
public function Parents() { public function Parents() {
$Parents = array(); $Parents = array();
$Parent = $this->Parent(); $Parent = $this->Parent();
while( $Parent ) { $seen_parents = array();
while ($Parent) {
$seen_parents[$Parent->Id()] = $Parent;
array_unshift($Parents, $Parent); array_unshift($Parents, $Parent);
$Parent = $Parent->Parent(); $Parent = $Parent->Parent();
if ($Parent and isset($seen_parents[$Parent->Id()])) {
Warning("Detected hierarchy loop in group {$Parent->Name()}");
break;
}
} }
return $Parents; return $Parents;
} }
@ -189,6 +195,9 @@ class Group extends ZM_Object {
public function canView($u=null) { public function canView($u=null) {
global $user; global $user;
if (!$u) $u = $user; if (!$u) $u = $user;
if (!count($this->Monitors()) and !count($this->Children())) {
return true;
}
# Can view if we can view any of the monitors in it. # Can view if we can view any of the monitors in it.
foreach ($this->Monitors() as $monitor) { foreach ($this->Monitors() as $monitor) {
if ($monitor->canView($u)) return true; if ($monitor->canView($u)) return true;

View File

@ -10,7 +10,7 @@ require_once('Group.php');
$FunctionTypes = null; $FunctionTypes = null;
function getMonitorFunctionTypes() { function getMonitorFunctionTypes() {
if ( !isset($FunctionTypes ) ) { if (!isset($FunctionTypes)) {
$FunctionTypes = array( $FunctionTypes = array(
'None' => translate('FnNone'), 'None' => translate('FnNone'),
'Monitor' => translate('FnMonitor'), 'Monitor' => translate('FnMonitor'),
@ -23,6 +23,21 @@ function getMonitorFunctionTypes() {
return $FunctionTypes; return $FunctionTypes;
} }
$Statuses = null;
function getMonitorStatuses() {
if (!isset($Statuses)) {
$Statuses = array(
-1 => 'Unknown',
0 => 'Idle',
1 => 'PreAlarm',
2 => 'Alarm',
3 => 'Alert',
4 => 'Tape'
);
}
return $Statuses;
}
class Monitor extends ZM_Object { class Monitor extends ZM_Object {
protected static $table = 'Monitors'; protected static $table = 'Monitors';
@ -126,6 +141,7 @@ class Monitor extends ZM_Object {
'Longitude' => null, 'Longitude' => null,
'RTSPServer' => array('type'=>'boolean', 'default'=>0), 'RTSPServer' => array('type'=>'boolean', 'default'=>0),
'RTSPStreamName' => '', 'RTSPStreamName' => '',
'Importance' => 'Normal',
); );
private $status_fields = array( private $status_fields = array(
'Status' => null, 'Status' => null,
@ -149,46 +165,46 @@ class Monitor extends ZM_Object {
); );
public function Control() { public function Control() {
if ( !property_exists($this, 'Control') ) { if (!property_exists($this, 'Control')) {
if ( $this->ControlId() ) if ($this->ControlId())
$this->{'Control'} = Control::find_one(array('Id'=>$this->{'ControlId'})); $this->{'Control'} = Control::find_one(array('Id'=>$this->{'ControlId'}));
if ( !(property_exists($this, 'Control') and $this->{'Control'}) ) if (!(property_exists($this, 'Control') and $this->{'Control'}))
$this->{'Control'} = new Control(); $this->{'Control'} = new Control();
} }
return $this->{'Control'}; return $this->{'Control'};
} }
public function Server() { public function Server() {
if ( !property_exists($this, 'Server') ) { if (!property_exists($this, 'Server')) {
if ( $this->ServerId() ) if ($this->ServerId())
$this->{'Server'} = Server::find_one(array('Id'=>$this->{'ServerId'})); $this->{'Server'} = Server::find_one(array('Id'=>$this->{'ServerId'}));
if ( !property_exists($this, 'Server') ) { if (!property_exists($this, 'Server')) {
$this->{'Server'} = new Server(); $this->{'Server'} = new Server();
} }
} }
return $this->{'Server'}; return $this->{'Server'};
} }
public function __call($fn, array $args){ public function __call($fn, array $args) {
if ( count($args) ) { if (count($args)) {
if ( is_array($this->defaults[$fn]) and $this->defaults[$fn]['type'] == 'set' ) { if (is_array($this->defaults[$fn]) and $this->defaults[$fn]['type'] == 'set') {
$this->{$fn} = is_array($args[0]) ? implode(',', $args[0]) : $args[0]; $this->{$fn} = is_array($args[0]) ? implode(',', $args[0]) : $args[0];
} else { } else {
$this->{$fn} = $args[0]; $this->{$fn} = $args[0];
} }
} }
if ( property_exists($this, $fn) ) { if (property_exists($this, $fn)) {
return $this->{$fn}; return $this->{$fn};
} else if ( array_key_exists($fn, $this->defaults) ) { } else if (array_key_exists($fn, $this->defaults)) {
if ( is_array($this->defaults[$fn]) ) { if ( is_array($this->defaults[$fn]) ) {
return $this->defaults[$fn]['default']; return $this->defaults[$fn]['default'];
} }
return $this->defaults[$fn]; return $this->defaults[$fn];
} else if ( array_key_exists($fn, $this->status_fields) ) { } else if (array_key_exists($fn, $this->status_fields)) {
$sql = 'SELECT * FROM `Monitor_Status` WHERE `MonitorId`=?'; $sql = 'SELECT * FROM `Monitor_Status` WHERE `MonitorId`=?';
$row = dbFetchOne($sql, NULL, array($this->{'Id'})); $row = dbFetchOne($sql, NULL, array($this->{'Id'}));
if ( !$row ) { if (!$row) {
Warning('Unable to load Monitor status record for Id='.$this->{'Id'}.' using '.$sql); Warning('Unable to load Monitor status record for Id='.$this->{'Id'}.' using '.$sql);
return null; return null;
} else { } else {
@ -197,10 +213,10 @@ class Monitor extends ZM_Object {
} }
} }
return $this->{$fn}; return $this->{$fn};
} else if ( array_key_exists($fn, $this->summary_fields) ) { } else if (array_key_exists($fn, $this->summary_fields)) {
$sql = 'SELECT * FROM `Event_Summaries` WHERE `MonitorId`=?'; $sql = 'SELECT * FROM `Event_Summaries` WHERE `MonitorId`=?';
$row = dbFetchOne($sql, NULL, array($this->{'Id'})); $row = dbFetchOne($sql, NULL, array($this->{'Id'}));
if ( !$row ) { if (!$row) {
Warning('Unable to load Event Summary record for Id='.$this->{'Id'}.' using '.$sql); Warning('Unable to load Event Summary record for Id='.$this->{'Id'}.' using '.$sql);
return null; return null;
} else { } else {
@ -218,7 +234,6 @@ class Monitor extends ZM_Object {
} }
public function getStreamSrc($args, $querySep='&amp;') { public function getStreamSrc($args, $querySep='&amp;') {
$streamSrc = $this->Server()->UrlToZMS( $streamSrc = $this->Server()->UrlToZMS(
ZM_MIN_STREAMING_PORT ? ZM_MIN_STREAMING_PORT ?
ZM_MIN_STREAMING_PORT+$this->{'Id'} : ZM_MIN_STREAMING_PORT+$this->{'Id'} :
@ -226,8 +241,8 @@ class Monitor extends ZM_Object {
$args['monitor'] = $this->{'Id'}; $args['monitor'] = $this->{'Id'};
if ( ZM_OPT_USE_AUTH ) { if (ZM_OPT_USE_AUTH) {
if ( ZM_AUTH_RELAY == 'hashed' ) { if (ZM_AUTH_RELAY == 'hashed') {
$args['auth'] = generateAuthHash(ZM_AUTH_HASH_IPS); $args['auth'] = generateAuthHash(ZM_AUTH_HASH_IPS);
} elseif ( ZM_AUTH_RELAY == 'plain' ) { } elseif ( ZM_AUTH_RELAY == 'plain' ) {
$args['user'] = $_SESSION['username']; $args['user'] = $_SESSION['username'];
@ -236,24 +251,24 @@ class Monitor extends ZM_Object {
$args['user'] = $_SESSION['username']; $args['user'] = $_SESSION['username'];
} }
} }
if ( (!isset($args['mode'])) or ( $args['mode'] != 'single' ) ) { if ((!isset($args['mode'])) or ($args['mode'] != 'single')) {
$args['connkey'] = $this->connKey(); $args['connkey'] = $this->connKey();
} }
if ( ZM_RAND_STREAM ) { if (ZM_RAND_STREAM) {
$args['rand'] = time(); $args['rand'] = time();
} }
# zms doesn't support width & height, so if no scale is set, default it # zms doesn't support width & height, so if no scale is set, default it
if ( ! isset($args['scale']) ) { if (!isset($args['scale'])) {
if ( isset($args['width']) and intval($args['width']) ) { if (isset($args['width']) and intval($args['width'])) {
$args['scale'] = intval((100*intval($args['width']))/$this->ViewWidth()); $args['scale'] = intval((100*intval($args['width']))/$this->ViewWidth());
} else if ( isset($args['height']) and intval($args['height']) ) { } else if (isset($args['height']) and intval($args['height'])) {
$args['scale'] = intval((100*intval($args['height']))/$this->ViewHeight()); $args['scale'] = intval((100*intval($args['height']))/$this->ViewHeight());
} }
} }
if ( isset($args['width']) ) if (isset($args['width']))
unset($args['width']); unset($args['width']);
if ( isset($args['height']) ) if (isset($args['height']))
unset($args['height']); unset($args['height']);
$streamSrc .= '?'.http_build_query($args, '', $querySep); $streamSrc .= '?'.http_build_query($args, '', $querySep);
@ -269,21 +284,21 @@ class Monitor extends ZM_Object {
} }
public function ViewWidth($new = null) { public function ViewWidth($new = null) {
if ( $new ) if ($new)
$this->{'Width'} = $new; $this->{'Width'} = $new;
$field = ( $this->Orientation() == 'ROTATE_90' or $this->Orientation() == 'ROTATE_270' ) ? 'Height' : 'Width'; $field = ( $this->Orientation() == 'ROTATE_90' or $this->Orientation() == 'ROTATE_270' ) ? 'Height' : 'Width';
if ( property_exists($this, $field) ) if (property_exists($this, $field))
return $this->{$field}; return $this->{$field};
return $this->defaults[$field]; return $this->defaults[$field];
} // end function Width } // end function Width
public function ViewHeight($new=null) { public function ViewHeight($new=null) {
if ( $new ) if ($new)
$this->{'Height'} = $new; $this->{'Height'} = $new;
$field = ( $this->Orientation() == 'ROTATE_90' or $this->Orientation() == 'ROTATE_270' ) ? 'Width' : 'Height'; $field = ( $this->Orientation() == 'ROTATE_90' or $this->Orientation() == 'ROTATE_270' ) ? 'Width' : 'Height';
if ( property_exists($this, $field) ) if (property_exists($this, $field))
return $this->{$field}; return $this->{$field};
return $this->defaults[$field]; return $this->defaults[$field];
} // end function Height } // end function Height
@ -296,50 +311,50 @@ class Monitor extends ZM_Object {
// Validate that it's a valid colour (we seem to allow color names, not just hex). // Validate that it's a valid colour (we seem to allow color names, not just hex).
// This also helps prevent XSS. // This also helps prevent XSS.
if ( property_exists($this, $field) && preg_match('/^[#0-9a-zA-Z]+$/', $this->{$field})) { if (property_exists($this, $field) && preg_match('/^[#0-9a-zA-Z]+$/', $this->{$field})) {
return $this->{$field}; return $this->{$field};
} }
return $this->defaults[$field]; return $this->defaults[$field];
} // end function SignalCheckColour } // end function SignalCheckColour
public static function find( $parameters = array(), $options = array() ) { public static function find($parameters = array(), $options = array()) {
return ZM_Object::_find(get_class(), $parameters, $options); return ZM_Object::_find(get_class(), $parameters, $options);
} }
public static function find_one( $parameters = array(), $options = array() ) { public static function find_one($parameters = array(), $options = array()) {
return ZM_Object::_find_one(get_class(), $parameters, $options); return ZM_Object::_find_one(get_class(), $parameters, $options);
} }
function zmcControl( $mode=false ) { function zmcControl($mode=false) {
if ( !(property_exists($this,'Id') and $this->{'Id'}) ) { if (!(property_exists($this,'Id') and $this->{'Id'})) {
Warning('Attempt to control a monitor with no Id'); Warning('Attempt to control a monitor with no Id');
return; return;
} }
if ( (!defined('ZM_SERVER_ID')) or ( property_exists($this, 'ServerId') and (ZM_SERVER_ID==$this->{'ServerId'}) ) ) { if ((!defined('ZM_SERVER_ID')) or ( property_exists($this, 'ServerId') and (ZM_SERVER_ID==$this->{'ServerId'}) )) {
if ( $this->Type() == 'Local' ) { if ($this->Type() == 'Local') {
$zmcArgs = '-d '.$this->{'Device'}; $zmcArgs = '-d '.$this->{'Device'};
} else { } else {
$zmcArgs = '-m '.$this->{'Id'}; $zmcArgs = '-m '.$this->{'Id'};
} }
if ( $mode == 'stop' ) { if ($mode == 'stop') {
daemonControl('stop', 'zmc', $zmcArgs); daemonControl('stop', 'zmc', $zmcArgs);
} else { } else {
if ( $mode == 'restart' ) { if ($mode == 'restart') {
daemonControl('stop', 'zmc', $zmcArgs); daemonControl('stop', 'zmc', $zmcArgs);
} }
if ( $this->{'Function'} != 'None' ) { if ($this->{'Function'} != 'None') {
daemonControl('start', 'zmc', $zmcArgs); daemonControl('start', 'zmc', $zmcArgs);
} }
} }
} else if ( $this->ServerId() ) { } else if ($this->ServerId()) {
$Server = $this->Server(); $Server = $this->Server();
$url = $Server->UrlToApi().'/monitors/daemonControl/'.$this->{'Id'}.'/'.$mode.'/zmc.json'; $url = $Server->UrlToApi().'/monitors/daemonControl/'.$this->{'Id'}.'/'.$mode.'/zmc.json';
if ( ZM_OPT_USE_AUTH ) { if (ZM_OPT_USE_AUTH) {
if ( ZM_AUTH_RELAY == 'hashed' ) { if (ZM_AUTH_RELAY == 'hashed') {
$url .= '?auth='.generateAuthHash(ZM_AUTH_HASH_IPS); $url .= '?auth='.generateAuthHash(ZM_AUTH_HASH_IPS);
} else if ( ZM_AUTH_RELAY == 'plain' ) { } else if (ZM_AUTH_RELAY == 'plain') {
$url .= '?user='.$_SESSION['username']; $url .= '?user='.$_SESSION['username'];
$url .= '?pass='.$_SESSION['password']; $url .= '?pass='.$_SESSION['password'];
} else { } else {
@ -349,13 +364,13 @@ class Monitor extends ZM_Object {
} }
Debug('sending command to '.$url); Debug('sending command to '.$url);
$context = stream_context_create(); $context = stream_context_create();
try { try {
$result = file_get_contents($url, false, $context); $result = file_get_contents($url, false, $context);
if ( $result === FALSE ) { /* Handle error */ if ($result === FALSE) { /* Handle error */
Error("Error restarting zmc using $url"); Error("Error restarting zmc using $url");
} }
} catch ( Exception $e ) { } catch (Exception $e) {
Error("Except $e thrown trying to restart zmc"); Error("Except $e thrown trying to restart zmc");
} }
} else { } else {
@ -363,19 +378,19 @@ class Monitor extends ZM_Object {
} }
} // end function zmcControl } // end function zmcControl
public function GroupIds( $new='' ) { public function GroupIds($new='') {
if ( $new != '' ) { if ($new != '') {
if ( !is_array($new) ) { if (!is_array($new)) {
$this->{'GroupIds'} = array($new); $this->{'GroupIds'} = array($new);
} else { } else {
$this->{'GroupIds'} = $new; $this->{'GroupIds'} = $new;
} }
} }
if ( !property_exists($this, 'GroupIds') ) { if (!property_exists($this, 'GroupIds')) {
if ( property_exists($this, 'Id') and $this->{'Id'} ) { if (property_exists($this, 'Id') and $this->{'Id'}) {
$this->{'GroupIds'} = dbFetchAll('SELECT `GroupId` FROM `Groups_Monitors` WHERE `MonitorId`=?', 'GroupId', array($this->{'Id'}) ); $this->{'GroupIds'} = dbFetchAll('SELECT `GroupId` FROM `Groups_Monitors` WHERE `MonitorId`=?', 'GroupId', array($this->{'Id'}));
if ( ! $this->{'GroupIds'} ) if (!$this->{'GroupIds'})
$this->{'GroupIds'} = array(); $this->{'GroupIds'} = array();
} else { } else {
$this->{'GroupIds'} = array(); $this->{'GroupIds'} = array();
@ -385,8 +400,8 @@ class Monitor extends ZM_Object {
} }
public function delete() { public function delete() {
if ( ! $this->{'Id'} ) { if (!$this->{'Id'}) {
Warning("Attempt to delete a monitor without id."); Warning('Attempt to delete a monitor without id.');
return; return;
} }
$this->zmcControl('stop'); $this->zmcControl('stop');
@ -394,17 +409,17 @@ class Monitor extends ZM_Object {
// If fast deletes are on, then zmaudit will clean everything else up later // If fast deletes are on, then zmaudit will clean everything else up later
// If fast deletes are off and there are lots of events then this step may // If fast deletes are off and there are lots of events then this step may
// well time out before completing, in which case zmaudit will still tidy up // well time out before completing, in which case zmaudit will still tidy up
if ( !ZM_OPT_FAST_DELETE ) { if (!ZM_OPT_FAST_DELETE) {
$markEids = dbFetchAll('SELECT Id FROM Events WHERE MonitorId=?', 'Id', array($this->{'Id'})); $markEids = dbFetchAll('SELECT Id FROM Events WHERE MonitorId=?', 'Id', array($this->{'Id'}));
foreach ($markEids as $markEid) foreach ($markEids as $markEid)
deleteEvent($markEid); deleteEvent($markEid);
if ( $this->{'Name'} ) if ($this->{'Name'})
deletePath(ZM_DIR_EVENTS.'/'.basename($this->{'Name'})); deletePath(ZM_DIR_EVENTS.'/'.basename($this->{'Name'}));
deletePath(ZM_DIR_EVENTS.'/'.$this->{'Id'}); deletePath(ZM_DIR_EVENTS.'/'.$this->{'Id'});
$Storage = $this->Storage(); $Storage = $this->Storage();
if ( $Storage->Path() != ZM_DIR_EVENTS ) { if ($Storage->Path() != ZM_DIR_EVENTS) {
if ( $this->{'Name'} ) if ($this->{'Name'})
deletePath($Storage->Path().'/'.basename($this->{'Name'})); deletePath($Storage->Path().'/'.basename($this->{'Name'}));
deletePath($Storage->Path().'/'.$this->{'Id'}); deletePath($Storage->Path().'/'.$this->{'Id'});
} }
@ -420,14 +435,14 @@ class Monitor extends ZM_Object {
} // end function delete } // end function delete
public function Storage($new = null) { public function Storage($new = null) {
if ( $new ) { if ($new) {
$this->{'Storage'} = $new; $this->{'Storage'} = $new;
} }
if ( ! ( property_exists($this, 'Storage') and $this->{'Storage'} ) ) { if (!(property_exists($this, 'Storage') and $this->{'Storage'})) {
$this->{'Storage'} = isset($this->{'StorageId'}) ? $this->{'Storage'} = isset($this->{'StorageId'}) ?
Storage::find_one(array('Id'=>$this->{'StorageId'})) : Storage::find_one(array('Id'=>$this->{'StorageId'})) :
new Storage(NULL); new Storage(NULL);
if ( ! $this->{'Storage'} ) if (!$this->{'Storage'})
$this->{'Storage'} = new Storage(NULL); $this->{'Storage'} = new Storage(NULL);
} }
return $this->{'Storage'}; return $this->{'Storage'};
@ -435,42 +450,42 @@ class Monitor extends ZM_Object {
public function Source( ) { public function Source( ) {
$source = ''; $source = '';
if ( $this->{'Type'} == 'Local' ) { if ($this->{'Type'} == 'Local') {
$source = $this->{'Device'}.' ('.$this->{'Channel'}.')'; $source = $this->{'Device'}.' ('.$this->{'Channel'}.')';
} else if ( $this->{'Type'} == 'Remote' ) { } else if ($this->{'Type'} == 'Remote') {
$source = preg_replace( '/^.*@/', '', $this->{'Host'} ); $source = preg_replace('/^.*@/', '', $this->{'Host'});
if ( $this->{'Port'} != '80' and $this->{'Port'} != '554' ) { if ($this->{'Port'} != '80' and $this->{'Port'} != '554') {
$source .= ':'.$this->{'Port'}; $source .= ':'.$this->{'Port'};
} }
} else if ( $this->{'Type'} == 'VNC' ) { } else if ($this->{'Type'} == 'VNC') {
$source = preg_replace( '/^.*@/', '', $this->{'Host'} ); $source = preg_replace( '/^.*@/', '', $this->{'Host'} );
if ( $this->{'Port'} != '5900' ) { if ($this->{'Port'} != '5900') {
$source .= ':'.$this->{'Port'}; $source .= ':'.$this->{'Port'};
} }
} else if ( $this->{'Type'} == 'Ffmpeg' || $this->{'Type'} == 'Libvlc' || $this->{'Type'} == 'WebSite' ) { } else if ($this->{'Type'} == 'Ffmpeg' || $this->{'Type'} == 'Libvlc' || $this->{'Type'} == 'WebSite') {
$url_parts = parse_url( $this->{'Path'} ); $url_parts = parse_url($this->{'Path'});
if ( ZM_WEB_FILTER_SOURCE == 'Hostname' ) { if (ZM_WEB_FILTER_SOURCE == 'Hostname') {
# Filter out everything but the hostname # Filter out everything but the hostname
if ( isset($url_parts['host']) ) { if (isset($url_parts['host'])) {
$source = $url_parts['host']; $source = $url_parts['host'];
} else { } else {
$source = $this->{'Path'}; $source = $this->{'Path'};
} }
} else if ( ZM_WEB_FILTER_SOURCE == 'NoCredentials' ) { } else if (ZM_WEB_FILTER_SOURCE == 'NoCredentials') {
# Filter out sensitive and common items # Filter out sensitive and common items
unset($url_parts['user']); unset($url_parts['user']);
unset($url_parts['pass']); unset($url_parts['pass']);
#unset($url_parts['scheme']); #unset($url_parts['scheme']);
unset($url_parts['query']); unset($url_parts['query']);
#unset($url_parts['path']); #unset($url_parts['path']);
if ( isset($url_parts['port']) and ( $url_parts['port'] == '80' or $url_parts['port'] == '554' ) ) if (isset($url_parts['port']) and ($url_parts['port'] == '80' or $url_parts['port'] == '554'))
unset($url_parts['port']); unset($url_parts['port']);
$source = unparse_url($url_parts); $source = unparse_url($url_parts);
} else { # Don't filter anything } else { # Don't filter anything
$source = $this->{'Path'}; $source = $this->{'Path'};
} }
} }
if ( $source == '' ) { if ($source == '') {
$source = 'Monitor ' . $this->{'Id'}; $source = 'Monitor ' . $this->{'Id'};
} }
return $source; return $source;
@ -478,7 +493,10 @@ class Monitor extends ZM_Object {
public function UrlToIndex($port=null) { public function UrlToIndex($port=null) {
return $this->Server()->UrlToIndex($port); return $this->Server()->UrlToIndex($port);
//ZM_MIN_STREAMING_PORT ? (ZM_MIN_STREAMING_PORT+$this->Id()) : null); }
public function UrlToZMS($port=null) {
return $this->Server()->UrlToZMS($port).'?mid='.$this->Id();
} }
public function sendControlCommand($command) { public function sendControlCommand($command) {
@ -486,78 +504,78 @@ class Monitor extends ZM_Object {
$options = array(); $options = array();
# Convert from a command line params to an option array # Convert from a command line params to an option array
foreach ( explode(' ', $command) as $option ) { foreach (explode(' ', $command) as $option) {
if ( preg_match('/--([^=]+)(?:=(.+))?/', $option, $matches) ) { if (preg_match('/--([^=]+)(?:=(.+))?/', $option, $matches)) {
$options[$matches[1]] = $matches[2]?$matches[2]:1; $options[$matches[1]] = $matches[2]?$matches[2]:1;
} else if ( $option != '' and $option != 'quit' and $option != 'start' and $option != 'stop' ) { } else if ($option != '' and $option != 'quit' and $option != 'start' and $option != 'stop') {
Warning("Ignored command for zmcontrol $option in $command"); Warning("Ignored command for zmcontrol $option in $command");
} }
} }
if ( !count($options) ) { if (!count($options)) {
if ( $command == 'quit' or $command == 'start' or $command == 'stop' ) { if ($command == 'quit' or $command == 'start' or $command == 'stop') {
# These are special as we now run zmcontrol as a daemon through zmdc. # These are special as we now run zmcontrol as a daemon through zmdc.
$status = daemonStatus('zmcontrol.pl', array('--id', $this->{'Id'})); $status = daemonStatus('zmcontrol.pl', array('--id', $this->{'Id'}));
Debug("Current status $status"); Debug("Current status $status");
if ( $status or ( (!defined('ZM_SERVER_ID')) or ( property_exists($this, 'ServerId') and (ZM_SERVER_ID==$this->{'ServerId'}) ) ) ) { if ($status or ((!defined('ZM_SERVER_ID')) or (property_exists($this, 'ServerId') and (ZM_SERVER_ID==$this->{'ServerId'})))) {
daemonControl($command, 'zmcontrol.pl', '--id '.$this->{'Id'}); daemonControl($command, 'zmcontrol.pl', '--id '.$this->{'Id'});
return; return;
} }
$options['command'] = $command; $options['command'] = $command;
} else { } else {
Warning("No commands to send to zmcontrol from $command"); Warning('No commands to send to zmcontrol from '.$command);
return false; return false;
} }
} }
if ( (!defined('ZM_SERVER_ID')) or ( property_exists($this, 'ServerId') and (ZM_SERVER_ID==$this->{'ServerId'}) ) ) { if ((!defined('ZM_SERVER_ID')) or (property_exists($this, 'ServerId') and (ZM_SERVER_ID==$this->{'ServerId'}))) {
# Local # Local
Debug('Trying to send options ' . print_r($options, true)); Debug('Trying to send options ' . print_r($options, true));
$optionString = jsonEncode($options); $optionString = jsonEncode($options);
Debug("Trying to send options $optionString"); Debug('Trying to send options '.$optionString);
// Either connects to running zmcontrol.pl or runs zmcontrol.pl to send the command. // Either connects to running zmcontrol.pl or runs zmcontrol.pl to send the command.
$socket = socket_create(AF_UNIX, SOCK_STREAM, 0); $socket = socket_create(AF_UNIX, SOCK_STREAM, 0);
if ( $socket < 0 ) { if ($socket < 0) {
Error('socket_create() failed: '.socket_strerror($socket)); Error('socket_create() failed: '.socket_strerror($socket));
return false; return false;
} }
$sockFile = ZM_PATH_SOCKS.'/zmcontrol-'.$this->{'Id'}.'.sock'; $sockFile = ZM_PATH_SOCKS.'/zmcontrol-'.$this->{'Id'}.'.sock';
if ( @socket_connect($socket, $sockFile) ) { if (@socket_connect($socket, $sockFile)) {
if ( !socket_write($socket, $optionString) ) { if (!socket_write($socket, $optionString)) {
Error('Can\'t write to control socket: '.socket_strerror(socket_last_error($socket))); Error('Can\'t write to control socket: '.socket_strerror(socket_last_error($socket)));
return false; return false;
} }
} else if ( $command != 'quit' ) { } else if ($command != 'quit') {
$command = ZM_PATH_BIN.'/zmcontrol.pl '.$command.' --id '.$this->{'Id'}; $command = ZM_PATH_BIN.'/zmcontrol.pl '.$command.' --id '.$this->{'Id'};
// Can't connect so use script // Can't connect so use script
$ctrlOutput = exec(escapeshellcmd($command)); $ctrlOutput = exec(escapeshellcmd($command));
} }
socket_close($socket); socket_close($socket);
} else if ( $this->ServerId() ) { } else if ($this->ServerId()) {
$Server = $this->Server(); $Server = $this->Server();
$url = $Server->UrlToApi().'/monitors/daemonControl/'.$this->{'Id'}.'/'.$command.'/zmcontrol.pl.json'; $url = $Server->UrlToApi().'/monitors/daemonControl/'.$this->{'Id'}.'/'.$command.'/zmcontrol.pl.json';
if ( ZM_OPT_USE_AUTH ) { if (ZM_OPT_USE_AUTH) {
if ( ZM_AUTH_RELAY == 'hashed' ) { if (ZM_AUTH_RELAY == 'hashed') {
$url .= '?auth='.generateAuthHash(ZM_AUTH_HASH_IPS); $url .= '?auth='.generateAuthHash(ZM_AUTH_HASH_IPS);
} else if ( ZM_AUTH_RELAY == 'plain' ) { } else if (ZM_AUTH_RELAY == 'plain') {
$url .= '?user='.$_SESSION['username']; $url .= '?user='.$_SESSION['username'];
$url .= '?pass='.$_SESSION['password']; $url .= '?pass='.$_SESSION['password'];
} else if ( ZM_AUTH_RELAY == 'none' ) { } else if (ZM_AUTH_RELAY == 'none') {
$url .= '?user='.$_SESSION['username']; $url .= '?user='.$_SESSION['username'];
} }
} }
Debug("sending command to $url"); Debug('sending command to '.$url);
$context = stream_context_create(); $context = stream_context_create();
try { try {
$result = file_get_contents($url, false, $context); $result = file_get_contents($url, false, $context);
if ( $result === FALSE ) { /* Handle error */ if ($result === FALSE) { /* Handle error */
Error("Error sending command using $url"); Error("Error sending command using $url");
return false; return false;
} }
} catch ( Exception $e ) { } catch (Exception $e) {
Error("Exception $e thrown trying to send command to $url"); Error("Exception $e thrown trying to send command to $url");
return false; return false;
} }
@ -569,18 +587,18 @@ class Monitor extends ZM_Object {
} // end function sendControlCommand($mid, $command) } // end function sendControlCommand($mid, $command)
function Groups($new='') { function Groups($new='') {
if ( $new != '' ) if ($new != '')
$this->Groups = $new; $this->Groups = $new;
if ( !property_exists($this, 'Groups') ) { if (!property_exists($this, 'Groups')) {
$this->Groups = Group::find(array('Id'=>$this->GroupIds())); $this->Groups = Group::find(array('Id'=>$this->GroupIds()));
} }
return $this->Groups; return $this->Groups;
} }
function connKey($new='') { function connKey($new='') {
if ( $new ) if ($new)
$this->connKey = $new; $this->connKey = $new;
if ( !isset($this->connKey) ) { if (!isset($this->connKey)) {
if ( !empty($GLOBALS['connkey']) ) { if (!empty($GLOBALS['connkey'])) {
$this->connKey = $GLOBALS['connkey']; $this->connKey = $GLOBALS['connkey'];
} else { } else {
$this->connKey = generateConnKey(); $this->connKey = generateConnKey();
@ -596,11 +614,21 @@ class Monitor extends ZM_Object {
function canView() { function canView() {
global $user; global $user;
return ( $user && ($user['Monitors'] != 'None') && ( !$this->{'Id'} || visibleMonitor($this->{'Id'}) )); if (!$user) {
# auth turned on and not logged in
return false;
}
if (!empty($user['MonitorIds']) ) {
# For the purposes of viewing, having specified monitors trumps the Monitor->canView setting.
if (in_array($this->{'Id'}, explode(',', $user['MonitorIds']))) {
return true;
}
}
return ($user['Monitors'] != 'None');
} }
function AlarmCommand($cmd) { function AlarmCommand($cmd) {
if ( (!defined('ZM_SERVER_ID')) or ( property_exists($this, 'ServerId') and (ZM_SERVER_ID==$this->{'ServerId'}) ) ) { if ((!defined('ZM_SERVER_ID')) or (property_exists($this, 'ServerId') and (ZM_SERVER_ID==$this->{'ServerId'}))) {
switch ($cmd) { switch ($cmd) {
case 'on' : $cmd = ' -a'; break; case 'on' : $cmd = ' -a'; break;
case 'off': $cmd = ' -c'; break; case 'off': $cmd = ' -c'; break;
@ -617,7 +645,7 @@ class Monitor extends ZM_Object {
return $output; return $output;
} }
if ( $this->ServerId() ) { if ($this->ServerId()) {
$Server = $this->Server(); $Server = $this->Server();
$url = $Server->UrlToApi().'/monitors/alarm/id:'.$this->{'Id'}.'/command:'.$cmd.'.json'; $url = $Server->UrlToApi().'/monitors/alarm/id:'.$this->{'Id'}.'/command:'.$cmd.'.json';
@ -629,15 +657,15 @@ class Monitor extends ZM_Object {
$context = stream_context_create(); $context = stream_context_create();
try { try {
$result = file_get_contents($url, false, $context); $result = file_get_contents($url, false, $context);
if ( $result === FALSE ) { /* Handle error */ if ($result === FALSE) { /* Handle error */
Error('Error sending command using '.$url); Error('Error sending command using '.$url);
return false; return false;
} }
Debug("Result $result"); Debug('Result '.$result);
$json = json_decode($result, true); $json = json_decode($result, true);
return $json['status']; return $json['status'];
} catch ( Exception $e ) { } catch (Exception $e) {
Error("Exception $e thrown trying to send command to $url"); Error("Exception $e thrown trying to send command to $url");
return false; return false;
} }
@ -647,10 +675,10 @@ class Monitor extends ZM_Object {
} }
function TriggerOn() { function TriggerOn() {
$output = $this->AlarmCommand('on'); $output = $this->AlarmCommand('on');
if ( $output and preg_match('/Alarmed event id: (\d+)$/', $output, $matches) ) { if ($output and preg_match('/Alarmed event id: (\d+)$/', $output, $matches)) {
return $matches[1]; return $matches[1];
} }
Warning("No event returned from TriggerOn"); Warning('No event returned from TriggerOn');
} }
function TriggerOff() { function TriggerOff() {
$output = $this->AlarmCommand('off'); $output = $this->AlarmCommand('off');

View File

@ -434,6 +434,11 @@ class ZM_Object {
$row = dbFetchOne("SELECT * FROM `$table` WHERE `Id`=?", NULL, array($this->Id())); $row = dbFetchOne("SELECT * FROM `$table` WHERE `Id`=?", NULL, array($this->Id()));
if ( !$row ) { if ( !$row ) {
Error("Unable to lock $class record for Id=".$this->Id()); Error("Unable to lock $class record for Id=".$this->Id());
} else {
// row may have been modified since initial load
foreach ($row as $k => $v) {
$this->{$k} = $v;
}
} }
} }
public function remove_from_cache() { public function remove_from_cache() {

View File

@ -30,22 +30,25 @@ class Server extends ZM_Object {
return ZM_Object::_find_one(get_class(), $parameters, $options); return ZM_Object::_find_one(get_class(), $parameters, $options);
} }
public function Hostname( $new = null ) { public function Hostname($new = null) {
if ( $new != null ) if ($new != null)
$this->{'Hostname'} = $new; $this->{'Hostname'} = $new;
if ( isset( $this->{'Hostname'}) and ( $this->{'Hostname'} != '' ) ) { if (isset( $this->{'Hostname'}) and ($this->{'Hostname'} != '')) {
return $this->{'Hostname'}; return $this->{'Hostname'};
} else if ( $this->Id() ) { } else if ( $this->Id() ) {
return $this->{'Name'}; return $this->{'Name'};
} }
# This theoretically will match ipv6 addresses as well if (isset($_SERVER['HTTP_HOST'])) {
if ( preg_match( '/^(\[[[:xdigit:]:]+\]|[^:]+)(:[[:digit:]]+)?$/', $_SERVER['HTTP_HOST'], $matches ) ) { # This theoretically will match ipv6 addresses as well
return $matches[1]; if ( preg_match( '/^(\[[[:xdigit:]:]+\]|[^:]+)(:[[:digit:]]+)?$/', $_SERVER['HTTP_HOST'], $matches ) ) {
} return $matches[1];
}
$result = explode(':', $_SERVER['HTTP_HOST']); $result = explode(':', $_SERVER['HTTP_HOST']);
return $result[0]; return $result[0];
}
return '';
} }
public function Protocol( $new = null ) { public function Protocol( $new = null ) {

View File

@ -29,6 +29,7 @@ class Snapshot extends ZM_Object {
public function delete() { public function delete() {
if ( property_exists($this, 'Id') ) { if ( property_exists($this, 'Id') ) {
dbQuery('DELETE FROM `Snapshot_Events` WHERE `SnapshotId`=?', array($this->{'Id'})); dbQuery('DELETE FROM `Snapshot_Events` WHERE `SnapshotId`=?', array($this->{'Id'}));
dbQuery('DELETE FROM `Snapshots` WHERE `Id`=?', array($this->{'Id'}));
} }
} }

View File

@ -20,40 +20,39 @@
global $error_message; global $error_message;
// Event scope actions, view permissions only required // Event scope actions, view permissions only required
if ( !canView('Events') ) { if (!canView('Events')) {
$error_message = 'You do not have permission to view Events.'; $error_message = 'You do not have permission to view Events.';
ZM\Warning($error_message); ZM\Warning($error_message);
return; return;
} }
if ( isset($_REQUEST['object']) and ( $_REQUEST['object'] == 'filter' ) ) { if (isset($_REQUEST['object']) and ($_REQUEST['object'] == 'filter')) {
if ( $action == 'addterm' ) { if ($action == 'addterm') {
$_REQUEST['filter'] = addFilterTerm($_REQUEST['filter'], $_REQUEST['line']); $_REQUEST['filter'] = addFilterTerm($_REQUEST['filter'], $_REQUEST['line']);
} elseif ( $action == 'delterm' ) { } else if ($action == 'delterm') {
$_REQUEST['filter'] = delFilterTerm($_REQUEST['filter'], $_REQUEST['line']); $_REQUEST['filter'] = delFilterTerm($_REQUEST['filter'], $_REQUEST['line']);
} else if ( canEdit('Events') ) { } else if (canEdit('Events')) {
require_once('includes/Filter.php'); require_once('includes/Filter.php');
$filter = new ZM\Filter($_REQUEST['Id']); $filter = new ZM\Filter($_REQUEST['Id']);
if ( $action == 'delete' ) { if ($action == 'delete') {
if ( !empty($_REQUEST['Id']) ) { if (!empty($_REQUEST['Id'])) {
if ( $filter->Background() ) { if ($filter->Background()) {
$filter->control('stop'); $filter->control('stop');
} }
$filter->delete(); $filter->delete();
} else { } else {
ZM\Error('No filter id passed when deleting'); ZM\Error('No filter id passed when deleting');
} }
} else if ( ( $action == 'Save' ) or ( $action == 'SaveAs' ) or ( $action == 'execute' ) ) { } else if (( $action == 'Save' ) or ( $action == 'SaveAs' ) or ( $action == 'execute' )) {
$_REQUEST['filter']['Query']['sort_field'] = validStr($_REQUEST['filter']['Query']['sort_field']); $_REQUEST['filter']['Query']['sort_field'] = validStr($_REQUEST['filter']['Query']['sort_field']);
$_REQUEST['filter']['Query']['sort_asc'] = validStr($_REQUEST['filter']['Query']['sort_asc']); $_REQUEST['filter']['Query']['sort_asc'] = validStr($_REQUEST['filter']['Query']['sort_asc']);
$_REQUEST['filter']['Query']['limit'] = validInt($_REQUEST['filter']['Query']['limit']); $_REQUEST['filter']['Query']['limit'] = validInt($_REQUEST['filter']['Query']['limit']);
$_REQUEST['filter']['AutoCopy'] = empty($_REQUEST['filter']['AutoCopy']) ? 0 : 1; $_REQUEST['filter']['AutoCopy'] = empty($_REQUEST['filter']['AutoCopy']) ? 0 : 1;
$_REQUEST['filter']['AutoCopyTo'] = empty($_REQUEST['filter']['AutoCopyTo']) ? 0 : $_REQUEST['filter']['AutoCopyTo'];
$_REQUEST['filter']['AutoMove'] = empty($_REQUEST['filter']['AutoMove']) ? 0 : 1; $_REQUEST['filter']['AutoMove'] = empty($_REQUEST['filter']['AutoMove']) ? 0 : 1;
$_REQUEST['filter']['AutoMoveTo'] = empty($_REQUEST['filter']['AutoMoveTo']) ? 0 : $_REQUEST['filter']['AutoMoveTo'];
$_REQUEST['filter']['AutoArchive'] = empty($_REQUEST['filter']['AutoArchive']) ? 0 : 1; $_REQUEST['filter']['AutoArchive'] = empty($_REQUEST['filter']['AutoArchive']) ? 0 : 1;
$_REQUEST['filter']['AutoVideo'] = empty($_REQUEST['filter']['AutoVideo']) ? 0 : 1; $_REQUEST['filter']['AutoVideo'] = empty($_REQUEST['filter']['AutoVideo']) ? 0 : 1;
$_REQUEST['filter']['AutoUpload'] = empty($_REQUEST['filter']['AutoUpload']) ? 0 : 1; $_REQUEST['filter']['AutoUpload'] = empty($_REQUEST['filter']['AutoUpload']) ? 0 : 1;
@ -65,44 +64,39 @@ if ( isset($_REQUEST['object']) and ( $_REQUEST['object'] == 'filter' ) ) {
$_REQUEST['filter']['Background'] = empty($_REQUEST['filter']['Background']) ? 0 : 1; $_REQUEST['filter']['Background'] = empty($_REQUEST['filter']['Background']) ? 0 : 1;
$_REQUEST['filter']['Concurrent'] = empty($_REQUEST['filter']['Concurrent']) ? 0 : 1; $_REQUEST['filter']['Concurrent'] = empty($_REQUEST['filter']['Concurrent']) ? 0 : 1;
$changes = $filter->changes($_REQUEST['filter']); $changes = $filter->changes($_REQUEST['filter']);
ZM\Debug('Changes: ' . print_r($changes,true)); ZM\Debug('Changes: ' . print_r($changes, true));
if ($filter->Id() and ($action == 'Save')) { if (count($changes)) {
if ($filter->Background()) $filter->control('stop'); if ($filter->Id() and ($action == 'Save') and $filter->Background()) {
if (!$filter->save($changes)) { $filter->control('stop');
$error_message = $filter->get_last_error(); } else if ($action == 'execute') {
return; # If there are changes use a temp filter to do the execute
} $filter->Name('_TempFilter'.time());
} else { $filter->Id(null);
if ( $action == 'execute' ) { } else if ($action == 'SaveAs') {
if ( count($changes) ) { $filter->Id(null);
$filter->Name('_TempFilter'.time()); }
$filter->Id(null); if (!$filter->save($changes)) {
} $error_message = $filter->get_last_error();
} else if ( $action == 'SaveAs' ) { return;
$filter->Id(null); }
} // We update the request id so that the newly saved filter is auto-selected
if (!$filter->save($changes)) { $_REQUEST['Id'] = $filter->Id();
$error_message = $filter->get_last_error(); } # end if changes
return;
}
// We update the request id so that the newly saved filter is auto-selected if ($action == 'execute') {
$_REQUEST['Id'] = $filter->Id();
}
if ( $action == 'execute' ) {
$filter->execute(); $filter->execute();
if ( count($changes) ) if (count($changes)) {
$filter->delete(); $filter->delete();
$filter->Id(null);
$view = 'events'; }
} else if ( $filter->Background() ) { } else if ($filter->Background()) {
$filter->control('start'); $filter->control('start');
} }
global $redirect;
$redirect = '?view=filter'.$filter->querystring('filter', '&'); $redirect = '?view=filter'.$filter->querystring('filter', '&');
} else if ( $action == 'control' ) { } else if ($action == 'control') {
if ( $_REQUEST['command'] == 'start' if ( $_REQUEST['command'] == 'start'
or $_REQUEST['command'] == 'stop' or $_REQUEST['command'] == 'stop'
or $_REQUEST['command'] == 'restart' or $_REQUEST['command'] == 'restart'
@ -114,5 +108,4 @@ if ( isset($_REQUEST['object']) and ( $_REQUEST['object'] == 'filter' ) ) {
} // end if save or execute } // end if save or execute
} // end if canEdit(Events) } // end if canEdit(Events)
} // end if object == filter } // end if object == filter
?> ?>

View File

@ -205,7 +205,7 @@ if ( $action == 'save' ) {
} // end if changes in width or height } // end if changes in width or height
} else { } else {
global $error_message; global $error_message;
$error_message = dbError(); $error_message = dbError('unknown');
} // end if successful save } // end if successful save
$restart = true; $restart = true;
} else { // new monitor } else { // new monitor

View File

@ -20,18 +20,18 @@
// Monitor control actions, require a monitor id and control view permissions for that monitor // Monitor control actions, require a monitor id and control view permissions for that monitor
if ( empty($_REQUEST['mid']) ) { if (empty($_REQUEST['mid'])) {
ZM\Warning('Settings requires a monitor id'); ZM\Warning('Settings requires a monitor id');
return; return;
} }
if ( ! canView('Control', $_REQUEST['mid']) ) { if (!canView('Control', $_REQUEST['mid'])) {
ZM\Warning('Settings requires the Control permission'); ZM\Warning('Settings requires the Control permission');
return; return;
} }
require_once('includes/Monitor.php'); require_once('includes/Monitor.php');
$mid = validInt($_REQUEST['mid']); $mid = validInt($_REQUEST['mid']);
if ( $action == 'settings' ) { if ($action == 'settings') {
$args = ' -m ' . escapeshellarg($mid); $args = ' -m ' . escapeshellarg($mid);
$args .= ' -B' . escapeshellarg($_REQUEST['newBrightness']); $args .= ' -B' . escapeshellarg($_REQUEST['newBrightness']);
$args .= ' -C' . escapeshellarg($_REQUEST['newContrast']); $args .= ' -C' . escapeshellarg($_REQUEST['newContrast']);
@ -45,5 +45,7 @@ if ( $action == 'settings' ) {
dbQuery( dbQuery(
'UPDATE Monitors SET Brightness = ?, Contrast = ?, Hue = ?, Colour = ? WHERE Id = ?', 'UPDATE Monitors SET Brightness = ?, Contrast = ?, Hue = ?, Colour = ? WHERE Id = ?',
array($brightness, $contrast, $hue, $colour, $mid)); array($brightness, $contrast, $hue, $colour, $mid));
global $redirect;
$redirect = '?view=watch&mid='.$mid;
} }
?> ?>

View File

@ -47,9 +47,12 @@ if ( $action == 'create' ) {
$monitor = new ZM\Monitor($monitor_id); $monitor = new ZM\Monitor($monitor_id);
$monitor->TriggerOff(); $monitor->TriggerOff();
} }
$dbConn->beginTransaction();
foreach ( $snapshot->Events() as $event ) { foreach ( $snapshot->Events() as $event ) {
$event->lock();
$event->save(array('Archived'=>1)); $event->save(array('Archived'=>1));
} }
$dbConn->commit();
$redirect = '?view=snapshot&id='.$snapshot->Id(); $redirect = '?view=snapshot&id='.$snapshot->Id();
return; return;
} }

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