diff --git a/README.md b/README.md index ad7b1c10..2d9265dd 100644 --- a/README.md +++ b/README.md @@ -159,6 +159,21 @@ The connection object stores the curl easy handle in an instance variable and uses that for the lifetime of the object. This means curl will [automatically reuse connections][curl_keepalive] made with that handle. +## Progress callback + +Two wrapper functions are provided to setup the progress callback for uploads/downloads. + +Calling `conn->SetFileProgressCallback(callback)` with a callback parameter matching the prototype `int progress_callback(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow)` will setup the progress callback. + +Calling `conn->SetFileProgressCallbackData(data)` is optional. This will set the data pointer which is the first parameter fed back to the progress callback - `clientp`. If this isn't set then `clientp` will default to the connection object `conn`. + +```cpp +// set CURLOPT_NOPROGRESS +// set CURLOPT_PROGRESSFUNCTION +conn->SetFileProgressCallback(progressFunc); +// set CURLOPT_PROGRESSDATA +conn->SetFileProgressCallbackData(data); +``` ## Thread Safety restclient-cpp leans heavily on libcurl as it aims to provide a thin wrapper diff --git a/include/restclient-cpp/connection.h b/include/restclient-cpp/connection.h index df3423fb..608fcef6 100644 --- a/include/restclient-cpp/connection.h +++ b/include/restclient-cpp/connection.h @@ -81,6 +81,10 @@ class Connection { * Member 'followRedirects' contains whether or not to follow redirects * @var Info::maxRedirects * Member 'maxRedirects' contains the maximum number of redirect to follow (-1 unlimited) + * @var Info::progressFn + * Member 'progressFn' file progress callback function + * @var Info::progressFnData + * Member 'progressFnData' file progress callback data * @var Info::basicAuth * Member 'basicAuth' contains information about basic auth * @var basicAuth::username @@ -109,6 +113,8 @@ class Connection { bool followRedirects; int maxRedirects; bool noSignal; + curl_progress_callback progressFn; + void* progressFnData; struct { std::string username; std::string password; @@ -136,6 +142,12 @@ class Connection { // set connection timeout to seconds void SetTimeout(int seconds); + // set file progress callback + void SetFileProgressCallback(curl_progress_callback progressFn); + + // set file progress callback data + void SetFileProgressCallbackData(void* data); + // set to not use signals void SetNoSignal(bool no); @@ -204,6 +216,8 @@ class Connection { bool followRedirects; int maxRedirects; bool noSignal; + curl_progress_callback progressFn; + void* progressFnData; struct { std::string username; std::string password; diff --git a/source/connection.cc b/source/connection.cc index 590972f5..224d4a8e 100644 --- a/source/connection.cc +++ b/source/connection.cc @@ -36,6 +36,8 @@ RestClient::Connection::Connection(const std::string& baseUrl) this->followRedirects = false; this->maxRedirects = -1l; this->noSignal = false; + this->progressFn = NULL; + this->progressFnData = NULL; } RestClient::Connection::~Connection() { @@ -60,6 +62,8 @@ RestClient::Connection::GetInfo() { ret.followRedirects = this->followRedirects; ret.maxRedirects = this->maxRedirects; ret.noSignal = this->noSignal; + ret.progressFn = this->progressFn; + ret.progressFnData = this->progressFnData; ret.basicAuth.username = this->basicAuth.username; ret.basicAuth.password = this->basicAuth.password; ret.customUserAgent = this->customUserAgent; @@ -188,6 +192,29 @@ RestClient::Connection::SetNoSignal(bool no) { this->noSignal = no; } +/** + * @brief set file progress callback function + * + * @param callback function pointer + * + */ +void +RestClient::Connection::SetFileProgressCallback(curl_progress_callback + progressFn) { + this->progressFn = progressFn; +} + +/** + * @brief set file progress callback data + * + * @param callback data pointer + * + */ +void +RestClient::Connection::SetFileProgressCallbackData(void* data) { + this->progressFnData = data; +} + /** * @brief set username and password for basic auth * @@ -354,6 +381,23 @@ RestClient::Connection::performCurlRequest(const std::string& uri) { curl_easy_setopt(this->curlHandle, CURLOPT_NOSIGNAL, 1); } + // set file progress callback + if (this->progressFn) { + curl_easy_setopt(this->curlHandle, CURLOPT_NOPROGRESS, 0); + curl_easy_setopt(this->curlHandle, + CURLOPT_PROGRESSFUNCTION, + this->progressFn); + if (this->progressFnData) { + curl_easy_setopt(this->curlHandle, + CURLOPT_PROGRESSDATA, + this->progressFnData); + } else { + curl_easy_setopt(this->curlHandle, + CURLOPT_PROGRESSDATA, + this); + } + } + // if provided, supply CA path if (!this->caInfoFilePath.empty()) { curl_easy_setopt(this->curlHandle, CURLOPT_CAINFO, diff --git a/test/test_connection.cc b/test/test_connection.cc index dfc6b67c..379aa35d 100644 --- a/test/test_connection.cc +++ b/test/test_connection.cc @@ -232,6 +232,26 @@ TEST_F(ConnectionTest, TestNoSignal) EXPECT_EQ(200, res.code); } +TEST_F(ConnectionTest, TestSetProgress) +{ + static double totalToDownload = 0; + static double totalDownloaded = 0; + + auto progressCallback = [](void* pData, double downloadTotal, double downloaded, double uploadTotal, double uploaded) -> int { + totalToDownload = downloadTotal; + totalDownloaded = downloaded; + return 0; + }; + + conn->SetFileProgressCallback(progressCallback); + conn->SetFileProgressCallbackData(NULL); + + RestClient::Response res = conn->get("/anything/{test_data}"); + EXPECT_EQ(200, res.code); + EXPECT_GT(totalDownloaded, 0); + EXPECT_EQ(totalDownloaded, totalToDownload); +} + TEST_F(ConnectionTest, TestProxy) { conn->SetProxy("127.0.0.1:3128");