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 
17 namespace WFUT {
18 
19 static 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?
23 static 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 
51 static 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.
81 static 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 
110 static 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 
117 int 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 
160 int 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 
194 int 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 
221 int 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 
251 int 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 
351 void IO::abortAll() {
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 
364 void 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 
375 void IO::abortDownload(DataStruct *ds) {
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 */
void abortDownload(const std::string &)
Definition: IO.cpp:364
static std::string encodeURL(const std::string &str)
Definition: Encoder.cpp:62
int poll()
Definition: IO.cpp:251
int init()
Definition: IO.cpp:117
sigc::signal< void, const std::string &, const std::string &, const std::string & > DownloadFailed
Definition: IO.h:103
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 & > DownloadComplete
Definition: IO.h:98
int downloadFile(const std::string &filename, const std::string &url, uLong expected_crc32)
Definition: IO.cpp:160
void abortAll()
Definition: IO.cpp:351