cURL is now working!

This commit is contained in:
Kfir Itzhak 2013-12-27 18:02:32 +02:00
parent bf708a8373
commit ff9a26273c
10 changed files with 489 additions and 69 deletions

View File

@ -63,6 +63,7 @@ set(ZM_MYSQL_ENGINE "InnoDB" CACHE STRING "MySQL engine to use with database, de
set(ZM_NO_MMAP "OFF" CACHE BOOL "Set to ON to not use mmap shared memory. Shouldn't be enabled unless you experience problems with the shared memory. default: OFF") set(ZM_NO_MMAP "OFF" CACHE BOOL "Set to ON to not use mmap shared memory. Shouldn't be enabled unless you experience problems with the shared memory. default: OFF")
set(ZM_NO_FFMPEG "OFF" CACHE BOOL "Set to ON to skip ffmpeg checks and force building ZM without ffmpeg. default: OFF") set(ZM_NO_FFMPEG "OFF" CACHE BOOL "Set to ON to skip ffmpeg checks and force building ZM without ffmpeg. default: OFF")
set(ZM_NO_LIBVLC "OFF" CACHE BOOL "Set to ON to skip libvlc checks and force building ZM without libvlc. default: OFF") set(ZM_NO_LIBVLC "OFF" CACHE BOOL "Set to ON to skip libvlc checks and force building ZM without libvlc. default: OFF")
set(ZM_NO_CURL "OFF" CACHE BOOL "Set to ON to skip cURL checks and force building ZM without cURL. default: OFF")
set(ZM_NO_X10 "OFF" CACHE BOOL "Set to ON to build ZoneMinder without X10 support. default: OFF") set(ZM_NO_X10 "OFF" CACHE BOOL "Set to ON to build ZoneMinder without X10 support. default: OFF")
set(ZM_PERL_SUBPREFIX "${CMAKE_INSTALL_LIBDIR}/perl5" CACHE PATH "Use a different directory for the zm perl modules. NOTE: This is a subprefix, e.g. lib will be turned into <prefix>/lib, default: <libdir>/perl5") set(ZM_PERL_SUBPREFIX "${CMAKE_INSTALL_LIBDIR}/perl5" CACHE PATH "Use a different directory for the zm perl modules. NOTE: This is a subprefix, e.g. lib will be turned into <prefix>/lib, default: <libdir>/perl5")
set(ZM_PERL_USE_PATH "${CMAKE_INSTALL_PREFIX}/${ZM_PERL_SUBPREFIX}" CACHE PATH "Override the include path for zm perl modules. Useful if you are moving the perl modules without using the ZM_PERL_SUBPREFIX option. default: <prefix>/<zmperlsubprefix>") set(ZM_PERL_USE_PATH "${CMAKE_INSTALL_PREFIX}/${ZM_PERL_SUBPREFIX}" CACHE PATH "Override the include path for zm perl modules. Useful if you are moving the perl modules without using the ZM_PERL_SUBPREFIX option. default: <prefix>/<zmperlsubprefix>")
@ -105,18 +106,21 @@ else(ZLIB_FOUND)
set(optlibsnotfound "${optlibsnotfound} zlib") set(optlibsnotfound "${optlibsnotfound} zlib")
endif(ZLIB_FOUND) endif(ZLIB_FOUND)
# cURL # Do not check for cURL if ZM_NO_CURL is on
find_package(CURL) if(NOT ZM_NO_CURL)
if(CURL_FOUND) # cURL
set(HAVE_LIBCURL 1) find_package(CURL)
list(APPEND ZM_BIN_LIBS ${CURL_LIBRARIES}) if(CURL_FOUND)
include_directories(${CURL_INCLUDE_DIRS}) set(HAVE_LIBCURL 1)
set(CMAKE_REQUIRED_INCLUDES ${CURL_INCLUDE_DIRS}) list(APPEND ZM_BIN_LIBS ${CURL_LIBRARIES})
check_include_file("curl/curl.h" HAVE_CURL_CURL_H) include_directories(${CURL_INCLUDE_DIRS})
set(optlibsfound "${optlibsfound} cURL") set(CMAKE_REQUIRED_INCLUDES ${CURL_INCLUDE_DIRS})
else(CURL_FOUND) check_include_file("curl/curl.h" HAVE_CURL_CURL_H)
set(optlibsnotfound "${optlibsnotfound} cURL") set(optlibsfound "${optlibsfound} cURL")
endif(CURL_FOUND) else(CURL_FOUND)
set(optlibsnotfound "${optlibsnotfound} cURL")
endif(CURL_FOUND)
endif(ZM_NO_CURL)
# jpeg # jpeg
find_package(JPEG) find_package(JPEG)

View File

@ -278,6 +278,7 @@ AC_CHECK_LIB(swscale,sws_scale,,,-lswscale)
AC_CHECK_LIB(vlc,libvlc_new,,AC_MSG_WARN(libvlc.a may be required for streaming)) AC_CHECK_LIB(vlc,libvlc_new,,AC_MSG_WARN(libvlc.a may be required for streaming))
AC_CHECK_LIB(bz2,BZ2_bzCompress,,AC_MSG_WARN(zm requires libbz2.a for recent versions of ffmpeg)) AC_CHECK_LIB(bz2,BZ2_bzCompress,,AC_MSG_WARN(zm requires libbz2.a for recent versions of ffmpeg))
AC_CHECK_LIB(z,compress,,) AC_CHECK_LIB(z,compress,,)
AC_CHECK_LIB(curl,curl_global_init,,)
# Checks for header files. # Checks for header files.
AC_FUNC_ALLOCA AC_FUNC_ALLOCA
@ -315,6 +316,7 @@ AC_CHECK_HEADERS(sys/shm.h,,,)
fi fi
AC_CHECK_HEADERS(zlib.h,,,) AC_CHECK_HEADERS(zlib.h,,,)
AC_CHECK_HEADERS(vlc/vlc.h,,,) AC_CHECK_HEADERS(vlc/vlc.h,,,)
AC_CHECK_HEADERS(curl/curl.h,,,)
if test "$ZM_SSL_LIB" == "openssl"; then if test "$ZM_SSL_LIB" == "openssl"; then
AC_CHECK_DECLS(MD5,,AC_MSG_ERROR([zm requires openssl/md5.h - use ZM_SSL_LIB option to select gnutls instead]),[#include <stdlib.h> AC_CHECK_DECLS(MD5,,AC_MSG_ERROR([zm requires openssl/md5.h - use ZM_SSL_LIB option to select gnutls instead]),[#include <stdlib.h>

View File

@ -63,7 +63,7 @@ DROP TABLE IF EXISTS `Controls`;
CREATE TABLE `Controls` ( CREATE TABLE `Controls` (
`Id` int(10) unsigned NOT NULL auto_increment, `Id` int(10) unsigned NOT NULL auto_increment,
`Name` varchar(64) NOT NULL default '', `Name` varchar(64) NOT NULL default '',
`Type` enum('Local','Remote','Ffmpeg','Libvlc') NOT NULL default 'Local', `Type` enum('Local','Remote','Ffmpeg','Libvlc','cURL') NOT NULL default 'Local',
`Protocol` varchar(64) default NULL, `Protocol` varchar(64) default NULL,
`CanWake` tinyint(3) unsigned NOT NULL default '0', `CanWake` tinyint(3) unsigned NOT NULL default '0',
`CanSleep` tinyint(3) unsigned NOT NULL default '0', `CanSleep` tinyint(3) unsigned NOT NULL default '0',
@ -282,7 +282,7 @@ DROP TABLE IF EXISTS `MonitorPresets`;
CREATE TABLE `MonitorPresets` ( CREATE TABLE `MonitorPresets` (
`Id` int(10) unsigned NOT NULL auto_increment, `Id` int(10) unsigned NOT NULL auto_increment,
`Name` varchar(64) NOT NULL default '', `Name` varchar(64) NOT NULL default '',
`Type` enum('Local','Remote','File','Ffmpeg','Libvlc') NOT NULL default 'Local', `Type` enum('Local','Remote','File','Ffmpeg','Libvlc','cURL') NOT NULL default 'Local',
`Device` tinytext, `Device` tinytext,
`Channel` tinytext, `Channel` tinytext,
`Format` int(10) unsigned default NULL, `Format` int(10) unsigned default NULL,
@ -313,7 +313,7 @@ DROP TABLE IF EXISTS `Monitors`;
CREATE TABLE `Monitors` ( CREATE TABLE `Monitors` (
`Id` int(10) unsigned NOT NULL auto_increment, `Id` int(10) unsigned NOT NULL auto_increment,
`Name` varchar(64) NOT NULL default '', `Name` varchar(64) NOT NULL default '',
`Type` enum('Local','Remote','File','Ffmpeg','Libvlc') NOT NULL default 'Local', `Type` enum('Local','Remote','File','Ffmpeg','Libvlc','cURL') NOT NULL default 'Local',
`Function` enum('None','Monitor','Modect','Record','Mocord','Nodect') NOT NULL default 'Monitor', `Function` enum('None','Monitor','Modect','Record','Mocord','Nodect') NOT NULL default 'Monitor',
`Enabled` tinyint(3) unsigned NOT NULL default '1', `Enabled` tinyint(3) unsigned NOT NULL default '1',
`LinkedMonitors` varchar(255) NOT NULL default '', `LinkedMonitors` varchar(255) NOT NULL default '',

View File

@ -3,9 +3,15 @@
-- --
-- --
-- Add Libvlc monitor type -- Add Libvlc and cURL monitor types
-- --
ALTER TABLE Controls modify column Type enum('Local','Remote','Ffmpeg','Libvlc') NOT NULL default 'Local'; ALTER TABLE Controls modify column Type enum('Local','Remote','Ffmpeg','Libvlc','cURL') NOT NULL default 'Local';
ALTER TABLE MonitorPresets modify column Type enum('Local','Remote','File','Ffmpeg','Libvlc') NOT NULL default 'Local'; ALTER TABLE MonitorPresets modify column Type enum('Local','Remote','File','Ffmpeg','Libvlc','cURL') NOT NULL default 'Local';
ALTER TABLE Monitors modify column Type enum('Local','Remote','File','Ffmpeg','Libvlc') NOT NULL default 'Local'; ALTER TABLE Monitors modify column Type enum('Local','Remote','File','Ffmpeg','Libvlc','cURL') NOT NULL default 'Local';
--
-- Add required fields for cURL authenication
--
ALTER TABLE `Monitors` ADD `User` VARCHAR(32) NOT NULL AFTER `SubPath`;
ALTER TABLE `Monitors` ADD `Pass` VARCHAR(32) NOT NULL AFTER `User`;

View File

@ -26,6 +26,7 @@ zm_SOURCES = \
zm_comms.cpp \ zm_comms.cpp \
zm_config.cpp \ zm_config.cpp \
zm_coord.cpp \ zm_coord.cpp \
zm_curl_camera.cpp \
zm.cpp \ zm.cpp \
zm_db.cpp \ zm_db.cpp \
zm_logger.cpp \ zm_logger.cpp \
@ -76,6 +77,7 @@ noinst_HEADERS = \
zm_config_defines.h \ zm_config_defines.h \
zm_config.h \ zm_config.h \
zm_coord.h \ zm_coord.h \
zm_curl_camera.h \
zm_db.h \ zm_db.h \
zm_logger.h \ zm_logger.h \
zm_event.h \ zm_event.h \

View File

@ -59,6 +59,7 @@ public:
bool IsFile() const { return( type == FILE_SRC ); } bool IsFile() const { return( type == FILE_SRC ); }
bool IsFfmpeg() const { return( type == FFMPEG_SRC ); } bool IsFfmpeg() const { return( type == FFMPEG_SRC ); }
bool IsLibvlc() const { return( type == LIBVLC_SRC ); } bool IsLibvlc() const { return( type == LIBVLC_SRC ); }
bool IscURL() const { return( type == CURL_SRC ); }
unsigned int Width() const { return( width ); } unsigned int Width() const { return( width ); }
unsigned int Height() const { return( height ); } unsigned int Height() const { return( height ); }
unsigned int Colours() const { return( colours ); } unsigned int Colours() const { return( colours ); }

View File

@ -18,26 +18,24 @@
// //
#include "zm.h" #include "zm.h"
#include "zm_curl_camera.h"
#if HAVE_LIBCURL #if HAVE_LIBCURL
static FILE* curldebugfile = NULL; // Remove later #define CURL_MAXRETRY 5
const char* content_length_match = "Content-Length:";
#include "zm_curl_camera.h" const char* content_type_match = "Content-Type:";
size_t content_length_match_len;
size_t content_type_match_len;
cURLCamera::cURLCamera( int p_id, const std::string &p_path, const std::string &p_user, const std::string &p_pass, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ) : cURLCamera::cURLCamera( int p_id, const std::string &p_path, const std::string &p_user, const std::string &p_pass, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ) :
Camera( p_id, 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 ), Camera( p_id, 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 ),
mPath( p_path ), mUser( p_user ), mPass ( p_pass ) mPath( p_path ), mUser( p_user ), mPass ( p_pass ), bTerminate( false ), mode ( MODE_UNSET )
{ {
c = NULL;
if ( capture ) if ( capture )
{ {
Initialise(); Initialise();
c = curl_easy_init();
if(c == NULL) {
Fatal("Failed getting easy handle from libcurl");
}
} }
} }
@ -45,57 +43,71 @@ cURLCamera::~cURLCamera()
{ {
if ( capture ) if ( capture )
{ {
if(c != NULL) {
curl_easy_cleanup(c);
c = NULL;
}
Terminate(); Terminate();
} }
} }
void cURLCamera::Initialise() void cURLCamera::Initialise()
{ {
ret = curl_global_init(CURL_GLOBAL_ALL); content_length_match_len = strlen(content_length_match);
if(ret != CURLE_OK) { content_type_match_len = strlen(content_type_match);
Fatal("libcurl initialization failed: ", curl_easy_strerror(ret));
/* cURL initialization */
cRet = curl_global_init(CURL_GLOBAL_ALL);
if(cRet != CURLE_OK) {
Fatal("libcurl initialization failed: ", curl_easy_strerror(cRet));
} }
Debug(2,"libcurl version: %s",curl_version()); Debug(2,"libcurl version: %s",curl_version());
curldebugfile = fopen("/tmp/curl_debug.log","w"); // Remove later /* Create the shared data mutex */
nRet = pthread_mutex_init(&shareddata_mutex, NULL);
if(nRet != 0) {
Fatal("Shared data mutex creation failed: %s",strerror(nRet));
}
/* Create the data available condition variable */
nRet = pthread_cond_init(&data_available_cond, NULL);
if(nRet != 0) {
Fatal("Data available condition variable creation failed: %s",strerror(nRet));
}
/* Create the request complete condition variable */
nRet = pthread_cond_init(&request_complete_cond, NULL);
if(nRet != 0) {
Fatal("Request complete condition variable creation failed: %s",strerror(nRet));
}
/* Create the thread */
nRet = pthread_create(&thread, NULL, thread_func_dispatcher, this);
if(nRet != 0) {
Fatal("Thread creation failed: %s",strerror(nRet));
}
} }
void cURLCamera::Terminate() void cURLCamera::Terminate()
{ {
/* Signal the thread to terminate */
bTerminate = true;
/* Wait for thread termination */
pthread_join(thread, NULL);
/* 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(); curl_global_cleanup();
fclose(curldebugfile); // Remove later
} }
int cURLCamera::PrimeCapture() int cURLCamera::PrimeCapture()
{ {
Info( "Priming capture from %s", mPath.c_str() ); Info( "Priming capture from %s", mPath.c_str() );
/* Temporary */
curl_easy_setopt(c, CURLOPT_VERBOSE, 1);
curl_easy_setopt(c, CURLOPT_STDERR, curldebugfile);
ret = curl_easy_setopt(c, CURLOPT_URL, mPath.c_str());
if(ret != CURLE_OK)
Fatal("Failed setting libcurl URL. error %d: ", curl_easy_strerror(ret));
ret = curl_easy_setopt(c, CURLOPT_HEADERFUNCTION, &cURLCamera::header_callback);
if(ret != CURLE_OK)
Fatal("Failed setting libcurl header callback function. error %d: ", curl_easy_strerror(ret));
ret = curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, &cURLCamera::data_callback);
if(ret != CURLE_OK)
Fatal("Failed setting libcurl header callback function. error %d: ", curl_easy_strerror(ret));
ret = curl_easy_setopt(c, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
if(ret != CURLE_OK)
Warning("Failed setting libcurl acceptable http authenication methods. error %d: ", curl_easy_strerror(ret));
return 0; return 0;
} }
@ -107,20 +119,165 @@ int cURLCamera::PreCapture()
int cURLCamera::Capture( Image &image ) int cURLCamera::Capture( Image &image )
{ {
bool frameComplete = false;
uint8_t* directbuffer; uint8_t* directbuffer;
// 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;
/* Request a writeable buffer of the target image */ /* Request a writeable buffer of the target image */
directbuffer = image.WriteBuffer(width, height, colours, subpixelorder); directbuffer = image.WriteBuffer(width, height, colours, subpixelorder);
if(directbuffer == NULL) { if(directbuffer == NULL) {
Error("Failed requesting writeable buffer for the captured image."); Error("Failed requesting writeable buffer for the captured image");
return (-1); return (-1);
} }
//success = curl_easy_perform(easyhandle); while (!frameComplete) {
return (0); lock();
if(mode == MODE_STREAM) {
/* Subheader parsing */
while(!SubHeadersParsingComplete && !need_more_data) {
size_t crlf_start, crlf_end, crlf_size;
std::string subheader;
/* Find crlf start */
crlf_start = memcspn(databuffer,"\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(subheader.compare(0,content_length_match_len,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(subheader.compare(0,content_type_match_len,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 <= 0) {
/* 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 */
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 -18;
}
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 */
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 -19;
}
}
} /* MODE_SINGLE */
unlock();
} /* frameComplete loop */
if(!frameComplete)
return -1;
return 0;
} }
int cURLCamera::PostCapture() int cURLCamera::PostCapture()
@ -129,12 +286,228 @@ int cURLCamera::PostCapture()
return( 0 ); return( 0 );
} }
size_t cURLCamera::data_callback(void *buffer, size_t size, size_t nmemb, void *userdata) { 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 */
nRet = pthread_cond_signal(&data_available_cond);
if(nRet != 0) {
Error("Failed signaling data available condition variable: %s",strerror(nRet));
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) {
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(content_type.compare(0, strlen(multipart_match), multipart_match) == 0) {
Debug(7,"Content type matched as multipart/x-mixed-replace");
mode = MODE_STREAM;
} else if(content_type.compare(0, strlen(image_jpeg_match), image_jpeg_match) == 0) {
Debug(7,"Content type matched as image/jpeg");
mode = MODE_SINGLE;
} else {
Fatal("Unknown Content-Type: %s", content_type.c_str());
}
unlock();
}
/* Return bytes processed */
return size*nmemb;
}
void* cURLCamera::thread_func()
{
int tRet;
double dSize;
c = curl_easy_init();
if(c == NULL) {
Fatal("Failed getting easy handle from libcurl");
}
/* Set URL */
cRet = curl_easy_setopt(c, CURLOPT_URL, mPath.c_str());
if(cRet != CURLE_OK)
Fatal("Failed setting libcurl URL: %s", curl_easy_strerror(cRet));
/* Header callback */
cRet = curl_easy_setopt(c, CURLOPT_HEADERFUNCTION, &header_callback_dispatcher);
if(cRet != CURLE_OK)
Fatal("Failed setting libcurl header callback function: %s", curl_easy_strerror(cRet));
cRet = curl_easy_setopt(c, CURLOPT_HEADERDATA, this);
if(cRet != CURLE_OK)
Fatal("Failed setting libcurl header callback object: %s", curl_easy_strerror(cRet));
/* Data callback */
cRet = curl_easy_setopt(c, CURLOPT_WRITEFUNCTION, &data_callback_dispatcher);
if(cRet != CURLE_OK)
Fatal("Failed setting libcurl data callback function: %s", curl_easy_strerror(cRet));
cRet = curl_easy_setopt(c, CURLOPT_WRITEDATA, this);
if(cRet != CURLE_OK)
Fatal("Failed setting libcurl data callback object: %s", curl_easy_strerror(cRet));
/* Progress callback */
cRet = curl_easy_setopt(c, CURLOPT_PROGRESSFUNCTION, &progress_callback_dispatcher);
if(cRet != CURLE_OK)
Fatal("Failed setting libcurl progress callback function: %s", curl_easy_strerror(cRet));
cRet = curl_easy_setopt(c, CURLOPT_PROGRESSDATA, this);
if(cRet != CURLE_OK)
Fatal("Failed setting libcurl progress callback object: %s", curl_easy_strerror(cRet));
/* Set username and password */
if(!mUser.empty()) {
cRet = curl_easy_setopt(c, CURLOPT_USERNAME, mUser.c_str());
if(cRet != CURLE_OK)
Error("Failed setting username: %s", curl_easy_strerror(cRet));
}
if(!mPass.empty()) {
cRet = curl_easy_setopt(c, CURLOPT_PASSWORD, mPass.c_str());
if(cRet != CURLE_OK)
Error("Failed setting password: %s", curl_easy_strerror(cRet));
}
/* Authenication preference */
cRet = curl_easy_setopt(c, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
if(cRet != CURLE_OK)
Warning("Failed setting libcurl acceptable http authenication methods: %s", curl_easy_strerror(cRet));
/* Work loop */
for(unsigned int attempt=0;attempt<CURL_MAXRETRY;attempt++) {
tRet = 0;
while(!bTerminate) {
/* Do the work */
cRet = curl_easy_perform(c);
if(mode == MODE_SINGLE) {
if(cRet != CURLE_OK) {
break;
}
/* Attempt to get the size of the file */
cRet = curl_easy_getinfo(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);
}
/* Signal the request completed condition variable */
tRet = pthread_cond_signal(&request_complete_cond);
if(tRet != 0) {
Error("Failed signaling request completed condition variable: %s",strerror(tRet));
}
/* Unlock */
unlock();
} else if (mode == MODE_STREAM) {
break;
}
}
/* Return value checking */
if(cRet == CURLE_ABORTED_BY_CALLBACK || bTerminate) {
break;
} else if (cRet != CURLE_OK) {
Error("cURL Request failed: %s",curl_easy_strerror(cRet));
if(attempt<4) {
Error("Retrying.. Attempt %d of %d: %s",attempt+1,CURL_MAXRETRY);
}
tRet = -50;
}
}
/* Cleanup */
curl_easy_cleanup(c);
c = NULL;
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; return 0;
} }
size_t cURLCamera::header_callback( void *buffer, size_t size, size_t nmemb, void *userdata) { /* These functions call the functions in the class for the correct object */
return 0; size_t data_callback_dispatcher(void *buffer, size_t size, size_t nmemb, void *userdata)
{
return ((cURLCamera*)userdata)->data_callback(buffer,size,nmemb,userdata);
} }
size_t header_callback_dispatcher(void *buffer, size_t size, size_t nmemb, void *userdata)
{
return ((cURLCamera*)userdata)->header_callback(buffer,size,nmemb,userdata);
}
int progress_callback_dispatcher(void *userdata, double dltotal, double dlnow, double ultotal, double ulnow)
{
return ((cURLCamera*)userdata)->progress_callback(userdata,dltotal,dlnow,ultotal,ulnow);
}
void* thread_func_dispatcher(void* object) {
return ((cURLCamera*)object)->thread_func();
}
#endif // HAVE_LIBCURL #endif // HAVE_LIBCURL

View File

@ -25,11 +25,18 @@
#include "zm_buffer.h" #include "zm_buffer.h"
#include "zm_regexp.h" #include "zm_regexp.h"
#include "zm_utils.h" #include "zm_utils.h"
#include "zm_signal.h"
#include <string>
#include <deque>
#if HAVE_CURL_CURL_H #if HAVE_CURL_CURL_H
#include <curl/curl.h> #include <curl/curl.h>
#endif #endif
#define MODE_UNSET 0
#define MODE_SINGLE 1
#define MODE_STREAM 2
// //
// Class representing 'remote' cameras, i.e. those which are // Class representing 'remote' cameras, i.e. those which are
// accessed over a network connection. // accessed over a network connection.
@ -45,6 +52,18 @@ protected:
CURL* c; CURL* c;
#endif #endif
/* Shared data */
bool bTerminate;
int 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: public:
cURLCamera( int p_id, const std::string &path, const std::string &username, const std::string &password, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture ); cURLCamera( int p_id, const std::string &path, const std::string &username, const std::string &password, int p_width, int p_height, int p_colours, int p_brightness, int p_contrast, int p_hue, int p_colour, bool p_capture );
~cURLCamera(); ~cURLCamera();
@ -63,11 +82,22 @@ public:
size_t data_callback(void *buffer, size_t size, size_t nmemb, void *userdata); 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); 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); int debug_callback(CURL* handle, curl_infotype type, char* str, size_t strsize, void* data);
void* thread_func();
int lock();
int unlock();
private: private:
CURLcode ret; int nRet;
CURLcode cRet;
}; };
/* 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 // ZM_CURL_CAMERA_H #endif // ZM_CURL_CAMERA_H

View File

@ -345,7 +345,7 @@ switch ( $tab )
?> ?>
<tr><th scope="row"><?= $SLANG['Name'] ?></th><td><input type="text" name="newControl[Name]" value="<?= validHtmlStr($newControl['Name']) ?>" size="24"/></td></tr> <tr><th scope="row"><?= $SLANG['Name'] ?></th><td><input type="text" name="newControl[Name]" value="<?= validHtmlStr($newControl['Name']) ?>" size="24"/></td></tr>
<?php <?php
$types = array( 'Local'=>$SLANG['Local'], 'Remote'=>$SLANG['Remote'], 'Ffmpeg'=>$SLANG['Ffmpeg'], 'Libvlc'=>$SLANG['Libvlc'] ); $types = array( 'Local'=>$SLANG['Local'], 'Remote'=>$SLANG['Remote'], 'Ffmpeg'=>$SLANG['Ffmpeg'], 'Libvlc'=>$SLANG['Libvlc'], 'cURL'=>"cURL");
?> ?>
<tr><th scope="row"><?= $SLANG['Type'] ?></th><td><?= buildSelect( "newControl[Type]", $types ); ?></td></tr> <tr><th scope="row"><?= $SLANG['Type'] ?></th><td><?= buildSelect( "newControl[Type]", $types ); ?></td></tr>
<tr><th scope="row"><?= $SLANG['Protocol'] ?></th><td><input type="text" name="newControl[Protocol]" value="<?= validHtmlStr($newControl['Protocol']) ?>" size="24"/></td></tr> <tr><th scope="row"><?= $SLANG['Protocol'] ?></th><td><input type="text" name="newControl[Protocol]" value="<?= validHtmlStr($newControl['Protocol']) ?>" size="24"/></td></tr>

View File

@ -506,10 +506,12 @@ if ( $tab != 'source' || ($newMonitor['Type'] != 'Local' && $newMonitor['Type']
<input type="hidden" name="newMonitor[Method]" value="<?= validHtmlStr($newMonitor['Method']) ?>"/> <input type="hidden" name="newMonitor[Method]" value="<?= validHtmlStr($newMonitor['Method']) ?>"/>
<?php <?php
} }
if ( $tab != 'source' || ($newMonitor['Type'] != 'Remote' && $newMonitor['Type'] != 'File' && $newMonitor['Type'] != 'Ffmpeg' && $newMonitor['Type'] != 'Libvlc') ) if ( $tab != 'source' || ($newMonitor['Type'] != 'Remote' && $newMonitor['Type'] != 'File' && $newMonitor['Type'] != 'Ffmpeg' && $newMonitor['Type'] != 'Libvlc' && $newMonitor['Type'] != 'cURL') )
{ {
?> ?>
<input type="hidden" name="newMonitor[Path]" value="<?= validHtmlStr($newMonitor['Path']) ?>"/> <input type="hidden" name="newMonitor[Path]" value="<?= validHtmlStr($newMonitor['Path']) ?>"/>
<input type="hidden" name="newMonitor[User]" value="<?= validHtmlStr($newMonitor['User']) ?>"/>
<input type="hidden" name="newMonitor[Pass]" value="<?= validHtmlStr($newMonitor['Pass']) ?>"/>
<?php <?php
} }
if ( $tab != 'source' ) if ( $tab != 'source' )