Deprecate cURL monitor type
parent
057159cde6
commit
2e10169cde
|
@ -0,0 +1 @@
|
||||||
|
UPDATE `Monitors` SET `Type`='Ffmpeg' WHERE `Type`='cURL';
|
|
@ -24,7 +24,7 @@
|
||||||
%endif
|
%endif
|
||||||
|
|
||||||
Name: zoneminder
|
Name: zoneminder
|
||||||
Version: 1.37.44
|
Version: 1.37.45
|
||||||
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
|
||||||
|
|
|
@ -1,659 +0,0 @@
|
||||||
//
|
|
||||||
// ZoneMinder cURL Camera Class Implementation, $Date: 2009-01-16 12:18:50 +0000 (Fri, 16 Jan 2009) $, $Revision: 2713 $
|
|
||||||
// Copyright (C) 2001-2008 Philip Coombes
|
|
||||||
//
|
|
||||||
// This program is free software; you can redistribute it and/or
|
|
||||||
// modify it under the terms of the GNU General Public License
|
|
||||||
// as published by the Free Software Foundation; either version 2
|
|
||||||
// of the License, or (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with this program; if not, write to the Free Software
|
|
||||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "zm_curl_camera.h"
|
|
||||||
|
|
||||||
#include "zm_packet.h"
|
|
||||||
#include <dlfcn.h>
|
|
||||||
|
|
||||||
#if HAVE_LIBCURL
|
|
||||||
|
|
||||||
/* Func ptrs for libcurl functions */
|
|
||||||
static void *curl_lib = nullptr;
|
|
||||||
static CURLcode (*curl_global_init_f)(long) = nullptr;
|
|
||||||
static void (*curl_global_cleanup_f)(void) = nullptr;
|
|
||||||
static const char* (*curl_easy_strerror_f)(CURLcode) = nullptr;
|
|
||||||
static char* (*curl_version_f)(void) = nullptr;
|
|
||||||
static CURL* (*curl_easy_init_f)(void) = nullptr;
|
|
||||||
static CURLcode (*curl_easy_getinfo_f)(CURL* , CURLINFO, ...) = nullptr;
|
|
||||||
static CURLcode (*curl_easy_perform_f)(CURL*) = nullptr;
|
|
||||||
static CURLcode (*curl_easy_setopt_f)(CURL*, CURLoption, ...) = nullptr;
|
|
||||||
static void (*curl_easy_cleanup_f)(CURL*) = nullptr;
|
|
||||||
|
|
||||||
#define CURL_MAXRETRY 5
|
|
||||||
#define CURL_BUFFER_INITIAL_SIZE 65536
|
|
||||||
|
|
||||||
const char* content_length_match = "Content-Length:";
|
|
||||||
const char* content_type_match = "Content-Type:";
|
|
||||||
size_t content_length_match_len;
|
|
||||||
size_t content_type_match_len;
|
|
||||||
|
|
||||||
void bind_libcurl_symbols() {
|
|
||||||
|
|
||||||
if (curl_lib)
|
|
||||||
return;
|
|
||||||
|
|
||||||
curl_lib = dlopen("libcurl.so", RTLD_LAZY | RTLD_GLOBAL);
|
|
||||||
if (!curl_lib) {
|
|
||||||
curl_lib = dlopen("libcurl.so.4", RTLD_LAZY | RTLD_GLOBAL);
|
|
||||||
if (!curl_lib) {
|
|
||||||
curl_lib = dlopen("libcurl-gnutls.so.4", RTLD_LAZY | RTLD_GLOBAL);
|
|
||||||
if (!curl_lib) {
|
|
||||||
Error("Could not load libcurl: %s", dlerror());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load up all required symbols here
|
|
||||||
*(void**) (&curl_global_init_f) = dlsym(curl_lib, "curl_global_init");
|
|
||||||
*(void**) (&curl_global_cleanup_f) = dlsym(curl_lib, "curl_global_cleanup");
|
|
||||||
*(void**) (&curl_easy_strerror_f) = dlsym(curl_lib, "curl_easy_strerror");
|
|
||||||
*(void**) (&curl_version_f) = dlsym(curl_lib, "curl_version");
|
|
||||||
*(void**) (&curl_easy_init_f) = dlsym(curl_lib, "curl_easy_init");
|
|
||||||
*(void**) (&curl_easy_getinfo_f) = dlsym(curl_lib, "curl_easy_getinfo");
|
|
||||||
*(void**) (&curl_easy_perform_f) = dlsym(curl_lib, "curl_easy_perform");
|
|
||||||
*(void**) (&curl_easy_setopt_f) = dlsym(curl_lib, "curl_easy_setopt");
|
|
||||||
*(void**) (&curl_easy_cleanup_f) = dlsym(curl_lib, "curl_easy_cleanup");
|
|
||||||
}
|
|
||||||
|
|
||||||
cURLCamera::cURLCamera(
|
|
||||||
const Monitor* monitor,
|
|
||||||
const std::string &p_path,
|
|
||||||
const std::string &p_user,
|
|
||||||
const std::string &p_pass,
|
|
||||||
unsigned int p_width,
|
|
||||||
unsigned int p_height,
|
|
||||||
int p_colours,
|
|
||||||
int p_brightness,
|
|
||||||
int p_contrast,
|
|
||||||
int p_hue,
|
|
||||||
int p_colour,
|
|
||||||
bool p_capture,
|
|
||||||
bool p_record_audio) :
|
|
||||||
Camera(monitor, CURL_SRC, p_width, p_height, p_colours, ZM_SUBPIX_ORDER_DEFAULT_FOR_COLOUR(p_colours), p_brightness, p_contrast, p_hue, p_colour, p_capture, p_record_audio),
|
|
||||||
mPath(p_path),
|
|
||||||
mUser(p_user),
|
|
||||||
mPass(p_pass),
|
|
||||||
bTerminate(false),
|
|
||||||
bReset(false),
|
|
||||||
mode(MODE_UNSET)
|
|
||||||
{
|
|
||||||
if (capture) {
|
|
||||||
Initialise();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cURLCamera::~cURLCamera() {
|
|
||||||
if (capture) {
|
|
||||||
Terminate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void cURLCamera::Initialise() {
|
|
||||||
content_length_match_len = strlen(content_length_match);
|
|
||||||
content_type_match_len = strlen(content_type_match);
|
|
||||||
|
|
||||||
databuffer.expand(CURL_BUFFER_INITIAL_SIZE);
|
|
||||||
|
|
||||||
bind_libcurl_symbols();
|
|
||||||
/* cURL initialization */
|
|
||||||
CURLcode cRet = (*curl_global_init_f)(CURL_GLOBAL_ALL);
|
|
||||||
if (cRet != CURLE_OK) {
|
|
||||||
Error("libcurl initialization failed: %s", (*curl_easy_strerror_f)(cRet));
|
|
||||||
dlclose(curl_lib);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug(2, "libcurl version: %s", (*curl_version_f)());
|
|
||||||
|
|
||||||
/* Create the shared data mutex */
|
|
||||||
int nRet = pthread_mutex_init(&shareddata_mutex, nullptr);
|
|
||||||
if (nRet != 0) {
|
|
||||||
Error("Shared data mutex creation failed: %s",strerror(nRet));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
/* Create the data available condition variable */
|
|
||||||
nRet = pthread_cond_init(&data_available_cond, nullptr);
|
|
||||||
if (nRet != 0) {
|
|
||||||
Error("Data available condition variable creation failed: %s",strerror(nRet));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
/* Create the request complete condition variable */
|
|
||||||
nRet = pthread_cond_init(&request_complete_cond, nullptr);
|
|
||||||
if (nRet != 0) {
|
|
||||||
Error("Request complete condition variable creation failed: %s",strerror(nRet));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Create the thread */
|
|
||||||
nRet = pthread_create(&thread, nullptr, thread_func_dispatcher, this);
|
|
||||||
if (nRet != 0) {
|
|
||||||
Error("Thread creation failed: %s",strerror(nRet));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void cURLCamera::Terminate() {
|
|
||||||
/* Signal the thread to terminate */
|
|
||||||
bTerminate = true;
|
|
||||||
|
|
||||||
/* Wait for thread termination */
|
|
||||||
pthread_join(thread, nullptr);
|
|
||||||
|
|
||||||
/* Destroy condition variables */
|
|
||||||
pthread_cond_destroy(&request_complete_cond);
|
|
||||||
pthread_cond_destroy(&data_available_cond);
|
|
||||||
|
|
||||||
/* Destroy mutex */
|
|
||||||
pthread_mutex_destroy(&shareddata_mutex);
|
|
||||||
|
|
||||||
/* cURL cleanup */
|
|
||||||
(*curl_global_cleanup_f)();
|
|
||||||
|
|
||||||
if (curl_lib)
|
|
||||||
dlclose(curl_lib);
|
|
||||||
}
|
|
||||||
|
|
||||||
int cURLCamera::PrimeCapture() {
|
|
||||||
getVideoStream();
|
|
||||||
//Info( "Priming capture from %s", mPath.c_str() );
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int cURLCamera::PreCapture() {
|
|
||||||
// Nothing to do here
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int cURLCamera::Capture(std::shared_ptr<ZMPacket> &zm_packet) {
|
|
||||||
bool frameComplete = false;
|
|
||||||
|
|
||||||
/* MODE_STREAM specific variables */
|
|
||||||
bool SubHeadersParsingComplete = false;
|
|
||||||
unsigned int frame_content_length = 0;
|
|
||||||
std::string frame_content_type;
|
|
||||||
bool need_more_data = false;
|
|
||||||
int nRet;
|
|
||||||
|
|
||||||
/* Grab the mutex to ensure exclusive access to the shared data */
|
|
||||||
lock();
|
|
||||||
|
|
||||||
while (!frameComplete) {
|
|
||||||
/* If the work thread did a reset, reset our local variables */
|
|
||||||
if (bReset) {
|
|
||||||
SubHeadersParsingComplete = false;
|
|
||||||
frame_content_length = 0;
|
|
||||||
frame_content_type.clear();
|
|
||||||
need_more_data = false;
|
|
||||||
bReset = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mode == MODE_UNSET) {
|
|
||||||
/* Don't have a mode yet. Sleep while waiting for data */
|
|
||||||
nRet = pthread_cond_wait(&data_available_cond,&shareddata_mutex);
|
|
||||||
if (nRet != 0) {
|
|
||||||
Error("Failed waiting for available data condition variable: %s",strerror(nRet));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mode == MODE_STREAM) {
|
|
||||||
|
|
||||||
/* Subheader parsing */
|
|
||||||
while (!SubHeadersParsingComplete && !need_more_data) {
|
|
||||||
|
|
||||||
size_t crlf_start, crlf_end, crlf_size;
|
|
||||||
std::string subheader;
|
|
||||||
|
|
||||||
/* Check if the buffer contains something */
|
|
||||||
if (databuffer.empty()) {
|
|
||||||
/* Empty buffer, wait for data */
|
|
||||||
need_more_data = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Find crlf start */
|
|
||||||
crlf_start = memcspn(reinterpret_cast<const char *>(databuffer.head()),"\r\n",databuffer.size());
|
|
||||||
if (crlf_start == databuffer.size()) {
|
|
||||||
/* Not found, wait for more data */
|
|
||||||
need_more_data = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* See if we have enough data for determining crlf length */
|
|
||||||
if (databuffer.size() < crlf_start+5) {
|
|
||||||
/* Need more data */
|
|
||||||
need_more_data = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Find crlf end and calculate crlf size */
|
|
||||||
crlf_end = memspn(((const char*)databuffer.head())+crlf_start,"\r\n",5);
|
|
||||||
crlf_size = (crlf_start + crlf_end) - crlf_start;
|
|
||||||
|
|
||||||
/* Is this the end of a previous stream? (This is just before the boundary) */
|
|
||||||
if (crlf_start == 0) {
|
|
||||||
databuffer.consume(crlf_size);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Check for invalid CRLF size */
|
|
||||||
if (crlf_size > 4) {
|
|
||||||
Error("Invalid CRLF length");
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Check if the crlf is \n\n or \r\n\r\n (marks end of headers, this is the last header) */
|
|
||||||
if (
|
|
||||||
(crlf_size == 2 && memcmp(((const char*)databuffer.head())+crlf_start,"\n\n",2) == 0)
|
|
||||||
||
|
|
||||||
(crlf_size == 4 && memcmp(((const char*)databuffer.head())+crlf_start,"\r\n\r\n",4) == 0)
|
|
||||||
) {
|
|
||||||
/* This is the last header */
|
|
||||||
SubHeadersParsingComplete = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Copy the subheader, excluding the crlf */
|
|
||||||
subheader.assign(databuffer, crlf_start);
|
|
||||||
|
|
||||||
/* Advance the buffer past this one */
|
|
||||||
databuffer.consume(crlf_start+crlf_size);
|
|
||||||
|
|
||||||
Debug(7, "Got subheader: %s",subheader.c_str());
|
|
||||||
|
|
||||||
/* Find where the data in this header starts */
|
|
||||||
size_t subheader_data_start = subheader.rfind(' ');
|
|
||||||
if (subheader_data_start == std::string::npos) {
|
|
||||||
subheader_data_start = subheader.find(':');
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Extract the data into a string */
|
|
||||||
std::string subheader_data = subheader.substr(subheader_data_start+1, std::string::npos);
|
|
||||||
|
|
||||||
Debug(8, "Got subheader data: %s", subheader_data.c_str());
|
|
||||||
|
|
||||||
/* Check the header */
|
|
||||||
if (strncasecmp(subheader.c_str(), content_length_match, content_length_match_len) == 0) {
|
|
||||||
/* Found the content-length header */
|
|
||||||
frame_content_length = atoi(subheader_data.c_str());
|
|
||||||
Debug(6,"Got content-length subheader: %d",frame_content_length);
|
|
||||||
} else if (strncasecmp(subheader.c_str(), content_type_match, content_type_match_len) == 0) {
|
|
||||||
/* Found the content-type header */
|
|
||||||
frame_content_type = subheader_data;
|
|
||||||
Debug(6,"Got content-type subheader: %s", frame_content_type.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Attempt to extract the frame */
|
|
||||||
if (!need_more_data) {
|
|
||||||
if (!SubHeadersParsingComplete) {
|
|
||||||
/* We haven't parsed all headers yet */
|
|
||||||
need_more_data = true;
|
|
||||||
} else if (!frame_content_length) {
|
|
||||||
/* Invalid frame */
|
|
||||||
Error("Invalid frame: invalid content length");
|
|
||||||
} else if (frame_content_type != "image/jpeg") {
|
|
||||||
/* Unsupported frame type */
|
|
||||||
Error("Unsupported frame: %s", frame_content_type.c_str());
|
|
||||||
} else if (frame_content_length > databuffer.size()) {
|
|
||||||
/* Incomplete frame, wait for more data */
|
|
||||||
need_more_data = true;
|
|
||||||
} else {
|
|
||||||
/* All good. decode the image */
|
|
||||||
if (!zm_packet->image) {
|
|
||||||
Debug(4, "Allocating image");
|
|
||||||
zm_packet->image = new Image(width, height, colours, subpixelorder);
|
|
||||||
}
|
|
||||||
zm_packet->keyframe = 1;
|
|
||||||
zm_packet->codec_type = AVMEDIA_TYPE_VIDEO;
|
|
||||||
zm_packet->packet->stream_index = mVideoStreamId;
|
|
||||||
zm_packet->stream = mVideoStream;
|
|
||||||
zm_packet->image->DecodeJpeg(databuffer.extract(frame_content_length), frame_content_length, colours, subpixelorder);
|
|
||||||
frameComplete = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Attempt to get more data */
|
|
||||||
if (need_more_data) {
|
|
||||||
nRet = pthread_cond_wait(&data_available_cond,&shareddata_mutex);
|
|
||||||
if (nRet != 0) {
|
|
||||||
Error("Failed waiting for available data condition variable: %s",strerror(nRet));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
need_more_data = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (mode == MODE_SINGLE) {
|
|
||||||
/* Check if we have anything */
|
|
||||||
if (!single_offsets.empty()) {
|
|
||||||
if ((single_offsets.front() > 0) && (databuffer.size() >= single_offsets.front())) {
|
|
||||||
/* Extract frame */
|
|
||||||
if (!zm_packet->image) {
|
|
||||||
Debug(4, "Allocating image");
|
|
||||||
zm_packet->image = new Image(width, height, colours, subpixelorder);
|
|
||||||
}
|
|
||||||
zm_packet->keyframe = 1;
|
|
||||||
zm_packet->codec_type = AVMEDIA_TYPE_VIDEO;
|
|
||||||
zm_packet->packet->stream_index = mVideoStreamId;
|
|
||||||
zm_packet->stream = mVideoStream;
|
|
||||||
zm_packet->image->DecodeJpeg(databuffer.extract(single_offsets.front()), single_offsets.front(), colours, subpixelorder);
|
|
||||||
single_offsets.pop_front();
|
|
||||||
frameComplete = true;
|
|
||||||
} else {
|
|
||||||
/* This shouldn't happen */
|
|
||||||
Error("Internal error. Attempting recovery");
|
|
||||||
databuffer.consume(single_offsets.front());
|
|
||||||
single_offsets.pop_front();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
/* Don't have a frame yet, wait for the request complete condition variable */
|
|
||||||
nRet = pthread_cond_wait(&request_complete_cond,&shareddata_mutex);
|
|
||||||
if(nRet != 0) {
|
|
||||||
Error("Failed waiting for request complete condition variable: %s",strerror(nRet));
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
/* Failed to match content-type */
|
|
||||||
Error("Unable to match Content-Type. Check URL, username and password");
|
|
||||||
return -21;
|
|
||||||
} /* mode */
|
|
||||||
|
|
||||||
} /* frameComplete loop */
|
|
||||||
|
|
||||||
/* Release the mutex */
|
|
||||||
unlock();
|
|
||||||
|
|
||||||
if (!frameComplete)
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int cURLCamera::PostCapture() {
|
|
||||||
// Nothing to do here
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t cURLCamera::data_callback(void *buffer, size_t size, size_t nmemb, void *userdata) {
|
|
||||||
lock();
|
|
||||||
|
|
||||||
/* Append the data we just received to our buffer */
|
|
||||||
databuffer.append((const char*)buffer, size*nmemb);
|
|
||||||
|
|
||||||
/* Signal data available */
|
|
||||||
int nRet = pthread_cond_signal(&data_available_cond);
|
|
||||||
if (nRet != 0) {
|
|
||||||
Error("Failed signaling data available condition variable: %s",strerror(nRet));
|
|
||||||
unlock();
|
|
||||||
return -16;
|
|
||||||
}
|
|
||||||
|
|
||||||
unlock();
|
|
||||||
|
|
||||||
/* Return bytes processed */
|
|
||||||
return size*nmemb;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t cURLCamera::header_callback( void *buffer, size_t size, size_t nmemb, void *userdata) {
|
|
||||||
std::string header;
|
|
||||||
header.assign((const char*)buffer, size*nmemb);
|
|
||||||
|
|
||||||
Debug(4, "Got header: %s", header.c_str());
|
|
||||||
|
|
||||||
/* Check Content-Type header */
|
|
||||||
if (strncasecmp(header.c_str(),content_type_match,content_type_match_len) == 0) {
|
|
||||||
size_t pos = header.find(';');
|
|
||||||
if (pos != std::string::npos) {
|
|
||||||
header.erase(pos, std::string::npos);
|
|
||||||
}
|
|
||||||
|
|
||||||
pos = header.rfind(' ');
|
|
||||||
if (pos == std::string::npos) {
|
|
||||||
pos = header.find(':');
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string content_type = header.substr(pos+1, std::string::npos);
|
|
||||||
Debug(6,"Content-Type is: %s",content_type.c_str());
|
|
||||||
|
|
||||||
lock();
|
|
||||||
|
|
||||||
const char* multipart_match = "multipart/x-mixed-replace";
|
|
||||||
const char* image_jpeg_match = "image/jpeg";
|
|
||||||
if (strncasecmp(content_type.c_str(),multipart_match,strlen(multipart_match)) == 0) {
|
|
||||||
Debug(7, "Content type matched as multipart/x-mixed-replace");
|
|
||||||
mode = MODE_STREAM;
|
|
||||||
} else if (strncasecmp(content_type.c_str(),image_jpeg_match,strlen(image_jpeg_match)) == 0) {
|
|
||||||
Debug(7, "Content type matched as image/jpeg");
|
|
||||||
mode = MODE_SINGLE;
|
|
||||||
}
|
|
||||||
|
|
||||||
unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Return bytes processed */
|
|
||||||
return size*nmemb;
|
|
||||||
}
|
|
||||||
|
|
||||||
void* cURLCamera::thread_func() {
|
|
||||||
long tRet;
|
|
||||||
double dSize;
|
|
||||||
|
|
||||||
c = (*curl_easy_init_f)();
|
|
||||||
if (c == nullptr) {
|
|
||||||
dlclose(curl_lib);
|
|
||||||
Error("Failed getting easy handle from libcurl");
|
|
||||||
tRet = -51;
|
|
||||||
return (void*)tRet;
|
|
||||||
}
|
|
||||||
|
|
||||||
CURLcode cRet;
|
|
||||||
/* Set URL */
|
|
||||||
cRet = (*curl_easy_setopt_f)(c, CURLOPT_URL, mPath.c_str());
|
|
||||||
if (cRet != CURLE_OK) {
|
|
||||||
Error("Failed setting libcurl URL: %s", (*curl_easy_strerror_f)(cRet));
|
|
||||||
tRet = -52;
|
|
||||||
return (void*)tRet;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Header callback */
|
|
||||||
cRet = (*curl_easy_setopt_f)(c, CURLOPT_HEADERFUNCTION, &header_callback_dispatcher);
|
|
||||||
if (cRet != CURLE_OK) {
|
|
||||||
Error("Failed setting libcurl header callback function: %s", (*curl_easy_strerror_f)(cRet));
|
|
||||||
tRet = -53;
|
|
||||||
return (void*)tRet;
|
|
||||||
}
|
|
||||||
|
|
||||||
cRet = (*curl_easy_setopt_f)(c, CURLOPT_HEADERDATA, this);
|
|
||||||
if (cRet != CURLE_OK) {
|
|
||||||
Error("Failed setting libcurl header callback object: %s", (*curl_easy_strerror_f)(cRet));
|
|
||||||
tRet = -54;
|
|
||||||
return (void*)tRet;
|
|
||||||
}
|
|
||||||
/* Data callback */
|
|
||||||
cRet = (*curl_easy_setopt_f)(c, CURLOPT_WRITEFUNCTION, &data_callback_dispatcher);
|
|
||||||
if (cRet != CURLE_OK) {
|
|
||||||
Error("Failed setting libcurl data callback function: %s", (*curl_easy_strerror_f)(cRet));
|
|
||||||
tRet = -55;
|
|
||||||
return (void*)tRet;
|
|
||||||
}
|
|
||||||
|
|
||||||
cRet = (*curl_easy_setopt_f)(c, CURLOPT_WRITEDATA, this);
|
|
||||||
if (cRet != CURLE_OK) {
|
|
||||||
Error("Failed setting libcurl data callback object: %s", (*curl_easy_strerror_f)(cRet));
|
|
||||||
tRet = -56;
|
|
||||||
return (void*)tRet;
|
|
||||||
}
|
|
||||||
/* Progress callback */
|
|
||||||
cRet = (*curl_easy_setopt_f)(c, CURLOPT_NOPROGRESS, 0);
|
|
||||||
if (cRet != CURLE_OK) {
|
|
||||||
Error("Failed enabling libcurl progress callback function: %s", (*curl_easy_strerror_f)(cRet));
|
|
||||||
tRet = -57;
|
|
||||||
return (void*)tRet;
|
|
||||||
}
|
|
||||||
|
|
||||||
cRet = (*curl_easy_setopt_f)(c, CURLOPT_PROGRESSFUNCTION, &progress_callback_dispatcher);
|
|
||||||
if (cRet != CURLE_OK) {
|
|
||||||
Error("Failed setting libcurl progress callback function: %s", (*curl_easy_strerror_f)(cRet));
|
|
||||||
tRet = -58;
|
|
||||||
return (void*)tRet;
|
|
||||||
}
|
|
||||||
|
|
||||||
cRet = (*curl_easy_setopt_f)(c, CURLOPT_PROGRESSDATA, this);
|
|
||||||
if (cRet != CURLE_OK) {
|
|
||||||
Error("Failed setting libcurl progress callback object: %s", (*curl_easy_strerror_f)(cRet));
|
|
||||||
tRet = -59;
|
|
||||||
return (void*)tRet;
|
|
||||||
}
|
|
||||||
/* Set username and password */
|
|
||||||
if (!mUser.empty()) {
|
|
||||||
cRet = (*curl_easy_setopt_f)(c, CURLOPT_USERNAME, mUser.c_str());
|
|
||||||
if (cRet != CURLE_OK)
|
|
||||||
Error("Failed setting username: %s", (*curl_easy_strerror_f)(cRet));
|
|
||||||
}
|
|
||||||
if (!mPass.empty()) {
|
|
||||||
cRet = (*curl_easy_setopt_f)(c, CURLOPT_PASSWORD, mPass.c_str());
|
|
||||||
if (cRet != CURLE_OK)
|
|
||||||
Error("Failed setting password: %s", (*curl_easy_strerror_f)(cRet));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Authentication preference */
|
|
||||||
cRet = (*curl_easy_setopt_f)(c, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
|
|
||||||
if (cRet != CURLE_OK)
|
|
||||||
Warning("Failed setting libcurl acceptable http authentication methods: %s", (*curl_easy_strerror_f)(cRet));
|
|
||||||
|
|
||||||
|
|
||||||
/* Work loop */
|
|
||||||
for (int attempt=1;attempt<=CURL_MAXRETRY;attempt++) {
|
|
||||||
tRet = 0;
|
|
||||||
while (!bTerminate) {
|
|
||||||
/* Do the work */
|
|
||||||
cRet = (*curl_easy_perform_f)(c);
|
|
||||||
|
|
||||||
if (mode == MODE_SINGLE) {
|
|
||||||
if (cRet != CURLE_OK) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
/* Attempt to get the size of the file */
|
|
||||||
cRet = (*curl_easy_getinfo_f)(c, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &dSize);
|
|
||||||
if (cRet != CURLE_OK) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
/* We need to lock for the offsets array and the condition variable */
|
|
||||||
lock();
|
|
||||||
/* Push the size into our offsets array */
|
|
||||||
if (dSize > 0) {
|
|
||||||
single_offsets.push_back(dSize);
|
|
||||||
} else {
|
|
||||||
Error("Unable to get the size of the image");
|
|
||||||
tRet = -60;
|
|
||||||
return (void*)tRet;
|
|
||||||
}
|
|
||||||
/* Signal the request complete condition variable */
|
|
||||||
tRet = pthread_cond_signal(&request_complete_cond);
|
|
||||||
if (tRet != 0) {
|
|
||||||
Error("Failed signaling request completed condition variable: %s",strerror(tRet));
|
|
||||||
tRet = -61;
|
|
||||||
return (void*)tRet;
|
|
||||||
}
|
|
||||||
/* Unlock */
|
|
||||||
unlock();
|
|
||||||
|
|
||||||
} else if (mode == MODE_STREAM) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Return value checking */
|
|
||||||
if (cRet == CURLE_ABORTED_BY_CALLBACK || bTerminate) {
|
|
||||||
/* Aborted */
|
|
||||||
break;
|
|
||||||
} else if (cRet != CURLE_OK) {
|
|
||||||
/* Some error */
|
|
||||||
Error("cURL Request failed: %s",(*curl_easy_strerror_f)(cRet));
|
|
||||||
if (attempt < CURL_MAXRETRY) {
|
|
||||||
Error("Retrying.. Attempt %d of %d",attempt,CURL_MAXRETRY);
|
|
||||||
/* Do a reset */
|
|
||||||
lock();
|
|
||||||
databuffer.clear();
|
|
||||||
single_offsets.clear();
|
|
||||||
mode = MODE_UNSET;
|
|
||||||
bReset = true;
|
|
||||||
unlock();
|
|
||||||
}
|
|
||||||
tRet = -50;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Cleanup */
|
|
||||||
(*curl_easy_cleanup_f)(c);
|
|
||||||
c = nullptr;
|
|
||||||
|
|
||||||
return (void*)tRet;
|
|
||||||
}
|
|
||||||
|
|
||||||
int cURLCamera::lock() {
|
|
||||||
int nRet;
|
|
||||||
|
|
||||||
/* Lock shared data */
|
|
||||||
nRet = pthread_mutex_lock(&shareddata_mutex);
|
|
||||||
if(nRet != 0) {
|
|
||||||
Error("Failed locking shared data mutex: %s",strerror(nRet));
|
|
||||||
}
|
|
||||||
return nRet;
|
|
||||||
}
|
|
||||||
|
|
||||||
int cURLCamera::unlock() {
|
|
||||||
int nRet;
|
|
||||||
|
|
||||||
/* Unlock shared data */
|
|
||||||
nRet = pthread_mutex_unlock(&shareddata_mutex);
|
|
||||||
if(nRet != 0) {
|
|
||||||
Error("Failed unlocking shared data mutex: %s",strerror(nRet));
|
|
||||||
}
|
|
||||||
return nRet;
|
|
||||||
}
|
|
||||||
|
|
||||||
int cURLCamera::progress_callback(void *userdata, double dltotal, double dlnow, double ultotal, double ulnow) {
|
|
||||||
/* Signal the curl thread to terminate */
|
|
||||||
if (bTerminate)
|
|
||||||
return -10;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* These functions call the functions in the class for the correct object */
|
|
||||||
size_t data_callback_dispatcher(void *buffer, size_t size, size_t nmemb, void *userdata) {
|
|
||||||
return reinterpret_cast<cURLCamera*>(userdata)->data_callback(buffer,size,nmemb,userdata);
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t header_callback_dispatcher(void *buffer, size_t size, size_t nmemb, void *userdata) {
|
|
||||||
return reinterpret_cast<cURLCamera*>(userdata)->header_callback(buffer,size,nmemb,userdata);
|
|
||||||
}
|
|
||||||
|
|
||||||
int progress_callback_dispatcher(void *userdata, double dltotal, double dlnow, double ultotal, double ulnow) {
|
|
||||||
return reinterpret_cast<cURLCamera*>(userdata)->progress_callback(userdata,dltotal,dlnow,ultotal,ulnow);
|
|
||||||
}
|
|
||||||
|
|
||||||
void* thread_func_dispatcher(void* object) {
|
|
||||||
return reinterpret_cast<cURLCamera*>(object)->thread_func();
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // HAVE_LIBCURL
|
|
|
@ -1,112 +0,0 @@
|
||||||
//
|
|
||||||
// ZoneMinder cURL Class Interface, $Date: 2008-07-25 10:33:23 +0100 (Fri, 25 Jul 2008) $, $Revision: 2611 $
|
|
||||||
// Copyright (C) 2001-2008 Philip Coombes
|
|
||||||
//
|
|
||||||
// This program is free software; you can redistribute it and/or
|
|
||||||
// modify it under the terms of the GNU General Public License
|
|
||||||
// as published by the Free Software Foundation; either version 2
|
|
||||||
// of the License, or (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU General Public License
|
|
||||||
// along with this program; if not, write to the Free Software
|
|
||||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef ZM_CURL_CAMERA_H
|
|
||||||
#define ZM_CURL_CAMERA_H
|
|
||||||
|
|
||||||
#include "zm_config.h"
|
|
||||||
#include "zm_buffer.h"
|
|
||||||
#include "zm_camera.h"
|
|
||||||
#include <deque>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#if HAVE_LIBCURL
|
|
||||||
|
|
||||||
#if HAVE_CURL_CURL_H
|
|
||||||
#include <curl/curl.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
//
|
|
||||||
// Class representing 'curl' cameras, i.e. those which are
|
|
||||||
// accessed using the curl library
|
|
||||||
//
|
|
||||||
class cURLCamera : public Camera {
|
|
||||||
protected:
|
|
||||||
typedef enum {MODE_UNSET, MODE_SINGLE, MODE_STREAM} mode_t;
|
|
||||||
|
|
||||||
std::string mPath;
|
|
||||||
std::string mUser;
|
|
||||||
std::string mPass;
|
|
||||||
|
|
||||||
/* cURL object(s) */
|
|
||||||
CURL* c;
|
|
||||||
|
|
||||||
/* Shared data */
|
|
||||||
volatile bool bTerminate;
|
|
||||||
volatile bool bReset;
|
|
||||||
volatile mode_t mode;
|
|
||||||
Buffer databuffer;
|
|
||||||
std::deque<size_t> single_offsets;
|
|
||||||
|
|
||||||
/* pthread objects */
|
|
||||||
pthread_t thread;
|
|
||||||
pthread_mutex_t shareddata_mutex;
|
|
||||||
pthread_cond_t data_available_cond;
|
|
||||||
pthread_cond_t request_complete_cond;
|
|
||||||
|
|
||||||
public:
|
|
||||||
cURLCamera(
|
|
||||||
const Monitor* monitor,
|
|
||||||
const std::string &path,
|
|
||||||
const std::string &username,
|
|
||||||
const std::string &password,
|
|
||||||
unsigned int p_width,
|
|
||||||
unsigned int p_height,
|
|
||||||
int p_colours,
|
|
||||||
int p_brightness,
|
|
||||||
int p_contrast,
|
|
||||||
int p_hue,
|
|
||||||
int p_colour,
|
|
||||||
bool p_capture,
|
|
||||||
bool p_record_audio
|
|
||||||
);
|
|
||||||
~cURLCamera();
|
|
||||||
|
|
||||||
const std::string &Path() const { return mPath; }
|
|
||||||
const std::string &Username() const { return mUser; }
|
|
||||||
const std::string &Password() const { return mPass; }
|
|
||||||
|
|
||||||
void Initialise();
|
|
||||||
void Terminate();
|
|
||||||
int Close() override { return 0; };
|
|
||||||
|
|
||||||
int PrimeCapture() override;
|
|
||||||
int PreCapture() override;
|
|
||||||
int Capture(std::shared_ptr<ZMPacket> &p)override;
|
|
||||||
int PostCapture()override ;
|
|
||||||
|
|
||||||
size_t data_callback(void *buffer, size_t size, size_t nmemb, void *userdata);
|
|
||||||
size_t header_callback(void *buffer, size_t size, size_t nmemb, void *userdata);
|
|
||||||
int progress_callback(void *userdata, double dltotal, double dlnow, double ultotal, double ulnow);
|
|
||||||
int debug_callback(CURL* handle, curl_infotype type, char* str, size_t strsize, void* data);
|
|
||||||
void* thread_func();
|
|
||||||
int lock();
|
|
||||||
int unlock();
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Dispatchers */
|
|
||||||
size_t header_callback_dispatcher(void *buffer, size_t size, size_t nmemb, void *userdata);
|
|
||||||
size_t data_callback_dispatcher(void *buffer, size_t size, size_t nmemb, void *userdata);
|
|
||||||
int progress_callback_dispatcher(void *userdata, double dltotal, double dlnow, double ultotal, double ulnow);
|
|
||||||
void* thread_func_dispatcher(void* object);
|
|
||||||
|
|
||||||
#endif // HAVE_LIBCURL
|
|
||||||
|
|
||||||
#endif // ZM_CURL_CAMERA_H
|
|
|
@ -1 +1 @@
|
||||||
1.37.44
|
1.37.45
|
||||||
|
|
|
@ -143,7 +143,6 @@ $sourceTypes = array(
|
||||||
'File' => translate('File'),
|
'File' => translate('File'),
|
||||||
'Ffmpeg' => translate('Ffmpeg'),
|
'Ffmpeg' => translate('Ffmpeg'),
|
||||||
'Libvlc' => translate('Libvlc'),
|
'Libvlc' => translate('Libvlc'),
|
||||||
'cURL' => 'cURL (HTTP(S) only)',
|
|
||||||
'WebSite'=> 'Web Site',
|
'WebSite'=> 'Web Site',
|
||||||
'NVSocket' => translate('NVSocket'),
|
'NVSocket' => translate('NVSocket'),
|
||||||
'VNC' => translate('VNC'),
|
'VNC' => translate('VNC'),
|
||||||
|
@ -733,20 +732,6 @@ include('_monitor_source_nvsocket.php');
|
||||||
<label><?php echo translate('SourcePath') ?></label>
|
<label><?php echo translate('SourcePath') ?></label>
|
||||||
<input type="text" name="newMonitor[Path]" value="<?php echo validHtmlStr($monitor->Path()) ?>"/>
|
<input type="text" name="newMonitor[Path]" value="<?php echo validHtmlStr($monitor->Path()) ?>"/>
|
||||||
</li>
|
</li>
|
||||||
<?php
|
|
||||||
} elseif ( $monitor->Type() == 'cURL' ) {
|
|
||||||
?>
|
|
||||||
<li><label><?php echo 'URL' ?></label>
|
|
||||||
<input type="text" name="newMonitor[Path]" value="<?php echo validHtmlStr($monitor->Path()) ?>"/>
|
|
||||||
</li>
|
|
||||||
<li><label><?php echo 'Username' ?></label>
|
|
||||||
<input type="text" name="newMonitor[User]" value="<?php echo validHtmlStr($monitor->User()) ?>"/>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<label><?php echo 'Password' ?></label>
|
|
||||||
<input type="password" id="newMonitor[Pass]" name="newMonitor[Pass]" value="<?php echo validHtmlStr($monitor->Pass()) ?>" autocomplete="new-password"/>
|
|
||||||
<span class="material-icons md-18" data-on-click-this="toggle_password_visibility" data-password-input="newMonitor[Pass]">visibility</span>
|
|
||||||
</li>
|
|
||||||
<?php
|
<?php
|
||||||
} elseif ( $monitor->Type() == 'WebSite' ) {
|
} elseif ( $monitor->Type() == 'WebSite' ) {
|
||||||
?>
|
?>
|
||||||
|
|
Loading…
Reference in New Issue