Skip to content

Instantly share code, notes, and snippets.

@rubentorresbonet
Last active June 23, 2020 04:17
Show Gist options
  • Save rubentorresbonet/18a1eef20843a4197296 to your computer and use it in GitHub Desktop.
Save rubentorresbonet/18a1eef20843a4197296 to your computer and use it in GitHub Desktop.
/*
* AsynchronousPictureLoader.cpp
*
* Created on: 13.08.2014
* Author: ruben.torres
*/
#include <curl/curl.h>
#include <thread>
#include "AsynchronousPictureLoader.h"
// ------------------------------------------------------------------------------
using namespace std;
// ------------------------------------------------------------------------------
AsynchronousPictureLoader* AsynchronousPictureLoader::classInstance = nullptr;
once_flag AsynchronousPictureLoader::singletonOnceFlag;
// ------------------------------------------------------------------------------
void AsynchronousPictureLoader::destroy() {
stopLoop = true;
queueConditionVariable.notify_one();
}
// ------------------------------------------------------------------------------
AsynchronousPictureLoader::~AsynchronousPictureLoader()
{
}
// ------------------------------------------------------------------------------
AsynchronousPictureLoader*
AsynchronousPictureLoader::instance()
{
std::call_once(AsynchronousPictureLoader::singletonOnceFlag, [] () {
classInstance = new AsynchronousPictureLoader();
curl_global_init(CURL_GLOBAL_ALL);
// 2. Run its loop in its own thread.
thread runLoopThread(&AsynchronousPictureLoader::runLoop, classInstance);
runLoopThread.detach();
});
return classInstance;
}
// ------------------------------------------------------------------------------
void
AsynchronousPictureLoader::addTask(const std::string url, const std::string cachePath, std::function<void(std::string)> callback)
{
// 1. Secure our queue access.
lock_guard<std::mutex> taskLock(queueMutex);
// 2. Insert the new task.
queue.emplace(make_tuple(url, cachePath, callback));
// 3. Notify the loop thread.
queueConditionVariable.notify_one();
}
// ------------------------------------------------------------------------------
void
AsynchronousPictureLoader::runLoop()
{
std::unique_lock<std::mutex> loopLock(waitMutex);
while (1)
{
// 1. Wait for an event if the queue is empty.
unique_lock<std::mutex> taskLock(queueMutex);
unsigned int queueSize = queue.size();
taskLock.unlock();
if (queueSize == 0) {
queueConditionVariable.wait(loopLock);
}
// 2. Check if daddy told us to stop.
if (stopLoop) {
return;
}
// 3. Get the next task.
taskLock.lock();
AsynchronousPictureTask task = queue.front();
queue.pop();
taskLock.unlock();
// 4. Start download.
processDownload(task);
}
}
// ------------------------------------------------------------------------------
static size_t
WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp)
{
size_t realsize = size * nmemb;
struct AsynchronousPictureLoader::MemoryStruct *mem = (struct AsynchronousPictureLoader::MemoryStruct *)userp;
mem->memory = (char*)realloc(mem->memory, mem->size + realsize + 1);
if(mem->memory == NULL) {
/* out of memory! */
printf("not enough memory (realloc returned NULL)\n");
return 0;
}
memcpy(&(mem->memory[mem->size]), contents, realsize);
mem->size += realsize;
mem->memory[mem->size] = 0;
return realsize;
}
// ------------------------------------------------------------------------------
void
AsynchronousPictureLoader::processDownload(const AsynchronousPictureTask &task)
{
string url = std::get<0>(task);
string cachePath = std::get<1>(task);
auto callback = std::get<2>(task);
CURL *curl_handle;
CURLcode res;
struct AsynchronousPictureLoader::MemoryStruct chunk;
chunk.memory = (char*)malloc(1); /* will be grown as needed by the realloc above */
chunk.size = 0; /* no data at this point */
curl_handle = curl_easy_init();
curl_easy_setopt(curl_handle, CURLOPT_URL, url.c_str());
curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)&chunk);
curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "libcurl-agent/1.0");
curl_easy_setopt(curl_handle, CURLOPT_CONNECTTIMEOUT, 2L );
curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1 );
res = curl_easy_perform(curl_handle);
CCImage* image = nullptr;
if(res != CURLE_OK) {
CCLOG("RBN: processDownload: error: %s", curl_easy_strerror(res));
fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
}
else {
// Do whatever you want with the image.
callback(url);
}
curl_easy_cleanup(curl_handle);
if(chunk.memory) {
free(chunk.memory);
}
}
// ------------------------------------------------------------------------------
/*
* AsynchronousPictureLoader.h
*
* Created on: 13.08.2014
* Author: ruben.torres
*/
#pragma once
#include <condition_variable>
#include <queue>
#include <mutex>
#include <thread>
class AsynchronousPictureLoader {
public:
static AsynchronousPictureLoader *instance();
/**
* Add a task to our download queue and inform the caller back whenever it was successfully (or not) completed.
*/
void addTask(const std::string url, const std::string cachePath, std::function<void(std::string)>);
void destroy();
struct MemoryStruct {
char *memory;
size_t size;
};
private:
AsynchronousPictureLoader() = default;
~AsynchronousPictureLoader();
// The main run loop: poll for tasks and download them.
void runLoop();
bool stopLoop = false;
// Synchronization.
std::condition_variable queueConditionVariable;
// Download.
typedef std::tuple<std::string, std::string, std::function<void(bool)>> AsynchronousPictureTask;
void processDownload(const AsynchronousPictureTask &task);
std::queue<AsynchronousPictureTask> queue;
std::mutex queueMutex;
std::mutex waitMutex;
// Singleton.
static AsynchronousPictureLoader *classInstance;
static std::once_flag singletonOnceFlag;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment