diff --git a/.github/workflows/create-packages.yml b/.github/workflows/create-packages.yml index 6a76da322..93279cc57 100644 --- a/.github/workflows/create-packages.yml +++ b/.github/workflows/create-packages.yml @@ -21,6 +21,7 @@ jobs: steps: - uses: actions/checkout@v2 with: + fetch-depth: '0' submodules: recursive - name: Run packpack env: @@ -29,3 +30,13 @@ jobs: DIST: ${{ matrix.os_dist.dist }} DOCKER_REPO: iconzm/packpack run: utils/packpack/startpackpack.sh + + - name: Publish + uses: easingthemes/ssh-deploy@main + env: + SSH_PRIVATE_KEY: ${{ secrets.ZMREPO_SSH_KEY }} + ARGS: "-rltgoDzvO" + SOURCE: build/ + REMOTE_HOST: ${{ secrets.ZMREPO_HOST }} + REMOTE_USER: ${{ secrets.ZMREPO_SSH_USER }} + TARGET: debian/master/mini-dinstall/incoming/ diff --git a/README.md b/README.md index da96b3e60..bcb53ba8a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ ZoneMinder ========== -[![Build Status](https://travis-ci.org/ZoneMinder/zoneminder.png)](https://travis-ci.org/ZoneMinder/zoneminder) [![Bounty Source](https://api.bountysource.com/badge/team?team_id=204&style=bounties_received)](https://www.bountysource.com/teams/zoneminder/issues?utm_source=ZoneMinder&utm_medium=shield&utm_campaign=bounties_received) [![Join Slack](https://github.com/ozonesecurity/ozonebase/blob/master/img/slacksm.png?raw=true)](https://join.slack.com/t/zoneminder-chat/shared_invite/enQtNTU0NDkxMDM5NDQwLTdhZmQ5Y2M2NWQyN2JkYTBiN2ZkMzIzZGQ0MDliMTRmM2FjZWRlYzUwYTQ2MjMwMTVjMzQ1NjYxOTdmMjE2MTE) @@ -25,7 +24,7 @@ https://github.com/ZoneMinder/zmdockerfiles This is the recommended method to install ZoneMinder onto your system. ZoneMinder packages are maintained for the following distros: -- Ubuntu via [Iconnor's PPA](https://launchpad.net/~iconnor) +- Ubuntu via [Isaac Connor's PPA](https://launchpad.net/~iconnor) - Debian from their [default repository](https://packages.debian.org/search?searchon=names&keywords=zoneminder) - RHEL/CentOS and clones via [RPM Fusion](http://rpmfusion.org) - Fedora via [RPM Fusion](http://rpmfusion.org) diff --git a/dep/RtspServer b/dep/RtspServer index cd7fd49be..eab328514 160000 --- a/dep/RtspServer +++ b/dep/RtspServer @@ -1 +1 @@ -Subproject commit cd7fd49becad6010a1b8466bfebbd93999a39878 +Subproject commit eab32851421ffe54fec0229c3efc44c642bc8d46 diff --git a/distros/ubuntu2004/control b/distros/ubuntu2004/control index 37c0e5e51..54ca58af1 100644 --- a/distros/ubuntu2004/control +++ b/distros/ubuntu2004/control @@ -42,7 +42,8 @@ Depends: ${shlibs:Depends}, ${misc:Depends}, ${perl:Depends} ,libswscale5|libswscale4 ,libswresample3|libswresample2 ,ffmpeg - ,libdate-manip-perl, libmime-lite-perl, libmime-tools-perl + ,libcurl4 + ,libdatetime-perl, libdate-manip-perl, libmime-lite-perl, libmime-tools-perl ,libdbd-mysql-perl ,libphp-serialization-perl ,libmodule-load-conditional-perl diff --git a/docs/installationguide/debian.rst b/docs/installationguide/debian.rst index b92ff267a..a5162d185 100644 --- a/docs/installationguide/debian.rst +++ b/docs/installationguide/debian.rst @@ -35,7 +35,7 @@ Run the following commands. 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``. +By default MariaDB uses `unix socket authentication`_, so no root user password is required (root MariaDB user access only available to local root Linux user). If you wish, you can set a root MariaDB password (and apply other security tweaks) by running `mariadb-secure-installation`_. **Step 3:** Setup permissions for zm.conf @@ -337,3 +337,6 @@ Reload Apache to enable your changes and then start ZoneMinder. sudo systemctl start zoneminder You are now ready to go with ZoneMinder. Open a browser and type either ``localhost/zm`` one the local machine or ``{IP-OF-ZM-SERVER}/zm`` if you connect from a remote computer. + +.. _unix socket authentication: https://mariadb.com/kb/en/authentication-plugin-unix-socket/ +.. _mariadb-secure-installation: https://mariadb.com/kb/en/mysql_secure_installation/ diff --git a/scripts/zmupdate.pl.in b/scripts/zmupdate.pl.in index d096c70b5..f62b14316 100644 --- a/scripts/zmupdate.pl.in +++ b/scripts/zmupdate.pl.in @@ -444,11 +444,6 @@ if ( $version ) { print( "\nUpgrading database to version ".ZM_VERSION."\n" ); -# Update config first of all - migratePaths(); - ZoneMinder::Config::loadConfigFromDB(); - ZoneMinder::Config::saveConfigToDB(); - my $cascade = undef; if ( $cascade || $version eq "1.19.0" ) { # Patch the database diff --git a/src/zm_comms.h b/src/zm_comms.h index 7e7329d5d..ac772ae26 100644 --- a/src/zm_comms.h +++ b/src/zm_comms.h @@ -245,6 +245,7 @@ class Socket : public CommsBase { } virtual ssize_t recv(std::string &msg) const { + msg.reserve(ZM_NETWORK_BUFSIZ); std::vector buffer(msg.capacity()); ssize_t nBytes; if ((nBytes = ::recv(mSd, buffer.data(), buffer.size(), 0)) < 0) { diff --git a/src/zm_db.cpp b/src/zm_db.cpp index f0b13d538..cc0797c12 100644 --- a/src/zm_db.cpp +++ b/src/zm_db.cpp @@ -251,7 +251,7 @@ void zmDbQueue::process() { mCondition.wait(lock); } while (!mQueue.empty()) { - if (mQueue.size() > 20) { + if (mQueue.size() > 30) { Logger *log = Logger::fetch(); Logger::Level db_level = log->databaseLevel(); log->databaseLevel(Logger::NOLOG); diff --git a/src/zm_event.cpp b/src/zm_event.cpp index e14c1a343..3ef7ea887 100644 --- a/src/zm_event.cpp +++ b/src/zm_event.cpp @@ -301,9 +301,9 @@ void Event::updateNotes(const StringSetMap &newNoteSetMap) { } // end if update } // void Event::updateNotes(const StringSetMap &newNoteSetMap) -void Event::AddPacket(const std::shared_ptr&packet) { +void Event::AddPacket(ZMLockedPacket *packetlock) { std::unique_lock lck(packet_queue_mutex); - packet_queue.push(packet); + packet_queue.push(packetlock); packet_queue_condition.notify_one(); } @@ -423,7 +423,7 @@ void Event::AddFrame(Image *image, // If this is the first frame, we should add a thumbnail to the event directory if ((frames == 1) || (score > max_score)) { write_to_db = true; // web ui might show this as thumbnail, so db needs to know about it. - Debug(1, "Writing snapshot"); + Debug(1, "Writing snapshot to %s", snapshot_file.c_str()); WriteFrameImage(image, timestamp, snapshot_file.c_str()); } else { Debug(1, "Not Writing snapshot because frames %d score %d > max %d", frames, score, max_score); @@ -435,17 +435,19 @@ void Event::AddFrame(Image *image, if (!alarm_frame_written) { write_to_db = true; // OD processing will need it, so the db needs to know about it alarm_frame_written = true; - Debug(1, "Writing alarm image"); - WriteFrameImage(image, timestamp, alarm_file.c_str()); + Debug(1, "Writing alarm image to %s", alarm_file.c_str()); + if (!WriteFrameImage(image, timestamp, alarm_file.c_str())) { + Error("Failed to write alarm frame image to %s", alarm_file.c_str()); + } } else { Debug(3, "Not Writing alarm image because alarm frame already written"); } if (alarm_image and (save_jpegs & 2)) { std::string event_file = stringtf(staticConfig.analyse_file_format.c_str(), path.c_str(), frames); - Debug(1, "Writing analysis frame %d", frames); + Debug(1, "Writing analysis frame %d to %s", frames, event_file.c_str()); if (!WriteFrameImage(alarm_image, timestamp, event_file.c_str(), true)) { - Error("Failed to write analysis frame image"); + Error("Failed to write analysis frame image to %s", event_file.c_str()); } } } // end if is an alarm frame @@ -682,7 +684,9 @@ void Event::Run() { while (true) { if (!packet_queue.empty()) { Debug(1, "adding packet"); - this->AddPacket_(packet_queue.front()); + const ZMLockedPacket * packet_lock = packet_queue.front(); + this->AddPacket_(packet_lock->packet_); + delete packet_lock; packet_queue.pop(); } else { if (terminate_ or zm_terminate) { diff --git a/src/zm_event.h b/src/zm_event.h index 31a23fad4..2029c062a 100644 --- a/src/zm_event.h +++ b/src/zm_event.h @@ -105,7 +105,7 @@ class Event { void createNotes(std::string ¬es); - std::queue> packet_queue; + std::queue packet_queue; std::mutex packet_queue_mutex; std::condition_variable packet_queue_condition; @@ -134,7 +134,7 @@ class Event { SystemTimePoint EndTime() const { return end_time; } TimePoint::duration Duration() const { return end_time - start_time; }; - void AddPacket(const std::shared_ptr &p); + void AddPacket(ZMLockedPacket *); void AddPacket_(const std::shared_ptr &p); bool WritePacket(const std::shared_ptr &p); bool SendFrameImage(const Image *image, bool alarm_frame=false); diff --git a/src/zm_ffmpeg.cpp b/src/zm_ffmpeg.cpp index 3986b38b8..4fe14051f 100644 --- a/src/zm_ffmpeg.cpp +++ b/src/zm_ffmpeg.cpp @@ -257,8 +257,8 @@ void zm_dump_stream_format(AVFormatContext *ic, int i, int index, int is_output) Debug(1, "ids [0x%x]", st->id); if (lang) Debug(1, "language (%s)", lang->value); - Debug(1, "frames:%d, frame_size:%d stream timebase: %d/%d", - st->codec_info_nb_frames, codec->frame_size, + Debug(1, "frame_size:%d stream timebase: %d/%d", + codec->frame_size, st->time_base.num, st->time_base.den ); diff --git a/src/zm_image.cpp b/src/zm_image.cpp index a4aad8b61..7260791a5 100644 --- a/src/zm_image.cpp +++ b/src/zm_image.cpp @@ -24,6 +24,7 @@ #include "zm_utils.h" #include #include +#include #include #include @@ -80,6 +81,8 @@ imgbufcpy_fptr_t fptr_imgbufcpy; /* Font */ static ZmFont font; +std::mutex jpeg_mutex; + void Image::update_function_pointers() { /* Because many loops are unrolled and work on 16 colours/time or 4 pixels/time, we have to meet requirements */ if ( pixels % 16 || pixels % 12 ) { @@ -1083,6 +1086,9 @@ bool Image::WriteJpeg(const std::string &filename, const int &quality_override, SystemTimePoint timestamp, bool on_blocking_abort) const { + // jpeg libs are not thread safe + std::unique_lock lck(jpeg_mutex); + if (config.colour_jpeg_files && (colours == ZM_COLOUR_GRAY8)) { Image temp_image(*this); temp_image.Colourise(ZM_COLOUR_RGB24, ZM_SUBPIX_ORDER_RGB); @@ -1366,6 +1372,8 @@ bool Image::EncodeJpeg(JOCTET *outbuffer, int *outbuffer_size, int quality_overr return temp_image.EncodeJpeg(outbuffer, outbuffer_size, quality_override); } + std::unique_lock lck(jpeg_mutex); + int quality = quality_override ? quality_override : config.jpeg_stream_quality; struct jpeg_compress_struct *cinfo = encodejpg_ccinfo[quality]; diff --git a/src/zm_monitor.cpp b/src/zm_monitor.cpp index 558a30380..56b3dfc04 100644 --- a/src/zm_monitor.cpp +++ b/src/zm_monitor.cpp @@ -2321,34 +2321,37 @@ bool Monitor::Analyse() { shared_data->state = state = IDLE; } // end if ( trigger_data->trigger_state != TRIGGER_OFF ) - if (event) event->AddPacket(snap); + packetqueue.clearPackets(snap); + + if (snap->codec_type == AVMEDIA_TYPE_VIDEO) { + // Only do these if it's a video packet. + shared_data->last_read_index = snap->image_index; + analysis_image_count++; + } + + if (event) { + event->AddPacket(packet_lock); + } else { + // In the case where people have pre-alarm frames, the web ui will generate the frame images + // from the mp4. So no one will notice anyways. + if (snap->image and (videowriter == PASSTHROUGH)) { + if (!savejpegs) { + Debug(1, "Deleting image data for %d", snap->image_index); + // Don't need raw images anymore + delete snap->image; + snap->image = nullptr; + } + if (snap->analysis_image and !(savejpegs & 2)) { + Debug(1, "Deleting analysis image data for %d", snap->image_index); + delete snap->analysis_image; + snap->analysis_image = nullptr; + } + } + delete packet_lock; + } } // end scope for event_lock - // In the case where people have pre-alarm frames, the web ui will generate the frame images - // from the mp4. So no one will notice anyways. - if (snap->image and (videowriter == PASSTHROUGH)) { - if (!savejpegs) { - Debug(1, "Deleting image data for %d", snap->image_index); - // Don't need raw images anymore - delete snap->image; - snap->image = nullptr; - } - if (snap->analysis_image and !(savejpegs & 2)) { - Debug(1, "Deleting analysis image data for %d", snap->image_index); - delete snap->analysis_image; - snap->analysis_image = nullptr; - } - } - - packetqueue.clearPackets(snap); - - if (snap->codec_type == AVMEDIA_TYPE_VIDEO) { - // Only do these if it's a video packet. - shared_data->last_read_index = snap->image_index; - analysis_image_count++; - } packetqueue.increment_it(analysis_it); - delete packet_lock; //packetqueue.unlock(packet_lock); shared_data->last_read_time = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); @@ -2921,16 +2924,16 @@ Event * Monitor::openEvent( // Write out starting packets, do not modify packetqueue it will garbage collect itself while (starting_packet and ((*start_it) != *analysis_it)) { - event->AddPacket(starting_packet); + event->AddPacket(starting_packet_lock); // Have added the packet, don't want to unlock it until we have locked the next packetqueue.increment_it(start_it); if ((*start_it) == *analysis_it) { - if (starting_packet_lock) delete starting_packet_lock; + //if (starting_packet_lock) delete starting_packet_lock; break; } ZMLockedPacket *lp = packetqueue.get_packet(start_it); - delete starting_packet_lock; + //delete starting_packet_lock; if (!lp) return nullptr; // only on terminate FIXME starting_packet_lock = lp; starting_packet = lp->packet_; diff --git a/src/zm_remote_camera_rtsp.cpp b/src/zm_remote_camera_rtsp.cpp index 8cdecdf94..ffb4a061d 100644 --- a/src/zm_remote_camera_rtsp.cpp +++ b/src/zm_remote_camera_rtsp.cpp @@ -22,6 +22,7 @@ #include "zm_config.h" #include "zm_monitor.h" #include "zm_packet.h" +#include "zm_signal.h" RemoteCameraRtsp::RemoteCameraRtsp( const Monitor *monitor, @@ -126,8 +127,8 @@ int RemoteCameraRtsp::Disconnect() { int RemoteCameraRtsp::PrimeCapture() { Debug(2, "Waiting for sources"); - for (int i = 0; i < 100 && !rtspThread->hasSources(); i++) { - std::this_thread::sleep_for(Microseconds(100)); + for (int i = 100; i && !zm_terminate && !rtspThread->hasSources(); i--) { + std::this_thread::sleep_for(Microseconds(10000)); } if (!rtspThread->hasSources()) { @@ -168,8 +169,10 @@ int RemoteCameraRtsp::PrimeCapture() { } } // end foreach stream - if ( mVideoStreamId == -1 ) - Fatal("Unable to locate video stream"); + if ( mVideoStreamId == -1 ) { + Error("Unable to locate video stream"); + return -1; + } if ( mAudioStreamId == -1 ) Debug(3, "Unable to locate audio stream"); @@ -179,17 +182,22 @@ int RemoteCameraRtsp::PrimeCapture() { // Find the decoder for the video stream AVCodec *codec = avcodec_find_decoder(mVideoCodecContext->codec_id); - if ( codec == nullptr ) - Panic("Unable to locate codec %d decoder", mVideoCodecContext->codec_id); + if ( codec == nullptr ) { + Error("Unable to locate codec %d decoder", mVideoCodecContext->codec_id); + return -1; + } // Open codec - if ( avcodec_open2(mVideoCodecContext, codec, nullptr) < 0 ) - Panic("Can't open codec"); + if ( avcodec_open2(mVideoCodecContext, codec, nullptr) < 0 ) { + Error("Can't open codec"); + return -1; + } int pSize = av_image_get_buffer_size(imagePixFormat, width, height, 1); if ( (unsigned int)pSize != imagesize ) { - Fatal("Image size mismatch. Required: %d Available: %llu", pSize, imagesize); + Error("Image size mismatch. Required: %d Available: %llu", pSize, imagesize); + return -1; } return 1; @@ -208,18 +216,13 @@ int RemoteCameraRtsp::PreCapture() { int RemoteCameraRtsp::Capture(std::shared_ptr &zm_packet) { int frameComplete = false; AVPacket *packet = &zm_packet->packet; - if ( !zm_packet->image ) { - Debug(1, "Allocating image %dx%d %d colours %d", width, height, colours, subpixelorder); - zm_packet->image = new Image(width, height, colours, subpixelorder); - } - while (!frameComplete) { buffer.clear(); - if (!rtspThread || rtspThread->IsStopped()) + if (!rtspThread || rtspThread->IsStopped() || zm_terminate) return -1; - if ( rtspThread->getFrame(buffer) ) { + if (rtspThread->getFrame(buffer)) { Debug(3, "Read frame %d bytes", buffer.size()); Hexdump(4, buffer.head(), 16); @@ -254,36 +257,20 @@ int RemoteCameraRtsp::Capture(std::shared_ptr &zm_packet) { //while ( (!frameComplete) && (buffer.size() > 0) ) { if ( buffer.size() > 0 ) { - packet->data = buffer.head(); + packet->data = (uint8_t*)av_malloc(buffer.size()); + memcpy(packet->data, buffer.head(), buffer.size()); + //packet->data = buffer.head(); packet->size = buffer.size(); bytes += packet->size; + buffer -= packet->size; struct timeval now; - gettimeofday(&now, NULL); + gettimeofday(&now, nullptr); packet->pts = packet->dts = now.tv_sec*1000000+now.tv_usec; - - int bytes_consumed = zm_packet->decode(mVideoCodecContext); - if ( bytes_consumed < 0 ) { - Error("Error while decoding frame %d", frameCount); - //Hexdump(Logger::ERROR, buffer.head(), buffer.size()>256?256:buffer.size()); - } - buffer -= packet->size; - if ( bytes_consumed ) { - zm_dump_video_frame(zm_packet->in_frame, "remote_rtsp_decode"); - if (!mVideoStream->codecpar->width) { - zm_dump_codec(mVideoCodecContext); - zm_dump_codecpar(mVideoStream->codecpar); - mVideoStream->codecpar->width = zm_packet->in_frame->width; - mVideoStream->codecpar->height = zm_packet->in_frame->height; - zm_dump_codecpar(mVideoStream->codecpar); - } - zm_packet->codec_type = mVideoCodecContext->codec_type; - zm_packet->stream = mVideoStream; - frameComplete = true; - Debug(2, "Frame: %d - %d/%d", frameCount, bytes_consumed, buffer.size()); - packet->data = nullptr; - packet->size = 0; - } + zm_packet->codec_type = mVideoCodecContext->codec_type; + zm_packet->stream = mVideoStream; + frameComplete = true; + Debug(2, "Frame: %d - %d/%d", frameCount, packet->size, buffer.size()); } } /* getFrame() */ } // end while true diff --git a/src/zm_rtp_ctrl.cpp b/src/zm_rtp_ctrl.cpp index 25d34f0ff..a82ff2b2a 100644 --- a/src/zm_rtp_ctrl.cpp +++ b/src/zm_rtp_ctrl.cpp @@ -277,7 +277,7 @@ void RtpCtrlThread::Run() { TimePoint last_receive = std::chrono::steady_clock::now(); bool timeout = false; // used as a flag that we had a timeout, and then sent an RR to see if we wake back up. Real timeout will happen when this is true. - while (!mTerminate && select.wait() >= 0) { + while (!mTerminate && (select.wait() >= 0)) { TimePoint now = std::chrono::steady_clock::now(); zm::Select::CommsList readable = select.getReadable(); if ( readable.size() == 0 ) { diff --git a/src/zm_rtp_source.cpp b/src/zm_rtp_source.cpp index 1862c1886..56ca2cf0d 100644 --- a/src/zm_rtp_source.cpp +++ b/src/zm_rtp_source.cpp @@ -45,8 +45,10 @@ RtpSource::RtpSource( mFrame(65536), mFrameCount(0), mFrameGood(true), + prevM(false), mFrameReady(false), - mFrameProcessed(false) + mFrameProcessed(false), + mTerminate(false) { char hostname[256] = ""; gethostname(hostname, sizeof(hostname)); diff --git a/src/zm_rtp_source.h b/src/zm_rtp_source.h index a39e8225f..71be9af2c 100644 --- a/src/zm_rtp_source.h +++ b/src/zm_rtp_source.h @@ -91,8 +91,6 @@ private: bool mFrameGood; bool prevM; - bool mTerminate; - bool mFrameReady; std::condition_variable mFrameReadyCv; std::mutex mFrameReadyMutex; @@ -100,6 +98,7 @@ private: bool mFrameProcessed; std::condition_variable mFrameProcessedCv; std::mutex mFrameProcessedMutex; + bool mTerminate; private: void init(uint16_t seq); diff --git a/src/zm_rtsp_server.cpp b/src/zm_rtsp_server.cpp index ea6f4e2b7..3da273e00 100644 --- a/src/zm_rtsp_server.cpp +++ b/src/zm_rtsp_server.cpp @@ -298,6 +298,14 @@ int main(int argc, char *argv[]) { session->GetMediaSessionId(), xop::channel_1, audioFifoPath); audioSource->setFrequency(monitor->GetAudioFrequency()); audioSource->setChannels(monitor->GetAudioChannels()); + } else if (std::string::npos != audioFifoPath.find("pcm_alaw")) { + Debug(1, "Adding G711A source at %dHz %d channels", + monitor->GetAudioFrequency(), monitor->GetAudioChannels()); + session->AddSource(xop::channel_1, xop::G711ASource::CreateNew()); + audioSource = new ADTS_ZoneMinderFifoSource(rtspServer, + session->GetMediaSessionId(), xop::channel_1, audioFifoPath); + audioSource->setFrequency(monitor->GetAudioFrequency()); + audioSource->setChannels(monitor->GetAudioChannels()); } else { Warning("Unknown format in %s", audioFifoPath.c_str()); } diff --git a/utils/packpack/startpackpack.sh b/utils/packpack/startpackpack.sh index 2ea949078..9737c794a 100755 --- a/utils/packpack/startpackpack.sh +++ b/utils/packpack/startpackpack.sh @@ -118,7 +118,7 @@ commonprep () { fi fi - RTSPVER="cd7fd49becad6010a1b8466bfebbd93999a39878" + RTSPVER="eab32851421ffe54fec0229c3efc44c642bc8d46" if [ -e "build/RtspServer-${RTSPVER}.tar.gz" ]; then echo "Found existing RtspServer ${RTSPVER} tarball..." else diff --git a/web/ajax/events.php b/web/ajax/events.php index 4db6b99cc..c95b404c4 100644 --- a/web/ajax/events.php +++ b/web/ajax/events.php @@ -187,6 +187,9 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim $col_str = 'E.*, M.Name AS Monitor'; $sql = 'SELECT ' .$col_str. ' FROM `Events` AS E INNER JOIN Monitors AS M ON E.MonitorId = M.Id'.$where.($sort?' ORDER BY '.$sort.' '.$order:''); + if ($filter->limit() and !count($filter->pre_sql_conditions()) and !count($filter->post_sql_conditions())) { + $sql .= ' LIMIT '.$filter->limit(); + } $storage_areas = ZM\Storage::find(); $StorageById = array(); @@ -213,6 +216,12 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim $unfiltered_rows[] = $row; } # end foreach row + # Filter limits come before pagination limits. + if ($filter->limit() and ($filter->limit() > count($unfiltered_rows))) { + ZM\Debug("Filtering rows due to filter->limit " . count($unfiltered_rows)." limit: ".$filter->limit()); + $unfiltered_rows = array_slice($unfiltered_rows, 0, $filter->limit()); + } + ZM\Debug('Have ' . count($unfiltered_rows) . ' events matching base filter.'); $filtered_rows = null; @@ -251,8 +260,10 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim $filtered_rows = $unfiltered_rows; } # end if search_filter->terms() > 1 - if ($limit) + if ($limit) { + ZM\Debug("Filtering rows due to limit " . count($filtered_rows)." offset: $offset limit: $limit"); $filtered_rows = array_slice($filtered_rows, $offset, $limit); + } $returned_rows = array(); foreach ($filtered_rows as $row) { diff --git a/web/js/MonitorStream.js b/web/js/MonitorStream.js index b5f9d0b6a..86b9b97e0 100644 --- a/web/js/MonitorStream.js +++ b/web/js/MonitorStream.js @@ -38,6 +38,14 @@ function MonitorStream(monitorData) { } return this.element; }; + this.getFrame = function() { + if (this.frame) return this.frame; + this.frame = document.getElementById('imageFeed'+this.id); + if (!this.frame) { + console.error("No frame div for #imageFeed"+this.id); + } + return this.frame; + }; /* if the img element didn't have a src, this would fill it in, causing it to show. */ this.show = function() { @@ -161,7 +169,7 @@ function MonitorStream(monitorData) { this.setup_onclick = function(func) { this.onclick = func; - const el = this.getElement(); + const el = this.getFrame(); if (!el) return; el.addEventListener('click', this.onclick, false); }; diff --git a/web/skins/classic/css/base/views/monitor.css b/web/skins/classic/css/base/views/monitor.css index f827af5ca..28afbf480 100644 --- a/web/skins/classic/css/base/views/monitor.css +++ b/web/skins/classic/css/base/views/monitor.css @@ -15,7 +15,11 @@ input[name="newMonitor[Path]"], input[name="newMonitor[SecondPath]"], input[name="newMonitor[LabelFormat]"], input[name="newMonitor[ControlDevice]"], -input[name="newMonitor[ControlAddress]"] { +input[name="newMonitor[ControlAddress]"], +input[name="newMonitor[ONVIF_URL]"], +input[name="newMonitor[ONVIF_Username]"], +input[name="newMonitor[ONVIF_Password]"], +input[name="newMonitor[ONVIF_Options]"] { width: 100%; } input[name="newMonitor[LabelFormat]"]{ diff --git a/web/skins/classic/js/skin.js.php b/web/skins/classic/js/skin.js.php index 2d443fbe1..67cf74fd0 100644 --- a/web/skins/classic/js/skin.js.php +++ b/web/skins/classic/js/skin.js.php @@ -109,7 +109,8 @@ stateStrings[STATE_TAPE] = ""; $c) { - if (!$c['Private']) - echo 'const '. $name . ' = \''.$c['Value'].'\''.PHP_EOL; + if (!$c['Private']) { + echo 'const '. $name . ' = \''.preg_replace('/(\n\r?)/', '\\\\$1', $c['Value']).'\';'.PHP_EOL; + } } ?> diff --git a/web/skins/classic/views/js/monitor.js.php b/web/skins/classic/views/js/monitor.js.php index 49f313143..7b06905f7 100644 --- a/web/skins/classic/views/js/monitor.js.php +++ b/web/skins/classic/views/js/monitor.js.php @@ -1,9 +1,3 @@ - var optControl = ; var hasOnvif = ; var defaultAspectRatio = '';