Imported Upstream version 0.9
[platform/upstream/syncevolution.git] / src / core / EvolutionSyncClient.cpp
1 /*
2  * Copyright (C) 2005-2009 Patrick Ohly <patrick.ohly@gmx.de>
3  * Copyright (C) 2009 Intel Corporation
4  *
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.
9  *
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.
14  *
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
18  * 02110-1301  USA
19  */
20
21 #ifndef _GNU_SOURCE
22 # define _GNU_SOURCE 1
23 #endif
24 #include <dlfcn.h>
25
26 #include "EvolutionSyncClient.h"
27 #include "EvolutionSyncSource.h"
28 #include "SyncEvolutionUtil.h"
29
30 #include "SafeConfigNode.h"
31 #include "FileConfigNode.h"
32
33 #include "LogStdout.h"
34 #include "TransportAgent.h"
35 #include "CurlTransportAgent.h"
36 #include "SoupTransportAgent.h"
37 using namespace SyncEvolution;
38
39 #include <list>
40 #include <memory>
41 #include <vector>
42 #include <sstream>
43 #include <fstream>
44 #include <iomanip>
45 #include <iostream>
46 #include <stdexcept>
47 #include <algorithm>
48 #include <ctime>
49 using namespace std;
50
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>
55
56 #include <sys/stat.h>
57 #include <pwd.h>
58 #include <unistd.h>
59 #include <signal.h>
60 #include <dirent.h>
61 #include <errno.h>
62
63 #include "synthesis/enginemodulebridge.h"
64 #include "synthesis/SDK_util.h"
65
66 SourceList *EvolutionSyncClient::m_sourceListPtr;
67
68 SuspendFlags EvolutionSyncClient::s_flags;
69
70 extern "C" void suspend_handler(int sig)
71 {
72   time_t current;
73   time (&current);
74   SuspendFlags& s_flags = EvolutionSyncClient::getSuspendFlags();
75   //first time suspend or already aborted
76   if (s_flags.state == SuspendFlags::CLIENT_NORMAL)
77   {
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!)");
81       return;
82   }
83   else 
84   {
85       if (current - s_flags.last_suspend
86               < s_flags.ABORT_INTERVAL) 
87       {
88           s_flags.state = SuspendFlags::CLIENT_ABORT;
89           SE_LOG_INFO(NULL, NULL, "Aboring sync as requested via CTRL-C ...");
90       }
91       else
92       {
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!)");
95       }
96   }
97 }
98
99
100 EvolutionSyncClient::EvolutionSyncClient(const string &server,
101                                          bool doLogging,
102                                          const set<string> &sources) :
103     EvolutionSyncConfig(server),
104     m_server(server),
105     m_sources(sources),
106     m_doLogging(doLogging),
107     m_quiet(false),
108     m_dryrun(false)
109 {
110 }
111
112 EvolutionSyncClient::~EvolutionSyncClient()
113 {
114 }
115
116
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 */
138
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] == '/') {
144             off--;
145         }
146         m_logdir = logdir.substr(0, off);
147
148         string lower = m_client.getServer();
149         boost::to_lower(lower);
150
151         if (boost::iends_with(m_logdir, "syncevolution")) {
152             // use just the server name as prefix
153             m_prefix = lower;
154         } else {
155             // SyncEvolution-<server>-<yyyy>-<mm>-<dd>-<hh>-<mm>
156             m_prefix = "SyncEvolution-";
157             m_prefix += lower;
158         }
159     }
160
161 public:
162     LogDir(EvolutionSyncClient &client) : m_client(client), m_parentLogger(LoggerBase::instance()), m_info(NULL), m_readonly(false), m_report(NULL)
163     {
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.
170         //
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());
176
177         setLogdir(SubstEnvironment("${XDG_CACHE_HOME}/syncevolution"));
178     }
179
180     /**
181      * Finds previous log directories. Reports errors via exceptions.
182      *
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
185      */
186     void previousLogdirs(const char *path, vector<string> &dirs) {
187         string logdir;
188
189         dirs.clear();
190         if (path && !strcasecmp(path, "none")) {
191             return;
192         } else {
193             if (path && path[0]) {
194                 setLogdir(SubstEnvironment(path));
195             }
196             getLogdirs(dirs);
197         }
198     }
199
200     /**
201      * Finds previous log directory. Returns empty string if anything went wrong.
202      *
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
205      */
206     string previousLogdir(const char *path) throw() {
207         try {
208             vector<string> dirs;
209             previousLogdirs(path, dirs);
210             return dirs.empty() ? "" : dirs.back();
211         } catch (...) {
212             SyncEvolutionException::handle();
213             return "";
214         }
215     }
216
217     /**
218      * access existing log directory to extract status information
219      */
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);
224         m_readonly = true;
225     }
226
227     /**
228      * read sync report for session selected with openLogdir()
229      */
230     void readReport(SyncReport &report) {
231         report.clear();
232         if (!m_info) {
233             return;
234         }
235         *m_info >> report;
236     }
237
238     /**
239      * write sync report for current session
240      */
241     void writeReport(SyncReport &report) {
242         if (m_info) {
243             *m_info << report;
244
245             /* write in slightly different format and flush at the end */
246             writeTimestamp("start", report.getStart(), false);
247             writeTimestamp("end", report.getEnd(), true);
248         }
249     }
250
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;
260         m_report = report;
261         m_logfile = "";
262         if (path && !strcasecmp(path, "none")) {
263             m_path = "";
264         } else {
265             if (path && path[0]) {
266                 setLogdir(SubstEnvironment(path));
267             }
268
269             if (!usePath) {
270                 // create unique directory name in the given directory
271                 time_t ts = time(NULL);
272                 struct tm *tm = localtime(&ts);
273                 stringstream base;
274                 base << m_logdir << "/"
275                      << m_prefix
276                      << "-"
277                      << setfill('0')
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;
283                 int seq = 0;
284                 while (true) {
285                     stringstream path;
286                     path << base.str();
287                     if (seq) {
288                         path << "-" << seq;
289                     }
290                     m_path = path.str();
291                     if (!isDir(m_path)) {
292                         mkdir_p(m_path);
293                         break;
294                     } else {
295                         seq++;
296                     }
297                 }
298             } else {
299                 m_path = m_logdir;
300                 if (mkdir(m_path.c_str(), S_IRWXU) &&
301                     errno != EEXIST) {
302                     SE_LOG_DEBUG(NULL, NULL, "%s: %s", m_path.c_str(), strerror(errno));
303                     EvolutionSyncClient::throwError(m_path, errno);
304                 }
305             }
306             m_logfile = m_path + "/" + "sysynclib_linux.html";
307         }
308
309         // update log level of default logger and our own replacement
310         Level level;
311         switch (logLevel) {
312         case 0:
313             // default for console output
314             level = INFO;
315             break;
316         case 1:
317             level = ERROR;
318             break;
319         case 2:
320             level = INFO;
321             break;
322         default:
323             if (m_logfile.empty()) {
324                 // no log file: print all information to the console
325                 level = DEBUG;
326             } else {
327                 // have log file: avoid excessive output to the console,
328                 // full information is in the log file
329                 level = INFO;
330             }
331             break;
332         }
333         if (!usePath) {
334             LoggerBase::instance().setLevel(level);
335         }
336         setLevel(level);
337         LoggerBase::pushLogger(this);
338
339         time_t start = time(NULL);
340         if (m_report) {
341             m_report->setStart(start);
342         }
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);
348         }
349     }
350
351     /** sets a fixed directory for database files without redirecting logging */
352     void setPath(const string &path) { m_path = path; }
353
354     // return log directory, empty if not enabled
355     const string &getLogdir() {
356         return m_path;
357     }
358
359     // return log file, empty if not enabled
360     const string &getLogfile() {
361         return m_logfile;
362     }
363
364     // remove oldest backup dirs if exceeding limit
365     void expire() {
366         if (m_logdir.size() && m_maxlogdirs > 0 ) {
367             vector<string> dirs;
368             getLogdirs(dirs);
369
370             int deleted = 0;
371             for (vector<string>::iterator it = dirs.begin();
372                  it != dirs.end() && (int)dirs.size() - deleted > m_maxlogdirs;
373                  ++it, ++deleted) {
374                 string &path = *it;
375                 string msg = "removing " + path;
376                 SE_LOG_INFO(NULL, NULL, "%s", msg.c_str());
377                 rm_r(path);
378             }
379         }
380     }
381
382     // remove redirection of logging
383     void restore() {
384         if (&LoggerBase::instance() == this) {
385             LoggerBase::popLogger();
386         }
387         time_t end = time(NULL);
388         if (m_report) {
389             m_report->setEnd(end);
390         }
391         if (m_info) {
392             if (!m_readonly) {
393                 writeTimestamp("end", end);
394                 if (m_report) {
395                     writeReport(*m_report);
396                 }
397                 m_info->flush();
398             }
399             delete m_info;
400             m_info = NULL;
401         }
402     }
403
404     ~LogDir() {
405         restore();
406     }
407
408
409     virtual void messagev(Level level,
410                           const char *prefix,
411                           const char *file,
412                           int line,
413                           const char *function,
414                           const char *format,
415                           va_list args)
416     {
417         if (m_client.getEngine().get()) {
418             va_list argscopy;
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);
422             va_end(argscopy);
423         }
424         // always to parent (usually stdout)
425         m_parentLogger.messagev(level, prefix, file, line, function, format, args);
426     }
427
428 private:
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)) {
432             return;
433         }
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);
438             }
439         }
440         sort(dirs.begin(), dirs.end());
441     }
442
443     // store time stamp in session info
444     void writeTimestamp(const string &key, time_t val, bool flush = true) {
445         if (m_info) {
446             char buffer[160];
447             struct tm tm;
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);
451             if (flush) {
452                 m_info->flush();
453             }
454         }
455     }
456 };
457
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 *> {
462 public:
463     enum LogLevel {
464         LOGGING_QUIET,    /**< avoid all extra output */
465         LOGGING_SUMMARY,  /**< sync report, but no database comparison */
466         LOGGING_FULL      /**< everything */
467     };
468
469 private:
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 */
477
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();
482         }
483         return logdir + "/" +
484             source.getName() + "." + suffix;
485     }
486
487 public:
488     LogLevel getLogLevel() const { return m_logLevel; }
489     void setLogLevel(LogLevel logLevel) { m_logLevel = logLevel; }
490
491     /**
492      * dump into files with a certain suffix,
493      * optionally store report in member of SyncSourceReport
494      */
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());
501             rm_r(dir);
502             mkdir_p(dir);
503             BackupReport dummy;
504             source->backupData(dir, *node,
505                                report ? source->*report : dummy);
506             SE_LOG_DEBUG(NULL, NULL, "%s created", dir.c_str());
507         }
508     }
509
510     void restoreDatabase(EvolutionSyncSource &source, const string &suffix, bool dryrun, SyncSourceReport &report)
511     {
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");
516         }
517         source.restoreData(dir, *node, dryrun, report);
518     }
519
520     SourceList(EvolutionSyncClient &client, bool doLogging) :
521         m_logdir(client),
522         m_prepared(false),
523         m_doLogging(doLogging),
524         m_reportTodo(true),
525         m_logLevel(LOGGING_FULL)
526     {
527     }
528     
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);
533         if (m_doLogging) {
534             m_logdir.startSession(logDirPath, maxlogdirs, logLevel, false, report, logname);
535         } else {
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
539             // as possible.
540             m_logdir.startSession(logDirPath, 0, 1, true, report, logname);
541         }
542     }
543
544     /** return log directory, empty if not enabled */
545     const string &getLogdir() {
546         return m_logdir.getLogdir();
547     }
548
549     /** return previous log dir found in startSession() */
550     const string &getPrevLogdir() const { return m_previousLogdir; }
551
552     /** set directory for database files without actually redirecting the logging */
553     void setPath(const string &path) { m_logdir.setPath(path); }
554
555     /**
556      * If possible (directory to compare against available) and enabled,
557      * then dump changes applied locally.
558      *
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"
562      */
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()) {
568             return false;
569         }
570
571         cout << intro;
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)) {
580             case 0:
581                 cout << "no changes\n";
582                 break;
583             case 10:
584                 break;
585             default:
586                 cout << "Comparison was impossible.\n";
587                 break;
588             }
589         }
590         cout << "\n";
591         return true;
592     }
593
594     // call when all sync sources are ready to dump
595     // pre-sync databases
596     void syncPrepare() {
597         if (m_logdir.getLogfile().size() &&
598             m_doLogging) {
599             // dump initial databases
600             dumpDatabases("before", &SyncSourceReport::m_backupBefore);
601             // compare against the old "after" database dump
602             dumpLocalChanges(getPrevLogdir(), "after", "before");
603
604             m_prepared = true;
605         }
606     }
607
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
613         if (report) {
614             report->setStatus(status == 0 ? STATUS_HTTP_OK : status);
615         }
616
617         if (m_doLogging) {
618             // dump database after sync, but not if already dumping it at the beginning didn't complete
619             if (m_reportTodo && m_prepared) {
620                 try {
621                     dumpDatabases("after", &SyncSourceReport::m_backupAfter);
622                 } catch (...) {
623                     SyncEvolutionException::handle();
624                     m_prepared = false;
625                 }
626                 if (report) {
627                     // update report with more recent information about m_backupAfter
628                     updateSyncReport(*report);
629                 }
630             }
631
632             // ensure that stderr is seen again, also writes out session status
633             m_logdir.restore();
634
635             if (m_reportTodo) {
636                 // haven't looked at result of sync yet;
637                 // don't do it again
638                 m_reportTodo = false;
639
640                 string logfile = m_logdir.getLogfile();
641                 cout << flush;
642                 cerr << flush;
643                 cout << "\n";
644                 if (status == STATUS_OK) {
645                     cout << "Synchronization successful.\n";
646                 } else if (logfile.size()) {
647                     cout << "Synchronization failed, see "
648                          << logfile
649                          << " for details.\n";
650                 } else {
651                     cout << "Synchronization failed.\n";
652                 }
653
654                 // pretty-print report
655                 if (m_logLevel > LOGGING_QUIET) {
656                     cout << "\nChanges applied during synchronization:\n";
657                 }
658                 if (m_logLevel > LOGGING_QUIET && report) {
659                     cout << *report;
660                 }
661
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;
667
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())) {
674                             // ignore error
675                         }
676                     }
677                     cout << "\n";
678                 }
679
680                 if (status == STATUS_OK) {
681                     m_logdir.expire();
682                 }
683             }
684         }
685     }
686
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);
691         }
692     }
693
694     /** returns names of active sources */
695     set<string> getSources() {
696         set<string> res;
697
698         BOOST_FOREACH(EvolutionSyncSource *source, *this) {
699             res.insert(source->getName());
700         }
701         return res;
702     }
703    
704     ~SourceList() {
705         // free sync sources
706         BOOST_FOREACH(EvolutionSyncSource *source, *this) {
707             delete source;
708         }
709     }
710
711     /** find sync source by name */
712     EvolutionSyncSource *operator [] (const string &name) {
713         BOOST_FOREACH(EvolutionSyncSource *source, *this) {
714             if (name == source->getName()) {
715                 return source;
716             }
717         }
718         return NULL;
719     }
720
721     /** find by index */
722     EvolutionSyncSource *operator [] (int index) { return vector<EvolutionSyncSource *>::operator [] (index); }
723 };
724
725 void unref(SourceList *sourceList)
726 {
727     delete sourceList;
728 }
729
730 string EvolutionSyncClient::askPassword(const string &descr)
731 {
732     char buffer[256];
733
734     printf("Enter password for %s: ",
735            descr.c_str());
736     fflush(stdout);
737     if (fgets(buffer, sizeof(buffer), stdin) &&
738         strcmp(buffer, "\n")) {
739         size_t len = strlen(buffer);
740         if (len && buffer[len - 1] == '\n') {
741             buffer[len - 1] = 0;
742         }
743         return buffer;
744     } else {
745         throwError(string("could not read password for ") + descr);
746         return "";
747     }
748 }
749
750 boost::shared_ptr<TransportAgent> EvolutionSyncClient::createTransportAgent()
751 {
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());
756 #else
757     boost::shared_ptr<TransportAgent> agent;
758     throw std::string("libsyncevolution was compiled without default transport, client must implement EvolutionSyncClient::createTransportAgent()");
759 #endif
760     return agent;
761 }
762
763 void EvolutionSyncClient::displayServerMessage(const string &message)
764 {
765     SE_LOG_INFO(NULL, NULL, "message from server: %s", message.c_str());
766 }
767
768 void EvolutionSyncClient::displaySyncProgress(sysync::TProgressEventEnum type,
769                                               int32_t extra1, int32_t extra2, int32_t extra3)
770 {
771     
772 }
773
774 void EvolutionSyncClient::displaySourceProgress(sysync::TProgressEventEnum type,
775                                                 EvolutionSyncSource &source,
776                                                 int32_t extra1, int32_t extra2, int32_t extra3)
777 {
778     switch(type) {
779     case sysync::PEV_PREPARING:
780         /* preparing (e.g. preflight in some clients), extra1=progress, extra2=total */
781         /* extra2 might be zero */
782         if (extra2) {
783             SE_LOG_INFO(NULL, NULL, "%s: preparing %d/%d",
784                         source.getName(), extra1, extra2);
785         } else {
786             SE_LOG_INFO(NULL, NULL, "%s: preparing %d",
787                         source.getName(), extra1);
788         }
789         break;
790     case sysync::PEV_DELETING:
791         /* deleting (zapping datastore), extra1=progress, extra2=total */
792         if (extra2) {
793             SE_LOG_INFO(NULL, NULL, "%s: deleting %d/%d",
794                         source.getName(), extra1, extra2);
795         } else {
796             SE_LOG_INFO(NULL, NULL, "%s: deleting %d",
797                         source.getName(), extra1);
798         }
799         break;
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",
805                     source.getName(),
806                     extra2 ? "resuming" : "starting",
807                     extra1 == 0 ? "normal" :
808                     extra1 == 1 ? "slow" :
809                     extra1 == 2 ? "first time" :
810                     "unknown",
811                     extra3 == 0 ? ", two-way" :
812                     extra3 == 1 ? " from server" :
813                     extra3 == 2 ? " from client" :
814                     ", unknown direction");
815
816         SyncMode mode = SYNC_NONE;
817         switch (extra1) {
818         case 0:
819             switch (extra3) {
820             case 0:
821                 mode = SYNC_TWO_WAY;
822                 break;
823             case 1:
824                 mode = SYNC_ONE_WAY_FROM_SERVER;
825                 break;
826             case 2:
827                 mode = SYNC_ONE_WAY_FROM_CLIENT;
828                 break;
829             }
830             break;
831         case 1:
832         case 2:
833             switch (extra3) {
834             case 0:
835                 mode = SYNC_SLOW;
836                 break;
837             case 1:
838                 mode = SYNC_REFRESH_FROM_SERVER;
839                 break;
840             case 2:
841                 mode = SYNC_REFRESH_FROM_CLIENT;
842                 break;
843             }
844             break;
845         }
846         source.recordFinalSyncMode(mode);
847         source.recordFirstSync(extra1 == 2);
848         source.recordResumeSync(extra2 == 1);
849         break;
850     }
851     case sysync::PEV_SYNCSTART:
852         /* sync started */
853         SE_LOG_INFO(NULL, NULL, "%s: started",
854                     source.getName());
855         break;
856     case sysync::PEV_ITEMRECEIVED:
857         /* item received, extra1=current item count,
858            extra2=number of expected changes (if >= 0) */
859         if (extra2 > 0) {
860             SE_LOG_INFO(NULL, NULL, "%s: received %d/%d",
861                         source.getName(), extra1, extra2);
862         } else {
863             SE_LOG_INFO(NULL, NULL, "%s: received %d",
864                      source.getName(), extra1);
865         }
866         break;
867     case sysync::PEV_ITEMSENT:
868         /* item sent,     extra1=current item count,
869            extra2=number of expected items to be sent (if >=0) */
870         if (extra2 > 0) {
871             SE_LOG_INFO(NULL, NULL, "%s: sent %d/%d",
872                      source.getName(), extra1, extra2);
873         } else {
874             SE_LOG_INFO(NULL, NULL, "%s: sent %d",
875                      source.getName(), extra1);
876         }
877         break;
878     case sysync::PEV_ITEMPROCESSED:
879         /* item locally processed,               extra1=# added, 
880            extra2=# updated,
881            extra3=# deleted */
882         SE_LOG_INFO(NULL, NULL, "%s: added %d, updated %d, removed %d",
883                  source.getName(), extra1, extra2, extra3);
884         break;
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",
890                  source.getName(),
891                  extra3 ? "resumed " : "",
892                  extra2 == 0 ? "normal" :
893                  extra2 == 1 ? "slow" :
894                  extra2 == 2 ? "first time" :
895                  "unknown",
896                  extra1 ? "unsuccessfully" : "successfully");
897         switch (extra1) {
898         case 401:
899             // TODO: reset cached password
900             SE_LOG_INFO(NULL, NULL, "authorization failed, check username '%s' and password", getUsername());
901             break;
902         case 403:
903             SE_LOG_INFO(&source, NULL, "log in succeeded, but server refuses access - contact server operator");
904             break;
905         case 407:
906             SE_LOG_INFO(NULL, NULL, "proxy authorization failed, check proxy username and password");
907             break;
908         case 404:
909             SE_LOG_INFO(&source, NULL, "server database not found, check URI '%s'", source.getURI());
910             break;
911         }
912         source.recordStatus(SyncMLStatus(extra1));
913         break;
914     case sysync::PEV_DSSTATS_L:
915         /* datastore statistics for local       (extra1=# added, 
916            extra2=# updated,
917            extra3=# deleted) */
918         source.setItemStat(EvolutionSyncSource::ITEM_LOCAL,
919                            EvolutionSyncSource::ITEM_ADDED,
920                            EvolutionSyncSource::ITEM_TOTAL,
921                            extra1);
922         source.setItemStat(EvolutionSyncSource::ITEM_LOCAL,
923                            EvolutionSyncSource::ITEM_UPDATED,
924                            EvolutionSyncSource::ITEM_TOTAL,
925                            extra2);
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() :
936                            extra3);
937         break;
938     case sysync::PEV_DSSTATS_R:
939         /* datastore statistics for remote      (extra1=# added, 
940            extra2=# updated,
941            extra3=# deleted) */
942         source.setItemStat(EvolutionSyncSource::ITEM_REMOTE,
943                            EvolutionSyncSource::ITEM_ADDED,
944                            EvolutionSyncSource::ITEM_TOTAL,
945                            extra1);
946         source.setItemStat(EvolutionSyncSource::ITEM_REMOTE,
947                            EvolutionSyncSource::ITEM_UPDATED,
948                            EvolutionSyncSource::ITEM_TOTAL,
949                            extra2);
950         source.setItemStat(EvolutionSyncSource::ITEM_REMOTE,
951                            EvolutionSyncSource::ITEM_REMOVED,
952                            EvolutionSyncSource::ITEM_TOTAL,
953                            extra3);
954         break;
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,
961                            extra1);
962         source.setItemStat(EvolutionSyncSource::ITEM_REMOTE,
963                            EvolutionSyncSource::ITEM_ANY,
964                            EvolutionSyncSource::ITEM_REJECT,
965                            extra2);
966         break;
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,
972                            extra1);
973         break;
974     case sysync::PEV_DSSTATS_C:
975         /* datastore statistics for server conflicts (extra1=# server won,
976            extra2=# client won,
977            extra3=# duplicated) */
978         source.setItemStat(EvolutionSyncSource::ITEM_REMOTE,
979                            EvolutionSyncSource::ITEM_ANY,
980                            EvolutionSyncSource::ITEM_CONFLICT_SERVER_WON,
981                            extra1);
982         source.setItemStat(EvolutionSyncSource::ITEM_REMOTE,
983                            EvolutionSyncSource::ITEM_ANY,
984                            EvolutionSyncSource::ITEM_CONFLICT_CLIENT_WON,
985                            extra2);
986         source.setItemStat(EvolutionSyncSource::ITEM_REMOTE,
987                            EvolutionSyncSource::ITEM_ANY,
988                            EvolutionSyncSource::ITEM_CONFLICT_DUPLICATED,
989                            extra3);
990         break;
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,
997                            extra1);
998         source.setItemStat(EvolutionSyncSource::ITEM_LOCAL,
999                            EvolutionSyncSource::ITEM_ANY,
1000                            EvolutionSyncSource::ITEM_RECEIVED_BYTES,
1001                            extra2);
1002         break;
1003     default:
1004         SE_LOG_DEBUG(NULL, NULL, "%s: progress event %d, extra %d/%d/%d",
1005                   source.getName(),
1006                   type, extra1, extra2, extra3);
1007     }
1008 }
1009
1010 void EvolutionSyncClient::throwError(const string &error)
1011 {
1012 #ifdef IPHONE
1013     /*
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...
1018      */
1019     fatalError(NULL, error.c_str());
1020 #else
1021     throw runtime_error(error);
1022 #endif
1023 }
1024
1025 void EvolutionSyncClient::throwError(const string &action, int error)
1026 {
1027     throwError(action + ": " + strerror(error));
1028 }
1029
1030 void EvolutionSyncClient::fatalError(void *object, const char *error)
1031 {
1032     SE_LOG_ERROR(NULL, NULL, "%s", error);
1033     if (m_sourceListPtr) {
1034         m_sourceListPtr->syncDone(STATUS_FATAL, NULL);
1035     }
1036     exit(1);
1037 }
1038
1039 /*
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
1043  * when EDS dies.
1044  */
1045 #if 0 && defined(HAVE_GLIB) && defined(HAVE_EDS)
1046 # define RUN_GLIB_LOOP
1047 #endif
1048
1049 #ifdef RUN_GLIB_LOOP
1050 #include <pthread.h>
1051 #include <signal.h>
1052 static void *mainLoopThread(void *)
1053 {
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.
1060     sigset_t blocked;
1061     sigemptyset(&blocked);
1062     sigaddset(&blocked, SIGALRM);
1063     pthread_sigmask(SIG_BLOCK, &blocked, NULL);
1064
1065     GMainLoop *mainloop = g_main_loop_new(NULL, TRUE);
1066     if (mainloop) {
1067         g_main_loop_run(mainloop);
1068         g_main_loop_unref(mainloop);
1069     }
1070     return NULL;
1071 }
1072 #endif
1073
1074 void EvolutionSyncClient::startLoopThread()
1075 {
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);
1083     }
1084 #endif
1085 }
1086
1087 EvolutionSyncSource *EvolutionSyncClient::findSource(const char *name)
1088 {
1089     return m_sourceListPtr ? (*m_sourceListPtr)[name] : NULL;
1090 }
1091
1092 void EvolutionSyncClient::setConfigFilter(bool sync, const FilterConfigNode::ConfigFilter &filter)
1093 {
1094     map<string, string>::const_iterator hasSync = filter.find(EvolutionSyncSourceConfig::m_sourcePropSync.getName());
1095
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);
1101     } else {
1102         EvolutionSyncConfig::setConfigFilter(sync, filter);
1103     }
1104 }
1105
1106 void EvolutionSyncClient::initSources(SourceList &sourceList)
1107 {
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));
1112         
1113         // is the source enabled?
1114         string sync = sc->getSync();
1115         bool enabled = sync != "disabled";
1116         string overrideMode = m_overrideMode;
1117
1118         // override state?
1119         if (m_sources.size()) {
1120             if (m_sources.find(sc->getName()) != m_sources.end()) {
1121                 if (!enabled) {
1122                     if (overrideMode.empty()) {
1123                         overrideMode = "two-way";
1124                     }
1125                     enabled = true;
1126                 }
1127                 unmatchedSources.erase(sc->getName());
1128             } else {
1129                 enabled = false;
1130             }
1131         }
1132         
1133         if (enabled) {
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),
1139                                              changeId);
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(),
1144                                                        overrideMode);
1145             }
1146             EvolutionSyncSource *syncSource =
1147                 EvolutionSyncSource::createSource(params);
1148             if (!syncSource) {
1149                 throwError(name + ": type unknown" );
1150             }
1151             sourceList.push_back(syncSource);
1152         }
1153     }
1154
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, " "));
1158     }
1159 }
1160
1161 // XML configuration converted to C string constant
1162 extern "C" {
1163     extern const char *SyncEvolutionXML;
1164 }
1165
1166 void EvolutionSyncClient::setSyncModes(const std::vector<EvolutionSyncSource *> &sources,
1167                                        const SyncModes &modes)
1168 {
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);
1174         }
1175     }
1176 }
1177
1178 void EvolutionSyncClient::getConfigTemplateXML(string &xml, string &configname)
1179 {
1180     try {
1181         configname = "syncclient_sample_config.xml";
1182         if (ReadFile(configname, xml)) {
1183             return;
1184         }
1185     } catch (...) {
1186         SyncEvolutionException::handle();
1187     }
1188
1189     /**
1190      * @TODO read from config directory
1191      */
1192
1193     configname = "builtin XML configuration";
1194     xml = SyncEvolutionXML;
1195 }
1196
1197 static void substTag(string &xml, const string &tagname, const string &replacement, bool replaceElement = false)
1198 {
1199     string tag;
1200     size_t index;
1201
1202     tag.reserve(tagname.size() + 3);
1203     tag += "<";
1204     tag += tagname;
1205     tag += "/>";
1206
1207     index = xml.find(tag);
1208     if (index != xml.npos) {
1209         string tmp;
1210         tmp.reserve(tagname.size() * 2 + 2 + 3 + replacement.size());
1211         if (!replaceElement) {
1212             tmp += "<";
1213             tmp += tagname;
1214             tmp += ">";
1215         }
1216         tmp += replacement;
1217         if (!replaceElement) {
1218             tmp += "</";
1219             tmp += tagname;
1220             tmp += ">";
1221         }
1222         xml.replace(index, tag.size(), tmp);
1223     }
1224 }
1225
1226 static void substTag(string &xml, const string &tagname, const char *replacement, bool replaceElement = false)
1227 {
1228     substTag(xml, tagname, std::string(replacement));
1229 }
1230
1231 template <class T> void substTag(string &xml, const string &tagname, const T replacement, bool replaceElement = false)
1232 {
1233     stringstream str;
1234     str << replacement;
1235     substTag(xml, tagname, str.str());
1236 }
1237
1238 void EvolutionSyncClient::getConfigXML(string &xml, string &configname)
1239 {
1240     getConfigTemplateXML(xml, configname);
1241
1242     string tag;
1243     size_t index;
1244     unsigned long hash = 0;
1245
1246     tag = "<debug/>";
1247     index = xml.find(tag);
1248     if (index != xml.npos) {
1249         stringstream debug;
1250         bool logging = !m_sourceListPtr->getLogdir().empty();
1251         int loglevel = getLogLevel();
1252
1253         debug <<
1254             "  <debug>\n"
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";
1266         if (logging) {
1267             debug <<
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) {
1273                 debug <<
1274                     "    <enable option=\"all\"/>\n"
1275                     "    <enable option=\"userdata\"/>\n"
1276                     "    <enable option=\"scripts\"/>\n"
1277                     "    <enable option=\"exotic\"/>\n";
1278             }
1279         } else {
1280             debug <<
1281                 "    <sessionlogs>no</sessionlogs>\n"
1282                 "    <globallogs>no</globallogs>\n"
1283                 "    <msgdump>no</msgdump>\n"
1284                 "    <xmltranslate>no</xmltranslate>\n"
1285                 "    <disable option=\"all\"/>";
1286         }
1287         debug <<
1288             "  </debug>\n";
1289
1290         xml.replace(index, tag.size(), debug.str());
1291     }
1292
1293     XMLConfigFragments fragments;
1294     tag = "<datastore/>";
1295     index = xml.find(tag);
1296     if (index != xml.npos) {
1297         stringstream datastores;
1298
1299         BOOST_FOREACH(EvolutionSyncSource *source, *m_sourceListPtr) {
1300             string fragment;
1301             source->getDatastoreXML(fragment, fragments);
1302             hash = Hash(source->getName()) % INT_MAX;
1303
1304             /**
1305              * @TODO handle hash collisions
1306              */
1307             if (!hash) {
1308                 hash = 1;
1309             }
1310             datastores << "    <datastore name='" << source->getName() << "' type='plugin'>\n" <<
1311                 "      <dbtypeid>" << hash << "</dbtypeid>\n" <<
1312                 fragment <<
1313                 "    </datastore>\n\n";
1314         }
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
1318             // really needed.
1319 #if 0
1320             datastores << "<datastore name=\"____dummy____\" type=\"plugin\">"
1321                 "<plugin_module>SyncEvolution</plugin_module>"
1322                 "<fieldmap fieldlist=\"contacts\"/>"
1323                 "<typesupport>"
1324                 "<use datatype=\"vCard30\"/>"
1325                 "</typesupport>"
1326                 "</datastore>";
1327 #endif
1328         }
1329         xml.replace(index, tag.size(), datastores.str());
1330     }
1331
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);
1336
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());
1347
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) {
1352         setConfigDate();
1353         setHashCode(hash);
1354         flush();
1355     }
1356     substTag(xml, "configdate", getConfigDate().c_str());
1357 }
1358
1359 SharedEngine EvolutionSyncClient::createEngine()
1360 {
1361     SharedEngine engine(new sysync::TEngineModuleBridge);
1362
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
1369     // sources).
1370     engine.Connect("[]", 0,
1371                    sysync::DBG_PLUGIN_NONE|
1372                    sysync::DBG_PLUGIN_INT|
1373                    sysync::DBG_PLUGIN_DB|
1374                    sysync::DBG_PLUGIN_EXOT);
1375
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");
1382     configvars.reset();
1383
1384     return engine;
1385 }
1386
1387 namespace {
1388     void GnutlsLogFunction(int level, const char *str)
1389     {
1390         SE_LOG_DEBUG(NULL, "GNUTLS", "level %d: %s", level, str);
1391     }
1392 }
1393
1394 SyncMLStatus EvolutionSyncClient::sync(SyncReport *report)
1395 {
1396     SyncMLStatus status = STATUS_OK;
1397
1398     if (!exists()) {
1399         SE_LOG_ERROR(NULL, NULL, "No configuration for server \"%s\" found.", m_server.c_str());
1400         throwError("cannot proceed without configuration");
1401     }
1402
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;
1409
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));
1416
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");
1419
1420         if (set_log_level && set_log_function) {
1421             set_log_level(atoi(getenv("SYNCEVOLUTION_GNUTLS_DEBUG")));
1422             set_log_function(GnutlsLogFunction);
1423         } else {
1424             SE_LOG_ERROR(NULL, NULL, "SYNCEVOLUTION_GNUTLS_DEBUG debugging not possible, log functions not found");
1425         }
1426     }
1427
1428     try {
1429         SyncReport buffer;
1430         if (!report) {
1431             report = &buffer;
1432         }
1433         report->clear();
1434
1435         // let derived classes override settings, like the log dir
1436         prepare();
1437
1438         // choose log directory
1439         sourceList.startSession(getLogDir(),
1440                                 getMaxLogDirs(),
1441                                 getLogLevel(),
1442                                 report,
1443                                 "client");
1444
1445         // create a Synthesis engine, used purely for logging purposes
1446         // at this time
1447         SwapEngine swapengine(*this);
1448         string xml, configname;
1449         getConfigXML(xml, configname);
1450         m_engine.InitEngineXML(xml.c_str());
1451
1452         try {
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());
1458
1459             // instantiate backends, but do not open them yet
1460             initSources(sourceList);
1461             if (sourceList.empty()) {
1462                 throwError("no sources active, check configuration");
1463             }
1464
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);
1471
1472             // start background thread if not running yet:
1473             // necessary to catch problems with Evolution backend
1474             startLoopThread();
1475
1476             // ask for passwords now
1477             checkPassword(*this);
1478             if (getUseProxy()) {
1479                 checkProxyPassword(*this);
1480             }
1481             BOOST_FOREACH(EvolutionSyncSource *source, sourceList) {
1482                 source->checkPassword(*this);
1483             }
1484
1485             // open each source - failing now is still safe
1486             BOOST_FOREACH(EvolutionSyncSource *source, sourceList) {
1487                 source->open();
1488             }
1489
1490             // give derived class also a chance to update the configs
1491             prepare(sourceList);
1492
1493             // ready to go: dump initial databases and prepare for final report
1494             sourceList.syncPrepare();
1495
1496             // run sync session
1497             status = doSync();
1498         } catch (...) {
1499             // handle the exception here while the engine (and logging!) is still alive
1500             SyncEvolutionException::handle(&status);
1501             goto report;
1502         }
1503     } catch (...) {
1504         SyncEvolutionException::handle(&status);
1505     }
1506
1507  report:
1508     try {
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();
1516                 break;
1517             }
1518         }
1519         sourceList.syncDone(status, report);
1520     } catch(...) {
1521         SyncEvolutionException::handle(&status);
1522     }
1523
1524     m_sourceListPtr = NULL;
1525     return status;
1526 }
1527
1528 SyncMLStatus EvolutionSyncClient::doSync()
1529 {
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;
1536     std::string s;
1537
1538     // re-init engine with all sources configured
1539     string xml, configname;
1540     getConfigXML(xml, configname);
1541     m_engine.InitEngineXML(xml.c_str());
1542
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");
1550     
1551     // open first profile
1552     SharedKey profile;
1553     try {
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);
1558     }
1559          
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 */);
1565
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");
1571     SharedKey target;
1572
1573     try {
1574         target = m_engine.OpenSubkey(targets, sysync::KEYVAL_ID_FIRST);
1575         while (true) {
1576             s = m_engine.GetStrValue(target, "dbname");
1577             EvolutionSyncSource *source = (*m_sourceListPtr)[s];
1578             if (source) {
1579                 m_engine.SetInt32Value(target, "enabled", 1);
1580                 int slow = 0;
1581                 int direction = 0;
1582                 string mode = source->getSync();
1583                 if (!strcasecmp(mode.c_str(), "slow")) {
1584                     slow = 1;
1585                     direction = 0;
1586                 } else if (!strcasecmp(mode.c_str(), "two-way")) {
1587                     slow = 0;
1588                     direction = 0;
1589                 } else if (!strcasecmp(mode.c_str(), "refresh-from-server")) {
1590                     slow = 1;
1591                     direction = 1;
1592                 } else if (!strcasecmp(mode.c_str(), "refresh-from-client")) {
1593                     slow = 1;
1594                     direction = 2;
1595                 } else if (!strcasecmp(mode.c_str(), "one-way-from-server")) {
1596                     slow = 0;
1597                     direction = 1;
1598                 } else if (!strcasecmp(mode.c_str(), "one-way-from-client")) {
1599                     slow = 0;
1600                     direction = 2;
1601                 } else {
1602                     source->throwError(string("invalid sync mode: ") + mode);
1603                 }
1604                 m_engine.SetInt32Value(target, "forceslow", slow);
1605                 m_engine.SetInt32Value(target, "syncmode", direction);
1606
1607                 m_engine.SetStrValue(target, "remotepath", source->getURI());
1608             } else {
1609                 m_engine.SetInt32Value(target, "enabled", 0);
1610             }
1611             target = m_engine.OpenSubkey(targets, sysync::KEYVAL_ID_NEXT);
1612         }
1613     } catch (NoSuchKey error) {
1614     }
1615
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());
1622     }
1623     agent->setUserAgent(getUserAgent());
1624     agent->setSSL(findSSLServerCertificate(),
1625                   getSSLVerifyServer(),
1626                   getSSLVerifyHost());
1627
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.
1631     target.reset();
1632     targets.reset();
1633     profile.reset();
1634     profiles.reset();
1635
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");
1641
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);
1647
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;
1652     int suspending = 0; 
1653     sysync::uInt16 previousStepCmd = stepCmd;
1654     do {
1655         try {
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
1659             // GOTDATA state.
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;
1664             }
1665
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
1670             //the data).
1671             if (checkForAbort() && stepCmd == (sysync::STEPCMD_SENDDATA 
1672                     || stepCmd == sysync::STEPCMD_NEEDDATA)) {
1673                 stepCmd = sysync::STEPCMD_ABORT;
1674             }
1675
1676             // take next step, but don't abort twice: instead
1677             // let engine contine with its shutdown
1678             if (stepCmd == sysync::STEPCMD_ABORT) {
1679                 if (aborting) {
1680                     stepCmd = previousStepCmd;
1681                 } else {
1682                     aborting = true;
1683                 }
1684             }
1685             // same for suspending
1686             if (stepCmd == sysync::STEPCMD_SUSPEND) {
1687                 if (suspending) {
1688                     stepCmd = previousStepCmd;
1689                     suspending++;
1690                 } else {
1691                     suspending++; 
1692                 }
1693             }
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
1697             if(suspending == 1)
1698             {
1699                 stepCmd = previousStepCmd;
1700                 continue;
1701             }
1702             switch (stepCmd) {
1703             case sysync::STEPCMD_OK:
1704                 // no progress info, call step again
1705                 stepCmd = sysync::STEPCMD_STEP;
1706                 break;
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,
1716                                              "displayalert");
1717                     displayServerMessage(s);
1718                 } else {
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);
1726                         break;
1727                     default:
1728                         // specific for a certain sync source:
1729                         // find it...
1730                         target = m_engine.OpenSubkey(targets, progressInfo.targetID);
1731                         s = m_engine.GetStrValue(target, "dbname");
1732                         EvolutionSyncSource *source = (*m_sourceListPtr)[s];
1733                         if (source) {
1734                             displaySourceProgress(sysync::TProgressEventEnum(progressInfo.eventtype),
1735                                                   *source,
1736                                                   progressInfo.extra1,
1737                                                   progressInfo.extra2,
1738                                                   progressInfo.extra3);
1739                         } else {
1740                             throwError(std::string("unknown target ") + s);
1741                         }
1742                         target.reset();
1743                         break;
1744                     }
1745                 }
1746                 stepCmd = sysync::STEPCMD_STEP;
1747                 break;
1748             case sysync::STEPCMD_ERROR:
1749                 // error, terminate (should not happen, as status is
1750                 // already checked above)
1751                 break;
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;
1757                 break;
1758             case sysync::STEPCMD_SENDDATA: {
1759                 // send data to remote
1760
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,
1765                                          "connectURI");
1766                 agent->setURL(s);
1767                 s = m_engine.GetStrValue(sessionKey,
1768                                          "contenttype");
1769                 agent->setContentType(s);
1770                 sessionKey.reset();
1771                     
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
1778                 break;
1779             }
1780             case sysync::STEPCMD_NEEDDATA:
1781                 switch (agent->wait()) {
1782                 case TransportAgent::ACTIVE:
1783                     stepCmd = sysync::STEPCMD_SENTDATA; // still sending the data?!
1784                     break;
1785                 case TransportAgent::GOT_REPLY: {
1786                     sendBuffer.reset();
1787                     const char *reply;
1788                     size_t replylen;
1789                     string contentType;
1790                     agent->getReply(reply, replylen, contentType);
1791
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,
1798                                                    reply,
1799                                                    replylen);
1800                         stepCmd = sysync::STEPCMD_GOTDATA; // we have received response data
1801                     } else {
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;
1806                     }
1807                     break;
1808                 }
1809                 default:
1810                     stepCmd = sysync::STEPCMD_TRANSPFAIL; // communication with server failed
1811                     break;
1812                 }
1813             }
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;
1829             } else {
1830                 SyncEvolutionException::handle(&status);
1831                 stepCmd = sysync::STEPCMD_ABORT;
1832             }
1833         } catch (...) {
1834             SyncEvolutionException::handle(&status);
1835             stepCmd = sysync::STEPCMD_ABORT;
1836         }
1837     } while (stepCmd != sysync::STEPCMD_DONE && stepCmd != sysync::STEPCMD_ERROR);
1838
1839     sigaction (SIGINT, &old_action, NULL);
1840     return status;
1841 }
1842
1843
1844 void EvolutionSyncClient::status()
1845 {
1846     if (!exists()) {
1847         SE_LOG_ERROR(NULL, NULL, "No configuration for server \"%s\" found.", m_server.c_str());
1848         throwError("cannot proceed without configuration");
1849     }
1850
1851     SourceList sourceList(*this, false);
1852     initSources(sourceList);
1853     BOOST_FOREACH(EvolutionSyncSource *source, sourceList) {
1854         source->checkPassword(*this);
1855     }
1856     BOOST_FOREACH(EvolutionSyncSource *source, sourceList) {
1857         source->open();
1858     }
1859
1860     SyncReport changes;
1861     checkSourceChanges(sourceList, changes);
1862
1863     stringstream out;
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",
1870                 out.str().c_str());
1871
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;
1876
1877     if (found) {
1878         try {
1879             sourceList.setPath(prevLogdir);
1880             sourceList.dumpDatabases("current", NULL);
1881             sourceList.dumpLocalChanges(sourceList.getPrevLogdir(), "after", "current");
1882         } catch(...) {
1883             SyncEvolutionException::handle();
1884         }
1885     } else {
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";
1889         }
1890     }
1891 }
1892
1893 void EvolutionSyncClient::checkStatus(SyncReport &report)
1894 {
1895     if (!exists()) {
1896         SE_LOG_ERROR(NULL, NULL, "No configuration for server \"%s\" found.", m_server.c_str());
1897         throwError("cannot proceed without configuration");
1898     }
1899
1900     SourceList sourceList(*this, false);
1901     initSources(sourceList);
1902     BOOST_FOREACH(EvolutionSyncSource *source, sourceList) {
1903         source->checkPassword(*this);
1904     }
1905     BOOST_FOREACH(EvolutionSyncSource *source, sourceList) {
1906         source->open();
1907     }
1908
1909     checkSourceChanges(sourceList, report);
1910 }
1911
1912 static void logRestoreReport(const SyncReport &report, bool dryrun)
1913 {
1914     if (!report.empty()) {
1915         stringstream out;
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",
1919                     out.str().c_str());
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.");
1922     }
1923 }
1924
1925 void EvolutionSyncClient::checkSourceChanges(SourceList &sourceList, SyncReport &changes)
1926 {
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);
1948         }
1949     }
1950     changes.setEnd(time(NULL));
1951 }
1952
1953 void EvolutionSyncClient::restore(const string &dirname, RestoreDatabase database)
1954 {
1955     if (!exists()) {
1956         SE_LOG_ERROR(NULL, NULL, "No configuration for server \"%s\" found.", m_server.c_str());
1957         throwError("cannot proceed without configuration");
1958     }
1959
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);
1966     }
1967
1968     string datadump = database == DATABASE_BEFORE_SYNC ? "before" : "after";
1969
1970     BOOST_FOREACH(EvolutionSyncSource *source, sourceList) {
1971         source->open();
1972     }
1973
1974     if (!m_quiet) {
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'");
1982     }
1983
1984     SyncReport report;
1985     try {
1986         BOOST_FOREACH(EvolutionSyncSource *source, sourceList) {
1987             SyncSourceReport sourcereport;
1988             try {
1989                 SE_LOG_DEBUG(NULL, NULL, "Restoring %s...", source->getName());
1990                 sourceList.restoreDatabase(*source,
1991                                            datadump,
1992                                            m_dryrun,
1993                                            sourcereport);
1994                 SE_LOG_DEBUG(NULL, NULL, "... %s restored.", source->getName());
1995                 report.addSyncSourceReport(source->getName(), sourcereport);
1996             } catch (...) {
1997                 sourcereport.recordStatus(STATUS_FATAL);
1998                 report.addSyncSourceReport(source->getName(), sourcereport);
1999                 throw;
2000             }
2001         }
2002     } catch (...) {
2003         logRestoreReport(report, m_dryrun);
2004         throw;
2005     }
2006     logRestoreReport(report, m_dryrun);
2007 }
2008
2009 void EvolutionSyncClient::getSessions(vector<string> &dirs)
2010 {
2011     LogDir logging(*this);
2012     logging.previousLogdirs(getLogDir(), dirs);
2013 }
2014
2015 void EvolutionSyncClient::readSessionInfo(const string &dir, SyncReport &report)
2016 {
2017     LogDir logging(*this);
2018     logging.openLogdir(dir);
2019     logging.readReport(report);
2020 }
2021
2022 std::string EvolutionSyncClient::findSSLServerCertificate()
2023 {
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)) {
2029             return file;
2030         }
2031     }
2032
2033     return "";
2034 }