Imported Upstream version 0.8~alpha1
[platform/upstream/syncevolution.git] / src / EvolutionSyncClient.cpp
1 /*
2  * Copyright (C) 2005-2006 Patrick Ohly
3  * Copyright (C) 2007 Funambol
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program 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
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  */
19
20 #include "EvolutionSyncClient.h"
21 #include "EvolutionSyncSource.h"
22 #include "SyncEvolutionUtil.h"
23
24 #include <posix/base/posixlog.h>
25
26 #include <list>
27 #include <memory>
28 #include <vector>
29 #include <sstream>
30 #include <fstream>
31 #include <iomanip>
32 #include <iostream>
33 #include <stdexcept>
34 using namespace std;
35
36 #include <boost/algorithm/string/predicate.hpp>
37 #include <boost/algorithm/string/replace.hpp>
38 #include <boost/foreach.hpp>
39
40 #include <sys/stat.h>
41 #include <pwd.h>
42 #include <unistd.h>
43 #include <dirent.h>
44 #include <errno.h>
45
46 SourceList *EvolutionSyncClient::m_sourceListPtr;
47
48 EvolutionSyncClient::EvolutionSyncClient(const string &server,
49                                          bool doLogging,
50                                          const set<string> &sources) :
51     EvolutionSyncConfig(server),
52     m_server(server),
53     m_sources(sources),
54     m_doLogging(doLogging),
55     m_syncMode(SYNC_NONE),
56     m_quiet(false)
57 {
58 }
59
60 EvolutionSyncClient::~EvolutionSyncClient()
61 {
62 }
63
64
65 // this class owns the logging directory and is responsible
66 // for redirecting output at the start and end of sync (even
67 // in case of exceptions thrown!)
68 class LogDir {
69     string m_logdir;         /**< configured backup root dir, empty if none */
70     int m_maxlogdirs;        /**< number of backup dirs to preserve, 0 if unlimited */
71     string m_prefix;         /**< common prefix of backup dirs */
72     string m_path;           /**< path to current logging and backup dir */
73     string m_logfile;        /**< path to log file there, empty if not writing one */
74     const string &m_server;  /**< name of the server for this synchronization */
75     LogLevel m_oldLogLevel;  /**< logging level to restore */
76     bool m_restoreLog;       /**< false if nothing needs to be restored because setLogdir() was never called */
77
78 public:
79     LogDir(const string &server) : m_server(server),
80                                    m_restoreLog(false)
81     {
82         // SyncEvolution-<server>-<yyyy>-<mm>-<dd>-<hh>-<mm>
83         m_prefix = "SyncEvolution-";
84         m_prefix += m_server;
85
86         // default: $TMPDIR/SyncEvolution-<username>-<server>
87         stringstream path;
88         char *tmp = getenv("TMPDIR");
89         if (tmp) {
90             path << tmp;
91         } else {
92             path << "/tmp";
93         }
94         path << "/SyncEvolution-";
95         struct passwd *user = getpwuid(getuid());
96         if (user && user->pw_name) {
97             path << user->pw_name;
98         } else {
99             path << getuid();
100         }
101         path << "-" << m_server;
102
103         m_path = path.str();
104     }
105
106     /**
107      * Finds previous log directory. Must be called before setLogdir().
108      *
109      * @param path        path to configured backup directy, NULL if defaulting to /tmp, "none" if not creating log file
110      * @return full path of previous log directory, empty string if not found
111      */
112     string previousLogdir(const char *path) {
113         string logdir;
114
115         if (path && !strcasecmp(path, "none")) {
116             return "";
117         } else if (path && path[0]) {
118             vector<string> entries;
119             try {
120                 getLogdirs(path, entries);
121             } catch(const std::exception &ex) {
122                 LOG.error("%s", ex.what());
123                 return "";
124             }
125
126             logdir = entries.size() ? string(path) + "/" + entries[entries.size()-1] : "";
127         } else {
128             logdir = m_path;
129         }
130
131         if (access(logdir.c_str(), R_OK|X_OK) == 0) {
132             return logdir;
133         } else {
134             return "";
135         }
136     }
137
138     // setup log directory and redirect logging into it
139     // @param path        path to configured backup directy, NULL if defaulting to /tmp, "none" if not creating log file
140     // @param maxlogdirs  number of backup dirs to preserve in path, 0 if unlimited
141     // @param logLevel    0 = default, 1 = ERROR, 2 = INFO, 3 = DEBUG
142     void setLogdir(const char *path, int maxlogdirs, int logLevel = 0) {
143         m_maxlogdirs = maxlogdirs;
144         if (path && !strcasecmp(path, "none")) {
145             m_logfile = "";
146         } else if (path && path[0]) {
147             m_logdir = path;
148
149             // create unique directory name in the given directory
150             time_t ts = time(NULL);
151             struct tm *tm = localtime(&ts);
152             stringstream base;
153             base << path << "/"
154                  << m_prefix
155                  << "-"
156                  << setfill('0')
157                  << setw(4) << tm->tm_year + 1900 << "-"
158                  << setw(2) << tm->tm_mon + 1 << "-"
159                  << setw(2) << tm->tm_mday << "-"
160                  << setw(2) << tm->tm_hour << "-"
161                  << setw(2) << tm->tm_min;
162             int seq = 0;
163             while (true) {
164                 stringstream path;
165                 path << base.str();
166                 if (seq) {
167                     path << "-" << seq;
168                 }
169                 m_path = path.str();
170                 if (!mkdir(m_path.c_str(), S_IRWXU)) {
171                     break;
172                 }
173                 if (errno != EEXIST) {
174                     LOG.debug("%s: %s", m_path.c_str(), strerror(errno));
175                     EvolutionSyncClient::throwError(m_path + ": " + strerror(errno));
176                 }
177                 seq++;
178             }
179             m_logfile = m_path + "/client.log";
180         } else {
181             // use the default temp directory
182             if (mkdir(m_path.c_str(), S_IRWXU)) {
183                 if (errno != EEXIST) {
184                     EvolutionSyncClient::throwError(m_path + ": " + strerror(errno));
185                 }
186             }
187             m_logfile = m_path + "/client.log";
188         }
189
190         if (m_logfile.size()) {
191             // redirect logging into that directory, including stderr,
192             // after truncating it
193             FILE *file = fopen(m_logfile.c_str(), "w");
194             if (file) {
195                 fclose(file);
196 #ifdef POSIX_LOG
197                 POSIX_LOG.
198 #endif
199                     setLogFile(NULL, m_logfile.c_str(), true);
200             } else {
201                 LOG.error("creating log file %s failed", m_logfile.c_str());
202             }
203         }
204         m_oldLogLevel = LOG.getLevel();
205         LOG.setLevel(logLevel > 0 ? (LogLevel)(logLevel - 1) /* fixed level */ :
206                      m_logfile.size() ? LOG_LEVEL_DEBUG /* default for log file */ :
207                      LOG_LEVEL_INFO /* default for console output */ );
208         m_restoreLog = true;
209     }
210
211     /** sets a fixed directory for database files without redirecting logging */
212     void setPath(const string &path) { m_path = path; }
213
214     // return log directory, empty if not enabled
215     const string &getLogdir() {
216         return m_path;
217     }
218
219     // return log file, empty if not enabled
220     const string &getLogfile() {
221         return m_logfile;
222     }
223
224     /** find all entries in a given directory, return as sorted array */
225     void getLogdirs(const string &logdir, vector<string> &entries) {
226         ReadDir dir(logdir);
227         for (ReadDir::const_iterator it = dir.begin();
228              it != dir.end();
229              ++it) {
230             if (boost::starts_with(*it, m_prefix)) {
231                 entries.push_back(*it);
232             }
233         }
234         sort(entries.begin(), entries.end());
235     }
236
237
238     // remove oldest backup dirs if exceeding limit
239     void expire() {
240         if (m_logdir.size() && m_maxlogdirs > 0 ) {
241             vector<string> entries;
242             getLogdirs(m_logdir, entries);
243
244             int deleted = 0;
245             for (vector<string>::iterator it = entries.begin();
246                  it != entries.end() && (int)entries.size() - deleted > m_maxlogdirs;
247                  ++it, ++deleted) {
248                 string path = m_logdir + "/" + *it;
249                 string msg = "removing " + path;
250                 LOG.info(msg.c_str());
251                 rm_r(path);
252             }
253         }
254     }
255
256     // remove redirection of stderr and (optionally) also of logging
257     void restore(bool all) {
258         if (!m_restoreLog) {
259             return;
260         }
261           
262         if (all) {
263             if (m_logfile.size()) {
264 #ifdef POSIX_LOG
265                 POSIX_LOG.
266 #endif
267                     setLogFile(NULL, "-", false);
268             }
269             LOG.setLevel(m_oldLogLevel);
270         } else {
271             if (m_logfile.size()) {
272 #ifdef POSIX_LOG
273                 POSIX_LOG.
274 #endif
275                     setLogFile(NULL, m_logfile.c_str(), false);
276             }
277         }
278     }
279
280     ~LogDir() {
281         restore(true);
282     }
283 };
284
285 // this class owns the sync sources and (together with
286 // a logdir) handles writing of per-sync files as well
287 // as the final report (
288 class SourceList : public vector<EvolutionSyncSource *> {
289     LogDir m_logdir;     /**< our logging directory */
290     bool m_prepared;     /**< remember whether syncPrepare() dumped databases successfully */
291     bool m_doLogging;    /**< true iff additional files are to be written during sync */
292     SyncClient &m_client; /**< client which holds the sync report after a sync */
293     bool m_reportTodo;   /**< true if syncDone() shall print a final report */
294     boost::scoped_array<SyncSource *> m_sourceArray;  /** owns the array that is expected by SyncClient::sync() */
295     const bool m_quiet;  /**< avoid redundant printing to screen */
296     string m_previousLogdir; /**< remember previous log dir before creating the new one */
297
298     /** create name in current (if set) or previous logdir */
299     string databaseName(EvolutionSyncSource &source, const string suffix, string logdir = "") {
300         if (!logdir.size()) {
301             logdir = m_logdir.getLogdir();
302         }
303         return logdir + "/" +
304             source.getName() + "." + suffix + "." +
305             source.fileSuffix();
306     }
307
308 public:
309     /**
310      * dump into files with a certain suffix
311      */
312     void dumpDatabases(const string &suffix) {
313         ofstream out;
314 #ifndef IPHONE
315         // output stream on iPhone raises exception even though it is in a good state;
316         // perhaps the missing C++ exception support is the reason:
317         // http://code.google.com/p/iphone-dev/issues/detail?id=48
318         out.exceptions(ios_base::badbit|ios_base::failbit|ios_base::eofbit);
319 #endif
320
321         for( iterator it = begin();
322              it != end();
323              ++it ) {
324             string file = databaseName(**it, suffix);
325             LOG.debug("creating %s", file.c_str());
326             out.open(file.c_str());
327             (*it)->exportData(out);
328             out.close();
329             LOG.debug("%s created", file.c_str());
330         }
331     }
332
333     /** remove database dumps with a specific suffix */
334     void removeDatabases(const string &removeSuffix) {
335         for( iterator it = begin();
336              it != end();
337              ++it ) {
338             string file;
339
340             file = databaseName(**it, removeSuffix);
341             unlink(file.c_str());
342         }
343     }
344         
345     SourceList(const string &server, bool doLogging, SyncClient &client, bool quiet) :
346         m_logdir(server),
347         m_prepared(false),
348         m_doLogging(doLogging),
349         m_client(client),
350         m_reportTodo(true),
351         m_quiet(quiet)
352     {
353     }
354     
355     // call as soon as logdir settings are known
356     void setLogdir(const char *logDirPath, int maxlogdirs, int logLevel) {
357         m_previousLogdir = m_logdir.previousLogdir(logDirPath);
358         if (m_doLogging) {
359             m_logdir.setLogdir(logDirPath, maxlogdirs, logLevel);
360         } else {
361             // at least increase log level
362             LOG.setLevel(LOG_LEVEL_DEBUG);
363         }
364     }
365
366     /** return previous log dir found in setLogdir() */
367     const string &getPrevLogdir() const { return m_previousLogdir; }
368
369     /** set directory for database files without actually redirecting the logging */
370     void setPath(const string &path) { m_logdir.setPath(path); }
371
372     /**
373      * If possible (m_previousLogdir found) and enabled (!m_quiet),
374      * then dump changes applied locally.
375      *
376      * @param oldSuffix      suffix of old database dump: usually "after"
377      * @param currentSuffix  the current database dump suffix: "current"
378      *                       when not doing a sync, otherwise "before"
379      */
380     bool dumpLocalChanges(const string &oldSuffix, const string &newSuffix) {
381         if (m_quiet || !m_previousLogdir.size()) {
382             return false;
383         }
384
385         cout << "Local changes to be applied to server during synchronization:\n";
386         for( iterator it = begin();
387              it != end();
388              ++it ) {
389             string oldFile = databaseName(**it, oldSuffix, m_previousLogdir);
390             string newFile = databaseName(**it, newSuffix);
391             cout << "*** " << (*it)->getName() << " ***\n" << flush;
392             string cmd = string("env CLIENT_TEST_COMPARISON_FAILED=10 CLIENT_TEST_LEFT_NAME='after last sync' CLIENT_TEST_RIGHT_NAME='current data' CLIENT_TEST_REMOVED='removed since last sync' CLIENT_TEST_ADDED='added since last sync' synccompare 2>/dev/null '" ) +
393                 oldFile + "' '" + newFile + "'";
394             int ret = system(cmd.c_str());
395             switch (ret == -1 ? ret : WEXITSTATUS(ret)) {
396             case 0:
397                 cout << "no changes\n";
398                 break;
399             case 10:
400                 break;
401             default:
402                 cout << "Comparison was impossible.\n";
403                 break;
404             }
405         }
406         cout << "\n";
407         return true;
408     }
409
410     // call when all sync sources are ready to dump
411     // pre-sync databases
412     void syncPrepare() {
413         if (m_logdir.getLogfile().size() &&
414             m_doLogging) {
415             // dump initial databases
416             dumpDatabases("before");
417             // compare against the old "after" database dump
418             dumpLocalChanges("after", "before");
419             // now remove the old database dump
420             removeDatabases("after");
421             m_prepared = true;
422         }
423     }
424
425     // call at the end of a sync with success == true
426     // if all went well to print report
427     void syncDone(bool success) {
428         if (m_doLogging) {
429             // ensure that stderr is seen again
430             m_logdir.restore(false);
431
432             if (m_reportTodo) {
433                 // haven't looked at result of sync yet;
434                 // don't do it again
435                 m_reportTodo = false;
436
437                 // dump datatbase after sync, but not if already dumping it at the beginning didn't complete
438                 if (m_prepared) {
439                     try {
440                         dumpDatabases("after");
441                     } catch (const std::exception &ex) {
442                         LOG.error( "%s", ex.what() );
443                         m_prepared = false;
444                     }
445                 }
446
447                 string logfile = m_logdir.getLogfile();
448 #ifndef LOG_HAVE_SET_LOGGER
449                 // scan for error messages
450                 if (!m_quiet && logfile.size()) {
451                     ifstream in;
452                     in.open(m_logdir.getLogfile().c_str());
453                     while (in.good()) {
454                         string line;
455                         getline(in, line);
456                         if (line.find("[ERROR]") != line.npos) {
457                             success = false;
458                            cout << line << "\n";
459                         } else if (line.find("[INFO]") != line.npos) {
460                             cout << line << "\n";
461                         }
462                     }
463                     in.close();
464                 }
465 #endif
466
467                 cout << flush;
468                 cerr << flush;
469                 cout << "\n";
470                 if (success) {
471                     cout << "Synchronization successful.\n";
472                 } else if (logfile.size()) {
473                     cout << "Synchronization failed, see "
474                          << logfile
475                          << " for details.\n";
476                 } else {
477                     cout << "Synchronization failed.\n";
478                 }
479
480                 // pretty-print report
481                 if (!m_quiet) {
482                     cout << "\nChanges applied during synchronization:\n";
483                 }
484                 SyncReport *report = m_client.getSyncReport();
485                 if (!m_quiet && report) {
486
487                     cout << "+-------------------|-------ON CLIENT-------|-------ON SERVER-------|\n";
488                     cout << "|                   |   successful / total  |   successful / total  |\n";
489                     cout << "|            Source |  NEW  |  MOD  |  DEL  |  NEW  |  MOD  |  DEL  |\n";
490                     const char *sep = 
491                         "+-------------------+-------+-------+-------+-------+-------+-------+\n";
492                     cout << sep;
493
494                     for (unsigned int i = 0; report->getSyncSourceReport(i); i++) {
495                         SyncSourceReport* ssr = report->getSyncSourceReport(i);
496
497                         if (ssr->getState() == SOURCE_INACTIVE) {
498                             continue;
499                         }
500                         
501                         cout << "|" << right << setw(18) << ssr->getSourceName() << " |";
502                         static const char * const targets[] =
503                             { CLIENT, SERVER, NULL };
504                         for (int target = 0;
505                              targets[target];
506                              target++) {
507                             static const char * const commands[] =
508                                 { COMMAND_ADD, COMMAND_REPLACE, COMMAND_DELETE, NULL };
509                             for (int command = 0;
510                                  commands[command];
511                                  command++) {
512                                 cout << right << setw(3) <<
513                                     ssr->getItemReportSuccessfulCount(targets[target], commands[command]);
514                                 cout << "/";
515                                 cout << left << setw(3) <<
516                                     ssr->getItemReportCount(targets[target], commands[command]);
517                                 cout << "|";
518                             }
519                         }
520                         cout << "\n";
521                     }
522                     cout << sep;
523                 }
524
525                 // compare databases?
526                 if (!m_quiet && m_prepared) {
527                     cout << "\nChanges applied to client during synchronization:\n";
528                     for( iterator it = begin();
529                          it != end();
530                          ++it ) {
531                         cout << "*** " << (*it)->getName() << " ***\n" << flush;
532
533                         string before = databaseName(**it, "before");
534                         string after = databaseName(**it, "after");
535                         string cmd = string("synccompare '" ) +
536                             before + "' '" + after +
537                             "' && echo 'no changes'";
538                         system(cmd.c_str());
539                     }
540                     cout << "\n";
541                 }
542
543                 if (success) {
544                     m_logdir.expire();
545                 }
546             }
547         }
548     }
549
550     /** returns current sources as array as expected by SyncClient::sync(), memory owned by this class */
551     SyncSource **getSourceArray() {
552         m_sourceArray.reset(new SyncSource *[size() + 1]);
553
554         int index = 0;
555         for (iterator it = begin();
556              it != end();
557              ++it) {
558             m_sourceArray[index] = *it;
559             index++;
560         }
561         m_sourceArray[index] = 0;
562         return &m_sourceArray[0];
563     }
564
565     /** returns names of active sources */
566     set<string> getSources() {
567         set<string> res;
568
569         BOOST_FOREACH(SyncSource *source, *this) {
570             res.insert(source->getName());
571         }
572         return res;
573     }
574    
575     ~SourceList() {
576         // free sync sources
577         for( iterator it = begin();
578              it != end();
579              ++it ) {
580             delete *it;
581         }
582     }
583
584     /** find sync source by name */
585     EvolutionSyncSource *operator [] (const string &name) {
586         for (iterator it = begin();
587              it != end();
588              ++it) {
589             if (name == (*it)->getName()) {
590                 return *it;
591             }
592         }
593         return NULL;
594     }
595
596     /** find by index */
597     EvolutionSyncSource *operator [] (int index) { return vector<EvolutionSyncSource *>::operator [] (index); }
598 };
599
600 void unref(SourceList *sourceList)
601 {
602     delete sourceList;
603 }
604
605 string EvolutionSyncClient::askPassword(const string &descr)
606 {
607     char buffer[256];
608
609     printf("Enter password for %s: ",
610            descr.c_str());
611     fflush(stdout);
612     if (fgets(buffer, sizeof(buffer), stdin) &&
613         strcmp(buffer, "\n")) {
614         size_t len = strlen(buffer);
615         if (len && buffer[len - 1] == '\n') {
616             buffer[len - 1] = 0;
617         }
618         return buffer;
619     } else {
620         throwError(string("could not read password for ") + descr);
621         return "";
622     }
623 }
624
625 void EvolutionSyncClient::throwError(const string &error)
626 {
627 #ifdef IPHONE
628     /*
629      * Catching the runtime_exception fails due to a toolchain problem,
630      * so do the error handling now and abort: because there is just
631      * one sync source this is probably the only thing that can be done.
632      * Still, it's not nice on the server...
633      */
634     fatalError(NULL, error.c_str());
635 #else
636     throw runtime_error(error);
637 #endif
638 }
639
640 void EvolutionSyncClient::fatalError(void *object, const char *error)
641 {
642     LOG.error("%s", error);
643     if (m_sourceListPtr) {
644         m_sourceListPtr->syncDone(false);
645     }
646     exit(1);
647 }
648
649 /*
650  * There have been segfaults inside glib in the background
651  * thread which ran the second event loop. Disabled it again,
652  * even though the synchronous EDS API calls will block then
653  * when EDS dies.
654  */
655 #if 0 && defined(HAVE_GLIB) && defined(HAVE_EDS)
656 # define RUN_GLIB_LOOP
657 #endif
658
659 #ifdef RUN_GLIB_LOOP
660 #include <pthread.h>
661 #include <signal.h>
662 static void *mainLoopThread(void *)
663 {
664     // The test framework uses SIGALRM for timeouts.
665     // Block the signal here because a) the signal handler
666     // prints a stack back trace when called and we are not
667     // interessted in the background thread's stack and b)
668     // it seems to have confused glib/libebook enough to
669     // access invalid memory and segfault when it gets the SIGALRM.
670     sigset_t blocked;
671     sigemptyset(&blocked);
672     sigaddset(&blocked, SIGALRM);
673     pthread_sigmask(SIG_BLOCK, &blocked, NULL);
674
675     GMainLoop *mainloop = g_main_loop_new(NULL, TRUE);
676     if (mainloop) {
677         g_main_loop_run(mainloop);
678         g_main_loop_unref(mainloop);
679     }
680     return NULL;
681 }
682 #endif
683
684 void EvolutionSyncClient::startLoopThread()
685 {
686 #ifdef RUN_GLIB_LOOP
687     // when using Evolution we must have a running main loop,
688     // otherwise loss of connection won't be reported to us
689     static pthread_t loopthread;
690     static bool loopthreadrunning;
691     if (!loopthreadrunning) {
692         loopthreadrunning = !pthread_create(&loopthread, NULL, mainLoopThread, NULL);
693     }
694 #endif
695 }
696
697 AbstractSyncSourceConfig* EvolutionSyncClient::getAbstractSyncSourceConfig(const char* name) const
698 {
699     return m_sourceListPtr ? (*m_sourceListPtr)[name] : NULL;
700 }
701
702 AbstractSyncSourceConfig* EvolutionSyncClient::getAbstractSyncSourceConfig(unsigned int i) const
703 {
704     return m_sourceListPtr ? (*m_sourceListPtr)[i] : NULL;
705 }
706
707 unsigned int EvolutionSyncClient::getAbstractSyncSourceConfigsCount() const
708 {
709     return m_sourceListPtr ? m_sourceListPtr->size() : 0;
710 }
711
712
713 void EvolutionSyncClient::initSources(SourceList &sourceList)
714 {
715     set<string> unmatchedSources = m_sources;
716     list<string> configuredSources = getSyncSources();
717     for (list<string>::const_iterator it = configuredSources.begin();
718          it != configuredSources.end();
719          it++) {
720         const string &name(*it);
721         boost::shared_ptr<PersistentEvolutionSyncSourceConfig> sc(getSyncSourceConfig(name));
722         
723         // is the source enabled?
724         string sync = sc->getSync();
725         bool enabled = sync != "disabled";
726         bool overrideMode = false;
727
728         // override state?
729         if (m_sources.size()) {
730             if (m_sources.find(sc->getName()) != m_sources.end()) {
731                 if (!enabled) {
732                     overrideMode = true;
733                     enabled = true;
734                 }
735                 unmatchedSources.erase(sc->getName());
736             } else {
737                 enabled = false;
738             }
739         }
740         
741         if (enabled) {
742             string url = getSyncURL();
743             boost::replace_first(url, "https://", "http://"); // do not distinguish between protocol in change tracking
744             string changeId = string("sync4jevolution:") + url + "/" + name;
745             EvolutionSyncSourceParams params(name,
746                                              getSyncSourceNodes(name),
747                                              changeId);
748             // the sync mode has to be set before instantiating the source
749             // because the client library reads the preferredSyncMode at that time:
750             // have to take a shortcut and set the property via its name
751             if (overrideMode) {
752                 params.m_nodes.m_configNode->addFilter("sync", "two-way");
753             }
754             EvolutionSyncSource *syncSource =
755                 EvolutionSyncSource::createSource(params);
756             if (!syncSource) {
757                 throwError(name + ": type unknown" );
758             }
759             sourceList.push_back(syncSource);
760         }
761     }
762
763     // check whether there were any sources specified which do not exist
764     if (unmatchedSources.size()) {
765         string sources;
766
767         for (set<string>::const_iterator it = unmatchedSources.begin();
768              it != unmatchedSources.end();
769              it++) {
770             if (sources.size()) {
771                 sources += " ";
772             }
773             sources += *it;
774         }
775         throwError(string("no such source(s): ") + sources);
776     }
777 }
778
779 int EvolutionSyncClient::sync()
780 {
781     int res = 1;
782     
783     if (!exists()) {
784         LOG.error("No configuration for server \"%s\" found.", m_server.c_str());
785         throwError("cannot proceed without configuration");
786     }
787
788     // redirect logging as soon as possible
789     SourceList sourceList(m_server, m_doLogging, *this, m_quiet);
790     m_sourceListPtr = &sourceList;
791
792     try {
793         sourceList.setLogdir(getLogDir(),
794                              getMaxLogDirs(),
795                              getLogLevel());
796
797         // dump some summary information at the beginning of the log
798 #ifdef LOG_HAVE_DEVELOPER
799 # define LOG_DEVELOPER developer
800 #else
801 # define LOG_DEVELOPER debug
802 #endif
803         LOG.LOG_DEVELOPER("SyncML server account: %s", getUsername());
804         LOG.LOG_DEVELOPER("client: SyncEvolution %s for %s",
805                           getSwv(), getDevType());
806
807         // instantiate backends, but do not open them yet
808         initSources(sourceList);
809
810         // request all config properties once: throwing exceptions
811         // now is okay, whereas later it would lead to leaks in the
812         // not exception safe client library
813         EvolutionSyncConfig dummy;
814         set<string> activeSources = sourceList.getSources();
815         dummy.copy(*this, &activeSources);
816
817         // start background thread if not running yet:
818         // necessary to catch problems with Evolution backend
819         startLoopThread();
820
821         // ask for passwords now
822         checkPassword(*this);
823         if (getUseProxy()) {
824             checkProxyPassword(*this);
825         }
826         BOOST_FOREACH(EvolutionSyncSource *source, sourceList) {
827             source->checkPassword(*this);
828         }
829
830         // open each source - failing now is still safe
831         BOOST_FOREACH(EvolutionSyncSource *source, sourceList) {
832             source->open();
833         }
834
835         // give derived class also a chance to update the configs
836         prepare(sourceList.getSourceArray());
837
838         // ready to go: dump initial databases and prepare for final report
839         sourceList.syncPrepare();
840
841         // do it
842         res = SyncClient::sync(*this, sourceList.getSourceArray());
843
844         // store modified properties: must be done even after failed
845         // sync because the source's anchor might have been reset
846         flush();
847
848         if (res) {
849             if (getLastErrorCode() && getLastErrorMsg() && getLastErrorMsg()[0]) {
850                 throwError(getLastErrorMsg());
851             }
852             // no error code/description?!
853             throwError("sync failed without an error description, check log");
854         }
855
856         // all went well: print final report before cleaning up
857         sourceList.syncDone(true);
858
859         res = 0;
860     } catch (const std::exception &ex) {
861         LOG.error( "%s", ex.what() );
862
863         // something went wrong, but try to write .after state anyway
864         m_sourceListPtr = NULL;
865         sourceList.syncDone(false);
866     } catch (...) {
867         LOG.error( "unknown error" );
868         m_sourceListPtr = NULL;
869         sourceList.syncDone(false);
870     }
871
872     m_sourceListPtr = NULL;
873     return res;
874 }
875
876 void EvolutionSyncClient::prepare(SyncSource **sources) {
877     if (m_syncMode != SYNC_NONE) {
878         for (SyncSource **source = sources;
879              *source;
880              source++) {
881             (*source)->setPreferredSyncMode(m_syncMode);
882         }
883     }
884 }
885
886 void EvolutionSyncClient::status()
887 {
888     EvolutionSyncConfig config(m_server);
889     if (!exists()) {
890         LOG.error("No configuration for server \"%s\" found.", m_server.c_str());
891         throwError("cannot proceed without configuration");
892     }
893
894     SourceList sourceList(m_server, false, *this, false);
895     initSources(sourceList);
896     BOOST_FOREACH(EvolutionSyncSource *source, sourceList) {
897         source->checkPassword(*this);
898     }
899     BOOST_FOREACH(EvolutionSyncSource *source, sourceList) {
900         source->open();
901     }
902
903     sourceList.setLogdir(getLogDir(), 0, LOG_LEVEL_NONE);
904     LOG.setLevel(LOG_LEVEL_INFO);
905     string prevLogdir = sourceList.getPrevLogdir();
906     bool found = access(prevLogdir.c_str(), R_OK|X_OK) == 0;
907
908     if (found) {
909         try {
910             sourceList.setPath(prevLogdir);
911             sourceList.dumpDatabases("current");
912             sourceList.dumpLocalChanges("after", "current");
913         } catch(const std::exception &ex) {
914             LOG.error("%s", ex.what());
915         }
916     } else {
917         cerr << "Previous log directory not found.\n";
918         if (!getLogDir() || !getLogDir()[0]) {
919             cerr << "Enable the 'logdir' option and synchronize to use this feature.\n";
920         }
921     }
922 }