Commit d2e5d2f3a05492c69e0c5e6523649551df4aee55

Forgot the actual download class
  
1/*
2 * The Mana World
3 * Copyright (C) 2009 The Mana World Development Team
4 *
5 * This file is part of The Mana World.
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, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 */
21
22#include "net/download.h"
23
24#include "log.h"
25#include "main.h"
26
27#include <SDL.h>
28#include <SDL_thread.h>
29
30#include <curl/curl.h>
31#include <zlib.h>
32
33const char *DOWNLOAD_ERROR_MESSAGE_THREAD = "Could not create download thread!";
34
35/**
36 * Calculates the Alder-32 checksum for the given file.
37 */
38static unsigned long fadler32(FILE *file)
39{
40 // Obtain file size
41 fseek(file, 0, SEEK_END);
42 long fileSize = ftell(file);
43 rewind(file);
44
45 // Calculate Adler-32 checksum
46 char *buffer = (char*) malloc(fileSize);
47 const size_t read = fread(buffer, 1, fileSize, file);
48 unsigned long adler = adler32(0L, Z_NULL, 0);
49 adler = adler32(adler, (Bytef*) buffer, read);
50 free(buffer);
51
52 return adler;
53}
54
55enum {
56 OPTIONS_NONE = 0,
57 OPTIONS_MEMORY = 1
58};
59
60namespace Net{
61Download::Download(void *ptr, const std::string &url,
62 DownloadUpdate updateFunction):
63 mPtr(ptr),
64 mUrl(url),
65 mFileName(""),
66 mWriteFunction(NULL),
67 mUpdateFunction(updateFunction),
68 mThread(NULL),
69 mCurl(NULL),
70 mHeaders(NULL)
71
72{
73 mError = (char*) malloc(CURL_ERROR_SIZE);
74 mError[0] = 0;
75
76 mOptions.cancel = false;
77}
78
79Download::~Download()
80{
81 delete mError;
82
83 if (mHeaders)
84 curl_slist_free_all(mHeaders);
85}
86
87void Download::addHeader(const std::string &header)
88{
89 mHeaders = curl_slist_append(mHeaders, header.c_str());
90}
91
92void Download::noCache()
93{
94 addHeader("pragma: no-cache");
95 addHeader("Cache-Control: no-cache");
96}
97
98void Download::setFile(const std::string &filename, Sint64 adler32)
99{
100 mOptions.memoryWrite = false;
101 mFileName = filename;
102
103 if (adler32 > -1)
104 {
105 mAdler = (unsigned long) adler32;
106 mOptions.checkAdler = true;
107 }
108 else
109 mOptions.checkAdler = false;
110}
111
112void Download::setWriteFunction(WriteFunction write)
113{
114 mOptions.memoryWrite = true;
115 mWriteFunction = write;
116}
117
118bool Download::start()
119{
120 logger->log("Starting download: %s\n", mUrl.c_str());
121
122 mThread = SDL_CreateThread(downloadThread, this);
123
124 if (!mThread)
125 {
126 logger->log(DOWNLOAD_ERROR_MESSAGE_THREAD);
127 strcpy(mError, DOWNLOAD_ERROR_MESSAGE_THREAD);
128 mUpdateFunction(mPtr, DOWNLOAD_STATUS_THREAD_ERROR, 0, 0);
129
130 return false;
131 }
132
133 return true;
134}
135
136void Download::cancel()
137{
138 logger->log("Canceling download: %s\n", mUrl.c_str());
139 mOptions.cancel = true;
140}
141
142char *Download::getError()
143{
144 return mError;
145}
146
147int Download::downloadProgress(void *clientp, double dltotal, double dlnow,
148 double ultotal, double ulnow)
149{
150 Download *d = reinterpret_cast<Download*>(clientp);
151
152 if (d->mOptions.cancel)
153 {
154 return d->mUpdateFunction(d->mPtr, DOWNLOAD_STATUS_CANCELLED, dltotal,
155 dlnow);
156 return -5;
157 }
158
159 return d->mUpdateFunction(d->mPtr, DOWNLOAD_STATUS_IDLE, dltotal, dlnow);
160}
161
162int Download::downloadThread(void *ptr)
163{
164 int attempts = 0;
165 bool complete = false;
166 Download *d = reinterpret_cast<Download*>(ptr);
167 CURLcode res;
168 std::string outFilename;
169
170 if (!d->mOptions.memoryWrite)
171 {
172 outFilename = d->mFileName + ".part";
173 }
174
175 while (attempts < 3 && !complete)
176 {
177 FILE *file = NULL;
178
179 d->mUpdateFunction(d->mPtr, DOWNLOAD_STATUS_STARTING, 0, 0);
180
181 d->mCurl = curl_easy_init();
182
183 if (d->mCurl)
184 {
185 logger->log("Downloading: %s", d->mUrl.c_str());
186
187 curl_easy_setopt(d->mCurl, CURLOPT_HTTPHEADER, d->mHeaders);
188
189 if (d->mOptions.memoryWrite)
190 {
191 curl_easy_setopt(d->mCurl, CURLOPT_FAILONERROR, 1);
192 curl_easy_setopt(d->mCurl, CURLOPT_WRITEFUNCTION, d->mWriteFunction);
193 curl_easy_setopt(d->mCurl, CURLOPT_WRITEDATA, d->mPtr);
194 }
195 else
196 {
197 file = fopen(outFilename.c_str(), "w+b");
198 curl_easy_setopt(d->mCurl, CURLOPT_WRITEDATA, file);
199 }
200
201#ifdef PACKAGE_VERSION
202 curl_easy_setopt(d->mCurl, CURLOPT_USERAGENT, "TMW/" PACKAGE_VERSION);
203#else
204 curl_easy_setopt(d->mCurl, CURLOPT_USERAGENT, "TMW");
205#endif
206 curl_easy_setopt(d->mCurl, CURLOPT_ERRORBUFFER, d->mError);
207 curl_easy_setopt(d->mCurl, CURLOPT_URL, d->mUrl.c_str());
208 curl_easy_setopt(d->mCurl, CURLOPT_NOPROGRESS, 0);
209 curl_easy_setopt(d->mCurl, CURLOPT_PROGRESSFUNCTION, downloadProgress);
210 curl_easy_setopt(d->mCurl, CURLOPT_PROGRESSDATA, ptr);
211 curl_easy_setopt(d->mCurl, CURLOPT_NOSIGNAL, 1);
212 curl_easy_setopt(d->mCurl, CURLOPT_CONNECTTIMEOUT, 15);
213
214 if ((res = curl_easy_perform(d->mCurl)) != 0)
215 {
216 switch (res)
217 {
218 case CURLE_COULDNT_CONNECT:
219 default:
220 logger->log("curl error %d: %s host: %s",
221 res, d->mError, d->mUrl.c_str());
222 break;
223 }
224
225 d->mUpdateFunction(d->mPtr, DOWNLOAD_STATUS_ERROR, 0, 0);
226
227 if (!d->mOptions.memoryWrite)
228 {
229 fclose(file);
230 ::remove(outFilename.c_str());
231 }
232 attempts++;
233 continue;
234 }
235
236 curl_easy_cleanup(d->mCurl);
237
238 if (!d->mOptions.memoryWrite)
239 {
240 // Don't check resources2.txt checksum
241 if (d->mOptions.checkAdler)
242 {
243 unsigned long adler = fadler32(file);
244
245 if (d->mAdler != adler)
246 {
247 fclose(file);
248
249 // Remove the corrupted file
250 ::remove(d->mFileName.c_str());
251 logger->log("Checksum for file %s failed: (%lx/%lx)",
252 d->mFileName.c_str(),
253 adler, d->mAdler);
254 attempts++;
255 continue; // Bail out here to avoid the renaming
256 }
257 }
258 fclose(file);
259
260 // Any existing file with this name is deleted first, otherwise
261 // the rename will fail on Windows.
262 ::remove(d->mFileName.c_str());
263 ::rename(outFilename.c_str(), d->mFileName.c_str());
264
265 // Check if we can open it and no errors were encountered
266 // during renaming
267 file = fopen(d->mFileName.c_str(), "rb");
268 if (file)
269 {
270 fclose(file);
271 complete = true;
272 }
273 }
274 else
275 {
276 // It's stored in memory, we're done
277 complete = true;
278 }
279 }
280 attempts++;
281 }
282
283 if (!complete) {
284 d->mUpdateFunction(d->mPtr, DOWNLOAD_STATUS_ERROR, 0, 0);
285 }
286 else
287 {
288 d->mUpdateFunction(d->mPtr, DOWNLOAD_STATUS_COMPLETE, 0, 0);
289 }
290
291 return 0;
292}
293
294
295} // namespace Net
  
1/*
2 * The Mana World
3 * Copyright (C) 2009 The Mana World Development Team
4 *
5 * This file is part of The Mana World.
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, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 */
21
22#include <SDL_types.h>
23#include <stdio.h>
24#include <string>
25
26#ifndef NET_DOWNLOAD_H
27#define NET_DOWNLOAD_H
28
29enum DownloadStatus
30{
31 DOWNLOAD_STATUS_CANCELLED = -3,
32 DOWNLOAD_STATUS_THREAD_ERROR = -2,
33 DOWNLOAD_STATUS_ERROR = -1,
34 DOWNLOAD_STATUS_STARTING = 0,
35 DOWNLOAD_STATUS_IDLE,
36 DOWNLOAD_STATUS_COMPLETE
37};
38
39typedef int (*DownloadUpdate)(void *ptr, DownloadStatus status,
40 size_t total, size_t remaining);
41
42// Matches what CURL expects
43typedef size_t (*WriteFunction)( void *ptr, size_t size, size_t nmemb,
44 void *stream);
45
46struct SDL_Thread;
47typedef void CURL;
48struct curl_slist;
49
50namespace Net {
51class Download
52{
53 public:
54 Download(void *ptr, const std::string &url, DownloadUpdate updateFunction);
55
56 ~Download();
57
58 void addHeader(const std::string &header);
59
60 /**
61 * Convience method for adding no-cache headers.
62 */
63 void noCache();
64
65 void setFile(const std::string &filename, Sint64 adler32 = -1);
66
67 void setWriteFunction(WriteFunction write);
68
69 /**
70 * Starts the download thread.
71 * @returns true if thread was created
72 * false if the thread could not be made or download wasn't
73 * properly setup
74 */
75 bool start();
76
77 /**
78 * Cancels the download. Returns immediately, the cancelled status will
79 * be noted in the next avialable update call.
80 */
81 void cancel();
82
83 char *getError();
84
85 private:
86 static int downloadThread(void *ptr);
87 static int downloadProgress(void *clientp, double dltotal, double dlnow,
88 double ultotal, double ulnow);
89 void *mPtr;
90 std::string mUrl;
91 struct {
92 unsigned cancel : 1;
93 unsigned memoryWrite: 1;
94 unsigned checkAdler: 1;
95 } mOptions;
96 std::string mFileName;
97 WriteFunction mWriteFunction;
98 unsigned long mAdler;
99 DownloadUpdate mUpdateFunction;
100 SDL_Thread *mThread;
101 CURL *mCurl;
102 curl_slist *mHeaders;
103 char *mError;
104};
105
106} // namespace Net
107
108#endif // NET_DOWNLOAD_H