2 * Copyright (C) 2005-2009 Patrick Ohly <patrick.ohly@gmx.de>
3 * Copyright (C) 2009 Intel Corporation
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) version 3.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 # define _GNU_SOURCE 1
26 #include "EvolutionSyncClient.h"
27 #include "EvolutionSyncSource.h"
28 #include "SyncEvolutionUtil.h"
30 #include "SafeConfigNode.h"
31 #include "FileConfigNode.h"
33 #include "LogStdout.h"
34 #include "TransportAgent.h"
35 #include "CurlTransportAgent.h"
36 #include "SoupTransportAgent.h"
37 using namespace SyncEvolution;
51 #include <boost/algorithm/string/predicate.hpp>
52 #include <boost/algorithm/string/join.hpp>
53 #include <boost/foreach.hpp>
54 #include <boost/algorithm/string/split.hpp>
63 #include "synthesis/enginemodulebridge.h"
64 #include "synthesis/SDK_util.h"
66 SourceList *EvolutionSyncClient::m_sourceListPtr;
68 SuspendFlags EvolutionSyncClient::s_flags;
70 extern "C" void suspend_handler(int sig)
74 SuspendFlags& s_flags = EvolutionSyncClient::getSuspendFlags();
75 //first time suspend or already aborted
76 if (s_flags.state == SuspendFlags::CLIENT_NORMAL)
78 s_flags.state = SuspendFlags::CLIENT_SUSPEND;
79 s_flags.last_suspend = current;
80 SE_LOG_INFO(NULL, NULL,"Asking server to suspend...\nPress CTRL-C again quickly (within 2s) to stop sync immediately (can cause problems during next sync!)");
85 if (current - s_flags.last_suspend
86 < s_flags.ABORT_INTERVAL)
88 s_flags.state = SuspendFlags::CLIENT_ABORT;
89 SE_LOG_INFO(NULL, NULL, "Aboring sync as requested via CTRL-C ...");
93 s_flags.last_suspend = current;
94 SE_LOG_INFO(NULL, NULL, "Suspend in progress...\nPress CTRL-C again quickly (within 2s) to stop sync immediately (can cause problems during next sync!)");
100 EvolutionSyncClient::EvolutionSyncClient(const string &server,
102 const set<string> &sources) :
103 EvolutionSyncConfig(server),
106 m_doLogging(doLogging),
112 EvolutionSyncClient::~EvolutionSyncClient()
117 // this class owns the logging directory and is responsible
118 // for redirecting output at the start and end of sync (even
119 // in case of exceptions thrown!)
120 class LogDir : public LoggerBase {
121 EvolutionSyncClient &m_client;
122 Logger &m_parentLogger; /**< the logger which was active before we started to intercept messages */
123 string m_logdir; /**< configured backup root dir */
124 int m_maxlogdirs; /**< number of backup dirs to preserve, 0 if unlimited */
125 string m_prefix; /**< common prefix of backup dirs */
126 string m_path; /**< path to current logging and backup dir */
127 string m_logfile; /**< Path to log file there, empty if not writing one.
128 The file is no longer written by this class, nor
129 does it control the basename of it. Writing the
130 log file is enabled by the XML configuration that
131 we prepare for the Synthesis engine; the base name
132 of the file is hard-coded in the engine. Despite
133 that this class still is the central point to ask
134 for the name of the log file. */
135 SafeConfigNode *m_info; /**< key/value representation of sync information */
136 bool m_readonly; /**< m_info is not to be written to */
137 SyncReport *m_report; /**< record start/end times here */
139 /** set m_logdir and adapt m_prefix accordingly */
140 void setLogdir(const string &logdir) {
141 // strip trailing slashes, but not the initial one
142 size_t off = logdir.size();
143 while (off > 0 && logdir[off - 1] == '/') {
146 m_logdir = logdir.substr(0, off);
148 string lower = m_client.getServer();
149 boost::to_lower(lower);
151 if (boost::iends_with(m_logdir, "syncevolution")) {
152 // use just the server name as prefix
155 // SyncEvolution-<server>-<yyyy>-<mm>-<dd>-<hh>-<mm>
156 m_prefix = "SyncEvolution-";
162 LogDir(EvolutionSyncClient &client) : m_client(client), m_parentLogger(LoggerBase::instance()), m_info(NULL), m_readonly(false), m_report(NULL)
164 // Set default log directory. This will be overwritten with a user-specified
165 // location later on, if one was selected by the user. SyncEvolution >= 0.9 alpha
166 // and < 0.9 beta 2 used XDG_DATA_HOME because the logs and data base dumps
167 // were not considered "non-essential data files". Because XDG_DATA_HOME is
168 // searched for .desktop files and creating large amounts of other files there
169 // slows down that search, the default was changed to XDG_CACHE_DIR.
171 // To migrate old installations seamlessly, this code here renames the old
172 // default directory to the new one. Errors (like not found) are silently ignored.
173 mkdir_p(SubstEnvironment("${XDG_CACHE_HOME}").c_str());
174 rename(SubstEnvironment("${XDG_DATA_HOME}/applications/syncevolution").c_str(),
175 SubstEnvironment("${XDG_CACHE_HOME}/syncevolution").c_str());
177 setLogdir(SubstEnvironment("${XDG_CACHE_HOME}/syncevolution"));
181 * Finds previous log directories. Reports errors via exceptions.
183 * @param path path to configured backup directy, NULL if defaulting to /tmp, "none" if not creating log file
184 * @retval dirs vector of full path names, oldest first
186 void previousLogdirs(const char *path, vector<string> &dirs) {
190 if (path && !strcasecmp(path, "none")) {
193 if (path && path[0]) {
194 setLogdir(SubstEnvironment(path));
201 * Finds previous log directory. Returns empty string if anything went wrong.
203 * @param path path to configured backup directy, NULL if defaulting to /tmp, "none" if not creating log file
204 * @return full path of previous log directory, empty string if not found
206 string previousLogdir(const char *path) throw() {
209 previousLogdirs(path, dirs);
210 return dirs.empty() ? "" : dirs.back();
212 SyncEvolutionException::handle();
218 * access existing log directory to extract status information
220 void openLogdir(const string &dir) {
221 boost::shared_ptr<ConfigNode> filenode(new FileConfigNode(dir, "status.ini", true));
222 m_info = new SafeConfigNode(filenode);
223 m_info->setMode(false);
228 * read sync report for session selected with openLogdir()
230 void readReport(SyncReport &report) {
239 * write sync report for current session
241 void writeReport(SyncReport &report) {
245 /* write in slightly different format and flush at the end */
246 writeTimestamp("start", report.getStart(), false);
247 writeTimestamp("end", report.getEnd(), true);
251 // setup log directory and redirect logging into it
252 // @param path path to configured backup directy, empty for using default, "none" if not creating log file
253 // @param maxlogdirs number of backup dirs to preserve in path, 0 if unlimited
254 // @param logLevel 0 = default, 1 = ERROR, 2 = INFO, 3 = DEBUG
255 // @param usePath write directly into path, don't create and manage subdirectories
256 // @param report record information about session here (may be NULL)
257 // @param logname the basename to be used for logs, traditionally "client" for syncs
258 void startSession(const char *path, int maxlogdirs, int logLevel, bool usePath, SyncReport *report, const string &logname) {
259 m_maxlogdirs = maxlogdirs;
262 if (path && !strcasecmp(path, "none")) {
265 if (path && path[0]) {
266 setLogdir(SubstEnvironment(path));
270 // create unique directory name in the given directory
271 time_t ts = time(NULL);
272 struct tm *tm = localtime(&ts);
274 base << m_logdir << "/"
278 << setw(4) << tm->tm_year + 1900 << "-"
279 << setw(2) << tm->tm_mon + 1 << "-"
280 << setw(2) << tm->tm_mday << "-"
281 << setw(2) << tm->tm_hour << "-"
282 << setw(2) << tm->tm_min;
291 if (!isDir(m_path)) {
300 if (mkdir(m_path.c_str(), S_IRWXU) &&
302 SE_LOG_DEBUG(NULL, NULL, "%s: %s", m_path.c_str(), strerror(errno));
303 EvolutionSyncClient::throwError(m_path, errno);
306 m_logfile = m_path + "/" + "sysynclib_linux.html";
309 // update log level of default logger and our own replacement
313 // default for console output
323 if (m_logfile.empty()) {
324 // no log file: print all information to the console
327 // have log file: avoid excessive output to the console,
328 // full information is in the log file
334 LoggerBase::instance().setLevel(level);
337 LoggerBase::pushLogger(this);
339 time_t start = time(NULL);
341 m_report->setStart(start);
343 if (!m_path.empty()) {
344 boost::shared_ptr<ConfigNode> filenode(new FileConfigNode(m_path, "status.ini", false));
345 m_info = new SafeConfigNode(filenode);
346 m_info->setMode(false);
347 writeTimestamp("start", start);
351 /** sets a fixed directory for database files without redirecting logging */
352 void setPath(const string &path) { m_path = path; }
354 // return log directory, empty if not enabled
355 const string &getLogdir() {
359 // return log file, empty if not enabled
360 const string &getLogfile() {
364 // remove oldest backup dirs if exceeding limit
366 if (m_logdir.size() && m_maxlogdirs > 0 ) {
371 for (vector<string>::iterator it = dirs.begin();
372 it != dirs.end() && (int)dirs.size() - deleted > m_maxlogdirs;
375 string msg = "removing " + path;
376 SE_LOG_INFO(NULL, NULL, "%s", msg.c_str());
382 // remove redirection of logging
384 if (&LoggerBase::instance() == this) {
385 LoggerBase::popLogger();
387 time_t end = time(NULL);
389 m_report->setEnd(end);
393 writeTimestamp("end", end);
395 writeReport(*m_report);
409 virtual void messagev(Level level,
413 const char *function,
417 if (m_client.getEngine().get()) {
419 va_copy(argscopy, args);
420 // once to Synthesis log, with full debugging
421 m_client.getEngine().doDebug(level, prefix, file, line, function, format, argscopy);
424 // always to parent (usually stdout)
425 m_parentLogger.messagev(level, prefix, file, line, function, format, args);
429 /** find all entries in a given directory, return as sorted array of full paths */
430 void getLogdirs(vector<string> &dirs) {
431 if (!isDir(m_logdir)) {
434 ReadDir dir(m_logdir);
435 BOOST_FOREACH(const string &entry, dir) {
436 if (boost::starts_with(entry, m_prefix)) {
437 dirs.push_back(m_logdir + "/" + entry);
440 sort(dirs.begin(), dirs.end());
443 // store time stamp in session info
444 void writeTimestamp(const string &key, time_t val, bool flush = true) {
448 // be nice and store a human-readable date in addition the seconds since the epoch
449 strftime(buffer, sizeof(buffer), "%s, %Y-%m-%d %H:%m:%S %z", localtime_r(&val, &tm));
450 m_info->setProperty(key, buffer);
458 // this class owns the sync sources and (together with
459 // a logdir) handles writing of per-sync files as well
460 // as the final report (
461 class SourceList : public vector<EvolutionSyncSource *> {
464 LOGGING_QUIET, /**< avoid all extra output */
465 LOGGING_SUMMARY, /**< sync report, but no database comparison */
466 LOGGING_FULL /**< everything */
470 LogDir m_logdir; /**< our logging directory */
471 bool m_prepared; /**< remember whether syncPrepare() dumped databases successfully */
472 bool m_doLogging; /**< true iff the normal logdir handling is enabled
473 (creating and expiring directoties, before/after comparison) */
474 bool m_reportTodo; /**< true if syncDone() shall print a final report */
475 LogLevel m_logLevel; /**< chooses how much information is printed */
476 string m_previousLogdir; /**< remember previous log dir before creating the new one */
478 /** create name in current (if set) or previous logdir */
479 string databaseName(EvolutionSyncSource &source, const string suffix, string logdir = "") {
480 if (!logdir.size()) {
481 logdir = m_logdir.getLogdir();
483 return logdir + "/" +
484 source.getName() + "." + suffix;
488 LogLevel getLogLevel() const { return m_logLevel; }
489 void setLogLevel(LogLevel logLevel) { m_logLevel = logLevel; }
492 * dump into files with a certain suffix,
493 * optionally store report in member of SyncSourceReport
495 void dumpDatabases(const string &suffix,
496 BackupReport SyncSourceReport::*report) {
497 BOOST_FOREACH(EvolutionSyncSource *source, *this) {
498 string dir = databaseName(*source, suffix);
499 boost::shared_ptr<ConfigNode> node = ConfigNode::createFileNode(dir + ".ini");
500 SE_LOG_DEBUG(NULL, NULL, "creating %s", dir.c_str());
504 source->backupData(dir, *node,
505 report ? source->*report : dummy);
506 SE_LOG_DEBUG(NULL, NULL, "%s created", dir.c_str());
510 void restoreDatabase(EvolutionSyncSource &source, const string &suffix, bool dryrun, SyncSourceReport &report)
512 string dir = databaseName(source, suffix);
513 boost::shared_ptr<ConfigNode> node = ConfigNode::createFileNode(dir + ".ini");
514 if (!node->exists()) {
515 EvolutionSyncClient::throwError(dir + ": no such database backup found");
517 source.restoreData(dir, *node, dryrun, report);
520 SourceList(EvolutionSyncClient &client, bool doLogging) :
523 m_doLogging(doLogging),
525 m_logLevel(LOGGING_FULL)
529 // call as soon as logdir settings are known
530 void startSession(const char *logDirPath, int maxlogdirs, int logLevel, SyncReport *report,
531 const string &logname) {
532 m_previousLogdir = m_logdir.previousLogdir(logDirPath);
534 m_logdir.startSession(logDirPath, maxlogdirs, logLevel, false, report, logname);
536 // Run debug session without paying attention to
537 // the normal logdir handling. The log level here
538 // refers to stdout. The log file will be as complete
540 m_logdir.startSession(logDirPath, 0, 1, true, report, logname);
544 /** return log directory, empty if not enabled */
545 const string &getLogdir() {
546 return m_logdir.getLogdir();
549 /** return previous log dir found in startSession() */
550 const string &getPrevLogdir() const { return m_previousLogdir; }
552 /** set directory for database files without actually redirecting the logging */
553 void setPath(const string &path) { m_logdir.setPath(path); }
556 * If possible (directory to compare against available) and enabled,
557 * then dump changes applied locally.
559 * @param oldSuffix suffix of old database dump: usually "after"
560 * @param currentSuffix the current database dump suffix: "current"
561 * when not doing a sync, otherwise "before"
563 bool dumpLocalChanges(const string &oldDir,
564 const string &oldSuffix, const string &newSuffix,
565 const string &intro = "Local data changes to be applied to server during synchronization:\n",
566 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'") {
567 if (m_logLevel <= LOGGING_SUMMARY || oldDir.empty()) {
572 BOOST_FOREACH(EvolutionSyncSource *source, *this) {
573 string oldFile = databaseName(*source, oldSuffix, oldDir);
574 string newFile = databaseName(*source, newSuffix);
575 cout << "*** " << source->getName() << " ***\n" << flush;
576 string cmd = string("env CLIENT_TEST_COMPARISON_FAILED=10 " + config + " synccompare 2>/dev/null '" ) +
577 oldFile + "' '" + newFile + "'";
578 int ret = system(cmd.c_str());
579 switch (ret == -1 ? ret : WEXITSTATUS(ret)) {
581 cout << "no changes\n";
586 cout << "Comparison was impossible.\n";
594 // call when all sync sources are ready to dump
595 // pre-sync databases
597 if (m_logdir.getLogfile().size() &&
599 // dump initial databases
600 dumpDatabases("before", &SyncSourceReport::m_backupBefore);
601 // compare against the old "after" database dump
602 dumpLocalChanges(getPrevLogdir(), "after", "before");
608 // call at the end of a sync with success == true
609 // if all went well to print report
610 void syncDone(SyncMLStatus status, SyncReport *report) {
611 // record status - failures from now only affect post-processing
612 // and thus do no longer change that result
614 report->setStatus(status == 0 ? STATUS_HTTP_OK : status);
618 // dump database after sync, but not if already dumping it at the beginning didn't complete
619 if (m_reportTodo && m_prepared) {
621 dumpDatabases("after", &SyncSourceReport::m_backupAfter);
623 SyncEvolutionException::handle();
627 // update report with more recent information about m_backupAfter
628 updateSyncReport(*report);
632 // ensure that stderr is seen again, also writes out session status
636 // haven't looked at result of sync yet;
638 m_reportTodo = false;
640 string logfile = m_logdir.getLogfile();
644 if (status == STATUS_OK) {
645 cout << "Synchronization successful.\n";
646 } else if (logfile.size()) {
647 cout << "Synchronization failed, see "
649 << " for details.\n";
651 cout << "Synchronization failed.\n";
654 // pretty-print report
655 if (m_logLevel > LOGGING_QUIET) {
656 cout << "\nChanges applied during synchronization:\n";
658 if (m_logLevel > LOGGING_QUIET && report) {
662 // compare databases?
663 if (m_logLevel > LOGGING_SUMMARY && m_prepared) {
664 cout << "\nChanges applied to client during synchronization:\n";
665 BOOST_FOREACH(EvolutionSyncSource *source, *this) {
666 cout << "*** " << source->getName() << " ***\n" << flush;
668 string before = databaseName(*source, "before");
669 string after = databaseName(*source, "after");
670 string cmd = string("synccompare '" ) +
671 before + "' '" + after +
672 "' && echo 'no changes'";
673 if (system(cmd.c_str())) {
680 if (status == STATUS_OK) {
687 /** copies information about sources into sync report */
688 void updateSyncReport(SyncReport &report) {
689 BOOST_FOREACH(EvolutionSyncSource *source, *this) {
690 report.addSyncSourceReport(source->getName(), *source);
694 /** returns names of active sources */
695 set<string> getSources() {
698 BOOST_FOREACH(EvolutionSyncSource *source, *this) {
699 res.insert(source->getName());
706 BOOST_FOREACH(EvolutionSyncSource *source, *this) {
711 /** find sync source by name */
712 EvolutionSyncSource *operator [] (const string &name) {
713 BOOST_FOREACH(EvolutionSyncSource *source, *this) {
714 if (name == source->getName()) {
722 EvolutionSyncSource *operator [] (int index) { return vector<EvolutionSyncSource *>::operator [] (index); }
725 void unref(SourceList *sourceList)
730 string EvolutionSyncClient::askPassword(const string &descr)
734 printf("Enter password for %s: ",
737 if (fgets(buffer, sizeof(buffer), stdin) &&
738 strcmp(buffer, "\n")) {
739 size_t len = strlen(buffer);
740 if (len && buffer[len - 1] == '\n') {
745 throwError(string("could not read password for ") + descr);
750 boost::shared_ptr<TransportAgent> EvolutionSyncClient::createTransportAgent()
752 #ifdef ENABLE_LIBSOUP
753 boost::shared_ptr<SoupTransportAgent> agent(new SoupTransportAgent());
754 #elif defined(ENABLE_LIBCURL)
755 boost::shared_ptr<CurlTransportAgent> agent(new CurlTransportAgent());
757 boost::shared_ptr<TransportAgent> agent;
758 throw std::string("libsyncevolution was compiled without default transport, client must implement EvolutionSyncClient::createTransportAgent()");
763 void EvolutionSyncClient::displayServerMessage(const string &message)
765 SE_LOG_INFO(NULL, NULL, "message from server: %s", message.c_str());
768 void EvolutionSyncClient::displaySyncProgress(sysync::TProgressEventEnum type,
769 int32_t extra1, int32_t extra2, int32_t extra3)
774 void EvolutionSyncClient::displaySourceProgress(sysync::TProgressEventEnum type,
775 EvolutionSyncSource &source,
776 int32_t extra1, int32_t extra2, int32_t extra3)
779 case sysync::PEV_PREPARING:
780 /* preparing (e.g. preflight in some clients), extra1=progress, extra2=total */
781 /* extra2 might be zero */
783 SE_LOG_INFO(NULL, NULL, "%s: preparing %d/%d",
784 source.getName(), extra1, extra2);
786 SE_LOG_INFO(NULL, NULL, "%s: preparing %d",
787 source.getName(), extra1);
790 case sysync::PEV_DELETING:
791 /* deleting (zapping datastore), extra1=progress, extra2=total */
793 SE_LOG_INFO(NULL, NULL, "%s: deleting %d/%d",
794 source.getName(), extra1, extra2);
796 SE_LOG_INFO(NULL, NULL, "%s: deleting %d",
797 source.getName(), extra1);
800 case sysync::PEV_ALERTED: {
801 /* datastore alerted (extra1=0 for normal, 1 for slow, 2 for first time slow,
802 extra2=1 for resumed session,
803 extra3 0=twoway, 1=fromserver, 2=fromclient */
804 SE_LOG_INFO(NULL, NULL, "%s: %s %s sync%s",
806 extra2 ? "resuming" : "starting",
807 extra1 == 0 ? "normal" :
808 extra1 == 1 ? "slow" :
809 extra1 == 2 ? "first time" :
811 extra3 == 0 ? ", two-way" :
812 extra3 == 1 ? " from server" :
813 extra3 == 2 ? " from client" :
814 ", unknown direction");
816 SyncMode mode = SYNC_NONE;
824 mode = SYNC_ONE_WAY_FROM_SERVER;
827 mode = SYNC_ONE_WAY_FROM_CLIENT;
838 mode = SYNC_REFRESH_FROM_SERVER;
841 mode = SYNC_REFRESH_FROM_CLIENT;
846 source.recordFinalSyncMode(mode);
847 source.recordFirstSync(extra1 == 2);
848 source.recordResumeSync(extra2 == 1);
851 case sysync::PEV_SYNCSTART:
853 SE_LOG_INFO(NULL, NULL, "%s: started",
856 case sysync::PEV_ITEMRECEIVED:
857 /* item received, extra1=current item count,
858 extra2=number of expected changes (if >= 0) */
860 SE_LOG_INFO(NULL, NULL, "%s: received %d/%d",
861 source.getName(), extra1, extra2);
863 SE_LOG_INFO(NULL, NULL, "%s: received %d",
864 source.getName(), extra1);
867 case sysync::PEV_ITEMSENT:
868 /* item sent, extra1=current item count,
869 extra2=number of expected items to be sent (if >=0) */
871 SE_LOG_INFO(NULL, NULL, "%s: sent %d/%d",
872 source.getName(), extra1, extra2);
874 SE_LOG_INFO(NULL, NULL, "%s: sent %d",
875 source.getName(), extra1);
878 case sysync::PEV_ITEMPROCESSED:
879 /* item locally processed, extra1=# added,
882 SE_LOG_INFO(NULL, NULL, "%s: added %d, updated %d, removed %d",
883 source.getName(), extra1, extra2, extra3);
885 case sysync::PEV_SYNCEND:
886 /* sync finished, probably with error in extra1 (0=ok),
887 syncmode in extra2 (0=normal, 1=slow, 2=first time),
888 extra3=1 for resumed session) */
889 SE_LOG_INFO(NULL, NULL, "%s: %s%s sync done %s",
891 extra3 ? "resumed " : "",
892 extra2 == 0 ? "normal" :
893 extra2 == 1 ? "slow" :
894 extra2 == 2 ? "first time" :
896 extra1 ? "unsuccessfully" : "successfully");
899 // TODO: reset cached password
900 SE_LOG_INFO(NULL, NULL, "authorization failed, check username '%s' and password", getUsername());
903 SE_LOG_INFO(&source, NULL, "log in succeeded, but server refuses access - contact server operator");
906 SE_LOG_INFO(NULL, NULL, "proxy authorization failed, check proxy username and password");
909 SE_LOG_INFO(&source, NULL, "server database not found, check URI '%s'", source.getURI());
912 source.recordStatus(SyncMLStatus(extra1));
914 case sysync::PEV_DSSTATS_L:
915 /* datastore statistics for local (extra1=# added,
918 source.setItemStat(EvolutionSyncSource::ITEM_LOCAL,
919 EvolutionSyncSource::ITEM_ADDED,
920 EvolutionSyncSource::ITEM_TOTAL,
922 source.setItemStat(EvolutionSyncSource::ITEM_LOCAL,
923 EvolutionSyncSource::ITEM_UPDATED,
924 EvolutionSyncSource::ITEM_TOTAL,
926 source.setItemStat(EvolutionSyncSource::ITEM_LOCAL,
927 EvolutionSyncSource::ITEM_REMOVED,
928 EvolutionSyncSource::ITEM_TOTAL,
929 // Synthesis engine doesn't count locally
930 // deleted items during
931 // refresh-from-server. That's a matter of
932 // taste. In SyncEvolution we'd like these
933 // items to show up, so add it here.
934 source.getFinalSyncMode() == SYNC_REFRESH_FROM_SERVER ?
935 source.getNumDeleted() :
938 case sysync::PEV_DSSTATS_R:
939 /* datastore statistics for remote (extra1=# added,
942 source.setItemStat(EvolutionSyncSource::ITEM_REMOTE,
943 EvolutionSyncSource::ITEM_ADDED,
944 EvolutionSyncSource::ITEM_TOTAL,
946 source.setItemStat(EvolutionSyncSource::ITEM_REMOTE,
947 EvolutionSyncSource::ITEM_UPDATED,
948 EvolutionSyncSource::ITEM_TOTAL,
950 source.setItemStat(EvolutionSyncSource::ITEM_REMOTE,
951 EvolutionSyncSource::ITEM_REMOVED,
952 EvolutionSyncSource::ITEM_TOTAL,
955 case sysync::PEV_DSSTATS_E:
956 /* datastore statistics for local/remote rejects (extra1=# locally rejected,
957 extra2=# remotely rejected) */
958 source.setItemStat(EvolutionSyncSource::ITEM_LOCAL,
959 EvolutionSyncSource::ITEM_ANY,
960 EvolutionSyncSource::ITEM_REJECT,
962 source.setItemStat(EvolutionSyncSource::ITEM_REMOTE,
963 EvolutionSyncSource::ITEM_ANY,
964 EvolutionSyncSource::ITEM_REJECT,
967 case sysync::PEV_DSSTATS_S:
968 /* datastore statistics for server slowsync (extra1=# slowsync matches) */
969 source.setItemStat(EvolutionSyncSource::ITEM_REMOTE,
970 EvolutionSyncSource::ITEM_ANY,
971 EvolutionSyncSource::ITEM_MATCH,
974 case sysync::PEV_DSSTATS_C:
975 /* datastore statistics for server conflicts (extra1=# server won,
977 extra3=# duplicated) */
978 source.setItemStat(EvolutionSyncSource::ITEM_REMOTE,
979 EvolutionSyncSource::ITEM_ANY,
980 EvolutionSyncSource::ITEM_CONFLICT_SERVER_WON,
982 source.setItemStat(EvolutionSyncSource::ITEM_REMOTE,
983 EvolutionSyncSource::ITEM_ANY,
984 EvolutionSyncSource::ITEM_CONFLICT_CLIENT_WON,
986 source.setItemStat(EvolutionSyncSource::ITEM_REMOTE,
987 EvolutionSyncSource::ITEM_ANY,
988 EvolutionSyncSource::ITEM_CONFLICT_DUPLICATED,
991 case sysync::PEV_DSSTATS_D:
992 /* datastore statistics for data volume (extra1=outgoing bytes,
993 extra2=incoming bytes) */
994 source.setItemStat(EvolutionSyncSource::ITEM_LOCAL,
995 EvolutionSyncSource::ITEM_ANY,
996 EvolutionSyncSource::ITEM_SENT_BYTES,
998 source.setItemStat(EvolutionSyncSource::ITEM_LOCAL,
999 EvolutionSyncSource::ITEM_ANY,
1000 EvolutionSyncSource::ITEM_RECEIVED_BYTES,
1004 SE_LOG_DEBUG(NULL, NULL, "%s: progress event %d, extra %d/%d/%d",
1006 type, extra1, extra2, extra3);
1010 void EvolutionSyncClient::throwError(const string &error)
1014 * Catching the runtime_exception fails due to a toolchain problem,
1015 * so do the error handling now and abort: because there is just
1016 * one sync source this is probably the only thing that can be done.
1017 * Still, it's not nice on the server...
1019 fatalError(NULL, error.c_str());
1021 throw runtime_error(error);
1025 void EvolutionSyncClient::throwError(const string &action, int error)
1027 throwError(action + ": " + strerror(error));
1030 void EvolutionSyncClient::fatalError(void *object, const char *error)
1032 SE_LOG_ERROR(NULL, NULL, "%s", error);
1033 if (m_sourceListPtr) {
1034 m_sourceListPtr->syncDone(STATUS_FATAL, NULL);
1040 * There have been segfaults inside glib in the background
1041 * thread which ran the second event loop. Disabled it again,
1042 * even though the synchronous EDS API calls will block then
1045 #if 0 && defined(HAVE_GLIB) && defined(HAVE_EDS)
1046 # define RUN_GLIB_LOOP
1049 #ifdef RUN_GLIB_LOOP
1050 #include <pthread.h>
1052 static void *mainLoopThread(void *)
1054 // The test framework uses SIGALRM for timeouts.
1055 // Block the signal here because a) the signal handler
1056 // prints a stack back trace when called and we are not
1057 // interessted in the background thread's stack and b)
1058 // it seems to have confused glib/libebook enough to
1059 // access invalid memory and segfault when it gets the SIGALRM.
1061 sigemptyset(&blocked);
1062 sigaddset(&blocked, SIGALRM);
1063 pthread_sigmask(SIG_BLOCK, &blocked, NULL);
1065 GMainLoop *mainloop = g_main_loop_new(NULL, TRUE);
1067 g_main_loop_run(mainloop);
1068 g_main_loop_unref(mainloop);
1074 void EvolutionSyncClient::startLoopThread()
1076 #ifdef RUN_GLIB_LOOP
1077 // when using Evolution we must have a running main loop,
1078 // otherwise loss of connection won't be reported to us
1079 static pthread_t loopthread;
1080 static bool loopthreadrunning;
1081 if (!loopthreadrunning) {
1082 loopthreadrunning = !pthread_create(&loopthread, NULL, mainLoopThread, NULL);
1087 EvolutionSyncSource *EvolutionSyncClient::findSource(const char *name)
1089 return m_sourceListPtr ? (*m_sourceListPtr)[name] : NULL;
1092 void EvolutionSyncClient::setConfigFilter(bool sync, const FilterConfigNode::ConfigFilter &filter)
1094 map<string, string>::const_iterator hasSync = filter.find(EvolutionSyncSourceConfig::m_sourcePropSync.getName());
1096 if (!sync && hasSync != filter.end()) {
1097 m_overrideMode = hasSync->second;
1098 FilterConfigNode::ConfigFilter strippedFilter = filter;
1099 strippedFilter.erase(EvolutionSyncSourceConfig::m_sourcePropSync.getName());
1100 EvolutionSyncConfig::setConfigFilter(sync, strippedFilter);
1102 EvolutionSyncConfig::setConfigFilter(sync, filter);
1106 void EvolutionSyncClient::initSources(SourceList &sourceList)
1108 set<string> unmatchedSources = m_sources;
1109 list<string> configuredSources = getSyncSources();
1110 BOOST_FOREACH(const string &name, configuredSources) {
1111 boost::shared_ptr<PersistentEvolutionSyncSourceConfig> sc(getSyncSourceConfig(name));
1113 // is the source enabled?
1114 string sync = sc->getSync();
1115 bool enabled = sync != "disabled";
1116 string overrideMode = m_overrideMode;
1119 if (m_sources.size()) {
1120 if (m_sources.find(sc->getName()) != m_sources.end()) {
1122 if (overrideMode.empty()) {
1123 overrideMode = "two-way";
1127 unmatchedSources.erase(sc->getName());
1134 string url = getSyncURL();
1135 boost::replace_first(url, "https://", "http://"); // do not distinguish between protocol in change tracking
1136 string changeId = string("sync4jevolution:") + url + "/" + name;
1137 EvolutionSyncSourceParams params(name,
1138 getSyncSourceNodes(name),
1140 // the sync mode has to be set before instantiating the source
1141 // because the client library reads the preferredSyncMode at that time
1142 if (!overrideMode.empty()) {
1143 params.m_nodes.m_configNode->addFilter(EvolutionSyncSourceConfig::m_sourcePropSync.getName(),
1146 EvolutionSyncSource *syncSource =
1147 EvolutionSyncSource::createSource(params);
1149 throwError(name + ": type unknown" );
1151 sourceList.push_back(syncSource);
1155 // check whether there were any sources specified which do not exist
1156 if (unmatchedSources.size()) {
1157 throwError(string("no such source(s): ") + boost::join(unmatchedSources, " "));
1161 // XML configuration converted to C string constant
1163 extern const char *SyncEvolutionXML;
1166 void EvolutionSyncClient::setSyncModes(const std::vector<EvolutionSyncSource *> &sources,
1167 const SyncModes &modes)
1169 BOOST_FOREACH(EvolutionSyncSource *source, sources) {
1170 SyncMode mode = modes.getSyncMode(source->getName());
1171 if (mode != SYNC_NONE) {
1172 string modeString(PrettyPrintSyncMode(mode));
1173 source->setSync(modeString, true);
1178 void EvolutionSyncClient::getConfigTemplateXML(string &xml, string &configname)
1181 configname = "syncclient_sample_config.xml";
1182 if (ReadFile(configname, xml)) {
1186 SyncEvolutionException::handle();
1190 * @TODO read from config directory
1193 configname = "builtin XML configuration";
1194 xml = SyncEvolutionXML;
1197 static void substTag(string &xml, const string &tagname, const string &replacement, bool replaceElement = false)
1202 tag.reserve(tagname.size() + 3);
1207 index = xml.find(tag);
1208 if (index != xml.npos) {
1210 tmp.reserve(tagname.size() * 2 + 2 + 3 + replacement.size());
1211 if (!replaceElement) {
1217 if (!replaceElement) {
1222 xml.replace(index, tag.size(), tmp);
1226 static void substTag(string &xml, const string &tagname, const char *replacement, bool replaceElement = false)
1228 substTag(xml, tagname, std::string(replacement));
1231 template <class T> void substTag(string &xml, const string &tagname, const T replacement, bool replaceElement = false)
1235 substTag(xml, tagname, str.str());
1238 void EvolutionSyncClient::getConfigXML(string &xml, string &configname)
1240 getConfigTemplateXML(xml, configname);
1244 unsigned long hash = 0;
1247 index = xml.find(tag);
1248 if (index != xml.npos) {
1250 bool logging = !m_sourceListPtr->getLogdir().empty();
1251 int loglevel = getLogLevel();
1255 // logpath is a config variable set by EvolutionSyncClient::doSync()
1256 " <logpath>$(logpath)</logpath>\n"
1257 " <logflushmode>flush</logflushmode>\n"
1258 " <logformat>html</logformat>\n"
1259 " <folding>auto</folding>\n"
1260 " <timestamp>yes</timestamp>\n"
1261 " <timestampall>yes</timestampall>\n"
1262 " <timedsessionlognames>no</timedsessionlognames>\n"
1263 " <subthreadmode>suppress</subthreadmode>\n"
1264 " <logsessionstoglobal>yes</logsessionstoglobal>\n"
1265 " <singlegloballog>yes</singlegloballog>\n";
1268 " <sessionlogs>yes</sessionlogs>\n"
1269 " <globallogs>yes</globallogs>\n";
1270 debug << "<msgdump>" << (loglevel >= 5 ? "yes" : "no") << "</msgdump>\n";
1271 debug << "<xmltranslate>" << (loglevel >= 4 ? "yes" : "no") << "</xmltranslate>\n";
1272 if (loglevel >= 3) {
1274 " <enable option=\"all\"/>\n"
1275 " <enable option=\"userdata\"/>\n"
1276 " <enable option=\"scripts\"/>\n"
1277 " <enable option=\"exotic\"/>\n";
1281 " <sessionlogs>no</sessionlogs>\n"
1282 " <globallogs>no</globallogs>\n"
1283 " <msgdump>no</msgdump>\n"
1284 " <xmltranslate>no</xmltranslate>\n"
1285 " <disable option=\"all\"/>";
1290 xml.replace(index, tag.size(), debug.str());
1293 XMLConfigFragments fragments;
1294 tag = "<datastore/>";
1295 index = xml.find(tag);
1296 if (index != xml.npos) {
1297 stringstream datastores;
1299 BOOST_FOREACH(EvolutionSyncSource *source, *m_sourceListPtr) {
1301 source->getDatastoreXML(fragment, fragments);
1302 hash = Hash(source->getName()) % INT_MAX;
1305 * @TODO handle hash collisions
1310 datastores << " <datastore name='" << source->getName() << "' type='plugin'>\n" <<
1311 " <dbtypeid>" << hash << "</dbtypeid>\n" <<
1313 " </datastore>\n\n";
1315 if (datastores.str().empty()) {
1316 // Add dummy datastore, the engine needs it. sync()
1317 // checks that we have a valid configuration if it is
1320 datastores << "<datastore name=\"____dummy____\" type=\"plugin\">"
1321 "<plugin_module>SyncEvolution</plugin_module>"
1322 "<fieldmap fieldlist=\"contacts\"/>"
1324 "<use datatype=\"vCard30\"/>"
1329 xml.replace(index, tag.size(), datastores.str());
1332 substTag(xml, "fieldlists", fragments.m_fieldlists.join(), true);
1333 substTag(xml, "profiles", fragments.m_profiles.join(), true);
1334 substTag(xml, "datatypes", fragments.m_datatypes.join(), true);
1335 substTag(xml, "remoterules", fragments.m_remoterules.join(), true);
1337 substTag(xml, "fakedeviceid", getDevID());
1338 substTag(xml, "model", getMod());
1339 substTag(xml, "manufacturer", getMan());
1340 substTag(xml, "hardwareversion", getHwv());
1341 // abuse (?) the firmware version to store the SyncEvolution version number
1342 substTag(xml, "firmwareversion", getSwv());
1343 substTag(xml, "devicetype", getDevType());
1344 substTag(xml, "maxmsgsize", std::max(getMaxMsgSize(), 10000ul));
1345 substTag(xml, "maxobjsize", std::max(getMaxObjSize(), 1024u));
1346 substTag(xml, "defaultauth", getClientAuthType());
1348 // if the hash code is changed, that means the content of the
1349 // config has changed, save the new hash and regen the configdate
1350 hash = Hash(xml.c_str());
1351 if (getHashCode() != hash) {
1356 substTag(xml, "configdate", getConfigDate().c_str());
1359 SharedEngine EvolutionSyncClient::createEngine()
1361 SharedEngine engine(new sysync::TEngineModuleBridge);
1363 // Use libsynthesis that we were linked against. The name of a
1364 // .so could be given here, too, to use that instead. This
1365 // instance of the engine is used outside of the sync session
1366 // itself. doSync() then creates another engine for the sync
1367 // itself. That is necessary because the engine shutdown depends
1368 // on the context of the sync (in particular instantiated sync
1370 engine.Connect("[]", 0,
1371 sysync::DBG_PLUGIN_NONE|
1372 sysync::DBG_PLUGIN_INT|
1373 sysync::DBG_PLUGIN_DB|
1374 sysync::DBG_PLUGIN_EXOT);
1376 SharedKey configvars = engine.OpenKeyByPath(SharedKey(), "/configvars");
1377 string logdir = m_sourceListPtr->getLogdir();
1378 engine.SetStrValue(configvars, "defout_path",
1379 logdir.size() ? logdir : "/dev/null");
1380 engine.SetStrValue(configvars, "conferrpath", "console");
1381 engine.SetStrValue(configvars, "binfilepath", getRootPath() + "/.synthesis");
1388 void GnutlsLogFunction(int level, const char *str)
1390 SE_LOG_DEBUG(NULL, "GNUTLS", "level %d: %s", level, str);
1394 SyncMLStatus EvolutionSyncClient::sync(SyncReport *report)
1396 SyncMLStatus status = STATUS_OK;
1399 SE_LOG_ERROR(NULL, NULL, "No configuration for server \"%s\" found.", m_server.c_str());
1400 throwError("cannot proceed without configuration");
1403 // redirect logging as soon as possible
1404 SourceList sourceList(*this, m_doLogging);
1405 sourceList.setLogLevel(m_quiet ? SourceList::LOGGING_QUIET :
1406 getPrintChanges() ? SourceList::LOGGING_FULL :
1407 SourceList::LOGGING_SUMMARY);
1408 m_sourceListPtr = &sourceList;
1410 if (getenv("SYNCEVOLUTION_GNUTLS_DEBUG")) {
1411 // Enable libgnutls debugging without creating a hard dependency on it,
1412 // because we don't call it directly and might not even be linked against
1413 // it. Therefore check for the relevant symbols via dlsym().
1414 void (*set_log_level)(int);
1415 void (*set_log_function)(void (*func)(int level, const char *str));
1417 set_log_level = (typeof(set_log_level))dlsym(RTLD_DEFAULT, "gnutls_global_set_log_level");
1418 set_log_function = (typeof(set_log_function))dlsym(RTLD_DEFAULT, "gnutls_global_set_log_function");
1420 if (set_log_level && set_log_function) {
1421 set_log_level(atoi(getenv("SYNCEVOLUTION_GNUTLS_DEBUG")));
1422 set_log_function(GnutlsLogFunction);
1424 SE_LOG_ERROR(NULL, NULL, "SYNCEVOLUTION_GNUTLS_DEBUG debugging not possible, log functions not found");
1435 // let derived classes override settings, like the log dir
1438 // choose log directory
1439 sourceList.startSession(getLogDir(),
1445 // create a Synthesis engine, used purely for logging purposes
1447 SwapEngine swapengine(*this);
1448 string xml, configname;
1449 getConfigXML(xml, configname);
1450 m_engine.InitEngineXML(xml.c_str());
1453 // dump some summary information at the beginning of the log
1454 SE_LOG_DEV(NULL, NULL, "SyncML server account: %s", getUsername());
1455 SE_LOG_DEV(NULL, NULL, "client: SyncEvolution %s for %s", getSwv(), getDevType());
1456 SE_LOG_DEV(NULL, NULL, "device ID: %s", getDevID());
1457 SE_LOG_DEV(NULL, NULL, "%s", EDSAbiWrapperDebug());
1459 // instantiate backends, but do not open them yet
1460 initSources(sourceList);
1461 if (sourceList.empty()) {
1462 throwError("no sources active, check configuration");
1465 // request all config properties once: throwing exceptions
1466 // now is okay, whereas later it would lead to leaks in the
1467 // not exception safe client library
1468 EvolutionSyncConfig dummy;
1469 set<string> activeSources = sourceList.getSources();
1470 dummy.copy(*this, &activeSources);
1472 // start background thread if not running yet:
1473 // necessary to catch problems with Evolution backend
1476 // ask for passwords now
1477 checkPassword(*this);
1478 if (getUseProxy()) {
1479 checkProxyPassword(*this);
1481 BOOST_FOREACH(EvolutionSyncSource *source, sourceList) {
1482 source->checkPassword(*this);
1485 // open each source - failing now is still safe
1486 BOOST_FOREACH(EvolutionSyncSource *source, sourceList) {
1490 // give derived class also a chance to update the configs
1491 prepare(sourceList);
1493 // ready to go: dump initial databases and prepare for final report
1494 sourceList.syncPrepare();
1499 // handle the exception here while the engine (and logging!) is still alive
1500 SyncEvolutionException::handle(&status);
1504 SyncEvolutionException::handle(&status);
1509 // Print final report before cleaning up.
1510 // Status was okay only if all sources succeeded.
1511 sourceList.updateSyncReport(*report);
1512 BOOST_FOREACH(EvolutionSyncSource *source, sourceList) {
1513 if (source->getStatus() != STATUS_OK &&
1514 status == STATUS_OK) {
1515 status = source->getStatus();
1519 sourceList.syncDone(status, report);
1521 SyncEvolutionException::handle(&status);
1524 m_sourceListPtr = NULL;
1528 SyncMLStatus EvolutionSyncClient::doSync()
1530 struct sigaction new_action, old_action;
1531 new_action.sa_handler= suspend_handler;
1532 sigemptyset (&new_action.sa_mask);
1533 new_action.sa_flags = 0;
1534 sigaction (SIGINT, &new_action, &old_action);
1535 SyncMLStatus status = STATUS_OK;
1538 // re-init engine with all sources configured
1539 string xml, configname;
1540 getConfigXML(xml, configname);
1541 m_engine.InitEngineXML(xml.c_str());
1543 // check the settings status (MUST BE DONE TO MAKE SETTINGS READY)
1544 SharedKey profiles = m_engine.OpenKeyByPath(SharedKey(), "/profiles");
1545 m_engine.GetStrValue(profiles, "settingsstatus");
1546 // allow creating new settings when existing settings are not up/downgradeable
1547 m_engine.SetStrValue(profiles, "overwrite", "1");
1548 // check status again
1549 m_engine.GetStrValue(profiles, "settingsstatus");
1551 // open first profile
1554 profile = m_engine.OpenSubkey(profiles, sysync::KEYVAL_ID_FIRST);
1555 } catch (NoSuchKey error) {
1556 // no profile exists yet, create default profile
1557 profile = m_engine.OpenSubkey(profiles, sysync::KEYVAL_ID_NEW_DEFAULT);
1560 m_engine.SetStrValue(profile, "serverURI", getSyncURL());
1561 m_engine.SetStrValue(profile, "serverUser", getUsername());
1562 m_engine.SetStrValue(profile, "serverPassword", getPassword());
1563 m_engine.SetInt32Value(profile, "encoding",
1564 getWBXML() ? 1 /* WBXML */ : 2 /* XML */);
1566 // Iterate over all data stores in the XML config
1567 // and match them with sync sources.
1568 // TODO: let sync sources provide their own
1569 // XML snippets (inside <client> and inside <datatypes>).
1570 SharedKey targets = m_engine.OpenKeyByPath(profile, "targets");
1574 target = m_engine.OpenSubkey(targets, sysync::KEYVAL_ID_FIRST);
1576 s = m_engine.GetStrValue(target, "dbname");
1577 EvolutionSyncSource *source = (*m_sourceListPtr)[s];
1579 m_engine.SetInt32Value(target, "enabled", 1);
1582 string mode = source->getSync();
1583 if (!strcasecmp(mode.c_str(), "slow")) {
1586 } else if (!strcasecmp(mode.c_str(), "two-way")) {
1589 } else if (!strcasecmp(mode.c_str(), "refresh-from-server")) {
1592 } else if (!strcasecmp(mode.c_str(), "refresh-from-client")) {
1595 } else if (!strcasecmp(mode.c_str(), "one-way-from-server")) {
1598 } else if (!strcasecmp(mode.c_str(), "one-way-from-client")) {
1602 source->throwError(string("invalid sync mode: ") + mode);
1604 m_engine.SetInt32Value(target, "forceslow", slow);
1605 m_engine.SetInt32Value(target, "syncmode", direction);
1607 m_engine.SetStrValue(target, "remotepath", source->getURI());
1609 m_engine.SetInt32Value(target, "enabled", 0);
1611 target = m_engine.OpenSubkey(targets, sysync::KEYVAL_ID_NEXT);
1613 } catch (NoSuchKey error) {
1616 // run an HTTP client sync session
1617 boost::shared_ptr<TransportAgent> agent(createTransportAgent());
1618 if (getUseProxy()) {
1619 agent->setProxy(getProxyHost());
1620 agent->setProxyAuth(getProxyUsername(),
1621 getProxyPassword());
1623 agent->setUserAgent(getUserAgent());
1624 agent->setSSL(findSSLServerCertificate(),
1625 getSSLVerifyServer(),
1626 getSSLVerifyHost());
1628 // Close all keys so that engine can flush the modified config.
1629 // Otherwise the session reads the unmodified values from the
1630 // created files while the updated values are still in memory.
1636 // reopen profile keys
1637 profiles = m_engine.OpenKeyByPath(SharedKey(), "/profiles");
1638 m_engine.GetStrValue(profiles, "settingsstatus");
1639 profile = m_engine.OpenSubkey(profiles, sysync::KEYVAL_ID_FIRST);
1640 targets = m_engine.OpenKeyByPath(profile, "targets");
1642 sysync::TEngineProgressInfo progressInfo;
1643 sysync::uInt16 stepCmd = sysync::STEPCMD_CLIENTSTART; // first step
1644 SharedSession session = m_engine.OpenSession();
1645 SharedBuffer sendBuffer;
1646 SessionSentinel sessionSentinel(*this, session);
1648 // Sync main loop: runs until SessionStep() signals end or error.
1649 // Exceptions are caught and lead to a call of SessionStep() with
1650 // parameter STEPCMD_ABORT -> abort session as soon as possible.
1651 bool aborting = false;
1653 sysync::uInt16 previousStepCmd = stepCmd;
1656 // check for suspend, if so, modify step command for next step
1657 // Since the suspend will actually be committed until it is
1658 // sending out a message, we can safely delay the suspend to
1660 // After exception occurs, stepCmd will be set to abort to force
1661 // aborting, must avoid to change it back to suspend cmd.
1662 if (checkForSuspend() && stepCmd == sysync::STEPCMD_GOTDATA) {
1663 stepCmd = sysync::STEPCMD_SUSPEND;
1666 //check for abort, if so, modify step command for next step.
1667 //We think abort is useful when the server is unresponsive or
1668 //too slow to the user; therefore, we can delay abort at other
1669 //points to this two points (before sending and before receiving
1671 if (checkForAbort() && stepCmd == (sysync::STEPCMD_SENDDATA
1672 || stepCmd == sysync::STEPCMD_NEEDDATA)) {
1673 stepCmd = sysync::STEPCMD_ABORT;
1676 // take next step, but don't abort twice: instead
1677 // let engine contine with its shutdown
1678 if (stepCmd == sysync::STEPCMD_ABORT) {
1680 stepCmd = previousStepCmd;
1685 // same for suspending
1686 if (stepCmd == sysync::STEPCMD_SUSPEND) {
1688 stepCmd = previousStepCmd;
1694 m_engine.SessionStep(session, stepCmd, &progressInfo);
1695 //During suspention we actually insert a STEPCMD_SUSPEND cmd
1696 //Should restore to the original step here
1699 stepCmd = previousStepCmd;
1703 case sysync::STEPCMD_OK:
1704 // no progress info, call step again
1705 stepCmd = sysync::STEPCMD_STEP;
1707 case sysync::STEPCMD_PROGRESS:
1708 // new progress info to show
1709 // Check special case of interactive display alert
1710 if (progressInfo.eventtype == sysync::PEV_DISPLAY100) {
1711 // alert 100 received from remote, message text is in
1712 // SessionKey's "displayalert" field
1713 SharedKey sessionKey = m_engine.OpenSessionKey(session);
1714 // get message from server to display
1715 s = m_engine.GetStrValue(sessionKey,
1717 displayServerMessage(s);
1719 switch (progressInfo.targetID) {
1720 case sysync::KEYVAL_ID_UNKNOWN:
1721 case 0 /* used with PEV_SESSIONSTART?! */:
1722 displaySyncProgress(sysync::TProgressEventEnum(progressInfo.eventtype),
1723 progressInfo.extra1,
1724 progressInfo.extra2,
1725 progressInfo.extra3);
1728 // specific for a certain sync source:
1730 target = m_engine.OpenSubkey(targets, progressInfo.targetID);
1731 s = m_engine.GetStrValue(target, "dbname");
1732 EvolutionSyncSource *source = (*m_sourceListPtr)[s];
1734 displaySourceProgress(sysync::TProgressEventEnum(progressInfo.eventtype),
1736 progressInfo.extra1,
1737 progressInfo.extra2,
1738 progressInfo.extra3);
1740 throwError(std::string("unknown target ") + s);
1746 stepCmd = sysync::STEPCMD_STEP;
1748 case sysync::STEPCMD_ERROR:
1749 // error, terminate (should not happen, as status is
1750 // already checked above)
1752 case sysync::STEPCMD_RESTART:
1753 // make sure connection is closed and will be re-opened for next request
1754 // tbd: close communication channel if still open to make sure it is
1755 // re-opened for the next request
1756 stepCmd = sysync::STEPCMD_STEP;
1758 case sysync::STEPCMD_SENDDATA: {
1759 // send data to remote
1761 // use OpenSessionKey() and GetValue() to retrieve "connectURI"
1762 // and "contenttype" to be used to send data to the server
1763 SharedKey sessionKey = m_engine.OpenSessionKey(session);
1764 s = m_engine.GetStrValue(sessionKey,
1767 s = m_engine.GetStrValue(sessionKey,
1769 agent->setContentType(s);
1772 // use GetSyncMLBuffer()/RetSyncMLBuffer() to access the data to be
1773 // sent or have it copied into caller's buffer using
1774 // ReadSyncMLBuffer(), then send it to the server
1775 sendBuffer = m_engine.GetSyncMLBuffer(session, true);
1776 agent->send(sendBuffer.get(), sendBuffer.size());
1777 stepCmd = sysync::STEPCMD_SENTDATA; // we have sent the data
1780 case sysync::STEPCMD_NEEDDATA:
1781 switch (agent->wait()) {
1782 case TransportAgent::ACTIVE:
1783 stepCmd = sysync::STEPCMD_SENTDATA; // still sending the data?!
1785 case TransportAgent::GOT_REPLY: {
1790 agent->getReply(reply, replylen, contentType);
1792 // sanity check for reply: if known at all, it must be either XML or WBXML
1793 if (contentType.empty() ||
1794 contentType.find("application/vnd.syncml+wbxml") != contentType.npos ||
1795 contentType.find("application/vnd.syncml+xml") != contentType.npos) {
1796 // put answer received earlier into SyncML engine's buffer
1797 m_engine.WriteSyncMLBuffer(session,
1800 stepCmd = sysync::STEPCMD_GOTDATA; // we have received response data
1802 SE_LOG_DEBUG(NULL, NULL, "unexpected content type '%s' in reply, %d bytes:\n%.*s",
1803 contentType.c_str(), (int)replylen, (int)replylen, reply);
1804 SE_LOG_ERROR(NULL, NULL, "unexpected reply from server; might be a temporary problem, try again later");
1805 stepCmd = sysync::STEPCMD_TRANSPFAIL;
1810 stepCmd = sysync::STEPCMD_TRANSPFAIL; // communication with server failed
1814 previousStepCmd = stepCmd;
1815 // loop until session done or aborted with error
1816 } catch (const BadSynthesisResult &result) {
1817 if (result.result() == sysync::LOCERR_USERABORT && aborting) {
1818 SE_LOG_INFO(NULL, NULL, "Aborted as requested.");
1819 stepCmd = sysync::STEPCMD_DONE;
1820 } else if (result.result() == sysync::LOCERR_USERSUSPEND && suspending) {
1821 SE_LOG_INFO(NULL, NULL, "Suspended as requested.");
1822 stepCmd = sysync::STEPCMD_DONE;
1823 } else if (aborting) {
1824 // aborting very early can lead to results different from LOCERR_USERABORT
1825 // => don't treat this as error
1826 SE_LOG_INFO(NULL, NULL, "Aborted with unexpected result (%d)",
1827 static_cast<int>(result.result()));
1828 stepCmd = sysync::STEPCMD_DONE;
1830 SyncEvolutionException::handle(&status);
1831 stepCmd = sysync::STEPCMD_ABORT;
1834 SyncEvolutionException::handle(&status);
1835 stepCmd = sysync::STEPCMD_ABORT;
1837 } while (stepCmd != sysync::STEPCMD_DONE && stepCmd != sysync::STEPCMD_ERROR);
1839 sigaction (SIGINT, &old_action, NULL);
1844 void EvolutionSyncClient::status()
1847 SE_LOG_ERROR(NULL, NULL, "No configuration for server \"%s\" found.", m_server.c_str());
1848 throwError("cannot proceed without configuration");
1851 SourceList sourceList(*this, false);
1852 initSources(sourceList);
1853 BOOST_FOREACH(EvolutionSyncSource *source, sourceList) {
1854 source->checkPassword(*this);
1856 BOOST_FOREACH(EvolutionSyncSource *source, sourceList) {
1861 checkSourceChanges(sourceList, changes);
1864 changes.prettyPrint(out,
1865 SyncReport::WITHOUT_SERVER|
1866 SyncReport::WITHOUT_CONFLICTS|
1867 SyncReport::WITHOUT_REJECTS|
1868 SyncReport::WITH_TOTAL);
1869 SE_LOG_INFO(NULL, NULL, "Local item changes:\n%s",
1872 sourceList.startSession(getLogDir(), 0, 0, NULL, "status");
1873 LoggerBase::instance().setLevel(Logger::INFO);
1874 string prevLogdir = sourceList.getPrevLogdir();
1875 bool found = access(prevLogdir.c_str(), R_OK|X_OK) == 0;
1879 sourceList.setPath(prevLogdir);
1880 sourceList.dumpDatabases("current", NULL);
1881 sourceList.dumpLocalChanges(sourceList.getPrevLogdir(), "after", "current");
1883 SyncEvolutionException::handle();
1886 cout << "Previous log directory not found.\n";
1887 if (!getLogDir() || !getLogDir()[0]) {
1888 cout << "Enable the 'logdir' option and synchronize to use this feature.\n";
1893 void EvolutionSyncClient::checkStatus(SyncReport &report)
1896 SE_LOG_ERROR(NULL, NULL, "No configuration for server \"%s\" found.", m_server.c_str());
1897 throwError("cannot proceed without configuration");
1900 SourceList sourceList(*this, false);
1901 initSources(sourceList);
1902 BOOST_FOREACH(EvolutionSyncSource *source, sourceList) {
1903 source->checkPassword(*this);
1905 BOOST_FOREACH(EvolutionSyncSource *source, sourceList) {
1909 checkSourceChanges(sourceList, report);
1912 static void logRestoreReport(const SyncReport &report, bool dryrun)
1914 if (!report.empty()) {
1916 report.prettyPrint(out, SyncReport::WITHOUT_SERVER|SyncReport::WITHOUT_CONFLICTS|SyncReport::WITH_TOTAL);
1917 SE_LOG_INFO(NULL, NULL, "Item changes %s applied to client during restore:\n%s",
1918 dryrun ? "to be" : "that were",
1920 SE_LOG_INFO(NULL, NULL, "The same incremental changes will be applied to the server during the next sync.");
1921 SE_LOG_INFO(NULL, NULL, "Use -sync refresh-from-client to replace the complete data on the server.");
1925 void EvolutionSyncClient::checkSourceChanges(SourceList &sourceList, SyncReport &changes)
1927 changes.setStart(time(NULL));
1928 BOOST_FOREACH(EvolutionSyncSource *source, sourceList) {
1929 if (source->checkStatus()) {
1930 SyncSourceReport local;
1931 local.setItemStat(SyncSourceReport::ITEM_LOCAL,
1932 SyncSourceReport::ITEM_ADDED,
1933 SyncSourceReport::ITEM_TOTAL,
1934 source->getNumNewItems());
1935 local.setItemStat(SyncSourceReport::ITEM_LOCAL,
1936 SyncSourceReport::ITEM_UPDATED,
1937 SyncSourceReport::ITEM_TOTAL,
1938 source->getNumUpdatedItems());
1939 local.setItemStat(SyncSourceReport::ITEM_LOCAL,
1940 SyncSourceReport::ITEM_REMOVED,
1941 SyncSourceReport::ITEM_TOTAL,
1942 source->getNumDeletedItems());
1943 local.setItemStat(SyncSourceReport::ITEM_LOCAL,
1944 SyncSourceReport::ITEM_ANY,
1945 SyncSourceReport::ITEM_TOTAL,
1946 source->getNumItems());
1947 changes.addSyncSourceReport(source->getName(), local);
1950 changes.setEnd(time(NULL));
1953 void EvolutionSyncClient::restore(const string &dirname, RestoreDatabase database)
1956 SE_LOG_ERROR(NULL, NULL, "No configuration for server \"%s\" found.", m_server.c_str());
1957 throwError("cannot proceed without configuration");
1960 SourceList sourceList(*this, false);
1961 sourceList.startSession(dirname.c_str(), 0, 0, NULL, "restore");
1962 LoggerBase::instance().setLevel(Logger::INFO);
1963 initSources(sourceList);
1964 BOOST_FOREACH(EvolutionSyncSource *source, sourceList) {
1965 source->checkPassword(*this);
1968 string datadump = database == DATABASE_BEFORE_SYNC ? "before" : "after";
1970 BOOST_FOREACH(EvolutionSyncSource *source, sourceList) {
1975 sourceList.dumpDatabases("current", NULL);
1976 sourceList.dumpLocalChanges(dirname, "current", datadump,
1977 "Data changes to be applied to local data during restore:\n",
1978 "CLIENT_TEST_LEFT_NAME='current data' "
1979 "CLIENT_TEST_REMOVED='after restore' "
1980 "CLIENT_TEST_REMOVED='to be removed' "
1981 "CLIENT_TEST_ADDED='to be added'");
1986 BOOST_FOREACH(EvolutionSyncSource *source, sourceList) {
1987 SyncSourceReport sourcereport;
1989 SE_LOG_DEBUG(NULL, NULL, "Restoring %s...", source->getName());
1990 sourceList.restoreDatabase(*source,
1994 SE_LOG_DEBUG(NULL, NULL, "... %s restored.", source->getName());
1995 report.addSyncSourceReport(source->getName(), sourcereport);
1997 sourcereport.recordStatus(STATUS_FATAL);
1998 report.addSyncSourceReport(source->getName(), sourcereport);
2003 logRestoreReport(report, m_dryrun);
2006 logRestoreReport(report, m_dryrun);
2009 void EvolutionSyncClient::getSessions(vector<string> &dirs)
2011 LogDir logging(*this);
2012 logging.previousLogdirs(getLogDir(), dirs);
2015 void EvolutionSyncClient::readSessionInfo(const string &dir, SyncReport &report)
2017 LogDir logging(*this);
2018 logging.openLogdir(dir);
2019 logging.readReport(report);
2022 std::string EvolutionSyncClient::findSSLServerCertificate()
2024 std::string paths = getSSLServerCertificates();
2025 std::vector< std::string > files;
2026 boost::split(files, paths, boost::is_any_of(":"));
2027 BOOST_FOREACH(std::string file, files) {
2028 if (!file.empty() && !access(file.c_str(), R_OK)) {