1 /** @file xapian-replicate.cc
2 * @brief Replicate a database from a master server to a local copy.
4 /* Copyright (C) 2008,2011,2012,2015 Olly Betts
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.
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.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
24 #include "net/replicatetcpclient.h"
28 #include "gnu_getopt.h"
29 #include "stringutils.h"
30 #include "safeunistd.h"
36 #define PROG_NAME "xapian-replicate"
37 #define PROG_DESC "Replicate a database from a master server to a local copy"
42 // Wait DEFAULT_INTERVAL seconds between updates unless --interval is passed.
43 #define DEFAULT_INTERVAL 60
45 // Number of seconds before we assume that a reader will be closed.
46 #define READER_CLOSE_TIME 30
48 // Socket level timeout (in seconds).
49 #define DEFAULT_TIMEOUT 0
51 static void show_usage() {
52 cout
<< "Usage: " PROG_NAME
" [OPTIONS] DATABASE\n\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" << endl
;
73 main(int argc
, char **argv
)
75 const char * opts
= "h:p:m:i:r:t:ofqv";
76 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
},
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
;
103 while ((c
= gnu_getopt_long(argc
, argv
, opts
, long_opts
, 0)) != -1) {
112 masterdb
.assign(optarg
);
115 interval
= atoi(optarg
);
118 reader_close_time
= atoi(optarg
);
121 timeout
= atoi(optarg
);
136 cout
<< PROG_NAME
" - " PROG_DESC
"\n\n";
140 cout
<< PROG_NAME
" - " PACKAGE_STRING
<< endl
;
148 if (argc
- optind
!= 1) {
154 cout
<< "Host required - specify with --host=HOST\n\n";
160 cout
<< "Port required - specify with --port=PORT\n\n";
165 // Path to the database to create/update.
166 string
dbpath(argv
[optind
]);
168 if (masterdb
.empty())
173 if (verbosity
== VERBOSE
) {
174 cout
<< "Connecting to " << host
<< ":" << port
<< endl
;
176 ReplicateTcpClient
client(host
, port
, 10.0, timeout
);
177 if (verbosity
== VERBOSE
) {
178 cout
<< "Getting update for " << dbpath
<< " from "
181 Xapian::ReplicationInfo info
;
182 client
.update_from_master(dbpath
, masterdb
, info
,
183 reader_close_time
, force_copy
);
184 if (verbosity
== VERBOSE
) {
185 cout
<< "Update complete: "
186 << info
.fullcopy_count
<< " copies, "
187 << info
.changeset_count
<< " changesets, "
188 << (info
.changed
? "new live database"
189 : "no changes to live database")
192 if (verbosity
!= QUIET
) {
193 if (info
.fullcopy_count
> 0 && !info
.changed
) {
195 "Replication using a full copy failed. This usually means that the master\n"
196 "database is changing too frequently. Ensure that sufficient changesets are\n"
197 "present by setting XAPIAN_MAX_CHANGESETS on the master." << endl
;
201 } catch (const Xapian::NetworkError
&error
) {
202 // Don't stop running if there's a network error - just log to
203 // stderr and retry at next timeout. This should make the client
204 // robust against temporary network failures.
205 cerr
<< argv
[0] << ": " << error
.get_description() << endl
;
207 // If we were running as a one-shot client though, we're going to
208 // exit anyway, so let's make the return value reflect that there
212 } catch (const Xapian::Error
&error
) {
213 cerr
<< argv
[0] << ": " << error
.get_description() << endl
;
215 } catch (const exception
&e
) {
216 cerr
<< "Caught standard exception: " << e
.what() << endl
;
219 cerr
<< "Caught unknown exception" << endl
;