Branch data Line data Source code
1 : : /** @file replication.cc
2 : : * @brief Replication support for Xapian databases.
3 : : */
4 : : /* Copyright (C) 2008 Lemur Consulting Ltd
5 : : * Copyright (C) 2008,2009,2010,2011 Olly Betts
6 : : *
7 : : * This program is free software; you can redistribute it and/or modify
8 : : * it under the terms of the GNU General Public License as published by
9 : : * the Free Software Foundation; either version 2 of the License, or
10 : : * (at your option) any later version.
11 : : *
12 : : * This program is distributed in the hope that it will be useful,
13 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 : : * GNU General Public License for more details.
16 : : *
17 : : * You should have received a copy of the GNU General Public License
18 : : * along with this program; if not, write to the Free Software
19 : : * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 : : */
21 : :
22 : : #include <config.h>
23 : :
24 : : #include "replication.h"
25 : :
26 : : #include "xapian/base.h"
27 : : #include "xapian/dbfactory.h"
28 : : #include "xapian/error.h"
29 : : #include "xapian/version.h"
30 : :
31 : : #include "database.h"
32 : : #include "databasereplicator.h"
33 : : #include "debuglog.h"
34 : : #include "fileutils.h"
35 : : #ifdef __WIN32__
36 : : # include "msvc_posix_wrapper.h"
37 : : #endif
38 : : #include "omassert.h"
39 : : #include "realtime.h"
40 : : #include "remoteconnection.h"
41 : : #include "replicationprotocol.h"
42 : : #include "safeerrno.h"
43 : : #include "safesysstat.h"
44 : : #include "safeunistd.h"
45 : : #include "serialise.h"
46 : : #include "str.h"
47 : : #include "utils.h"
48 : :
49 : : #include "autoptr.h"
50 : : #include <cstdio> // For rename().
51 : : #include <fstream>
52 : : #include <string>
53 : :
54 : : using namespace std;
55 : : using namespace Xapian;
56 : :
57 : : // The banner comment used at the top of the replica's stub database file.
58 : : #define REPLICA_STUB_BANNER \
59 : : "# Automatically generated by Xapian::DatabaseReplica v"XAPIAN_VERSION".\n" \
60 : : "# Do not manually edit - replication operations may regenerate this file.\n"
61 : :
62 : : void
63 : 57 : DatabaseMaster::write_changesets_to_fd(int fd,
64 : : const string & start_revision,
65 : : ReplicationInfo * info) const
66 : : {
67 : : LOGCALL_VOID(REPLICA, "DatabaseMaster::write_changesets_to_fd", fd | start_revision | info);
68 [ + - ]: 57 : if (info != NULL)
69 : 57 : info->clear();
70 : 57 : Database db;
71 : : try {
72 : 57 : db = Database(path);
73 : 0 : } catch (const Xapian::DatabaseError & e) {
74 : 0 : RemoteConnection conn(-1, fd, "");
75 : : conn.send_message(REPL_REPLY_FAIL,
76 : : "Can't open database: " + e.get_msg(),
77 : 0 : 0.0);
78 : 0 : return;
79 : : }
80 [ - + ]: 57 : if (db.internal.size() != 1) {
81 : 0 : throw Xapian::InvalidOperationError("DatabaseMaster needs to be pointed at exactly one subdatabase");
82 : : }
83 : :
84 : : // Extract the UUID from start_revision and compare it to the database.
85 : 57 : bool need_whole_db = false;
86 : 57 : string revision;
87 [ - + ]: 57 : if (start_revision.empty()) {
88 : 0 : need_whole_db = true;
89 : : } else {
90 : 57 : const char * ptr = start_revision.data();
91 : 57 : const char * end = ptr + start_revision.size();
92 : 57 : size_t uuid_length = decode_length(&ptr, end, true);
93 : 57 : string request_uuid(ptr, uuid_length);
94 : 57 : ptr += uuid_length;
95 : 57 : string db_uuid = db.internal[0]->get_uuid();
96 [ + + ]: 57 : if (request_uuid != db_uuid) {
97 : 14 : need_whole_db = true;
98 : : }
99 : 57 : revision.assign(ptr, end - ptr);
100 : : }
101 : :
102 : 57 : db.internal[0]->write_changesets_to_fd(fd, revision, need_whole_db, info);
103 : : }
104 : :
105 : : string
106 : 0 : DatabaseMaster::get_description() const
107 : : {
108 : 0 : return "DatabaseMaster(" + path + ")";
109 : : }
110 : :
111 : : /// Internal implementation of DatabaseReplica
112 : : class DatabaseReplica::Internal : public Xapian::Internal::RefCntBase {
113 : : /// Don't allow assignment.
114 : : void operator=(const Internal &);
115 : :
116 : : /// Don't allow copying.
117 : : Internal(const Internal &);
118 : :
119 : : /// The path to the replica directory.
120 : : string path;
121 : :
122 : : /// The id of the currently live database in the replica (0 or 1).
123 : : int live_id;
124 : :
125 : : /** The live database being replicated.
126 : : *
127 : : * This needs to be mutable because it is sometimes lazily opened.
128 : : */
129 : : mutable WritableDatabase live_db;
130 : :
131 : : /** Do we have an offline database currently?
132 : : *
133 : : * The offline database is a new copy of the database we're bringing up
134 : : * to the required revision, which can't yet be made live.
135 : : */
136 : : bool have_offline_db;
137 : :
138 : : /** Flag to indicate that the only valid operation next is a full copy.
139 : : */
140 : : bool need_copy_next;
141 : :
142 : : /** The revision that the secondary database has been updated to.
143 : : */
144 : : string offline_revision;
145 : :
146 : : /** The UUID of the secondary database.
147 : : */
148 : : string offline_uuid;
149 : :
150 : : /** The revision that the secondary database must reach before it can be
151 : : * made live.
152 : : */
153 : : string offline_needed_revision;
154 : :
155 : : /** The time at which a changeset was last applied to the live database.
156 : : *
157 : : * Set to 0 if no changeset applied to the live database so far.
158 : : */
159 : : double last_live_changeset_time;
160 : :
161 : : /// The remote connection we're using.
162 : : RemoteConnection * conn;
163 : :
164 : : /** Update the stub database which points to a single database.
165 : : *
166 : : * The stub database file is created at a separate path, and then
167 : : * atomically moved into place to replace the old stub database. This
168 : : * should allow searches to continue uninterrupted.
169 : : */
170 : : void update_stub_database() const;
171 : :
172 : : /** Delete the offline database. */
173 : : void remove_offline_db();
174 : :
175 : : /** Apply a set of DB copy messages from the connection.
176 : : */
177 : : void apply_db_copy(double end_time);
178 : :
179 : : /** Check that a message type is as expected.
180 : : *
181 : : * Throws a NetworkError if the type is not the expected one.
182 : : */
183 : : void check_message_type(char type, char expected) const;
184 : :
185 : : /** Check if the offline database has reached the required version.
186 : : *
187 : : * If so, make it live, and remove the old live database.
188 : : *
189 : : * @return true iff the offline database is made live
190 : : */
191 : : bool possibly_make_offline_live();
192 : :
193 : 365 : string get_replica_path(int id) const {
194 : 365 : string p = path;
195 : 365 : p += "/replica_";
196 : 365 : p += char('0' + id);
197 : 0 : return p;
198 : : }
199 : :
200 : : public:
201 : : /// Open a new DatabaseReplica::Internal for the specified path.
202 : : Internal(const string & path_);
203 : :
204 : : /// Destructor.
205 [ + - ]: 17 : ~Internal() { delete conn; }
206 : :
207 : : /// Get a string describing the current revision of the replica.
208 : : string get_revision_info() const;
209 : :
210 : : /// Set the file descriptor to read changesets from.
211 : : void set_read_fd(int fd);
212 : :
213 : : /// Read and apply the next changeset.
214 : : bool apply_next_changeset(ReplicationInfo * info,
215 : : double reader_close_time);
216 : :
217 : : /// Return a string describing this object.
218 : 0 : string get_description() const { return path; }
219 : : };
220 : :
221 : : // Methods of DatabaseReplica
222 : :
223 : 0 : DatabaseReplica::DatabaseReplica(const DatabaseReplica & other)
224 : 0 : : internal(other.internal)
225 : : {
226 : : LOGCALL_CTOR(REPLICA, "DatabaseReplica", other);
227 : 0 : }
228 : :
229 : : void
230 : 3 : DatabaseReplica::operator=(const DatabaseReplica & other)
231 : : {
232 : : LOGCALL_VOID(REPLICA, "DatabaseReplica::operator=", other);
233 : 3 : internal = other.internal;
234 : 3 : }
235 : :
236 : 0 : DatabaseReplica::DatabaseReplica()
237 : 0 : : internal(0)
238 : : {
239 : : LOGCALL_CTOR(REPLICA, "DatabaseReplica", NO_ARGS);
240 : 0 : }
241 : :
242 : 17 : DatabaseReplica::DatabaseReplica(const string & path)
243 : 17 : : internal(new DatabaseReplica::Internal(path))
244 : : {
245 : : LOGCALL_CTOR(REPLICA, "DatabaseReplica", path);
246 : 17 : }
247 : :
248 : 17 : DatabaseReplica::~DatabaseReplica()
249 : : {
250 : : LOGCALL_DTOR(REPLICA, "DatabaseReplica");
251 : 17 : }
252 : :
253 : : string
254 : 57 : DatabaseReplica::get_revision_info() const
255 : : {
256 : : LOGCALL(REPLICA, string, "DatabaseReplica::get_revision_info", NO_ARGS);
257 [ - + ]: 57 : if (internal.get() == NULL)
258 : 0 : throw Xapian::InvalidOperationError("Attempt to call DatabaseReplica::get_revision_info on a closed replica.");
259 : 57 : RETURN(internal->get_revision_info());
260 : : }
261 : :
262 : : void
263 : 173 : DatabaseReplica::set_read_fd(int fd)
264 : : {
265 : : LOGCALL_VOID(REPLICA, "DatabaseReplica::set_read_fd", fd);
266 [ - + ]: 173 : if (internal.get() == NULL)
267 : 0 : throw Xapian::InvalidOperationError("Attempt to call DatabaseReplica::set_read_fd on a closed replica.");
268 : 173 : internal->set_read_fd(fd);
269 : 173 : }
270 : :
271 : : bool
272 : 208 : DatabaseReplica::apply_next_changeset(ReplicationInfo * info,
273 : : double reader_close_time)
274 : : {
275 : : LOGCALL(REPLICA, bool, "DatabaseReplica::apply_next_changeset", info | reader_close_time);
276 [ + - ]: 208 : if (info != NULL)
277 : 208 : info->clear();
278 [ - + ]: 208 : if (internal.get() == NULL)
279 : 0 : throw Xapian::InvalidOperationError("Attempt to call DatabaseReplica::apply_next_changeset on a closed replica.");
280 : 208 : RETURN(internal->apply_next_changeset(info, reader_close_time));
281 : : }
282 : :
283 : : void
284 : 17 : DatabaseReplica::close()
285 : : {
286 : : LOGCALL(REPLICA, bool, "DatabaseReplica::close", NO_ARGS);
287 : 17 : internal = NULL;
288 : 17 : }
289 : :
290 : : string
291 : 0 : DatabaseReplica::get_description() const
292 : : {
293 : 0 : string desc("DatabaseReplica(");
294 [ # # ]: 0 : if (internal.get()) {
295 : 0 : desc += internal->get_description();
296 : : }
297 : 0 : desc += ')';
298 : 0 : return desc;
299 : : }
300 : :
301 : : // Methods of DatabaseReplica::Internal
302 : :
303 : : void
304 : 35 : DatabaseReplica::Internal::update_stub_database() const
305 : : {
306 : 35 : string stub_path = path;
307 : 35 : stub_path += "/XAPIANDB";
308 : 35 : string tmp_path = stub_path;
309 : 35 : tmp_path += ".tmp";
310 : : {
311 : 35 : ofstream stub(tmp_path.c_str());
312 : : stub << REPLICA_STUB_BANNER
313 : 35 : "auto replica_" << live_id << endl;
314 : : }
315 : : int result;
316 : : #ifdef __WIN32__
317 : : result = msvc_posix_rename(tmp_path.c_str(), stub_path.c_str());
318 : : #else
319 : 35 : result = rename(tmp_path.c_str(), stub_path.c_str());
320 : : #endif
321 [ - + ]: 35 : if (result == -1) {
322 : 0 : string msg("Failed to update stub db file for replica: ");
323 : 0 : msg += path;
324 : 0 : throw Xapian::DatabaseOpeningError(msg);
325 : 35 : }
326 : 35 : }
327 : :
328 : 17 : DatabaseReplica::Internal::Internal(const string & path_)
329 : : : path(path_), live_id(0), live_db(), have_offline_db(false),
330 : : need_copy_next(false), offline_revision(), offline_needed_revision(),
331 : 17 : last_live_changeset_time(), conn(NULL)
332 : : {
333 : : LOGCALL_CTOR(REPLICA, "DatabaseReplica::Internal", path_);
334 : : #if ! defined XAPIAN_HAS_FLINT_BACKEND && ! defined XAPIAN_HAS_CHERT_BACKEND
335 : : throw FeatureUnavailableError("Replication requires the Flint or Chert backend to be enabled");
336 : : #else
337 [ + + # # ]: 17 : if (mkdir(path, 0777) == 0) {
338 : : // The database doesn't already exist - make a directory, containing a
339 : : // stub database, and point it to a new database.
340 : : //
341 : : // Create an empty database - the backend doesn't matter as if the
342 : : // master is a different type, then the replica will become that type
343 : : // automatically.
344 : : live_db = WritableDatabase(get_replica_path(live_id),
345 : 14 : Xapian::DB_CREATE);
346 : 14 : update_stub_database();
347 : : } else {
348 [ - + ][ # # ]: 3 : if (errno != EEXIST) {
349 : 0 : throw DatabaseOpeningError("Couldn't create directory '" + path + "'", errno);
350 : : }
351 [ - + ][ # # ]: 3 : if (!dir_exists(path)) {
352 : 0 : throw DatabaseOpeningError("Replica path must be a directory");
353 : : }
354 : 3 : string stub_path = path;
355 : 3 : stub_path += "/XAPIANDB";
356 : 3 : live_db = Auto::open_stub(stub_path, Xapian::DB_OPEN);
357 : : // FIXME: simplify all this?
358 : 3 : ifstream stub(stub_path.c_str());
359 : 3 : string line;
360 [ + - ][ # # ]: 9 : while (getline(stub, line)) {
361 [ + - ][ + + ]: 9 : if (!line.empty() && line[0] != '#') {
[ + + ][ # # ]
[ # # ][ # # ]
362 : 3 : live_id = line[line.size() - 1] - '0';
363 : 3 : break;
364 : : }
365 : 3 : }
366 : : }
367 : : #endif
368 : 17 : }
369 : :
370 : : string
371 : 57 : DatabaseReplica::Internal::get_revision_info() const
372 : : {
373 : : LOGCALL(REPLICA, string, "DatabaseReplica::Internal::get_revision_info", NO_ARGS);
374 [ + + ]: 57 : if (live_db.internal.empty())
375 : 3 : live_db = WritableDatabase(get_replica_path(live_id), Xapian::DB_OPEN);
376 [ - + ]: 57 : if (live_db.internal.size() != 1)
377 : 0 : throw Xapian::InvalidOperationError("DatabaseReplica needs to be pointed at exactly one subdatabase");
378 : :
379 : 57 : string uuid = (live_db.internal[0])->get_uuid();
380 : 57 : string buf = encode_length(uuid.size());
381 : 57 : buf += uuid;
382 : 57 : buf += (live_db.internal[0])->get_revision_info();
383 : 57 : RETURN(buf);
384 : : }
385 : :
386 : : void
387 : 21 : DatabaseReplica::Internal::remove_offline_db()
388 : : {
389 : : // Delete the offline database.
390 : 21 : removedir(get_replica_path(live_id ^ 1));
391 : 21 : have_offline_db = false;
392 : 21 : }
393 : :
394 : : void
395 : 21 : DatabaseReplica::Internal::apply_db_copy(double end_time)
396 : : {
397 : 21 : have_offline_db = true;
398 : 21 : last_live_changeset_time = 0;
399 : 21 : string offline_path = get_replica_path(live_id ^ 1);
400 : : // If there's already an offline database, discard it. This happens if one
401 : : // copy of the database was sent, but further updates were needed before it
402 : : // could be made live, and the remote end was then unable to send those
403 : : // updates (probably due to not having changesets available, or the remote
404 : : // database being replaced by a new database).
405 : 21 : removedir(offline_path);
406 [ - + ]: 21 : if (mkdir(offline_path, 0777)) {
407 : : throw Xapian::DatabaseError("Cannot make directory '" +
408 : 0 : offline_path + "'", errno);
409 : : }
410 : :
411 : : {
412 : 21 : string buf;
413 : 21 : char type = conn->get_message(buf, end_time);
414 : 21 : check_message_type(type, REPL_REPLY_DB_HEADER);
415 : 21 : const char * ptr = buf.data();
416 : 21 : const char * end = ptr + buf.size();
417 : 21 : size_t uuid_length = decode_length(&ptr, end, true);
418 : 21 : offline_uuid.assign(ptr, uuid_length);
419 : 21 : offline_revision.assign(buf, ptr + uuid_length - buf.data(), buf.npos);
420 : : }
421 : :
422 : : // Now, read the files for the database from the connection and create it.
423 [ - + + ]: 314 : while (true) {
424 : 314 : string filename;
425 : 314 : char type = conn->sniff_next_message_type(end_time);
426 [ - + ]: 314 : if (type == REPL_REPLY_FAIL)
427 : : return;
428 [ + + ]: 314 : if (type == REPL_REPLY_DB_FOOTER)
429 : : break;
430 : :
431 : 293 : type = conn->get_message(filename, end_time);
432 : 293 : check_message_type(type, REPL_REPLY_DB_FILENAME);
433 : :
434 : : // Check that the filename doesn't contain '..'. No valid database
435 : : // file contains .., so we don't need to check that the .. is a path.
436 [ - + ]: 293 : if (filename.find("..") != string::npos) {
437 : 0 : throw NetworkError("Filename in database contains '..'");
438 : : }
439 : :
440 : 293 : type = conn->sniff_next_message_type(end_time);
441 [ - + ]: 293 : if (type == REPL_REPLY_FAIL)
442 : : return;
443 : :
444 : 293 : string filepath = offline_path + "/" + filename;
445 : 293 : type = conn->receive_file(filepath, end_time);
446 : 293 : check_message_type(type, REPL_REPLY_DB_FILEDATA);
447 : : }
448 : 21 : char type = conn->get_message(offline_needed_revision, end_time);
449 : 21 : check_message_type(type, REPL_REPLY_DB_FOOTER);
450 : 21 : need_copy_next = false;
451 : : }
452 : :
453 : : void
454 : 628 : DatabaseReplica::Internal::check_message_type(char type, char expected) const
455 : : {
456 [ - + ]: 628 : if (type != expected) {
457 : : throw NetworkError("Unexpected replication protocol message type (got "
458 : : + str(type) + ", expected "
459 : 0 : + str(expected) + ")");
460 : : }
461 : 628 : }
462 : :
463 : : bool
464 : 21 : DatabaseReplica::Internal::possibly_make_offline_live()
465 : : {
466 : 21 : string replica_path(get_replica_path(live_id ^ 1));
467 : 21 : AutoPtr<DatabaseReplicator> replicator;
468 : : try {
469 : 21 : replicator.reset(DatabaseReplicator::open(replica_path));
470 : 0 : } catch (const Xapian::DatabaseError &) {
471 : 0 : return false;
472 : : }
473 [ - + ]: 21 : if (offline_needed_revision.empty()) {
474 : 0 : return false;
475 : : }
476 [ - + ]: 21 : if (!replicator->check_revision_at_least(offline_revision,
477 : 21 : offline_needed_revision)) {
478 : 0 : return false;
479 : : }
480 : :
481 : 21 : string replicated_uuid = replicator->get_uuid();
482 [ - + ]: 21 : if (replicated_uuid.empty()) {
483 : 0 : return false;
484 : : }
485 : :
486 [ - + ]: 21 : if (replicated_uuid != offline_uuid) {
487 : 0 : return false;
488 : : }
489 : :
490 : 21 : live_id ^= 1;
491 : : // Open the database first, so that if there's a problem, an exception
492 : : // will be thrown before we make the new database live.
493 : 21 : live_db = WritableDatabase(replica_path, Xapian::DB_OPEN);
494 : 21 : update_stub_database();
495 : 21 : remove_offline_db();
496 : 21 : return true;
497 : : }
498 : :
499 : : void
500 : 173 : DatabaseReplica::Internal::set_read_fd(int fd)
501 : : {
502 [ + + ]: 173 : delete conn;
503 : 173 : conn = NULL;
504 : 173 : conn = new RemoteConnection(fd, -1, "");
505 : 173 : }
506 : :
507 : : bool
508 : 208 : DatabaseReplica::Internal::apply_next_changeset(ReplicationInfo * info,
509 : : double reader_close_time)
510 : : {
511 : : LOGCALL(REPLICA, bool, "DatabaseReplica::Internal::apply_next_changeset", info | reader_close_time);
512 [ + + ]: 208 : if (live_db.internal.empty())
513 : 113 : live_db = WritableDatabase(get_replica_path(live_id), Xapian::DB_OPEN);
514 [ - + ]: 208 : if (live_db.internal.size() != 1)
515 : 0 : throw Xapian::InvalidOperationError("DatabaseReplica needs to be pointed at exactly one subdatabase");
516 : :
517 : 21 : while (true) {
518 : 229 : char type = conn->sniff_next_message_type(0.0);
519 [ + + + - : 226 : switch (type) {
- ]
520 : : case REPL_REPLY_END_OF_CHANGES: {
521 : 54 : string buf;
522 : 54 : (void)conn->get_message(buf, 0.0);
523 : 54 : RETURN(false);
524 : : }
525 : : case REPL_REPLY_DB_HEADER:
526 : : // Apply the copy - remove offline db in case of any error.
527 : : try {
528 : 21 : apply_db_copy(0.0);
529 [ + - ]: 21 : if (info != NULL)
530 : 21 : ++(info->fullcopy_count);
531 : 21 : string replica_uuid;
532 : : {
533 : : AutoPtr<DatabaseReplicator> replicator(
534 : 21 : DatabaseReplicator::open(get_replica_path(live_id ^ 1)));
535 : 21 : replica_uuid = replicator->get_uuid();
536 : : }
537 [ - + ]: 21 : if (replica_uuid != offline_uuid) {
538 : 0 : remove_offline_db();
539 : : // We've been sent an database with the wrong uuid,
540 : : // which only happens if the database at the server
541 : : // got changed during the copy, so the only safe
542 : : // action next is a new copy. Set a flag to ensure
543 : : // that this happens, or we're at risk of database
544 : : // corruption.
545 : 0 : need_copy_next = true;
546 : 21 : }
547 : 0 : } catch (...) {
548 : 0 : remove_offline_db();
549 : 0 : throw;
550 : : }
551 [ + - ]: 21 : if (possibly_make_offline_live()) {
552 [ + - ]: 21 : if (info != NULL)
553 : 21 : info->changed = true;
554 : : }
555 : : break;
556 : : case REPL_REPLY_CHANGESET:
557 [ - + ]: 151 : if (need_copy_next) {
558 : 0 : throw NetworkError("Needed a database copy next");
559 : : }
560 [ + - ]: 151 : if (!have_offline_db) {
561 : : // Close the live db.
562 : 151 : string replica_path(get_replica_path(live_id));
563 : 151 : live_db = WritableDatabase();
564 : :
565 [ + + ]: 151 : if (last_live_changeset_time != 0.0) {
566 : : // Wait until at least "reader_close_time" seconds have
567 : : // passed since the last changeset was applied, to
568 : : // allow any active readers to finish and be reopened.
569 : : double until;
570 : 20 : until = last_live_changeset_time + reader_close_time;
571 : 20 : RealTime::sleep(until);
572 : : }
573 : :
574 : : // Open a replicator for the live path, and apply the
575 : : // changeset.
576 : : {
577 : : AutoPtr<DatabaseReplicator> replicator(
578 : 151 : DatabaseReplicator::open(replica_path));
579 : :
580 : : // Ignore the returned revision number, since we are
581 : : // live so the changeset must be safe to apply to a
582 : : // live DB.
583 : 267 : replicator->apply_changeset_from_conn(*conn, 0.0, true);
584 : : }
585 : 35 : last_live_changeset_time = RealTime::now();
586 : :
587 [ + - ]: 35 : if (info != NULL) {
588 : 35 : ++(info->changeset_count);
589 : 35 : info->changed = true;
590 : : }
591 : : // Now the replicator is closed, open the live db again.
592 : 35 : live_db = WritableDatabase(replica_path, Xapian::DB_OPEN);
593 : 151 : RETURN(true);
594 : : }
595 : :
596 : : {
597 : : AutoPtr<DatabaseReplicator> replicator(
598 : 0 : DatabaseReplicator::open(get_replica_path(live_id ^ 1)));
599 : :
600 : : offline_revision = replicator->
601 : 0 : apply_changeset_from_conn(*conn, 0.0, false);
602 : :
603 [ # # ]: 0 : if (info != NULL) {
604 : 0 : ++(info->changeset_count);
605 : 0 : }
606 : : }
607 [ # # ]: 0 : if (possibly_make_offline_live()) {
608 [ # # ]: 0 : if (info != NULL)
609 : 0 : info->changed = true;
610 : : }
611 : 0 : RETURN(true);
612 : : case REPL_REPLY_FAIL: {
613 : 0 : string buf;
614 : 0 : (void)conn->get_message(buf, 0.0);
615 : 0 : throw NetworkError("Unable to fully synchronise: " + buf);
616 : : }
617 : : default:
618 : : throw NetworkError("Unknown replication protocol message ("
619 : 89 : + str(type) + ")");
620 : : }
621 : : }
622 : : }
|