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"
28 #include "util/log.hpp"
33 size_t my_curl_string_append(void* ptr, size_t size, size_t nmemb, void* userdata)
35 std::string& s = *static_cast<std::string*>(userdata);
36 std::string buf(static_cast<char*>(ptr), size * nmemb);
38 log_debug << "read " << size * nmemb << " bytes of data..." << std::endl;
42 size_t my_curl_physfs_write(void* ptr, size_t size, size_t nmemb, void* userdata)
44 PHYSFS_file* f = static_cast<PHYSFS_file*>(userdata);
45 PHYSFS_sint64 written = PHYSFS_write(f, ptr, size, nmemb);
46 log_debug << "read " << size * nmemb << " bytes of data..." << std::endl;
47 return size * written;
53 TransferStatus::abort()
55 m_downloader.abort(id);
59 TransferStatus::update()
61 m_downloader.update();
67 Downloader& m_downloader;
72 std::array<char, CURL_ERROR_SIZE> m_error_buffer;
74 TransferStatusPtr m_status;
75 std::unique_ptr<PHYSFS_file, int(*)(PHYSFS_File*)> m_fout;
78 Transfer(Downloader& downloader, TransferId id,
79 const std::string& url,
80 const std::string& outfile) :
81 m_downloader(downloader),
85 m_error_buffer({{'\0'}}),
86 m_status(new TransferStatus(m_downloader, id)),
87 m_fout(PHYSFS_openWrite(outfile.c_str()), PHYSFS_close)
91 std::ostringstream out;
92 out << "PHYSFS_openRead() failed: " << PHYSFS_getLastError();
93 throw std::runtime_error(out.str());
96 m_handle = curl_easy_init();
99 throw std::runtime_error("curl_easy_init() failed");
103 curl_easy_setopt(m_handle, CURLOPT_URL, url.c_str());
104 curl_easy_setopt(m_handle, CURLOPT_USERAGENT, "SuperTux/" PACKAGE_VERSION " libcURL");
106 curl_easy_setopt(m_handle, CURLOPT_WRITEDATA, this);
107 curl_easy_setopt(m_handle, CURLOPT_WRITEFUNCTION, &Transfer::on_data_wrap);
109 curl_easy_setopt(m_handle, CURLOPT_ERRORBUFFER, m_error_buffer.data());
110 curl_easy_setopt(m_handle, CURLOPT_NOSIGNAL, 1);
111 curl_easy_setopt(m_handle, CURLOPT_FAILONERROR, 1);
112 curl_easy_setopt(m_handle, CURLOPT_FOLLOWLOCATION, 1);
114 curl_easy_setopt(m_handle, CURLOPT_NOPROGRESS, 0);
115 curl_easy_setopt(m_handle, CURLOPT_PROGRESSDATA, this);
116 curl_easy_setopt(m_handle, CURLOPT_PROGRESSFUNCTION, &Transfer::on_progress_wrap);
122 curl_easy_cleanup(m_handle);
125 TransferStatusPtr get_status() const
130 const char* get_error_buffer() const
132 return m_error_buffer.data();
135 TransferId get_id() const
140 CURL* get_curl_handle() const
145 std::string get_url() const
150 size_t on_data(void* ptr, size_t size, size_t nmemb)
152 PHYSFS_write(m_fout.get(), ptr, size, nmemb);
156 int on_progress(double dltotal, double dlnow,
157 double ultotal, double ulnow)
159 m_status->dltotal = static_cast<int>(dltotal);
160 m_status->dlnow = static_cast<int>(dlnow);
162 m_status->ultotal = static_cast<int>(ultotal);
163 m_status->ulnow = static_cast<int>(ulnow);
169 static size_t on_data_wrap(char* ptr, size_t size, size_t nmemb, void* userdata)
171 return static_cast<Transfer*>(userdata)->on_data(ptr, size, nmemb);
174 static int on_progress_wrap(void* userdata,
175 double dltotal, double dlnow,
176 double ultotal, double ulnow)
178 return static_cast<Transfer*>(userdata)->on_progress(dltotal, dlnow, ultotal, ulnow);
182 Transfer(const Transfer&) = delete;
183 Transfer& operator=(const Transfer&) = delete;
186 Downloader::Downloader() :
189 m_next_transfer_id(1)
191 curl_global_init(CURL_GLOBAL_ALL);
192 m_multi_handle = curl_multi_init();
195 throw std::runtime_error("curl_multi_init() failed");
199 Downloader::~Downloader()
201 for(auto& transfer : m_transfers)
203 curl_multi_remove_handle(m_multi_handle, transfer->get_curl_handle());
207 curl_multi_cleanup(m_multi_handle);
208 curl_global_cleanup();
212 Downloader::download(const std::string& url,
213 size_t (*write_func)(void* ptr, size_t size, size_t nmemb, void* userdata),
216 log_info << "Downloading " << url << std::endl;
218 char error_buffer[CURL_ERROR_SIZE+1];
220 CURL* curl_handle = curl_easy_init();
221 curl_easy_setopt(curl_handle, CURLOPT_URL, url.c_str());
222 curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "SuperTux/" PACKAGE_VERSION " libcURL");
223 curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, write_func);
224 curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, userdata);
225 curl_easy_setopt(curl_handle, CURLOPT_ERRORBUFFER, error_buffer);
226 curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 1);
227 curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1);
228 curl_easy_setopt(curl_handle, CURLOPT_FAILONERROR, 1);
229 curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1);
230 CURLcode result = curl_easy_perform(curl_handle);
231 curl_easy_cleanup(curl_handle);
233 if (result != CURLE_OK)
235 std::string why = error_buffer[0] ? error_buffer : "unhandled error";
236 throw std::runtime_error(url + ": download failed: " + why);
241 Downloader::download(const std::string& url)
244 download(url, my_curl_string_append, &result);
249 Downloader::download(const std::string& url, const std::string& filename)
251 std::unique_ptr<PHYSFS_file, int(*)(PHYSFS_File*)> fout(PHYSFS_openWrite(filename.c_str()),
253 download(url, my_curl_physfs_write, fout.get());
257 Downloader::abort(TransferId id)
259 auto it = std::find_if(m_transfers.begin(), m_transfers.end(),
260 [&id](const std::unique_ptr<Transfer>& rhs)
262 return id == rhs->get_id();
264 if (it == m_transfers.end())
266 log_warning << "transfer not found: " << id << std::endl;
270 TransferStatusPtr status = (*it)->get_status();
272 curl_multi_remove_handle(m_multi_handle, (*it)->get_curl_handle());
273 m_transfers.erase(it);
275 for(auto& callback : status->callbacks)
281 catch(const std::exception& err)
283 log_warning << "Illegal exception in Downloader: " << err.what() << std::endl;
292 // read data from the network
295 while((ret = curl_multi_perform(m_multi_handle, &running_handles)) == CURLM_CALL_MULTI_PERFORM)
297 log_debug << "updating" << std::endl;
300 // check if any downloads got finished
303 while ((msg = curl_multi_info_read(m_multi_handle, &msgs_in_queue)))
309 log_info << "Download completed with " << msg->data.result << std::endl;
310 curl_multi_remove_handle(m_multi_handle, msg->easy_handle);
312 auto it = std::find_if(m_transfers.begin(), m_transfers.end(),
313 [&msg](const std::unique_ptr<Transfer>& rhs) {
314 return rhs->get_curl_handle() == msg->easy_handle;
316 assert(it != m_transfers.end());
317 TransferStatusPtr status = (*it)->get_status();
318 status->error_msg = (*it)->get_error_buffer();
319 m_transfers.erase(it);
321 if (msg->data.result == CURLE_OK)
324 for(auto& callback : status->callbacks)
330 catch(const std::exception& err)
333 log_warning << "Exception in Downloader: " << err.what() << std::endl;
334 status->error_msg = err.what();
340 log_warning << "Error: " << curl_easy_strerror(msg->data.result) << std::endl;
341 for(auto& callback : status->callbacks)
347 catch(const std::exception& err)
349 log_warning << "Illegal exception in Downloader: " << err.what() << std::endl;
357 log_warning << "unhandled cURL message: " << msg->msg << std::endl;
364 Downloader::request_download(const std::string& url, const std::string& outfile)
366 std::unique_ptr<Transfer> transfer(new Transfer(*this, m_next_transfer_id++, url, outfile));
367 curl_multi_add_handle(m_multi_handle, transfer->get_curl_handle());
368 m_transfers.push_back(std::move(transfer));
369 return m_transfers.back()->get_status();