From ad774d0b294e3761bcddddaa781129cef0c52b73 Mon Sep 17 00:00:00 2001 From: Patrick Ohly Date: Thu, 23 Apr 2009 16:47:07 +0200 Subject: [PATCH] added --restore and fixed --status --status was broken by the last commit: XDG_DATA_DIR was not expanded. Fixed it as part of the improvements for printing changes in --restore. Also made some other minor changes to utility classes as part of these improvements. The new --restore option can restore data from a data dump, identified by the directory and a before/after flag. The corresponding API is EvolutionSyncClient::restore(). --- src/client-test-app.cpp | 2 +- src/core/EvolutionSyncClient.cpp | 127 ++++++++++++++++++++++++------ src/core/EvolutionSyncClient.h | 15 ++++ src/core/EvolutionSyncSource.h | 5 +- src/core/SyncEvolutionCmdline.cpp | 69 ++++++++++++++++ src/core/SyncEvolutionCmdline.h | 4 + src/core/SyncML.cpp | 22 ++++-- src/core/SyncML.h | 6 ++ src/core/TrackingSyncSource.cpp | 92 +++++++++++++++++++++- src/core/TrackingSyncSource.h | 5 +- 10 files changed, 307 insertions(+), 40 deletions(-) diff --git a/src/client-test-app.cpp b/src/client-test-app.cpp index ff5bdd02..1f4b1c11 100644 --- a/src/client-test-app.cpp +++ b/src/client-test-app.cpp @@ -84,7 +84,7 @@ public: virtual SyncItem *createItem(const string &uid) { return m_source->createItem(uid); } virtual void close() { m_source->close(); } virtual void backupData(const string &dir, ConfigNode &node, BackupReport &report) { m_source->backupData(dir, node, report); } - virtual void restoreData(const string &dir, const ConfigNode &node) { m_source->restoreData(dir, node); } + virtual void restoreData(const string &dir, const ConfigNode &node, bool dryrun, SyncSourceReport &report) { m_source->restoreData(dir, node, dryrun, report); } virtual const char *getMimeType() const { return m_source->getMimeType(); } virtual const char *getMimeVersion() const { return m_source->getMimeVersion(); } virtual const char* getSupportedTypes() const { return m_source->getSupportedTypes(); } diff --git a/src/core/EvolutionSyncClient.cpp b/src/core/EvolutionSyncClient.cpp index c9c4c628..6f71035c 100644 --- a/src/core/EvolutionSyncClient.cpp +++ b/src/core/EvolutionSyncClient.cpp @@ -50,6 +50,7 @@ EvolutionSyncClient::EvolutionSyncClient(const string &server, m_sources(sources), m_doLogging(doLogging), m_quiet(false), + m_dryrun(false), m_engine(new sysync::TEngineModuleBridge()) { // Use libsynthesis that we were linked against. The name of a @@ -294,7 +295,8 @@ public: // @param logLevel 0 = default, 1 = ERROR, 2 = INFO, 3 = DEBUG // @param usePath write directly into path, don't create and manage subdirectories // @param report record information about session here (may be NULL) - void startSession(const char *path, int maxlogdirs, int logLevel, bool usePath, SyncReport *report) { + // @param logname the basename to be used for logs, traditionally "client" for syncs + void startSession(const char *path, int maxlogdirs, int logLevel, bool usePath, SyncReport *report, const string &logname) { m_maxlogdirs = maxlogdirs; m_report = report; if (path && !strcasecmp(path, "none")) { @@ -335,14 +337,14 @@ public: } } } else { - m_path = path; + m_path = m_logdir; if (mkdir(m_path.c_str(), S_IRWXU) && errno != EEXIST) { SE_LOG_DEBUG(NULL, NULL, "%s: %s", m_path.c_str(), strerror(errno)); EvolutionSyncClient::throwError(m_path, errno); } } - m_logfile = m_path + "/client.log"; + m_logfile = m_path + "/" + logname + ".log"; } if (m_logfile.size()) { @@ -550,18 +552,11 @@ public: */ void dumpDatabases(const string &suffix, BackupReport SyncSourceReport::*report) { - ofstream out; -#ifndef IPHONE - // output stream on iPhone raises exception even though it is in a good state; - // perhaps the missing C++ exception support is the reason: - // http://code.google.com/p/iphone-dev/issues/detail?id=48 - out.exceptions(ios_base::badbit|ios_base::failbit|ios_base::eofbit); -#endif - BOOST_FOREACH(EvolutionSyncSource *source, *this) { string dir = databaseName(*source, suffix); boost::shared_ptr node = ConfigNode::createFileNode(dir + ".ini"); SE_LOG_DEBUG(NULL, NULL, "creating %s", dir.c_str()); + rm_r(dir); mkdir_p(dir); BackupReport dummy; source->backupData(dir, *node, @@ -570,6 +565,16 @@ public: } } + void restoreDatabase(EvolutionSyncSource &source, const string &suffix, bool dryrun, SyncSourceReport &report) + { + string dir = databaseName(source, suffix); + boost::shared_ptr node = ConfigNode::createFileNode(dir + ".ini"); + if (!node->exists()) { + EvolutionSyncClient::throwError(dir + ": no such database backup found"); + } + source.restoreData(dir, *node, dryrun, report); + } + SourceList(const string &server, bool doLogging) : m_logdir(server), m_prepared(false), @@ -580,16 +585,17 @@ public: } // call as soon as logdir settings are known - void startSession(const char *logDirPath, int maxlogdirs, int logLevel, SyncReport *report) { + void startSession(const char *logDirPath, int maxlogdirs, int logLevel, SyncReport *report, + const string &logname) { m_previousLogdir = m_logdir.previousLogdir(logDirPath); if (m_doLogging) { - m_logdir.startSession(logDirPath, maxlogdirs, logLevel, false, report); + m_logdir.startSession(logDirPath, maxlogdirs, logLevel, false, report, logname); } else { // Run debug session without paying attention to // the normal logdir handling. The log level here // refers to stdout. The log file will be as complete // as possible. - m_logdir.startSession(logDirPath, 0, 1, true, report); + m_logdir.startSession(logDirPath, 0, 1, true, report, logname); } } @@ -605,24 +611,27 @@ public: void setPath(const string &path) { m_logdir.setPath(path); } /** - * If possible (m_previousLogdir found) and enabled, + * If possible (directory to compare against available) and enabled, * then dump changes applied locally. * * @param oldSuffix suffix of old database dump: usually "after" * @param currentSuffix the current database dump suffix: "current" * when not doing a sync, otherwise "before" */ - bool dumpLocalChanges(const string &oldSuffix, const string &newSuffix) { - if (m_logLevel <= LOGGING_SUMMARY || !m_previousLogdir.size()) { + bool dumpLocalChanges(const string &oldDir, + const string &oldSuffix, const string &newSuffix, + const string &intro = "Local changes to be applied to server during synchronization:\n", + const string &config = "CLIENT_TEST_LEFT_NAME='after last sync' CLIENT_TEST_RIGHT_NAME='current data' CLIENT_TEST_REMOVED='removed since last sync' CLIENT_TEST_ADDED='added since last sync'") { + if (m_logLevel <= LOGGING_SUMMARY || oldDir.empty()) { return false; } - cout << "Local changes to be applied to server during synchronization:\n"; + cout << intro; BOOST_FOREACH(EvolutionSyncSource *source, *this) { - string oldFile = databaseName(*source, oldSuffix, m_previousLogdir); + string oldFile = databaseName(*source, oldSuffix, oldDir); string newFile = databaseName(*source, newSuffix); cout << "*** " << source->getName() << " ***\n" << flush; - string cmd = string("env CLIENT_TEST_COMPARISON_FAILED=10 CLIENT_TEST_LEFT_NAME='after last sync' CLIENT_TEST_RIGHT_NAME='current data' CLIENT_TEST_REMOVED='removed since last sync' CLIENT_TEST_ADDED='added since last sync' synccompare 2>/dev/null '" ) + + string cmd = string("env CLIENT_TEST_COMPARISON_FAILED=10 " + config + " synccompare 2>/dev/null '" ) + oldFile + "' '" + newFile + "'"; int ret = system(cmd.c_str()); switch (ret == -1 ? ret : WEXITSTATUS(ret)) { @@ -648,7 +657,7 @@ public: // dump initial databases dumpDatabases("before", &SyncSourceReport::m_backupBefore); // compare against the old "after" database dump - dumpLocalChanges("after", "before"); + dumpLocalChanges(getPrevLogdir(), "after", "before"); m_prepared = true; } @@ -1370,7 +1379,8 @@ SyncMLStatus EvolutionSyncClient::sync(SyncReport *report) sourceList.startSession(getLogDir(), getMaxLogDirs(), getLogLevel(), - report); + report, + "client"); // dump some summary information at the beginning of the log SE_LOG_DEV(NULL, NULL, "SyncML server account: %s", getUsername()); @@ -1777,7 +1787,7 @@ void EvolutionSyncClient::status() source->open(); } - sourceList.startSession(getLogDir(), 0, 0, NULL); + sourceList.startSession(getLogDir(), 0, 0, NULL, "status"); LoggerBase::instance().setLevel(Logger::INFO); string prevLogdir = sourceList.getPrevLogdir(); bool found = access(prevLogdir.c_str(), R_OK|X_OK) == 0; @@ -1786,7 +1796,7 @@ void EvolutionSyncClient::status() try { sourceList.setPath(prevLogdir); sourceList.dumpDatabases("current", NULL); - sourceList.dumpLocalChanges("after", "current"); + sourceList.dumpLocalChanges(sourceList.getPrevLogdir(), "after", "current"); } catch(...) { SyncEvolutionException::handle(); } @@ -1798,6 +1808,75 @@ void EvolutionSyncClient::status() } } +static void logRestoreReport(const SyncReport &report, bool dryrun) +{ + if (!report.empty()) { + stringstream out; + out << report; + SE_LOG_INFO(NULL, NULL, "Item changes %s applied to client during restore:\n%s", + dryrun ? "to be" : "that were", + out.str().c_str()); + SE_LOG_INFO(NULL, NULL, "The same incremental changes will be applied to the server during the next sync."); + SE_LOG_INFO(NULL, NULL, "Use -sync refresh-from-client to replace the complete data on the server."); + } +} + +void EvolutionSyncClient::restore(const string &dirname, RestoreDatabase database) +{ + if (!exists()) { + SE_LOG_ERROR(NULL, NULL, "No configuration for server \"%s\" found.", m_server.c_str()); + throwError("cannot proceed without configuration"); + } + + SourceList sourceList(m_server, false); + sourceList.startSession(dirname.c_str(), 0, 0, NULL, "restore"); + LoggerBase::instance().setLevel(Logger::INFO); + initSources(sourceList); + BOOST_FOREACH(EvolutionSyncSource *source, sourceList) { + source->checkPassword(*this); + } + + string datadump = database == DATABASE_BEFORE_SYNC ? "before" : "after"; + + BOOST_FOREACH(EvolutionSyncSource *source, sourceList) { + source->open(); + } + + if (!m_quiet) { + sourceList.dumpDatabases("current", NULL); + sourceList.dumpLocalChanges(dirname, "current", datadump, + "Data changes to be applied to local data during restore:\n", + "CLIENT_TEST_LEFT_NAME='current data' " + "CLIENT_TEST_REMOVED='after restore' " + "CLIENT_TEST_REMOVED='to be removed' " + "CLIENT_TEST_ADDED='to be added'"); + } + + SyncReport report; + try { + BOOST_FOREACH(EvolutionSyncSource *source, sourceList) { + SyncSourceReport sourcereport; + try { + SE_LOG_DEBUG(NULL, NULL, "Restoring %s...", source->getName()); + sourceList.restoreDatabase(*source, + datadump, + m_dryrun, + sourcereport); + SE_LOG_DEBUG(NULL, NULL, "... %s restored.", source->getName()); + report.addSyncSourceReport(source->getName(), sourcereport); + } catch (...) { + sourcereport.recordStatus(STATUS_FATAL); + report.addSyncSourceReport(source->getName(), sourcereport); + throw; + } + } + } catch (...) { + logRestoreReport(report, m_dryrun); + throw; + } + logRestoreReport(report, m_dryrun); +} + void EvolutionSyncClient::getSessions(vector &dirs) { LogDir logging(m_server); diff --git a/src/core/EvolutionSyncClient.h b/src/core/EvolutionSyncClient.h index 71435d73..1a430986 100644 --- a/src/core/EvolutionSyncClient.h +++ b/src/core/EvolutionSyncClient.h @@ -43,6 +43,7 @@ class EvolutionSyncClient : public EvolutionSyncConfig, public ConfigUserInterfa const set m_sources; const bool m_doLogging; bool m_quiet; + bool m_dryrun; /** * a pointer to the active SourceList instance if one exists; @@ -91,6 +92,9 @@ class EvolutionSyncClient : public EvolutionSyncConfig, public ConfigUserInterfa bool getQuiet() { return m_quiet; } void setQuiet(bool quiet) { m_quiet = quiet; } + bool getDryRun() { return m_dryrun; } + void setDryRun(bool dryrun) { m_dryrun = dryrun; } + /** * Executes the sync, throws an exception in case of failure. * Handles automatic backups and report generation. @@ -106,6 +110,17 @@ class EvolutionSyncClient : public EvolutionSyncConfig, public ConfigUserInterfa */ void status(); + enum RestoreDatabase { + DATABASE_BEFORE_SYNC, + DATABASE_AFTER_SYNC + }; + + /** + * Restore data of selected sources from before or after the given + * sync session, identified by absolute path to the log dir. + */ + void restore(const string &dirname, RestoreDatabase database); + /** * fills vector with absolute path to information about previous * sync sessions, oldest one first diff --git a/src/core/EvolutionSyncSource.h b/src/core/EvolutionSyncSource.h index 451d8020..15119326 100644 --- a/src/core/EvolutionSyncSource.h +++ b/src/core/EvolutionSyncSource.h @@ -369,9 +369,10 @@ class EvolutionSyncSource : public EvolutionSyncSourceConfig, public LoggerBase, virtual void backupData(const string &dirname, ConfigNode &node, BackupReport &report) = 0; /** - * Restore database from data stored in backupData(). + * Restore database from data stored in backupData(). Will be + * called inside open()/close() pair. beginSync() is *not* called. */ - virtual void restoreData(const string &dirname, const ConfigNode &node) = 0; + virtual void restoreData(const string &dirname, const ConfigNode &node, bool dryrun, SyncSourceReport &report) = 0; /** * Returns the preferred mime type of the items handled by the sync source. diff --git a/src/core/SyncEvolutionCmdline.cpp b/src/core/SyncEvolutionCmdline.cpp index 309c0e12..748a5e3b 100644 --- a/src/core/SyncEvolutionCmdline.cpp +++ b/src/core/SyncEvolutionCmdline.cpp @@ -98,6 +98,27 @@ bool SyncEvolutionCmdline::parse() } else if(boost::iequals(m_argv[opt], "--run") || boost::iequals(m_argv[opt], "-r")) { m_run = true; + } else if(boost::iequals(m_argv[opt], "--restore")) { + opt++; + if (opt >= m_argc) { + usage(true, string("missing parameter for ") + cmdOpt(m_argv[opt - 1])); + return false; + } + m_restore = m_argv[opt]; + if (m_restore.empty()) { + usage(true, string("missing parameter for ") + cmdOpt(m_argv[opt - 1])); + return false; + } + if (!isDir(m_restore)) { + usage(true, string("parameter '") + m_restore + "' for " + cmdOpt(m_argv[opt - 1]) + " must be log directory"); + return false; + } + } else if(boost::iequals(m_argv[opt], "--before")) { + m_before = true; + } else if(boost::iequals(m_argv[opt], "--after")) { + m_after = true; + } else if(boost::iequals(m_argv[opt], "--dry-run")) { + m_dryrun = true; } else if(boost::iequals(m_argv[opt], "--migrate")) { m_migrate = true; } else if(boost::iequals(m_argv[opt], "--status") || @@ -129,6 +150,11 @@ bool SyncEvolutionCmdline::parse() } bool SyncEvolutionCmdline::run() { + // --dry-run is only supported by some operations. + // Be very strict about it and make sure it is off in all + // potentially harmful operations, otherwise users might + // expect it to have an effect when it doesn't. + if (m_usage) { usage(true); } else if (m_version) { @@ -140,6 +166,7 @@ bool SyncEvolutionCmdline::run() { } else if (m_dontrun) { // user asked for information } else if (m_argc == 1) { + // no parameters: list databases and short usage const SourceRegistry ®istry(EvolutionSyncSource::getSourceRegistry()); boost::shared_ptr configNode(new VolatileConfigNode()); boost::shared_ptr hiddenNode(new VolatileConfigNode()); @@ -206,6 +233,10 @@ bool SyncEvolutionCmdline::run() { usage(true, "server name missing"); return false; } else if (m_configure || m_migrate) { + if (m_dryrun) { + EvolutionSyncClient::throwError("--dry-run not supported for configuration changes"); + } + bool fromScratch = false; // Both config changes and migration are implemented as copying from @@ -330,6 +361,10 @@ bool SyncEvolutionCmdline::run() { // done, now write it to->flush(); } else if (m_remove) { + if (m_dryrun) { + EvolutionSyncClient::throwError("--dry-run not supported for removing configurations"); + } + // extra sanity check if (!m_sources.empty() || !m_syncProps.empty() || @@ -345,6 +380,7 @@ bool SyncEvolutionCmdline::run() { } else { EvolutionSyncClient client(m_server, true, m_sources); client.setQuiet(m_quiet); + client.setDryRun(m_dryrun); client.setConfigFilter(true, m_syncProps); client.setConfigFilter(false, m_sourceProps); if (m_status) { @@ -366,7 +402,26 @@ bool SyncEvolutionCmdline::run() { cout << report; } } + } else if (!m_restore.empty()) { + // sanity checks: either --after or --before must be given, sources must be selected + if ((!m_after && !m_before) || + (m_after && m_before)) { + usage(false, "--restore must be used with either --after (restore database as it was after that sync) or --before (restore data from before sync)"); + return false; + } + if (m_sources.empty()) { + usage(false, "Sources must be selected explicitly for --restore to prevent accidental restore."); + return false; + } + client.restore(m_restore, + m_after ? + EvolutionSyncClient::DATABASE_AFTER_SYNC : + EvolutionSyncClient::DATABASE_BEFORE_SYNC); } else { + if (m_dryrun) { + EvolutionSyncClient::throwError("--dry-run not supported for running a synchronization"); + } + // safety catch: if props are given, then --run // is required if (!m_run && @@ -577,6 +632,8 @@ void SyncEvolutionCmdline::usage(bool full, const string &error, const string &p out << "Run a synchronization:" << endl; out << " " << m_argv[0] << " [ ...]" << endl; out << " " << m_argv[0] << " --run [ ...]" << endl; + out << "Restore data from the automatic backups:" << endl; + out << " " << m_argv[0] << " --restore --before|--after [--dry-run] ..." << endl; out << "Remove a configuration:" << endl; out << " " << m_argv[0] << " --remove " << endl; out << "Modify configuration:" << endl; @@ -636,6 +693,18 @@ void SyncEvolutionCmdline::usage(bool full, const string &error, const string &p " --migrate implies --configure and can be combined with modifying" << endl << " properties." << endl << "" << endl << + "--restore" << endl << + " Restores the data of the selected sources to the state from before or after the" << endl << + " selected synchronization. The synchronization is selected via its log directory" << endl << + " (see --print-sessions). Other directories can also be given as long as" << endl << + " they contain database dumps in the format created by SyncEvolution." << endl << + " The output includes information about the changes made during the" << endl << + " restore, both in terms of item changes and content changes (which is" << endl << + " not always the same, see manual for details). This output can be suppressed" << endl << + " with --quiet." << endl << + " In combination with --dry-run, the changes to local data are only simulated." << endl << + " This can be used to check that --restore will not remove valuable information." << endl << + "" << endl << "--remove" << endl << " This removes only the configuration files and related meta information." << endl << " If other files were added to the config directory of the server, then" << endl << diff --git a/src/core/SyncEvolutionCmdline.h b/src/core/SyncEvolutionCmdline.h index d4cfb521..2b5f10ad 100644 --- a/src/core/SyncEvolutionCmdline.h +++ b/src/core/SyncEvolutionCmdline.h @@ -48,6 +48,7 @@ private: ostream &m_out, &m_err; Bool m_quiet; + Bool m_dryrun; Bool m_status; Bool m_version; Bool m_usage; @@ -63,6 +64,9 @@ private: const ConfigPropertyRegistry &m_validSyncProps; const ConfigPropertyRegistry &m_validSourceProps; + string m_restore; + Bool m_before, m_after; + string m_server; string m_template; set m_sources; diff --git a/src/core/SyncML.cpp b/src/core/SyncML.cpp index dca46afa..b9936fe0 100644 --- a/src/core/SyncML.cpp +++ b/src/core/SyncML.cpp @@ -180,17 +180,25 @@ std::ostream &operator << (std::ostream &out, const SyncReport &report) std::stringstream line; - line << - PrettyPrintSyncMode(source.getFinalSyncMode()) << ", " << + if (source.getFinalSyncMode() != SYNC_NONE || source.getItemStat(SyncSourceReport::ITEM_LOCAL, SyncSourceReport::ITEM_ANY, - SyncSourceReport::ITEM_SENT_BYTES) / 1024 << - " KB sent by client, " << + SyncSourceReport::ITEM_SENT_BYTES) || source.getItemStat(SyncSourceReport::ITEM_LOCAL, SyncSourceReport::ITEM_ANY, - SyncSourceReport::ITEM_RECEIVED_BYTES) / 1024 << - " KB received"; - flushRight(out, line.str()); + SyncSourceReport::ITEM_RECEIVED_BYTES)) { + line << + PrettyPrintSyncMode(source.getFinalSyncMode()) << ", " << + source.getItemStat(SyncSourceReport::ITEM_LOCAL, + SyncSourceReport::ITEM_ANY, + SyncSourceReport::ITEM_SENT_BYTES) / 1024 << + " KB sent by client, " << + source.getItemStat(SyncSourceReport::ITEM_LOCAL, + SyncSourceReport::ITEM_ANY, + SyncSourceReport::ITEM_RECEIVED_BYTES) / 1024 << + " KB received"; + flushRight(out, line.str()); + } if (total_conflicts > 0) { for (SyncSourceReport::ItemResult result = SyncSourceReport::ITEM_CONFLICT_SERVER_WON; diff --git a/src/core/SyncML.h b/src/core/SyncML.h index 830ab5d8..c7fa8341 100644 --- a/src/core/SyncML.h +++ b/src/core/SyncML.h @@ -61,6 +61,7 @@ class SyncItem { const char *getData() const { return m_data.c_str(); } size_t getDataSize() const { return m_data.size(); } void setData(const char *data, size_t size) { m_data.assign(data, size); } + void setData(const std::string &data) { m_data = data; } void setDataType(const std::string &datatype) { m_datatype = datatype; } std::string getDataType() const { return m_datatype; } @@ -199,6 +200,11 @@ class SyncSourceReport { int count) { m_stat[location][state][success] = count; } + void incrementItemStat(ItemLocation location, + ItemState state, + ItemResult success) { + m_stat[location][state][success]++; + } void recordFinalSyncMode(SyncMode mode) { m_mode = mode; } SyncMode getFinalSyncMode() const { return m_mode; } diff --git a/src/core/TrackingSyncSource.cpp b/src/core/TrackingSyncSource.cpp index e6621df8..12018356 100644 --- a/src/core/TrackingSyncSource.cpp +++ b/src/core/TrackingSyncSource.cpp @@ -157,14 +157,98 @@ void TrackingSyncSource::backupData(const string &dir, ConfigNode &node, BackupR report.setNumItems(counter - 1); } -void TrackingSyncSource::restoreData(const string &dir, const ConfigNode &node) +void TrackingSyncSource::restoreData(const string &dir, const ConfigNode &node, bool dryrun, SyncSourceReport &report) { RevisionMap_t revisions; listAllItems(revisions); - // int counter = 1; - // BOOST_FOREACH(const StringPair &mapping, revisions) { - // } + long numitems; + string strval; + strval = node.readProperty("numitems"); + stringstream stream(strval); + stream >> numitems; + + for (long counter = 1; counter <= numitems; counter++) { + stringstream key; + key << counter << "-uid"; + string uid = node.readProperty(key.str()); + key.clear(); + key << counter << "-rev"; + string rev = node.readProperty(key.str()); + RevisionMap_t::iterator it = revisions.find(uid); + if (it != revisions.end() && + it->second == rev) { + // item exists in backup and database with same revision: + // nothing to do + } else { + // add or update, so need item + SyncItem item; + stringstream filename; + filename << dir << "/" << counter; + string data; + if (!ReadFile(filename.str(), data)) { + throwError(StringPrintf("restoring %s from %s failed: could not read file", + uid.c_str(), + filename.str().c_str())); + } + item.setData(data); + item.setDataType("raw"); + + // TODO: it would be nicer to recreate the item + // with the original revision. If multiple peers + // synchronize against us, then some of them + // might still be in sync with that revision. By + // updating the revision here we force them to + // needlessly receive an update. + // + // For the current peer for which we restore this is + // avoided by the revision check above: unchanged + // items aren't touched. + SyncSourceReport::ItemState state = + it == revisions.end() ? + SyncSourceReport::ITEM_ADDED : // not found in database, create anew + SyncSourceReport::ITEM_UPDATED; // found, update existing item + try { + report.incrementItemStat(report.ITEM_LOCAL, + state, + report.ITEM_TOTAL); + if (!dryrun) { + insertItem(it == revisions.end() ? "" : uid, + item); + } + } catch (...) { + report.incrementItemStat(report.ITEM_LOCAL, + state, + report.ITEM_REJECT); + throw; + } + } + + // remove handled item from revision list so + // that when we are done, the only remaining + // items listed there are the ones which did + // no exist in the backup + if (it != revisions.end()) { + revisions.erase(it); + } + } + + // now remove items that were not in the backup + BOOST_FOREACH(const StringPair &mapping, revisions) { + try { + report.incrementItemStat(report.ITEM_LOCAL, + report.ITEM_REMOVED, + report.ITEM_TOTAL); + if (!dryrun) { + deleteItem(mapping.first); + } + } catch(...) { + report.incrementItemStat(report.ITEM_LOCAL, + report.ITEM_REMOVED, + report.ITEM_REJECT); + throw; + } + } } diff --git a/src/core/TrackingSyncSource.h b/src/core/TrackingSyncSource.h index 0f8d44a8..3a20453b 100644 --- a/src/core/TrackingSyncSource.h +++ b/src/core/TrackingSyncSource.h @@ -75,9 +75,10 @@ class TrackingSyncSource : public EvolutionSyncSource virtual void backupData(const string &dirname, ConfigNode &node, BackupReport &report); /** - * Restore database from data stored in backupData(). + * Restore database from data stored in backupData(). Will be + * called inside open()/close() pair. beginSync() is *not* called. */ - virtual void restoreData(const string &dirname, const ConfigNode &node); + virtual void restoreData(const string &dirname, const ConfigNode &node, bool dryrun, SyncSourceReport &report); typedef map RevisionMap_t;