wfut  0.2.4
A client side C++ implementation of WFUT (WorldForge Update Tool).
wfut.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 #ifdef HAVE_CONFIG_H
6  #include "config.h"
7 #endif
8 
9 #include <getopt.h>
10 
11 #include <dirent.h>
12 #include <sys/stat.h>
13 #include <sys/types.h>
14 #include <time.h>
15 
16 #include <algorithm>
17 #include <cstdio>
18 
19 #include <sigc++/bind.h>
20 
21 #include <libwfut/WFUT.h>
22 #include <libwfut/Encoder.h>
23 #include <libwfut/platform.h>
24 
25 using namespace WFUT;
26 
27 static const bool debug = true;
28 
29 // getopt long argument struct.
30 static struct option long_options [] =
31 {
32  { "update", 1, 0, 'u' },
33  { "system", 1, 0, 's' },
34  { "server", 1, 0, 'S' },
35  { "prefix", 1, 0, 'p' },
36  { "version", 0, 0, 'v' },
37  { "help", 0, 0, 'h' },
38 
39 };
40 
41 // getopt short argument struct. One char per command
42 // follow by a : to indicate that an argument it required
43 static char short_options [] = "u:s:p:vS:h";
44 
45 static void recordUpdate(const FileObject &fo, const std::string &tmpfile) {
46  FILE *fp = 0;
47  if (!os_exists(tmpfile)) {
48  // Write header
49  fp = fopen(tmpfile.c_str(), "wt");
50  if (!fp) {
51  //error
52  return;
53  }
54  fprintf(fp, "<?xml version=\"1.0\"?>\n");
55  fprintf(fp, "<fileList dir=\"\">\n");
56  } else {
57  fp = fopen(tmpfile.c_str(), "at");
58  if (!fp) {
59  //error
60  return;
61  }
62  }
63  fprintf(fp, "<file filename=\"%s\" version=\"%d\" crc32=\"%lu\" size=\"%ld\" execute=\"%s\"/>\n", Encoder::encodeString(fo.filename).c_str(), fo.version, fo.crc32, fo.size, (fo.execute) ? ("true") : ("false"));
64  fclose(fp);
65 
66 }
67 
68 namespace {
69 // Signal handler called when a file is sicessfully downloaded
70 // We use this to update the local file list with the updated details
71 void onDownloadComplete(const std::string& u, const std::string& f, const ChannelFileList& updates, ChannelFileList* local, const std::string& tmpfile) {
72  printf("Downloaded: %s\n", f.c_str());
73 
74  const WFUT::FileMap& ulist = updates.getFiles();
75  WFUT::FileMap::const_iterator I = ulist.find(f);
76  // The fileobject should exist, otherwise how did we get here?
77  assert (I != ulist.end());
78  // Add the updated file record to the local list.
79  // addFile will overwrite any existing fileobject with the same
80  // filename
81  local->addFile(I->second);
82 
83  // We store in a tmp file the fact that we sucessfully downloaded
84  // this file, incase of a crash.
85  recordUpdate(I->second, tmpfile);
86 }
87 
88 // Signal handler called when a download fails.
89 void onDownloadFailed(const std::string& u, const std::string& f, const std::string& r, int* error) {
90  fprintf(stderr, "Error downloading: %s - %s\n", u.c_str(), r.c_str());
91  // Increment error count
92  ++error;
93 }
94 
95 void onUpdateReason(const std::string& filename, const WFUT::WFUTUpdateReason wu) {
96  if (wu == WFUT::WFUT_UPDATE_MODIFIED) {
97  printf("%s has been modified, will not update.\n", filename.c_str());
98  }
99 }
100 
101 void print_usage(const char* name) {
102  printf("WFUT Version: %s\n", VERSION);
103  printf("Usage: %s [options]\n", name);
104  printf("\nOptions:\n");
105  printf("\t-p, --prefix channel_name -- The destination directory. (Optional)\n");
106  printf("\t-s, --system channel_name -- The system channels directory. (Optional)\n");
107  printf("\t-S, --server channel_name -- The URL to the update server.(Optional)\n");
108  printf("\t-u, --update channel_name -- The name of the channel to update.\n");
109  printf("\t-v, --version -- Display the version information.\n");
110  printf("\t-h, --help -- Display this message.\n");
111 }
112 
113 }
114 int main(int argc, char *argv[]) {
115 
116  // Set some default values which we can override with command line parameters.
117  std::string server_root = "http://white.worldforgedev.org/WFUT/";
118  std::string mirror_file = "mirrors.xml";
119  std::string channel_file = "wfut.xml";
120  std::string tmpfile = "tempwfut.xml";
121 
122  std::string channel = ".";
123  std::string local_path = "./";
124  std::string system_path = "";
125 
126  if (argc == 1) {
127  print_usage(argv[0]);
128  return 0;
129  }
130 
131  while (true) {
132  int opt_index = 0;
133  int c = getopt_long(argc, argv, short_options, long_options, &opt_index);
134  if (c == -1) break;
135  switch (c) {
136  case '?':
137  case 'h':
138  print_usage(argv[0]);
139  return 0;
140  break;
141  case 'v':
142  fprintf(stderr, "WFUT Version: %s\n", VERSION);
143  return 0;
144  break;
145  case 'u':
146  if (optarg) {
147  channel = optarg;
148  } else {
149  fprintf(stderr, "Missing channel name\n");
150  }
151  break;
152  case 's':
153  if (optarg) {
154  system_path = optarg;
155  } else {
156  fprintf(stderr, "Missing system path\n");
157  }
158  break;
159  case 'p':
160  if (optarg) {
161  local_path = optarg;
162  } else {
163  fprintf(stderr, "Missing prefix\n");
164  }
165  break;
166  case 'S':
167  if (optarg) {
168  server_root = optarg;
169  } else {
170  fprintf(stderr, "Missing system path\n");
171  }
172  break;
173 
174  default:
175  fprintf(stderr, "Unknown command: %c\n", c);
176  }
177  }
178 
179  if (debug) printf("Channel name: %s\n", channel.c_str());
180  // This is the base path for all files that will be downloaded
181  const std::string &local_root = local_path + "/" + channel + "/";
182 
183  WFUTClient wfut;
184  // Initialise wfut. This does the curl setup.
185  wfut.init();
186 
187  // Get the list of mirrors
188  MirrorList mirrors;
189  const std::string mirror_url = server_root + "/" + mirror_file;
190  wfut.getMirrorList(mirror_url, mirrors);
191 
192  // Randomly select a mirror to use.
193  if (mirrors.empty() == false) {
194  // Initialise random number generators. random_shuffle could use either
195  srand((unsigned)time(NULL));
196 #if defined (WIN32) || defined (_WIN32) || defined( __WIN32__)
197 #else
198  srand48((unsigned)time(NULL));
199 #endif
200  // Shuffle mirror list
201  std::random_shuffle(mirrors.begin(), mirrors.end());
202  // Pick first mirror
203  server_root = (*mirrors.begin()).url;
204  }
205 
206  // Define the channelfilelist objects we will use
207  ChannelFileList local, system, server, updates, tmplist;
208 
209 
210  // Look for local wfut file.
211  const std::string &local_wfut = local_path + "/" + channel + "/" + channel_file;
212  if (debug) printf("Local wfut: %s\n", local_wfut.c_str());
213 
214  if (os_exists(local_wfut)) {
215  if (wfut.getLocalList(local_wfut, local)) {
216  fprintf(stderr, "Error reading local wfut.xml file\n");
217  wfut.shutdown();
218  return 1;
219  } else {
220  if (channel == ".") channel = local.getName();
221  }
222  }
223 
224  // Look for tmpwfut file. If it exists, update the local files list.
225  const std::string &tmp_wfut = local_path + "/" + tmpfile;
226  if (debug) printf("Tmp wfut: %s\n", tmp_wfut.c_str());
227 
228  if (os_exists(tmp_wfut)) {
229  if (wfut.getLocalList(tmp_wfut, tmplist)) {
230  fprintf(stderr, "Error reading tmpwfut.xml file\n");
231  wfut.shutdown();
232  return 1;
233  } else {
234  const FileMap &fm = tmplist.getFiles();
235  FileMap::const_iterator I = fm.begin();
236  FileMap::const_iterator Iend = fm.end();
237  for (; I != Iend; ++I) {
238  local.addFile(I->second);
239  }
240  }
241  }
242 
243  // Look for a system wfut file
244  if (!system_path.empty()) {
245  const std::string &system_wfut = system_path + "/" + channel + "/" + channel_file;
246  if (debug) printf("System wfut: %s\n", system_wfut.c_str());
247 
248  if (os_exists(system_wfut)) {
249  if (wfut.getLocalList(system_wfut, system)) {
250  fprintf(stderr, "Error reading system wfut.xml file\n");
251  wfut.shutdown();
252  return 1;
253  } else {
254  if (channel == ".") channel = system.getName();
255  }
256  }
257  }
258  // By now we should have a proper channel name. If not, then there is nothing
259  // we can do to find the server updates.
260  if (channel.empty() || channel == ".") {
261  fprintf(stderr, "Unable to determine channel name.\n");
262  wfut.shutdown();
263  return 1;
264  }
265 
266  const std::string &server_wfut = server_root + "/" + channel + "/" + channel_file;
267  if (debug) printf("Server wfut: %s\n", server_wfut.c_str());
268 
269  if (wfut.getFileList(server_wfut, server)) {
270  printf("Error downloading server channel file\n");
271  wfut.shutdown();
272  return 1;
273  }
274 
275  if (debug) printf("Local Root: %s\n", local_root.c_str());
276 
277  printf("Updating Channel: %s\n", channel.c_str());
278 
279  // Now we have loaded all our data files, lets find out what we really need
280  // to download
281  wfut.UpdateReason.connect(sigc::ptr_fun(onUpdateReason));
282  if (wfut.calculateUpdates(server, system, local, updates, local_root)) {
283  printf("Error determining file to update\n");
284  wfut.shutdown();
285  return 1;
286  }
287 
288  // Make sure the local file has the correct channel name
289  local.setName(server.getName());
290 
291  // Queue the list of files to download
292  wfut.updateChannel(updates, server_root + "/" + channel, local_root);
293 
294  // error counts the number of download failures. This is incremented in the
295  // download failed signal handler
296  int error = 0;
297  // Connect up the signal handlers
298  wfut.DownloadComplete.connect(sigc::bind(sigc::ptr_fun(onDownloadComplete), updates, &local, tmp_wfut));
299  wfut.DownloadFailed.connect(sigc::bind(sigc::ptr_fun(onDownloadFailed), &error));
300 
301  // Keep polling to download some more file bits.
302  while (wfut.poll());
303 
304  // Save the completed download list
305  wfut.saveLocalList(local, local_wfut);
306  // Delete tmpwfut.xml if we get here. We only keep it around in case of
307  // an abnormal exit and the real wfut.xml has not been saved.
308  if (os_exists(tmp_wfut)) remove(tmp_wfut.c_str());
309 
310  // Clean up WFUT. Closes curl handles
311  wfut.shutdown();
312 
313  if (error) {
314  fprintf(stderr, "%d files failed to download.\n", error);
315  }
316 
317 
318  // Return error. Will be 0 on success
319  return error;
320 }
sigc::signal< void, const std::string &, const std::string & > DownloadComplete
Definition: WFUT.h:149
static std::string encodeString(const std::string &str)
Definition: Encoder.cpp:13
sigc::signal< void, const std::string &, const std::string &, const std::string & > DownloadFailed
Definition: WFUT.h:158
WFUTError shutdown()
Definition: WFUT.cpp:41
WFUTError getLocalList(const std::string &filename, ChannelFileList &files)
Definition: WFUT.cpp:192
std::string getName() const
const FileMap & getFiles() const
void setName(const std::string &name)
WFUTError getMirrorList(const std::string &url, MirrorList &mirrors)
Definition: WFUT.cpp:81
sigc::signal< void, const std::string &, const WFUTUpdateReason > UpdateReason
Definition: WFUT.h:166
WFUTError getFileList(const std::string &url, ChannelFileList &files)
Definition: WFUT.cpp:154
void addFile(const FileObject &fo)
WFUTError calculateUpdates(const ChannelFileList &server, const ChannelFileList &system, const ChannelFileList &local, ChannelFileList &updates, const std::string &prefix)
Definition: WFUT.cpp:215
WFUTError saveLocalList(const ChannelFileList &files, const std::string &filename)
Definition: WFUT.cpp:201
void updateChannel(const ChannelFileList &updates, const std::string &urlPrefix, const std::string &pathPrefix)
Definition: WFUT.cpp:53
WFUTError init()
Definition: WFUT.cpp:23