2 // Copyright (C) 2007 Christoph Sommer <christoph.sommer@2007.expires.deltadevelopment.de>
3 // 2014 Ingo Ruhnke <grumbel@gmail.com>
5 // This program is free software: you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation, either version 3 of the License, or
8 // (at your option) any later version.
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program. If not, see <http://www.gnu.org/licenses/>.
18 #include "addon/downloader.hpp"
25 #include "util/log.hpp"
30 size_t my_curl_string_append(void* ptr, size_t size, size_t nmemb, void* userdata)
32 std::string& s = *static_cast<std::string*>(userdata);
33 std::string buf(static_cast<char*>(ptr), size * nmemb);
35 log_debug << "read " << size * nmemb << " bytes of data..." << std::endl;
39 size_t my_curl_physfs_write(void* ptr, size_t size, size_t nmemb, void* userdata)
41 PHYSFS_file* f = static_cast<PHYSFS_file*>(userdata);
42 PHYSFS_sint64 written = PHYSFS_write(f, ptr, size, nmemb);
43 log_debug << "read " << size * nmemb << " bytes of data..." << std::endl;
44 return size * written;
49 class cURLTransfer : public Transfer
52 Downloader& m_downloader;
56 std::array<char, CURL_ERROR_SIZE> m_error_buffer;
64 cURLTransfer(Downloader& downloader, const std::string& url) :
65 m_downloader(downloader),
67 m_handle(curl_easy_init()),
76 throw std::runtime_error("curl_easy_init() failed");
80 curl_easy_setopt(m_handle, CURLOPT_URL, url.c_str());
81 curl_easy_setopt(m_handle, CURLOPT_USERAGENT, "SuperTux/" PACKAGE_VERSION " libcURL");
83 curl_easy_setopt(m_handle, CURLOPT_WRITEDATA, this);
84 curl_easy_setopt(m_handle, CURLOPT_WRITEFUNCTION,
85 [](void* ptr, size_t size, size_t nmemb, void* userdata) -> size_t
87 return static_cast<cURLTransfer*>(userdata)
88 ->on_data(ptr, size, nmemb);
91 curl_easy_setopt(m_handle, CURLOPT_ERRORBUFFER, m_error_buffer.data());
92 curl_easy_setopt(m_handle, CURLOPT_NOSIGNAL, 1);
93 curl_easy_setopt(m_handle, CURLOPT_FAILONERROR, 1);
94 curl_easy_setopt(m_handle, CURLOPT_FOLLOWLOCATION, 1);
96 curl_easy_setopt(m_handle, CURLOPT_NOPROGRESS, 0);
97 curl_easy_setopt(m_handle, CURLOPT_XFERINFODATA, this);
98 curl_easy_setopt(m_handle, CURLOPT_XFERINFOFUNCTION,
100 curl_off_t dltotal, curl_off_t dlnow,
101 curl_off_t ultotal, curl_off_t ulnow)
103 return static_cast<cURLTransfer*>(userdata)
104 ->on_progress(dltotal, dlnow,
112 curl_easy_cleanup(m_handle);
115 CURL* get_curl_handle() const
120 std::string get_url() const
125 size_t on_data(void* ptr, size_t size, size_t nmemb)
130 void on_progress(curl_off_t dltotal, curl_off_t dlnow,
131 curl_off_t ultotal, curl_off_t ulnow)
142 m_downloader.abort(*this);
146 cURLTransfer(const cURLTransfer&) = delete;
147 cURLTransfer& operator=(const cURLTransfer&) = delete;
150 Downloader::Downloader() :
154 curl_global_init(CURL_GLOBAL_ALL);
155 m_multi_handle = curl_multi_init();
158 throw std::runtime_error("curl_multi_init() failed");
162 Downloader::~Downloader()
164 for(auto& transfer : m_transfers)
166 curl_multi_remove_handle(m_multi_handle, transfer->get_curl_handle());
170 curl_multi_cleanup(m_multi_handle);
171 curl_global_cleanup();
175 Downloader::download(const std::string& url,
176 size_t (*write_func)(void* ptr, size_t size, size_t nmemb, void* userdata),
179 log_info << "Downloading " << url << std::endl;
181 char error_buffer[CURL_ERROR_SIZE+1];
183 CURL* curl_handle = curl_easy_init();
184 curl_easy_setopt(curl_handle, CURLOPT_URL, url.c_str());
185 curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "SuperTux/" PACKAGE_VERSION " libcURL");
186 curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, write_func);
187 curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, userdata);
188 curl_easy_setopt(curl_handle, CURLOPT_ERRORBUFFER, error_buffer);
189 curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 1);
190 curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1);
191 curl_easy_setopt(curl_handle, CURLOPT_FAILONERROR, 1);
192 curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1);
193 CURLcode result = curl_easy_perform(curl_handle);
194 curl_easy_cleanup(curl_handle);
196 if (result != CURLE_OK)
198 std::string why = error_buffer[0] ? error_buffer : "unhandled error";
199 throw std::runtime_error(url + ": download failed: " + why);
204 Downloader::download(const std::string& url)
207 download(url, my_curl_string_append, &result);
212 Downloader::download(const std::string& url, const std::string& filename)
214 std::unique_ptr<PHYSFS_file, int(*)(PHYSFS_File*)> fout(PHYSFS_openWrite(filename.c_str()),
216 download(url, my_curl_physfs_write, fout.get());
220 Downloader::abort(const cURLTransfer& transfer)
222 auto it = std::find_if(m_transfers.begin(), m_transfers.end(),
223 [&transfer](const std::unique_ptr<cURLTransfer>& rhs)
225 return transfer.get_curl_handle() == rhs->get_curl_handle();
227 if (it == m_transfers.end())
229 log_warning << "transfer not found: " << transfer.get_url() << std::endl;
233 curl_multi_remove_handle(m_multi_handle, (*it)->get_curl_handle());
234 m_transfers.erase(it);
241 // read data from the network
244 while((ret = curl_multi_perform(m_multi_handle, &running_handles)) == CURLM_CALL_MULTI_PERFORM)
247 // check if any downloads got finished
250 while ((msg = curl_multi_info_read(m_multi_handle, &msgs_in_queue)))
255 curl_multi_remove_handle(m_multi_handle, msg->easy_handle);
256 //FIXME: finish_transfer(msg->easy_handle);
260 log_warning << "unhandled cURL message: " << msg->msg << std::endl;
267 Downloader::request_download(const std::string& url, const std::string& filename)
269 std::unique_ptr<cURLTransfer> transfer(new cURLTransfer(*this, url));
270 curl_multi_add_handle(m_multi_handle, transfer->get_curl_handle());
271 m_transfers.push_back(std::move(transfer));
272 return m_transfers.back().get();