Mana
Loading...
Searching...
No Matches
download.cpp
Go to the documentation of this file.
1/*
2 * The Mana Client
3 * Copyright (C) 2009-2012 The Mana Developers
4 *
5 * This file is part of The Mana Client.
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21#include "net/download.h"
22
23#include "configuration.h"
24#include "log.h"
25#include "main.h"
26
27#include "utils/stringutils.h"
28
29#include <SDL.h>
30#include <SDL_thread.h>
31
32#include <zlib.h>
33
34constexpr char DOWNLOAD_ERROR_MESSAGE_THREAD[] = "Could not create download thread!";
35
36namespace Net {
37
41unsigned long Download::fadler32(FILE *file)
42{
43 if (!file || fseek(file, 0, SEEK_END) != 0)
44 return 0;
45
46 const long fileSize = ftell(file);
47 if (fileSize < 0)
48 return 0;
49
50 rewind(file);
51
52 // Calculate Adler-32 checksum
53 void *buffer = malloc(fileSize);
54 const size_t read = fread(buffer, 1, fileSize, file);
55 unsigned long adler = adler32_z(0L, Z_NULL, 0);
56 adler = adler32_z(adler, (Bytef*) buffer, read);
57 free(buffer);
58
59 return adler;
60}
61
62Download::Download(const std::string &url)
63 : mUrl(url)
64{
65 mError[0] = 0;
66}
67
69{
70 mCancel = true;
71 SDL_WaitThread(mThread, nullptr);
72
73 curl_slist_free_all(mHeaders);
74 free(mBuffer);
75}
76
77void Download::addHeader(const char *header)
78{
79 assert(!mThread); // Cannot add headers after starting download
80
81 mHeaders = curl_slist_append(mHeaders, header);
82}
83
85{
86 addHeader("pragma: no-cache");
87 addHeader("Cache-Control: no-cache");
88}
89
90void Download::setFile(const std::string &filename,
91 std::optional<unsigned long> adler32)
92{
93 assert(!mThread); // Cannot set file after starting download
94
95 mMemoryWrite = false;
96 mFileName = filename;
97 mAdler = adler32;
98}
99
101{
102 assert(!mThread); // Cannot set write function after starting download
103
104 mMemoryWrite = true;
105}
106
108{
109 assert(!mThread); // Download already started
110
111 Log::info("Starting download: %s", mUrl.c_str());
112
113 mThread = SDL_CreateThread(downloadThread, "Download", this);
114
115 if (!mThread)
116 {
118 strncpy(mError, DOWNLOAD_ERROR_MESSAGE_THREAD, CURL_ERROR_SIZE - 1);
119 mState.lock()->status = DownloadStatus::Error;
120 return false;
121 }
122
123 return true;
124}
125
127{
128 Log::info("Canceling download: %s", mUrl.c_str());
129 mCancel = true;
130}
131
132std::string_view Download::getBuffer() const
133{
134 assert(mMemoryWrite); // Buffer not used
135 return std::string_view(mBuffer, mDownloadedBytes);
136}
137
142 curl_off_t dltotal, curl_off_t dlnow,
143 curl_off_t ultotal, curl_off_t ulnow)
144{
145 auto *d = reinterpret_cast<Download*>(clientp);
146
147 auto state = d->mState.lock();
148 state->status = DownloadStatus::InProgress;
149 state->progress = 0.0f;
150 if (dltotal > 0)
151 state->progress = static_cast<float>(dlnow) / dltotal;
152
153 return d->mCancel;
154}
155
159size_t Download::writeBuffer(char *ptr, size_t size, size_t nmemb, void *stream)
160{
161 auto *d = reinterpret_cast<Download *>(stream);
162
163 const size_t totalMem = size * nmemb;
164 d->mBuffer = (char *) realloc(d->mBuffer, d->mDownloadedBytes + totalMem);
165 if (d->mBuffer)
166 {
167 memcpy(d->mBuffer + d->mDownloadedBytes, ptr, totalMem);
168 d->mDownloadedBytes += totalMem;
169 }
170
171 return totalMem;
172}
173
175{
176 auto *d = reinterpret_cast<Download*>(ptr);
177 bool complete = false;
178 std::string outFilename;
179
180 if (!d->mMemoryWrite)
181 outFilename = d->mFileName + ".part";
182
183 for (int attempts = 0; attempts < 3 && !complete && !d->mCancel; ++attempts)
184 {
185 CURL *curl = curl_easy_init();
186 if (!curl)
187 break;
188
189 Log::info("Downloading: %s", d->mUrl.c_str());
190
191 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
192 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, d->mHeaders);
193
194 FILE *file = nullptr;
195
196 if (d->mMemoryWrite)
197 {
198 curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1);
199 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &Download::writeBuffer);
200 curl_easy_setopt(curl, CURLOPT_WRITEDATA, ptr);
201 }
202 else
203 {
204 file = fopen(outFilename.c_str(), "w+b");
205 curl_easy_setopt(curl, CURLOPT_WRITEDATA, file);
206 }
207
208 const std::string appShort = branding.getStringValue("appShort");
209 const std::string userAgent =
210 strprintf(PACKAGE_EXTENDED_VERSION, appShort.c_str());
211
212 curl_easy_setopt(curl, CURLOPT_USERAGENT, userAgent.c_str());
213 curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, d->mError);
214 curl_easy_setopt(curl, CURLOPT_URL, d->mUrl.c_str());
215 curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
216 curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, &Download::downloadProgress);
217 curl_easy_setopt(curl, CURLOPT_XFERINFODATA, ptr);
218 curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
219 curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 15);
220
221 const CURLcode res = curl_easy_perform(curl);
222 curl_easy_cleanup(curl);
223
224 if (res == CURLE_ABORTED_BY_CALLBACK)
225 {
226 d->mCancel = true;
227
228 if (file)
229 {
230 fclose(file);
231 ::remove(outFilename.c_str());
232 }
233
234 break;
235 }
236
237 if (res != CURLE_OK)
238 {
239 Log::info("curl error %d: %s host: %s",
240 res, d->mError, d->mUrl.c_str());
241
242 if (file)
243 {
244 fclose(file);
245 ::remove(outFilename.c_str());
246 }
247
248 break;
249 }
250
251 if (!d->mMemoryWrite)
252 {
253 // Check the checksum if available
254 if (d->mAdler)
255 {
256 unsigned long adler = fadler32(file);
257
258 if (d->mAdler != adler)
259 {
260 if (file)
261 fclose(file);
262
263 // Remove the corrupted file
264 ::remove(outFilename.c_str());
265 Log::info("Checksum for file %s failed: (%lx/%lx)",
266 d->mFileName.c_str(),
267 adler, *d->mAdler);
268
269 continue; // Bail out here to avoid the renaming
270 }
271 }
272
273 if (file)
274 fclose(file);
275
276 // Any existing file with this name is deleted first, otherwise
277 // the rename will fail on Windows.
278 ::remove(d->mFileName.c_str());
279 ::rename(outFilename.c_str(), d->mFileName.c_str());
280
281 // Check if we can open it and no errors were encountered
282 // during renaming
283 file = fopen(d->mFileName.c_str(), "rb");
284 if (file)
285 {
286 fclose(file);
287 file = nullptr;
288 complete = true;
289 }
290 }
291 else
292 {
293 // It's stored in memory, we're done
294 complete = true;
295 }
296
297 if (file)
298 fclose(file);
299 }
300
301 auto state = d->mState.lock();
302 if (d->mCancel)
303 state->status = DownloadStatus::Canceled;
304 else if (complete)
305 state->status = DownloadStatus::Complete;
306 else
307 state->status = DownloadStatus::Error;
308
309 return 0;
310}
311
312} // namespace Net
std::string getStringValue(const std::string &key) const
char * mBuffer
Buffer for files downloaded to memory.
Definition download.h:114
std::optional< unsigned long > mAdler
Definition download.h:105
void setFile(const std::string &filename, std::optional< unsigned long > adler32={})
Definition download.cpp:90
std::string_view getBuffer() const
Returns a view on the downloaded data.
Definition download.cpp:132
bool mMemoryWrite
Definition download.h:103
void noCache()
Convience method for adding no-cache headers.
Definition download.cpp:84
void cancel()
Cancels the download.
Definition download.cpp:126
char mError[CURL_ERROR_SIZE]
Definition download.h:108
static int downloadProgress(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
A libcurl callback for reporting progress.
Definition download.cpp:141
static size_t writeBuffer(char *ptr, size_t size, size_t nmemb, void *stream)
A libcurl callback for writing to memory.
Definition download.cpp:159
curl_slist * mHeaders
Definition download.h:107
static unsigned long fadler32(FILE *file)
Calculates the Alder-32 checksum for the given file.
Definition download.cpp:41
ThreadSafe< State > mState
Definition download.h:100
static int downloadThread(void *ptr)
Definition download.cpp:174
Download(const std::string &url)
Definition download.cpp:62
SDL_Thread * mThread
Definition download.h:106
void addHeader(const char *header)
Definition download.cpp:77
std::string mFileName
Definition download.h:104
std::string mUrl
Definition download.h:101
size_t mDownloadedBytes
Byte count currently downloaded in mMemoryBuffer.
Definition download.h:111
void setUseBuffer()
Definition download.cpp:100
bool start()
Starts the download thread.
Definition download.cpp:107
Configuration branding
XML branding information reader.
Definition client.cpp:98
constexpr char DOWNLOAD_ERROR_MESSAGE_THREAD[]
Definition download.cpp:34
#define PACKAGE_EXTENDED_VERSION
Definition main.h:84
void info(const char *log_text,...) LOG_PRINTF_ATTR
The network communication layer.
std::string strprintf(char const *format,...)
A safe version of sprintf that returns a std::string of the result.