#include "addon/downloader.hpp"
#include <algorithm>
+#include <array>
+#include <assert.h>
#include <memory>
#include <physfs.h>
+#include <sstream>
#include <stdexcept>
#include "util/log.hpp"
} // namespace
-class cURLTransfer : public Transfer
+void
+TransferStatus::abort()
+{
+ m_downloader.abort(id);
+}
+
+void
+TransferStatus::update()
+{
+ m_downloader.update();
+}
+
+class Transfer
{
private:
Downloader& m_downloader;
+ TransferId m_id;
std::string m_url;
CURL* m_handle;
std::array<char, CURL_ERROR_SIZE> m_error_buffer;
- curl_off_t m_dltotal;
- curl_off_t m_dlnow;
- curl_off_t m_ultotal;
- curl_off_t m_ulnow;
+ TransferStatusPtr m_status;
+ std::unique_ptr<PHYSFS_file, int(*)(PHYSFS_File*)> m_fout;
public:
- cURLTransfer(Downloader& downloader, const std::string& url) :
+ Transfer(Downloader& downloader, TransferId id,
+ const std::string& url,
+ const std::string& outfile) :
m_downloader(downloader),
+ m_id(id),
m_url(url),
- m_handle(curl_easy_init()),
- m_error_buffer(),
- m_dltotal(0),
- m_dlnow(0),
- m_ultotal(0),
- m_ulnow(0)
+ m_handle(),
+ m_error_buffer({{'\0'}}),
+ m_status(new TransferStatus(m_downloader, id)),
+ m_fout(PHYSFS_openWrite(outfile.c_str()), PHYSFS_close)
{
+ if (!m_fout)
+ {
+ std::ostringstream out;
+ out << "PHYSFS_openRead() failed: " << PHYSFS_getLastError();
+ throw std::runtime_error(out.str());
+ }
+
+ m_handle = curl_easy_init();
if (!m_handle)
{
throw std::runtime_error("curl_easy_init() failed");
curl_easy_setopt(m_handle, CURLOPT_USERAGENT, "SuperTux/" PACKAGE_VERSION " libcURL");
curl_easy_setopt(m_handle, CURLOPT_WRITEDATA, this);
- curl_easy_setopt(m_handle, CURLOPT_WRITEFUNCTION,
- [](void* ptr, size_t size, size_t nmemb, void* userdata) -> size_t
- {
- return static_cast<cURLTransfer*>(userdata)
- ->on_data(ptr, size, nmemb);
- });
+ curl_easy_setopt(m_handle, CURLOPT_WRITEFUNCTION, &Transfer::on_data_wrap);
curl_easy_setopt(m_handle, CURLOPT_ERRORBUFFER, m_error_buffer.data());
curl_easy_setopt(m_handle, CURLOPT_NOSIGNAL, 1);
curl_easy_setopt(m_handle, CURLOPT_FOLLOWLOCATION, 1);
curl_easy_setopt(m_handle, CURLOPT_NOPROGRESS, 0);
- curl_easy_setopt(m_handle, CURLOPT_XFERINFODATA, this);
- curl_easy_setopt(m_handle, CURLOPT_XFERINFOFUNCTION,
- [](void* userdata,
- curl_off_t dltotal, curl_off_t dlnow,
- curl_off_t ultotal, curl_off_t ulnow)
- {
- return static_cast<cURLTransfer*>(userdata)
- ->on_progress(dltotal, dlnow,
- ultotal, ulnow);
- });
+ curl_easy_setopt(m_handle, CURLOPT_PROGRESSDATA, this);
+ curl_easy_setopt(m_handle, CURLOPT_PROGRESSFUNCTION, &Transfer::on_progress_wrap);
}
}
- ~cURLTransfer()
+ ~Transfer()
{
curl_easy_cleanup(m_handle);
}
+ TransferStatusPtr get_status() const
+ {
+ return m_status;
+ }
+
+ const char* get_error_buffer() const
+ {
+ return m_error_buffer.data();
+ }
+
+ TransferId get_id() const
+ {
+ return m_id;
+ }
+
CURL* get_curl_handle() const
{
return m_handle;
size_t on_data(void* ptr, size_t size, size_t nmemb)
{
- return 0;
+ PHYSFS_write(m_fout.get(), ptr, size, nmemb);
+ return size * nmemb;
}
- void on_progress(curl_off_t dltotal, curl_off_t dlnow,
- curl_off_t ultotal, curl_off_t ulnow)
+ int on_progress(double dltotal, double dlnow,
+ double ultotal, double ulnow)
{
- m_dltotal = dltotal;
- m_dlnow = dlnow;
+ m_status->dltotal = static_cast<int>(dltotal);
+ m_status->dlnow = static_cast<int>(dlnow);
+
+ m_status->ultotal = static_cast<int>(ultotal);
+ m_status->ulnow = static_cast<int>(ulnow);
+
+ return 0;
+ }
- m_ultotal = ultotal;
- m_ulnow = ulnow;
+private:
+ static size_t on_data_wrap(char* ptr, size_t size, size_t nmemb, void* userdata)
+ {
+ return static_cast<Transfer*>(userdata)->on_data(ptr, size, nmemb);
}
- void abort()
+ static int on_progress_wrap(void* userdata,
+ double dltotal, double dlnow,
+ double ultotal, double ulnow)
{
- m_downloader.abort(*this);
+ return static_cast<Transfer*>(userdata)->on_progress(dltotal, dlnow, ultotal, ulnow);
}
private:
- cURLTransfer(const cURLTransfer&) = delete;
- cURLTransfer& operator=(const cURLTransfer&) = delete;
+ Transfer(const Transfer&) = delete;
+ Transfer& operator=(const Transfer&) = delete;
};
Downloader::Downloader() :
m_multi_handle(),
- m_transfers()
+ m_transfers(),
+ m_next_transfer_id(1)
{
curl_global_init(CURL_GLOBAL_ALL);
m_multi_handle = curl_multi_init();
}
void
-Downloader::abort(const cURLTransfer& transfer)
+Downloader::abort(TransferId id)
{
auto it = std::find_if(m_transfers.begin(), m_transfers.end(),
- [&transfer](const std::unique_ptr<cURLTransfer>& rhs)
+ [&id](const std::unique_ptr<Transfer>& rhs)
{
- return transfer.get_curl_handle() == rhs->get_curl_handle();
+ return id == rhs->get_id();
});
if (it == m_transfers.end())
{
- log_warning << "transfer not found: " << transfer.get_url() << std::endl;
+ log_warning << "transfer not found: " << id << std::endl;
}
else
{
+ TransferStatusPtr status = (*it)->get_status();
+
curl_multi_remove_handle(m_multi_handle, (*it)->get_curl_handle());
m_transfers.erase(it);
+
+ for(auto& callback : status->callbacks)
+ {
+ try
+ {
+ callback(false);
+ }
+ catch(const std::exception& err)
+ {
+ log_warning << "Illegal exception in Downloader: " << err.what() << std::endl;
+ }
+ }
}
}
CURLMcode ret;
int running_handles;
while((ret = curl_multi_perform(m_multi_handle, &running_handles)) == CURLM_CALL_MULTI_PERFORM)
- {}
+ {
+ log_debug << "updating" << std::endl;
+ }
// check if any downloads got finished
int msgs_in_queue;
switch(msg->msg)
{
case CURLMSG_DONE:
- curl_multi_remove_handle(m_multi_handle, msg->easy_handle);
- //FIXME: finish_transfer(msg->easy_handle);
+ {
+ log_info << "Download completed with " << msg->data.result << std::endl;
+ curl_multi_remove_handle(m_multi_handle, msg->easy_handle);
+
+ auto it = std::find_if(m_transfers.begin(), m_transfers.end(),
+ [&msg](const std::unique_ptr<Transfer>& rhs) {
+ return rhs->get_curl_handle() == msg->easy_handle;
+ });
+ assert(it != m_transfers.end());
+ TransferStatusPtr status = (*it)->get_status();
+ status->error_msg = (*it)->get_error_buffer();
+ m_transfers.erase(it);
+
+ if (msg->data.result == CURLE_OK)
+ {
+ bool success = true;
+ for(auto& callback : status->callbacks)
+ {
+ try
+ {
+ callback(success);
+ }
+ catch(const std::exception& err)
+ {
+ success = false;
+ log_warning << "Exception in Downloader: " << err.what() << std::endl;
+ status->error_msg = err.what();
+ }
+ }
+ }
+ else
+ {
+ log_warning << "Error: " << curl_easy_strerror(msg->data.result) << std::endl;
+ for(auto& callback : status->callbacks)
+ {
+ try
+ {
+ callback(false);
+ }
+ catch(const std::exception& err)
+ {
+ log_warning << "Illegal exception in Downloader: " << err.what() << std::endl;
+ }
+ }
+ }
+ }
break;
default:
}
}
-TransferHandle
-Downloader::request_download(const std::string& url, const std::string& filename)
+TransferStatusPtr
+Downloader::request_download(const std::string& url, const std::string& outfile)
{
- std::unique_ptr<cURLTransfer> transfer(new cURLTransfer(*this, url));
+ std::unique_ptr<Transfer> transfer(new Transfer(*this, m_next_transfer_id++, url, outfile));
curl_multi_add_handle(m_multi_handle, transfer->get_curl_handle());
m_transfers.push_back(std::move(transfer));
- return m_transfers.back().get();
+ return m_transfers.back()->get_status();
}
/* EOF */