I perform curl_multi_perform in a background thread of my GUI app. I am
downloading data from HTTPS server. Usually, all is working, however after
internet connection is lost and restored again, I receive CURLE_COULDNT_
RESOLVE_HOST and no new download is ever started again (the server is OK -
if I stop app and start it again, all is working again until the connection
lost)
I am using curl with iOS ssl library.
My code is attached (MyStringAnsi is more or less std::string)
#include "./DataDownloader.h"
#if defined(_MSC_VER) && defined(__clang__)
#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN
#endif
#ifdef _MSC_VER
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "libcurl.lib")
#include <winsock2.h>
#else
#include <unistd.h>
#endif
#include <cstdlib>
#include <thread>
#include <chrono>
#include <curl/curl.h>
//===================================================================================
// Data downloader
//===================================================================================
const std::vector<char> DataDownloader::EMPTY_DATA = std::vector<char>();
/// <summary>
/// ctor
/// </summary>
DataDownloader::DataDownloader() : internetConnectionAvailable(true),
dlBeginCallback(nullptr), dlEndedCallback(nullptr),
connectionFailureCallback(nullptr), connectionBackCallback(nullptr),
lastID(0), running(false)
{
curl_global_init(CURL_GLOBAL_DEFAULT);
this->curlm = curl_multi_init();
this->lastFailureTime = std::chrono::time_point<std::chrono::high_resolution_clock>::max();
}
/// <summary>
/// dtor
/// </summary>
DataDownloader::~DataDownloader()
{
for (auto j : this->jobs)
{
j.second->Kill();
}
if (this->dlThread.joinable())
{
this->dlThread.join();
}
curl_multi_cleanup(this->curlm);
curl_global_cleanup();
}
/// <summary>
/// Manually enable / disable internet connection
/// </summary>
/// <param name="val"></param>
void DataDownloader::SetIsInternetConnectionAvailable(bool val)
{
this->internetConnectionAvailable = val;
}
/// <summary>
/// Test if we manualy enabled / disabled internet connection is active
/// </summary>
/// <returns></returns>
bool DataDownloader::IsInternetConnectionAvailable() const
{
return this->internetConnectionAvailable;
}
/// <summary>
/// Test if we can download data based on result of last download.
/// If last download ended with failure (some CURL error), and time since this failure
/// is < N ms, returns false
/// otherwise returns true - new download can be "added" and tries if it succeed
///
/// Warning:
/// If download failes from server A, it does not mean that it will fail from server B.
/// However, this is not used and failure is globall for all servers. One faild = all failed
/// </summary>
/// <returns></returns>
bool DataDownloader::CanDownloadAfterFailure() const
{
if (this->lastFailureTime.load() == std::chrono::time_point<std::chrono::high_resolution_clock>::max())
{
return true;
}
auto curTime = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(curTime - this->lastFailureTime.load()).count();
if (duration < 300)
{
return false;
}
return true;
}
/// <summary>
/// Check if download job exist
/// </summary>
/// <param name="downloadID">download job ID</param>
/// <returns>true / false</returns>
bool DataDownloader::Exist(DownloadID downloadID) const
{
if (this->jobs.size() == 0)
{
return false;
}
auto it = this->jobs.find(downloadID);
if (it == this->jobs.end())
{
return false;
}
return true;
}
/// <summary>
/// Test if any of jobs has the given URL
/// </summary>
/// <param name="url">downloaded URL</param>
/// <returns>true / false</returns>
bool DataDownloader::ExistURL(const MyStringAnsi & url) const
{
if (this->jobs.size() == 0)
{
return false;
}
for (auto & j : this->jobs)
{
if (j.second->url == url)
{
return true;
}
}
return false;
}
void DataDownloader::SetDownloadBeginCallback(std::function<void()> dlBeginCallback)
{
this->dlBeginCallback = dlBeginCallback;
}
void DataDownloader::SetDownloadEndedCallback(std::function<void()> dlEndedCallback)
{
this->dlEndedCallback = dlEndedCallback;
}
void DataDownloader::SetNoInternetConnectionFailureCallback(std::function<void()> connectionFailureCallback,
std::function<void()> connectionBackCallback)
{
this->connectionFailureCallback = connectionFailureCallback;
this->connectionBackCallback = connectionBackCallback;
}
/// <summary>
/// Get download job by its DownloadID
/// </summary>
/// <param name="downloadID">download job ID</param>
/// <returns>download job or nullptr</returns>
std::shared_ptr<DownloadJob> DataDownloader::GetDownloadJob(DownloadID downloadID) const
{
auto it = this->jobs.find(downloadID);
if (it == this->jobs.end())
{
return nullptr;
}
return it->second;
}
/// <summary>
/// Add new download to queue and start it immediately
/// If there is no internet connection, download wont be added at all !!!
/// If last download failed, new download will be added only after N ms timeout
/// If download with the same URL exist, do not add and return nullptr
/// The name of download will be the same as url
/// </summary>
/// <param name="url">URL for download</param>
/// <returns>new DownloadJob or nullptr</returns>
std::shared_ptr<DownloadJob> DataDownloader::AddDownload(const MyStringAnsi & url)
{
return this->AddDownload(url, url);
}
/// <summary>
/// Add new download to queue and start it immediately
/// If there is no internet connection, download wont be added at all !!!
/// If last download failed, new download will be added only after N ms timeout
/// If download with the same URL exist, do not add and return nullptr
/// </summary>
/// <param name="url">URL for download</param>
/// <param name="name">name of download</param>
/// <returns>new DownloadJob or nullptr</returns>
std::shared_ptr<DownloadJob> DataDownloader::AddDownload(const MyStringAnsi & url, const MyStringAnsi & name)
{
return this->AddDownload(url, name, nullptr, DATA_TYPE::BINARY);
}
/// <summary>
/// Add new download to queue and start it immediately
/// If there is no internet connection, download wont be added at all !!!
/// If last download failed, new download will be added only after N ms timeout
/// If download with the same URL exist, do not add and return nullptr
/// Can specify onFinish callback, that is called after data are downloaded
/// (no matter if successfully or not)
/// </summary>
/// <param name="url">URL for download</param>
/// <param name="name">name of download</param>
/// <param name="onFinish">onFinish callback (have pointer to dowloadJob)</param>
/// <param name="dataType">specify data type. For text data, 0 is appended to downloaded data</param>
/// <returns>new DownloadJob or nullptr</returns>
std::shared_ptr<DownloadJob> DataDownloader::AddDownload(const MyStringAnsi & url, const MyStringAnsi & name,
std::function<void(std::shared_ptr<DownloadJob>)> onFinish, DATA_TYPE dataType)
{
return this->AddDownload(url, name, onFinish, nullptr, dataType);
}
/// <summary>
/// Add new download to queue and start it immediately
/// If there is no internet connection, download wont be added at all !!!
/// If last download failed, new download will be added only after N ms timeout
/// If download with the same URL exist, do not add and return nullptr
/// Can specify onFinish callback, that is called after data are downloaded
/// (no matter if successfully or not)
/// Can specify onFinishMainThread callback, that is called after data are downloaded
/// (no matter if successfully or not) on main thread -> for this to work, Update method must
/// be called
/// </summary>
/// <param name="url">URL for download</param>
/// <param name="name">name of download</param>
/// <param name="onFinish">onFinish callback (have pointer to dowloadJob)</param>
/// <param name="onFinishMainThread">onFinishMainThread callback (have pointer to dowloadJob)</param>
/// <param name="dataType">specify data type. For text data, 0 is appended to downloaded data</param>
/// <returns>new DownloadJob or nullptr</returns>
std::shared_ptr<DownloadJob> DataDownloader::AddDownload(const MyStringAnsi & url, const MyStringAnsi & name,
std::function<void(std::shared_ptr<DownloadJob>)> onFinish,
std::function<void(std::shared_ptr<DownloadJob>)> onFinishMainThread, DATA_TYPE dataType)
{
if (this->IsInternetConnectionAvailable() == false)
{
return nullptr;
}
if (this->CanDownloadAfterFailure() == false)
{
return nullptr;
}
if (this->activeURLs.find(url) != this->activeURLs.end())
{
return nullptr;
}
std::shared_ptr<DownloadJob> job = std::shared_ptr<DownloadJob>(new DownloadJob(url, name, lastID, this));
job->onFinish = onFinish;
job->onFinishMainThread = onFinishMainThread;
job->dataType = dataType;
lastID++;
//https://solarianprogrammer.com/2012/10/17/cpp-11-async-tutorial/
//http://stackoverflow.com/questions/13669094/how-to-in-c11-use-stdasync-on-a-member-function
this->jobs[job->id] = job;
this->activeURLs.insert(url);
job->Start();
return job;
}
/// <summary>
/// Update called from main thread
/// It will call onFinishMainThread callbacks and remove finished downloads
/// </summary>
void DataDownloader::UpdateMainThread()
{
if (this->jobs.size() == 0)
{
return;
}
for (auto j : this->jobs)
{
if ((j.second->IsFinished()) && (j.second->onFinishMainThread != nullptr))
{
j.second->onFinishMainThread(j.second);
}
}
this->RemoveFinishedDownloads();
}
/// <summary>
/// Remve finished downloads
/// </summary>
void DataDownloader::RemoveFinishedDownloads()
{
if (this->jobs.size() == 0)
{
return;
}
std::list<DownloadID> finished;
for (auto j : this->jobs)
{
if (j.second->IsFinished())
{
finished.push_back(j.first);
}
}
for (auto dlId : finished)
{
auto job = this->GetDownloadJob(dlId);
job->RemoveFromDataDownloader();
}
}
/// <summary>
/// Check if something is downloading
/// </summary>
/// <returns></returns>
bool DataDownloader::IsDownloading() const
{
return this->running;
}
/// <summary>
/// Check if given download is downloading
/// </summary>
/// <param name="downloadID"></param>
/// <returns></returns>
bool DataDownloader::IsDownloading(DownloadID downloadID) const
{
return (this->GetDownloadJob(downloadID)->IsFinished() ? false : true);
}
/// <summary>
/// Try to manually start downloads if something failed
/// in automatic starts
/// </summary>
void DataDownloader::TryStart()
{
std::unique_lock<std::mutex> lck(dlThreadLock);
if (this->running.load())
{
return;
}
if (this->jobQueue.size() == 0)
{
return;
}
this->Start();
}
void DataDownloader::WaitToFinish(DownloadID downloadID)
{
auto j = this->GetDownloadJob(downloadID);
unsigned timeOutMiliSeconds = 10;
while (j->IsFinished() == false)
{
#ifdef WIN32
Sleep(timeOutMiliSeconds);
#else
usleep(timeOutMiliSeconds * 1000);
#endif
this->TryStart();
}
j->RemoveFromDataDownloader();
}
/// <summary>
/// Start downloading in background thread
/// </summary>
void DataDownloader::Start()
{
this->running.store(true);
if (this->dlThread.joinable())
{
this->dlThread.join();
}
this->dlThread = std::thread(&DataDownloader::UpdateBackgroundThread, this);
}
void DataDownloader::DownloadFailedBackgroundThread(std::shared_ptr<DownloadJob> job, CURLMsg * msg)
{
switch (msg->data.result)
{
case CURLE_COULDNT_CONNECT:
case CURLE_COULDNT_RESOLVE_HOST:
case CURLE_COULDNT_RESOLVE_PROXY:
case CURLE_OPERATION_TIMEDOUT:
printf("No internet connection\n");
if (this->connectionFailureCallback != nullptr)
{
this->connectionFailureCallback();
}
break;
default:
break;
}
printf("DL error: %d - %s <%s = %lu>\n", msg->data.result, curl_easy_strerror(msg->data.result), job->url.c_str(), job->data.size());
this->lastFailureTime = std::chrono::high_resolution_clock::now();
}
void DataDownloader::DownloadOKBackgroundThread(std::shared_ptr<DownloadJob> job, CURLMsg * msg)
{
if (this->lastFailureTime.load() != std::chrono::time_point<std::chrono::high_resolution_clock>::max())
{
if (this->connectionBackCallback != nullptr)
{
this->connectionBackCallback();
}
this->lastFailureTime = std::chrono::time_point<std::chrono::high_resolution_clock>::max();
}
printf("DL ok: <%s = %lu>\n", job->url.c_str(), job->data.size());
}
/// <summary>
/// Update cURL
/// </summary>
/// <returns>EXIT_FAILURE or EXIT_SUCCESS</returns>
int DataDownloader::UpdateBackgroundThread()
{
if (this->dlBeginCallback)
{
this->dlBeginCallback();
}
this->running.store(true);
int remainHandles = -1;
int queueSize = -1;
int descriptorsCount = -1;
long timeOutMs = -1;
fd_set R, W, E;
struct timeval T;
while (remainHandles)
{
std::unique_lock<std::mutex> lck(dlThreadLock);
CURLMcode mErr = curl_multi_perform(this->curlm, &remainHandles);
if (mErr != CURLM_OK)
{
printf("curl_multi_perform error %i\n", mErr);
}
if (remainHandles)
{
FD_ZERO(&R);
FD_ZERO(&W);
FD_ZERO(&E);
if (curl_multi_fdset(this->curlm, &R, &W, &E, &descriptorsCount))
{
printf("E: curl_multi_fdset\n");
return EXIT_FAILURE;
}
if (curl_multi_timeout(this->curlm, &timeOutMs))
{
printf("E: curl_multi_timeout\n");
return EXIT_FAILURE;
}
if (timeOutMs == -1)
{
timeOutMs = 100;
}
if (descriptorsCount == -1)
{
#ifdef WIN32
Sleep(timeOutMs);
#else
usleep(static_cast<useconds_t>(timeOutMs * 1000));
#endif
}
else
{
T.tv_sec = timeOutMs / 1000;
T.tv_usec = (timeOutMs % 1000) * 1000;
if (select(descriptorsCount + 1, &R, &W, &E, &T) < 0)
{
printf("E: select(%i,,,,%li): %i: %s\n", descriptorsCount + 1, timeOutMs, errno, strerror(errno));
return EXIT_FAILURE;
}
}
}
while (CURLMsg * msg = curl_multi_info_read(this->curlm, &queueSize))
{
if (msg->msg == CURLMSG_DONE)
{
CURL *e = msg->easy_handle;
//Find job with the curl handle
std::shared_ptr<DownloadJob> job = nullptr;
for (auto j : this->jobs)
{
if (j.second == nullptr)
{
continue;
}
if (j.second->curl == e)
{
job = j.second;
break;
}
}
if (job != nullptr)
{
job->resCode = static_cast<int>(msg->data.result);
if (job->resCode != 0)
{
this->DownloadFailedBackgroundThread(job, msg);
}
else //resCode == 0
{
this->DownloadOKBackgroundThread(job, msg);
if (job->dataType == DATA_TYPE::TEXT)
{
//append trailing 0 to data
job->data.push_back(0);
}
}
if ((job->onFinish != nullptr) && (job->shouldCancel == false))
{
//onFinish callback called only if download is complete
//not canceled
job->onFinish(job);
}
job->finished.store(true);
//unlock it, so we can "kill" job
//during killing job, mutex is locked again, because we are editing multi api
lck.unlock();
job->Kill();
lck.lock();
}
}
else
{
printf("E: CURLMsg (%d)\n", msg->msg);
}
}
if (this->jobQueue.size() != 0)
{
curl_multi_add_handle(this->curlm, this->jobQueue.front()->curl);
this->jobQueue.pop_front();
remainHandles++;
}
}
this->running.store(false);
if (this->dlEndedCallback)
{
this->dlEndedCallback();
}
return EXIT_SUCCESS;
}
//===================================================================================
// Static callbacks
//===================================================================================
/// <summary>
/// cURL write data callback
/// </summary>
/// <param name="data">downloaded data</param>
/// <param name="size">size of single value</param>
/// <param name="nmemb">number of values</param>
/// <param name="j">void pointer to DownloadJob</param>
/// <returns>byte size of downloaded data (size * nmemb)</returns>
size_t DownloadJob::curlWriteData(char *data, size_t size, size_t nmemb, void * j)
{
if (j == nullptr)
{
return 0;
}
DownloadJob * job = static_cast<DownloadJob *>(j);
job->data.insert(job->data.end(), data, data + size * nmemb);
return size * nmemb;
}
/// <summary>
/// cURL download progress info
/// </summary>
/// <param name="j">void pointer to DownloadJob</param>
/// <param name="dltotal"></param>
/// <param name="dlnow"></param>
/// <param name="ultotal"></param>
/// <param name="ulnow"></param>
/// <returns>1 for download cancel, 0 for continue</returns>
size_t DownloadJob::curlDownloadInfo(void * j,
curl_off_t dltotal, curl_off_t dlnow,
curl_off_t ultotal, curl_off_t ulnow)
{
if (j == nullptr)
{
return 0;
}
DownloadJob * job = static_cast<DownloadJob *>(j);
if (job->shouldCancel)
{
return 1;
}
return 0;
}
//===================================================================================
// Download job
//===================================================================================
DownloadJob::DownloadJob(const MyStringAnsi & url, const MyStringAnsi & name, DownloadID id, DataDownloader * dd) :
id(id),
url(url),
name(name),
data(DataDownloader::EMPTY_DATA),
finished(false),
dd(dd),
curl(nullptr),
resCode(-1),
shouldCancel(false),
onFinish(nullptr),
onFinishMainThread(nullptr),
dataType(DataDownloader::BINARY)
{
};
/// <summary>
/// ctor
/// </summary>
DownloadJob::~DownloadJob()
{
this->RemoveFromDataDownloader();
}
/// <summary>
/// Remove job from data downloader
/// If download is not finished, it will wait in while
/// loop with sleeps
/// </summary>
void DownloadJob::RemoveFromDataDownloader()
{
if (dd->Exist(this->id) == false)
{
return;
}
while (this->finished.load() == false)
{
#ifdef WIN32
Sleep(10);
#else
usleep(10 * 1000);
#endif
}
//std::lock_guard<std::mutex> lock(memCacheLock); ?
dd->jobs.erase(this->id);
dd->activeURLs.erase(this->url);
}
/// <summary>
/// Start downloading
/// </summary>
/// <returns></returns>
int DownloadJob::Start()
{
if (dd->jobs.find(this->id) == dd->jobs.end())
{
return EXIT_FAILURE;
}
this->curl = curl_easy_init();
if (this->curl == nullptr)
{
return EXIT_FAILURE;
}
curl_easy_setopt(this->curl, CURLOPT_URL, this->url.c_str());
curl_easy_setopt(this->curl, CURLOPT_FOLLOWLOCATION, 1L);
//curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); //Prevent "longjmp causes uninitialized stack frame" bug
//curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, "deflate");
curl_easy_setopt(this->curl, CURLOPT_WRITEFUNCTION, DownloadJob::curlWriteData);
curl_easy_setopt(this->curl, CURLOPT_WRITEDATA, this);
//enable cancellation: https://curl.haxx.se/libcurl/c/progressfunc.html - see CURLOPT_XFERINFOFUNCTION
curl_easy_setopt(this->curl, CURLOPT_XFERINFOFUNCTION, DownloadJob::curlDownloadInfo);
curl_easy_setopt(this->curl, CURLOPT_XFERINFODATA, this);
curl_easy_setopt(this->curl, CURLOPT_NOPROGRESS, 0L);
//abort if slower than 30 bytes/sec during 10 seconds
curl_easy_setopt(this->curl, CURLOPT_LOW_SPEED_LIMIT, 30L);
curl_easy_setopt(this->curl, CURLOPT_LOW_SPEED_TIME, 5L);
//https://stackoverflow.com/questions/1341644/curl-and-https-cannot-resolve-host
//curl_easy_setopt(this->curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
//curl_easy_setopt(this->curl, CURLOPT_VERBOSE, 1);
//enable https: https://curl.haxx.se/libcurl/c/https.html
if (this->url.Find("https://") != -1)
{
curl_easy_setopt(this->curl, CURLOPT_SSL_VERIFYPEER, 0L);
curl_easy_setopt(this->curl, CURLOPT_SSL_VERIFYHOST, 0L);
}
if (dd->running.load())
{
dd->jobQueue.push_back(dd->jobs[this->id]);
}
else
{
printf("not-running\n");
curl_multi_add_handle(dd->curlm, this->curl);
dd->Start();
}
return EXIT_SUCCESS;
}
/// <summary>
/// Kill cURL
/// If download is running, cancel it first
/// Canceled download will call again Kill method
/// </summary>
void DownloadJob::Kill()
{
if (this->curl == nullptr)
{
return;
}
if (dd->Exist(this->id) == false)
{
return;
}
if (this->finished.load() == false)
{
this->Cancel();
}
else
{
std::lock_guard<std::mutex> lck(dd->dlThreadLock);
if (this->curl == nullptr)
{
return;
}
curl_multi_remove_handle(dd->curlm, this->curl);
curl_easy_cleanup(this->curl);
this->curl = nullptr;
}
}
/// <summary>
/// Set cancel flag
/// </summary>
void DownloadJob::Cancel()
{
this->shouldCancel = true;
}
#ifndef _DATA_DOWNLOADER_H_
#define _DATA_DOWNLOADER_H_
#include <unordered_map>
#include <vector>
#include <memory>
#include <future>
#include <atomic>
#include <thread>
#include <mutex>
#include <list>
#include <set>
#include <functional>
#include <chrono>
#include "./Strings/MyString.h"
class DownloadJob;
struct CURLMsg;
typedef size_t DownloadID;
typedef void CURL;
typedef void CURLM;
#ifdef _WIN32
typedef long long curl_off_t;
#elif __ANDROID_API__
#include "curl/curlbuild.h" // typedef is here
#else
typedef long curl_off_t;
#endif
//===================================================================================
class DataDownloader
{
public:
typedef enum DATA_TYPE
{
BINARY = 0,
TEXT = 1
} DATA_TYPE;
DataDownloader();
~DataDownloader();
void SetIsInternetConnectionAvailable(bool val);
bool IsInternetConnectionAvailable() const;
std::shared_ptr<DownloadJob> AddDownload(const MyStringAnsi & url);
std::shared_ptr<DownloadJob> AddDownload(const MyStringAnsi & url, const MyStringAnsi & name);
std::shared_ptr<DownloadJob> AddDownload(const MyStringAnsi & url, const MyStringAnsi & name,
std::function<void(std::shared_ptr<DownloadJob>)> onFinish, DATA_TYPE dataType);
std::shared_ptr<DownloadJob> AddDownload(const MyStringAnsi & url, const MyStringAnsi & name,
std::function<void(std::shared_ptr<DownloadJob>)> onFinish,
std::function<void(std::shared_ptr<DownloadJob>)> onFinishMainThread, DATA_TYPE dataType);
void SetDownloadBeginCallback(std::function<void()> dlBeginCallback);
void SetDownloadEndedCallback(std::function<void()> dlEndedCallback);
void SetNoInternetConnectionFailureCallback(std::function<void()> connectionFailureCallback,
std::function<void()> connectionBackCallback);
bool IsDownloading() const;
bool IsDownloading(DownloadID downloadID) const;
void TryStart();
void WaitToFinish(DownloadID downloadID);
bool Exist(DownloadID downloadID) const;
bool ExistURL(const MyStringAnsi & url) const;
std::shared_ptr<DownloadJob> GetDownloadJob(DownloadID downloadID) const;
void UpdateMainThread();
friend class DownloadJob;
protected:
static const std::vector<char> EMPTY_DATA;
bool internetConnectionAvailable;
std::function<void()> dlBeginCallback;
std::function<void()> dlEndedCallback;
std::function<void()> connectionFailureCallback;
std::function<void()> connectionBackCallback;
CURLM * curlm;
std::thread dlThread;
std::mutex dlThreadLock;
std::atomic<bool> running;
std::unordered_map<DownloadID, std::shared_ptr<DownloadJob>> jobs;
std::set<MyStringAnsi> activeURLs;
std::list<std::shared_ptr<DownloadJob>> jobQueue;
DownloadID lastID;
//nemelo by to byt atomic?
std::atomic<std::chrono::time_point<std::chrono::high_resolution_clock>> lastFailureTime;
void Start();
int UpdateBackgroundThread();
void DownloadFailedBackgroundThread(std::shared_ptr<DownloadJob> job, CURLMsg * msg);
void DownloadOKBackgroundThread(std::shared_ptr<DownloadJob> job, CURLMsg * msg);
void RemoveFinishedDownloads();
bool CanDownloadAfterFailure() const;
};
//===================================================================================
class DownloadJob
{
public:
const DownloadID id;
~DownloadJob();
void RemoveFromDataDownloader();
int GetResultStatus() const { return this->resCode; }
bool IsFinished() const { return (finished.load()); }
void Cancel();
const MyStringAnsi & GetName() const { return this->name; }
const MyStringAnsi & GetURL() const { return this->url; }
const std::vector<char> & GetData() const { return this->data; }
friend class DataDownloader;
//friend static int curlWriteData(char *data, size_t size, size_t nmemb, void * j);
protected:
DownloadJob(const MyStringAnsi & url, const MyStringAnsi & name, DownloadID id, DataDownloader * dd);
MyStringAnsi url;
MyStringAnsi name;
std::vector<char> data;
std::atomic<bool> finished;
DataDownloader * dd;
CURL * curl;
int resCode;
bool shouldCancel;
std::function<void(std::shared_ptr<DownloadJob>)> onFinish;
std::function<void(std::shared_ptr<DownloadJob>)> onFinishMainThread;
DataDownloader::DATA_TYPE dataType;
static size_t curlWriteData(char *data, size_t size, size_t nmemb, void * j);
static size_t curlDownloadInfo(void *j, curl_off_t dltotal, curl_off_t dlnow,
curl_off_t ultotal, curl_off_t ulnow);
int Start();
void Kill();
};
#endif
-------------------------------------------------------------------
Unsubscribe: https://cool.haxx.se/list/listinfo/curl-library
Etiquette: https://curl.haxx.se/mail/etiquette.html