xapian-core  2.0.0
xapian-replicate.cc
Go to the documentation of this file.
1 
4 /* Copyright (C) 2008,2011,2012,2015 Olly Betts
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of the
9  * License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, see
18  * <https://www.gnu.org/licenses/>.
19  */
20 
21 #include <config.h>
22 
23 #include "net/replicatetcpclient.h"
24 
25 #include <xapian.h>
26 
27 #include "gnu_getopt.h"
28 #include "parseint.h"
29 #include "stringutils.h"
30 #include "safeunistd.h"
31 
32 #include <iostream>
33 
34 using namespace std;
35 
36 #define PROG_NAME "xapian-replicate"
37 #define PROG_DESC "Replicate a database from a master server to a local copy"
38 
39 #define OPT_HELP 1
40 #define OPT_VERSION 2
41 
42 // Wait DEFAULT_INTERVAL seconds between updates unless --interval is passed.
43 #define DEFAULT_INTERVAL 60
44 
45 // Number of seconds before we assume that a reader will be closed.
46 #define READER_CLOSE_TIME 30
47 
48 // Socket level timeout (in seconds).
49 #define DEFAULT_TIMEOUT 0
50 
51 static void show_usage() {
52  cout << "Usage: " PROG_NAME " [OPTIONS] DATABASE\n\n"
53 "Options:\n"
54 " -h, --host=HOST host to connect to (required)\n"
55 " -p, --port=PORT port to connect to (required)\n"
56 " -m, --master=DB replicate database DB from the master (default: DATABASE)\n"
57 " -i, --interval=N wait N seconds between each connection to the master\n"
58 " (default: " STRINGIZE(DEFAULT_INTERVAL) ")\n"
59 " -r, --reader-time=N wait N seconds to allow readers time to close before\n"
60 " applying repeated changesets (default: " STRINGIZE(READER_CLOSE_TIME) ")\n"
61 " -t, --timeout=N set socket timeouts (if supported) to N seconds; N=0 for\n"
62 " no timeout (default: " STRINGIZE(DEFAULT_TIMEOUT) ")\n"
63 " -f, --force-copy force a full copy of the database to be sent (and then\n"
64 " replicate as normal)\n"
65 " -o, --one-shot replicate only once and then exit\n"
66 " -q, --quiet only report errors\n"
67 " -v, --verbose be more verbose\n"
68 " --help display this help and exit\n"
69 " --version output version information and exit\n";
70 }
71 
72 int
73 main(int argc, char **argv)
74 {
75  const char * opts = "h:p:m:i:r:t:ofqv";
76  static const struct option long_opts[] = {
77  {"host", required_argument, 0, 'h'},
78  {"port", required_argument, 0, 'p'},
79  {"master", required_argument, 0, 'm'},
80  {"interval", required_argument, 0, 'i'},
81  {"reader-time", required_argument, 0, 'r'},
82  {"timeout", required_argument, 0, 't'},
83  {"one-shot", no_argument, 0, 'o'},
84  {"force-copy", no_argument, 0, 'f'},
85  {"quiet", no_argument, 0, 'q'},
86  {"verbose", no_argument, 0, 'v'},
87  {"help", no_argument, 0, OPT_HELP},
88  {"version", no_argument, 0, OPT_VERSION},
89  {NULL, 0, 0, 0}
90  };
91 
92  string host;
93  int port = 0;
94  string masterdb;
95  int interval = DEFAULT_INTERVAL;
96  bool one_shot = false;
97  enum { NORMAL, VERBOSE, QUIET } verbosity = NORMAL;
98  bool force_copy = false;
99  int reader_close_time = READER_CLOSE_TIME;
100  int timeout = DEFAULT_TIMEOUT;
101 
102  int c;
103  while ((c = gnu_getopt_long(argc, argv, opts, long_opts, 0)) != -1) {
104  switch (c) {
105  case 'h':
106  host.assign(optarg);
107  break;
108  case 'p':
109  if (!parse_signed(optarg, port) ||
110  (port < 1 || port > 65535)) {
111  cerr << "Error: must specify a valid port number "
112  "(between 1 and 65535). \n";
113  exit(1);
114  }
115  break;
116  case 'm':
117  masterdb.assign(optarg);
118  break;
119  case 'i': {
120  unsigned int i_val;
121  if (!parse_unsigned(optarg, i_val)) {
122  cout << "Interval must be a non-negative integer\n";
123  show_usage();
124  exit(1);
125  }
126  interval = i_val;
127  break;
128  }
129  case 'r': {
130  unsigned int reader_time;
131  if (!parse_unsigned(optarg, reader_time)) {
132  cout << "reader close time must be a "
133  "non-negative integer\n";
134  show_usage();
135  exit(1);
136  }
137  reader_close_time = reader_time;
138  break;
139  }
140  case 't':
141  unsigned int socket_timeout;
142  if (!parse_unsigned(optarg, socket_timeout)) {
143  cout << "timeout must be a non-negative integer\n";
144  show_usage();
145  exit(1);
146  }
147  timeout = socket_timeout;
148  break;
149  case 'f':
150  force_copy = true;
151  break;
152  case 'o':
153  one_shot = true;
154  break;
155  case 'q':
156  verbosity = QUIET;
157  break;
158  case 'v':
159  verbosity = VERBOSE;
160  break;
161  case OPT_HELP:
162  cout << PROG_NAME " - " PROG_DESC "\n\n";
163  show_usage();
164  exit(0);
165  case OPT_VERSION:
166  cout << PROG_NAME " - " PACKAGE_STRING "\n";
167  exit(0);
168  default:
169  show_usage();
170  exit(1);
171  }
172  }
173 
174  if (argc - optind != 1) {
175  show_usage();
176  exit(1);
177  }
178 
179  if (host.empty()) {
180  cout << "Host required - specify with --host=HOST\n\n";
181  show_usage();
182  exit(1);
183  }
184 
185  if (port == 0) {
186  cout << "Port required - specify with --port=PORT\n\n";
187  show_usage();
188  exit(1);
189  }
190 
191  // Path to the database to create/update.
192  string dbpath(argv[optind]);
193 
194  if (masterdb.empty())
195  masterdb = dbpath;
196 
197  while (true) {
198  try {
199  if (verbosity == VERBOSE) {
200  cout << "Connecting to " << host << ":" << port << '\n';
201  }
202  ReplicateTcpClient client(host, port, 10.0, timeout);
203  if (verbosity == VERBOSE) {
204  cout << "Getting update for " << dbpath << " from "
205  << masterdb << '\n';
206  }
208  client.update_from_master(dbpath, masterdb, info,
209  reader_close_time, force_copy);
210  if (verbosity == VERBOSE) {
211  cout << "Update complete: "
212  << info.fullcopy_count << " copies, "
213  << info.changeset_count << " changesets, "
214  << (info.changed ? "new live database"
215  : "no changes to live database")
216  << '\n';
217  }
218  if (verbosity != QUIET) {
219  if (info.fullcopy_count > 0 && !info.changed) {
220  cout <<
221 "Replication using a full copy failed. This usually means that the master\n"
222 "database is changing too frequently. Ensure that sufficient changesets are\n"
223 "present by setting XAPIAN_MAX_CHANGESETS on the master.\n";
224  }
225  }
226  force_copy = false;
227  } catch (const Xapian::NetworkError &error) {
228  // Don't stop running if there's a network error - just log to
229  // stderr and retry at next timeout. This should make the client
230  // robust against temporary network failures.
231  cerr << argv[0] << ": " << error.get_description() << '\n';
232 
233  // If we were running as a one-shot client though, we're going to
234  // exit anyway, so let's make the return value reflect that there
235  // was a failure.
236  if (one_shot)
237  exit(1);
238  } catch (const Xapian::Error &error) {
239  cerr << argv[0] << ": " << error.get_description() << '\n';
240  exit(1);
241  } catch (const exception &e) {
242  cerr << "Caught standard exception: " << e.what() << '\n';
243  exit(1);
244  } catch (...) {
245  cerr << "Caught unknown exception\n";
246  exit(1);
247  }
248  if (one_shot) break;
249  sleep(interval);
250  }
251 }
All exceptions thrown by Xapian are subclasses of Xapian::Error.
Definition: error.h:41
std::string get_description() const
Return a string describing this object.
Definition: error.cc:93
Indicates a problem communicating with a remote database.
Definition: error.h:791
#define PACKAGE_STRING
Definition: config.h:361
int optind
Definition: getopt.cc:93
char * optarg
Definition: getopt.cc:78
Wrappers to allow GNU getopt to be used cleanly from C++ code.
#define no_argument
Definition: gnu_getopt.h:78
#define required_argument
Definition: gnu_getopt.h:79
int gnu_getopt_long(int argc_, char *const *argv_, const char *shortopts_, const struct option *longopts_, int *optind_)
Definition: gnu_getopt.h:96
void sleep(double t)
Sleep until the time represented by this object.
Definition: realtime.h:127
Parse signed and unsigned type from string and check for trailing characters.
bool parse_signed(const char *p, T &res)
Definition: parseint.h:44
bool parse_unsigned(const char *p, T &res)
Definition: parseint.h:29
TCP/IP replication client class.
<unistd.h>, but with compat.
Various handy string-related helpers.
#define STRINGIZE(X)
The STRINGIZE macro converts its parameter into a string constant.
Definition: stringutils.h:41
Information about the steps involved in performing a replication.
Definition: replication.h:32
bool changed
True if and only if the replication corresponds to a change in the live version of the database.
Definition: replication.h:44
int fullcopy_count
Number of times a full database copy was performed.
Definition: replication.h:37
int changeset_count
Number of changesets applied.
Definition: replication.h:34
static const char * opts
static const struct option long_opts[]
static void show_usage()
#define DEFAULT_INTERVAL
#define OPT_VERSION
int main(int argc, char **argv)
#define PROG_NAME
#define PROG_DESC
#define DEFAULT_TIMEOUT
#define OPT_HELP
#define READER_CLOSE_TIME
Public interfaces for the Xapian library.