+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;
+
+ TransferStatusPtr m_status;
+ std::unique_ptr<PHYSFS_file, int(*)(PHYSFS_File*)> m_fout;
+
+public:
+ Transfer(Downloader& downloader, TransferId id,
+ const std::string& url,
+ const std::string& outfile) :
+ m_downloader(downloader),
+ m_id(id),
+ m_url(url),
+ 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");
+ }
+ else
+ {
+ curl_easy_setopt(m_handle, CURLOPT_URL, url.c_str());
+ 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, &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_FAILONERROR, 1);
+ curl_easy_setopt(m_handle, CURLOPT_FOLLOWLOCATION, 1);
+
+ curl_easy_setopt(m_handle, CURLOPT_NOPROGRESS, 0);
+ curl_easy_setopt(m_handle, CURLOPT_PROGRESSDATA, this);
+ curl_easy_setopt(m_handle, CURLOPT_PROGRESSFUNCTION, &Transfer::on_progress_wrap);
+ }
+ }
+
+ ~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;
+ }
+
+ std::string get_url() const
+ {
+ return m_url;
+ }
+
+ size_t on_data(void* ptr, size_t size, size_t nmemb)
+ {
+ PHYSFS_write(m_fout.get(), ptr, size, nmemb);
+ return size * nmemb;
+ }
+
+ int on_progress(double dltotal, double dlnow,
+ double ultotal, double ulnow)
+ {
+ 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;
+ }
+
+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);
+ }
+
+ static int on_progress_wrap(void* userdata,
+ double dltotal, double dlnow,
+ double ultotal, double ulnow)
+ {
+ return static_cast<Transfer*>(userdata)->on_progress(dltotal, dlnow, ultotal, ulnow);
+ }
+
+private:
+ Transfer(const Transfer&) = delete;
+ Transfer& operator=(const Transfer&) = delete;
+};
+
+Downloader::Downloader() :
+ m_multi_handle(),
+ m_transfers(),
+ m_next_transfer_id(1)