1// SuperTux
2// Copyright (C) 2007 Christoph Sommer <christoph.sommer@2007.expires.deltadevelopment.de>
3// 2014 Ingo Ruhnke <grumbel@gmail.com>
4//
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.
9//
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.
14//
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/>.
17
18#include "addon/downloader.hpp"
19
20#include <algorithm>
21#include <array>
22#include <assert.h>
23#include <memory>
24#include <physfs.h>
25#include <sstream>
26#include <stdexcept>
27#include <version.h>
28
29#include "util/log.hpp"
30
31namespace {
32
33size_t my_curl_string_append(void* ptr, size_t size, size_t nmemb, void* userdata)
34{
35 std::string& s = *static_cast<std::string*>(userdata);
36 std::string buf(static_cast<char*>(ptr), size * nmemb);
37 s += buf;
38 log_debug << "read " << size * nmemb << " bytes of data..." << std::endl;
39 return size * nmemb;
40}
41
42size_t my_curl_physfs_write(void* ptr, size_t size, size_t nmemb, void* userdata)
43{
44 PHYSFS_file* f = static_cast<PHYSFS_file*>(userdata);
45 PHYSFS_sint64 written = PHYSFS_writeBytes(f, ptr, size * nmemb);
46 log_debug << "read " << size * nmemb << " bytes of data..." << std::endl;
47 if (written < 0)
48 {
49 return 0;
50 }
51 else
52 {
53 return static_cast<size_t>(written);
54 }
55}
56
57} // namespace
58
59void
60TransferStatus::abort()
61{
62 m_downloader.abort(id);
63}
64
65void
66TransferStatus::update()
67{
68 m_downloader.update();
69}
70
71class Transfer final
72{
73private:
74 Downloader& m_downloader;
75 TransferId m_id;
76
77 std::string m_url;
78 CURL* m_handle;
79 std::array<char, CURL_ERROR_SIZE> m_error_buffer;
80
81 TransferStatusPtr m_status;
82 std::unique_ptr<PHYSFS_file, int(*)(PHYSFS_File*)> m_fout;
83
84public:
85 Transfer(Downloader& downloader, TransferId id,
86 const std::string& url,
87 const std::string& outfile) :
88 m_downloader(downloader),
89 m_id(id),
90 m_url(url),
91 m_handle(),
92 m_error_buffer({{'\0'}}),
93 m_status(new TransferStatus(m_downloader, id)),
94 m_fout(PHYSFS_openWrite(outfile.c_str()), PHYSFS_close)
95 {
96 if (!m_fout)
97 {
98 std::ostringstream out;
99 out << "PHYSFS_openRead() failed: " << PHYSFS_getLastErrorCode();
100 throw std::runtime_error(out.str());
101 }
102
103 m_handle = curl_easy_init();
104 if (!m_handle)
105 {
106 throw std::runtime_error("curl_easy_init() failed");
107 }
108 else
109 {
110 curl_easy_setopt(m_handle, CURLOPT_URL, url.c_str());
111 curl_easy_setopt(m_handle, CURLOPT_USERAGENT, "SuperTux/" PACKAGE_VERSION " libcURL");
112
113 curl_easy_setopt(m_handle, CURLOPT_WRITEDATA, this);
114 curl_easy_setopt(m_handle, CURLOPT_WRITEFUNCTION, &Transfer::on_data_wrap);
115
116 curl_easy_setopt(m_handle, CURLOPT_ERRORBUFFER, m_error_buffer.data());
117 curl_easy_setopt(m_handle, CURLOPT_NOSIGNAL, 1);
118 curl_easy_setopt(m_handle, CURLOPT_FAILONERROR, 1);
119 curl_easy_setopt(m_handle, CURLOPT_FOLLOWLOCATION, 1);
120
121 curl_easy_setopt(m_handle, CURLOPT_NOPROGRESS, 0);
122 curl_easy_setopt(m_handle, CURLOPT_PROGRESSDATA, this);
123 curl_easy_setopt(m_handle, CURLOPT_PROGRESSFUNCTION, &Transfer::on_progress_wrap);
124 }
125 }
126
127 ~Transfer()
128 {
129 curl_easy_cleanup(m_handle);
130 }
131
132 TransferStatusPtr get_status() const
133 {
134 return m_status;
135 }
136
137 const char* get_error_buffer() const
138 {
139 return m_error_buffer.data();
140 }
141
142 TransferId get_id() const
143 {
144 return m_id;
145 }
146
147 CURL* get_curl_handle() const
148 {
149 return m_handle;
150 }
151
152 std::string get_url() const
153 {
154 return m_url;
155 }
156
157 size_t on_data(void* ptr, size_t size, size_t nmemb)
158 {
159 PHYSFS_writeBytes(m_fout.get(), ptr, size * nmemb);
160 return size * nmemb;
161 }
162
163 int on_progress(double dltotal, double dlnow,
164 double ultotal, double ulnow)
165 {
166 m_status->dltotal = static_cast<int>(dltotal);
167 m_status->dlnow = static_cast<int>(dlnow);
168
169 m_status->ultotal = static_cast<int>(ultotal);
170 m_status->ulnow = static_cast<int>(ulnow);
171
172 return 0;
173 }
174
175private:
176 static size_t on_data_wrap(char* ptr, size_t size, size_t nmemb, void* userdata)
177 {
178 return static_cast<Transfer*>(userdata)->on_data(ptr, size, nmemb);
179 }
180
181 static int on_progress_wrap(void* userdata,
182 double dltotal, double dlnow,
183 double ultotal, double ulnow)
184 {
185 return static_cast<Transfer*>(userdata)->on_progress(dltotal, dlnow, ultotal, ulnow);
186 }
187
188private:
189 Transfer(const Transfer&) = delete;
190 Transfer& operator=(const Transfer&) = delete;
191};
192
193Downloader::Downloader() :
194 m_multi_handle(),
195 m_transfers(),
196 m_next_transfer_id(1)
197{
198 curl_global_init(CURL_GLOBAL_ALL);
199 m_multi_handle = curl_multi_init();
200 if (!m_multi_handle)
201 {
202 throw std::runtime_error("curl_multi_init() failed");
203 }
204}
205
206Downloader::~Downloader()
207{
208 for (auto& transfer : m_transfers)
209 {
210 curl_multi_remove_handle(m_multi_handle, transfer->get_curl_handle());
211 }
212 m_transfers.clear();
213
214 curl_multi_cleanup(m_multi_handle);
215 curl_global_cleanup();
216}
217
218void
219Downloader::download(const std::string& url,
220 size_t (*write_func)(void* ptr, size_t size, size_t nmemb, void* userdata),
221 void* userdata)
222{
223 log_info << "Downloading " << url << std::endl;
224
225 char error_buffer[CURL_ERROR_SIZE+1];
226
227 CURL* curl_handle = curl_easy_init();
228 curl_easy_setopt(curl_handle, CURLOPT_URL, url.c_str());
229 curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "SuperTux/" PACKAGE_VERSION " libcURL");
230 curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, write_func);
231 curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, userdata);
232 curl_easy_setopt(curl_handle, CURLOPT_ERRORBUFFER, error_buffer);
233 curl_easy_setopt(curl_handle, CURLOPT_NOPROGRESS, 1);
234 curl_easy_setopt(curl_handle, CURLOPT_NOSIGNAL, 1);
235 curl_easy_setopt(curl_handle, CURLOPT_FAILONERROR, 1);
236 curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1);
237 CURLcode result = curl_easy_perform(curl_handle);
238 curl_easy_cleanup(curl_handle);
239
240 if (result != CURLE_OK)
241 {
242 std::string why = error_buffer[0] ? error_buffer : "unhandled error";
243 throw std::runtime_error(url + ": download failed: " + why);
244 }
245}
246
247std::string
248Downloader::download(const std::string& url)
249{
250 std::string result;
251 download(url, my_curl_string_append, &result);
252 return result;
253}
254
255void
256Downloader::download(const std::string& url, const std::string& filename)
257{
258 log_info << "download: " << url << " to " << filename << std::endl;
259 std::unique_ptr<PHYSFS_file, int(*)(PHYSFS_File*)> fout(PHYSFS_openWrite(filename.c_str()),
260 PHYSFS_close);
261 download(url, my_curl_physfs_write, fout.get());
262}
263
264void
265Downloader::abort(TransferId id)
266{
267 auto it = std::find_if(m_transfers.begin(), m_transfers.end(),
268 [&id](const std::unique_ptr<Transfer>& rhs)
269 {
270 return id == rhs->get_id();
271 });
272 if (it == m_transfers.end())
273 {
274 log_warning << "transfer not found: " << id << std::endl;
275 }
276 else
277 {
278 TransferStatusPtr status = (*it)->get_status();
279
280 curl_multi_remove_handle(m_multi_handle, (*it)->get_curl_handle());
281 m_transfers.erase(it);
282
283 for (auto& callback : status->callbacks)
284 {
285 try
286 {
287 callback(false);
288 }
289 catch(const std::exception& err)
290 {
291 log_warning << "Illegal exception in Downloader: " << err.what() << std::endl;
292 }
293 }
294 }
295}
296
297void
298Downloader::update()
299{
300 // read data from the network
301 CURLMcode ret;
302 int running_handles;
303 while ((ret = curl_multi_perform(m_multi_handle, &running_handles)) == CURLM_CALL_MULTI_PERFORM)
304 {
305 log_debug << "updating" << std::endl;
306 }
307
308 // check if any downloads got finished
309 int msgs_in_queue;
310 CURLMsg* msg;
311 while ((msg = curl_multi_info_read(m_multi_handle, &msgs_in_queue)))
312 {
313 switch (msg->msg)
314 {
315 case CURLMSG_DONE:
316 {
317 CURLcode resultfromcurl = msg->data.result;
318 log_info << "Download completed with " << resultfromcurl << std::endl;
319 curl_multi_remove_handle(m_multi_handle, msg->easy_handle);
320
321 auto it = std::find_if(m_transfers.begin(), m_transfers.end(),
322 [&msg](const std::unique_ptr<Transfer>& rhs) {
323 return rhs->get_curl_handle() == msg->easy_handle;
324 });
325 assert(it != m_transfers.end());
326 TransferStatusPtr status = (*it)->get_status();
327 status->error_msg = (*it)->get_error_buffer();
328 m_transfers.erase(it);
329
330 if (resultfromcurl == CURLE_OK)
331 {
332 bool success = true;
333 for (auto& callback : status->callbacks)
334 {
335 try
336 {
337 callback(success);
338 }
339 catch(const std::exception& err)
340 {
341 success = false;
342 log_warning << "Exception in Downloader: " << err.what() << std::endl;
343 status->error_msg = err.what();
344 }
345 }
346 }
347 else
348 {
349 log_warning << "Error: " << curl_easy_strerror(resultfromcurl) << std::endl;
350 for (auto& callback : status->callbacks)
351 {
352 try
353 {
354 callback(false);
355 }
356 catch(const std::exception& err)
357 {
358 log_warning << "Illegal exception in Downloader: " << err.what() << std::endl;
359 }
360 }
361 }
362 }
363 break;
364
365 default:
366 log_warning << "unhandled cURL message: " << msg->msg << std::endl;
367 break;
368 }
369 }
370}
371
372TransferStatusPtr
373Downloader::request_download(const std::string& url, const std::string& outfile)
374{
375 log_info << "request_download: " << url << std::endl;
376 auto transfer = std::make_unique<Transfer>(*this, m_next_transfer_id++, url, outfile);
377 curl_multi_add_handle(m_multi_handle, transfer->get_curl_handle());
378 m_transfers.push_back(std::move(transfer));
379 return m_transfers.back()->get_status();
380}
381
382/* EOF */
383