wfut 0.2.4
A client side C++ implementation of WFUT (WorldForge Update Tool).
IO.cpp
1// This file may be redistributed and modified only under the terms of
2// the GNU Lesser General Public License (See COPYING for details).
3// Copyright (C) 2005 - 2008 Simon Goodall
4
5#include <dirent.h>
6#include <cstdio>
7#include <algorithm>
8
9#include "libwfut/IO.h"
10#include "libwfut/Encoder.h"
11#include "libwfut/platform.h"
12
13#ifdef HAVE_CONFIG_H
14#include "config.h"
15#endif
16
17namespace WFUT {
18
19static const bool debug = false;
20
21// Create the parent dir of the file.
22// TODO Does this work if the parent parent dir does not exist?
23static int createParentDirs(const std::string &filename) {
24 int err = 1;
25 // TODO This function may not work correctly or be portable.
26 // Perhaps should only search for '\\' on win32, '/' otherwise
27 size_t pos = filename.find_last_of("\\/");
28 // Return if no separator is found, or if it is the first
29 // character, e.g. /home
30 if (pos == std::string::npos || pos == 0) return 0;
31
32 const std::string &path = filename.substr(0, pos);
33
34 if ((err = createParentDirs(path))) {
35 // There was an error creating the parent path.
36 return err;
37 }
38
39 // See if the directory already exists
40 DIR *d = opendir(path.c_str());
41 if (!d) {
42 // Make dir as it doesn't exist
43 err = os_mkdir(path);
44 } else{
45 closedir(d);
46 err = 0;
47 }
48 return err;
49}
50
51static int copy_file(FILE *fp, const std::string &target_filename) {
52
53 if (createParentDirs(target_filename)) {
54 // Error making dir structure
55 fprintf(stderr, "There was an error creating the required directory tree for %s.\n", target_filename.c_str());
56 return 1;
57 }
58 FILE *tp = fopen(target_filename.c_str(), "wb");
59 if (!tp) {
60 // Error opening file to write
61 return 1;
62 }
63
64 if (fp) {
65 rewind(fp);
66 char buf[1024];
67 size_t num;
68 while ((num = fread(buf, sizeof(char), 1024, fp)) != 0) {
69 fwrite(buf, sizeof(char), num, tp);
70 }
71 } else {
72 // No fp? we should only get here if the file was empty
73 // so all this function will do is create an empty file.
74 }
75 fclose(tp);
76
77 return 0;
78}
79
80// Callback function to write downloaded data to a file.
81static size_t write_data(void *buffer, size_t size, size_t nmemb,void *userp) {
82 assert(userp != NULL);
83 DataStruct *ds = reinterpret_cast<DataStruct*>(userp);
84
85 // Need to create a file in fp is NULL
86 if (ds->fp == NULL) {
87 // Open File handle
88 ds->fp = os_create_tmpfile();
89 // TODO Check that filehandle is valid
90 if (ds->fp == NULL) {
91 fprintf(stderr, "Error opening file for writing\n");
92 return 0;
93 }
94 // Initialise CRC32 value
95 ds->actual_crc32 = crc32(0L, Z_NULL, 0);
96 }
97
98 assert(ds->fp != NULL);
99
100 // Update crc32 value
101 ds->actual_crc32 = crc32(ds->actual_crc32, reinterpret_cast<Bytef*>(buffer), size * nmemb);
102
103 // Write data to file
104 return fwrite(buffer, size, nmemb, ds->fp);
105}
106
110static int setDefaultOpts(CURL *handle) {
111 curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1);
112 curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, write_data);
113 curl_easy_setopt(handle, CURLOPT_FAILONERROR, 1);
114 return 0;
115}
116
117int IO::init() {
118 assert (m_initialised == false);
119
120 curl_global_init(CURL_GLOBAL_ALL);
121
122 m_mhandle = curl_multi_init();
123#ifdef HAVE_CURL_MULTI_PIPELINING
124 curl_multi_setopt(m_mhandle, CURLMOPT_PIPELINING, 1);
125#endif
126
127 m_initialised = true;
128
129 return 0;
130}
131
133 assert (m_initialised == true);
134
135 curl_multi_cleanup(m_mhandle);
136 m_mhandle = NULL;
137
138 while (!m_files.empty()) {
139 DataStruct *ds = m_files.begin()->second;
140 assert(ds);
141 if (ds->handle) {
142 curl_easy_cleanup(ds->handle);
143 ds->handle = NULL;
144 }
145 if (ds->fp) {
146 fclose(ds->fp);
147 ds->fp = NULL;
148 }
149 delete ds;
150 m_files.erase(m_files.begin());
151 }
152
153 curl_global_cleanup();
154
155 m_initialised = false;
156
157 return 0;
158}
159
160int IO::downloadFile(const std::string &filename, const std::string &url, uLong expected_crc32) {
161
162 DataStruct ds;
163 ds.fp = NULL;
164 ds.url = Encoder::encodeURL(url);
165 ds.filename = filename;
166 ds.executable = false;
167 ds.actual_crc32 = crc32(0L, Z_NULL, 0);
168 ds.expected_crc32 = expected_crc32;
169 ds.handle = curl_easy_init();
170
171 setDefaultOpts(ds.handle);
172 curl_easy_setopt(ds.handle, CURLOPT_URL, ds.url.c_str());
173 curl_easy_setopt(ds.handle, CURLOPT_WRITEDATA, &ds);
174
175 CURLcode err = curl_easy_perform(ds.handle);
176 // TODO: Report back the error message
177 // Either convert code to message
178 // Or set CURLOPT_ERRORBUFFER using curl_easy_setopt
179 // to record the message.
180 int error = 1;
181 if (err == 0) {
182 if (copy_file(ds.fp, ds.filename) == 0) {
183 error = 0;
184 }
185 }
186
187 if (ds.fp) fclose(ds.fp);
188 curl_easy_cleanup(ds.handle);
189
190 // Zero on success
191 return error;
192}
193
194int IO::downloadFile(FILE *fp, const std::string &url, uLong expected_crc32) {
195
196 DataStruct ds;
197 ds.fp = fp;
198 ds.url = Encoder::encodeURL(url);
199 ds.executable = false;
200 ds.filename = "";
201 ds.actual_crc32 = crc32(0L, Z_NULL, 0);
202 ds.expected_crc32 = expected_crc32;
203 ds.handle = curl_easy_init();
204
205 setDefaultOpts(ds.handle);
206 curl_easy_setopt(ds.handle, CURLOPT_URL, ds.url.c_str());
207 curl_easy_setopt(ds.handle, CURLOPT_WRITEDATA, &ds);
208 CURLcode err = curl_easy_perform(ds.handle);
209
210 curl_easy_cleanup(ds.handle);
211
212 // TODO: Report back the error message
213 // Either convert code to message
214 // Or set CURLOPT_ERRORBUFFER using curl_easy_setopt
215 // to record the message.
216
217 // Zero on success
218 return (err != 0);
219}
220
221int IO::queueFile(const std::string &path, const std::string &filename, const std::string &url, uLong expected_crc32, bool executable) {
222 if (m_files.find(url) != m_files.end()) {
223 fprintf(stderr, "Error file is already in queue\n");
224 // Url already in queue
225 return 1;
226 }
227
228 DataStruct *ds = new DataStruct();
229 ds->fp = NULL;
230 ds->url = Encoder::encodeURL(url);
231 ds->filename = filename;
232 ds->path = path;
233 ds->executable = executable;
234 ds->actual_crc32 = crc32(0L, Z_NULL, 0);
235 ds->expected_crc32 = expected_crc32;
236 ds->handle = curl_easy_init();
237
238 m_files[ds->url] = ds;
239 setDefaultOpts(ds->handle);
240 curl_easy_setopt(ds->handle, CURLOPT_URL, ds->url.c_str());
241 curl_easy_setopt(ds->handle, CURLOPT_WRITEDATA, ds);
242 curl_easy_setopt(ds->handle, CURLOPT_PRIVATE, ds);
243
244 // Add handle to our queue instead of to curl directly so that
245 // we can limit the number of connections we make to the server.
246 m_handles.push_back(ds->handle);
247
248 return 0;
249}
250
251int IO::poll() {
252 // Do some work and get number of handles in progress
253 int num_handles;
254 curl_multi_perform(m_mhandle, &num_handles);
255
256 struct CURLMsg *msg = NULL;
257 int msgs;
258
259 // Get messages back out of CURL
260 while ((msg = curl_multi_info_read(m_mhandle, &msgs)) != NULL) {
261
262 DataStruct *ds = NULL;
263 int err = curl_easy_getinfo(msg->easy_handle, CURLINFO_PRIVATE, &ds);
264 if (err != CURLE_OK) {
265 // Do something on error
266 fprintf(stderr, "Got some error on curl_easy_getinfo (%d)\n", err);
267 continue;
268 }
269
270 bool failed = true;
271 std::string errormsg = "Unknown failure";
272 switch (msg->msg) {
273 case CURLMSG_DONE: {
274 if (msg->data.result == CURLE_OK) {
275 assert(ds != NULL);
276 if (ds->expected_crc32 == 0L ||
277 ds->expected_crc32 == ds->actual_crc32) {
278 // Download success!
279 failed = false;
280 // Copy file to proper location
281 if (copy_file(ds->fp, ds->path + "/" + ds->filename)) {
282 errormsg = "Error copying file to target location.\n";
283 failed = true;
284 }
285
286 // Set executable if required
287 if (ds->executable) {
288 os_set_executable(ds->path + "/" + ds->filename);
289 }
290 } else {
291 // CRC32 check failed
292 failed = true;
293 errormsg = "CRC32 mismatch";
294 }
295 } else {
296 // Error downloading file
297 failed = true;
298 errormsg = "There was an error downloading the requested file: "
299 + std::string(curl_easy_strerror(msg->data.result));
300 }
301 break;
302 }
303 default:
304 // Something not too good with curl...
305 failed = true;
306 errormsg = "There was an unknown error downloading the requested file";
307 }
308
309 if (debug) printf("Removing Handle\n");
310
311 // Close handle
312 curl_multi_remove_handle(m_mhandle, msg->easy_handle);
313
314 // Clean up
315 if (ds) {
316 if (ds->fp) os_free_tmpfile(ds->fp);
317 ds->fp = NULL;
318 if (failed) {
319 if (debug) printf("Download Failed\n");
320 DownloadFailed.emit(ds->url, ds->filename, errormsg);
321 } else {
322 if (debug) printf("Download Complete\n");
323 DownloadComplete.emit(ds->url, ds->filename);
324 }
325 m_files.erase(m_files.find(ds->url));
326 curl_easy_cleanup(ds->handle);
327 delete ds;
328 }
329 }
330
331 // Spare capacity? Lets queue some more items.
332 int diff = m_num_to_process - num_handles;
333 if (diff > 0) {
334 while (diff--) {
335
336 if (!m_handles.empty()) {
337 // This is where we tell curl about our downloads.
338 curl_multi_add_handle(m_mhandle, m_handles.front());
339 m_handles.pop_front();
340 ++num_handles;
341 }
342 }
343 }
344
345 return num_handles;
346}
347
352
353 while (!m_files.empty()) {
354 DataStruct *ds = (m_files.begin())->second;
355 abortDownload(ds);
356 delete ds;
357 m_files.erase(m_files.begin());
358 }
359}
360
364void IO::abortDownload(const std::string &filename) {
365 std::map<std::string, DataStruct*>::iterator I = m_files.find(filename);
366
367 if (I != m_files.end()) {
368 DataStruct *ds = I->second;
369 abortDownload(ds);
370 delete ds;
371 m_files.erase(I);
372 }
373}
374
376
377 if (ds->handle) {
378 // Find handle in pending list
379 std::deque<CURL*>::iterator I = std::find(m_handles.begin(), m_handles.end(), ds->handle);
380 // Not found? Must be currently downloading.
381 if (I != m_handles.end()) {
382 m_handles.erase(I);
383 } else {
384 curl_multi_remove_handle(m_mhandle, ds->handle);
385 }
386
387 // Clean up curl handle
388 curl_easy_cleanup(ds->handle);
389 ds->handle = NULL;
390 }
391
392 // Clean up file pointer
393 if (ds->fp) {
394 os_free_tmpfile(ds->fp);
395 ds->fp = NULL;
396 }
397
398 // Trigger user feedback
399 DownloadFailed.emit(ds->url, ds->filename, "Aborted");
400}
401
402} /* namespace WFUT */
static std::string encodeURL(const std::string &str)
Definition: Encoder.cpp:62
sigc::signal< void, const std::string &, const std::string & > DownloadComplete
Definition: IO.h:98
int poll()
Definition: IO.cpp:251
int queueFile(const std::string &path, const std::string &filename, const std::string &url, uLong expected_crc32, bool executable)
Definition: IO.cpp:221
int shutdown()
Definition: IO.cpp:132
sigc::signal< void, const std::string &, const std::string &, const std::string & > DownloadFailed
Definition: IO.h:103
int init()
Definition: IO.cpp:117
void abortAll()
Definition: IO.cpp:351
int downloadFile(const std::string &filename, const std::string &url, uLong expected_crc32)
Definition: IO.cpp:160
void abortDownload(const std::string &)
Definition: IO.cpp:364