Imported Upstream version 1.4.1
[platform/upstream/syncevolution.git] / src / syncevo / SyncSource.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 <syncevo/SyncSource.h>
27 #include <syncevo/SyncContext.h>
28 #include <syncevo/util.h>
29
30 #include <syncevo/SynthesisEngine.h>
31 #include <synthesis/SDK_util.h>
32 #include <synthesis/sync_dbapidef.h>
33
34 #include <boost/bind.hpp>
35 #include <boost/algorithm/string/join.hpp>
36 #include <boost/algorithm/string/split.hpp>
37 #include <boost/algorithm/string/classification.hpp>
38 #include <boost/algorithm/string/predicate.hpp>
39 #include <boost/lambda/lambda.hpp>
40
41 #include <ctype.h>
42 #include <errno.h>
43 #include <unistd.h>
44
45 #include <fstream>
46 #include <iostream>
47
48 #ifdef ENABLE_UNIT_TESTS
49 #include "test.h"
50 #endif
51
52 #include <syncevo/declarations.h>
53 SE_BEGIN_CXX
54
55 void SyncSourceBase::throwError(const string &action, int error)
56 {
57     std::string what = action + ": " + strerror(error);
58     // be as specific if we can be: relevant for the file backend,
59     // which is expected to return STATUS_NOT_FOUND == 404 for "file
60     // not found"
61     if (error == ENOENT) {
62         throwError(STATUS_NOT_FOUND, what);
63     } else {
64         throwError(what);
65     }
66 }
67
68 void SyncSourceBase::throwError(const string &failure)
69 {
70     SyncContext::throwError(string(getDisplayName()) + ": " + failure);
71 }
72
73 void SyncSourceBase::throwError(SyncMLStatus status, const string &failure)
74 {
75     SyncContext::throwError(status, getDisplayName() + ": " + failure);
76 }
77
78 SyncMLStatus SyncSourceBase::handleException(HandleExceptionFlags flags)
79 {
80     SyncMLStatus res = Exception::handle(getDisplayName(), flags);
81     return res == STATUS_FATAL ?
82         STATUS_DATASTORE_FAILURE :
83         res;
84 }
85
86 void SyncSourceBase::getDatastoreXML(string &xml, XMLConfigFragments &fragments)
87 {
88     stringstream xmlstream;
89     SynthesisInfo info;
90
91     getSynthesisInfo(info, fragments);
92
93     xmlstream <<
94         "      <plugin_module>SyncEvolution</plugin_module>\n";
95     if (info.m_earlyStartDataRead) {
96         xmlstream <<
97             "      <plugin_earlystartdataread>yes</plugin_earlystartdataread>\n";
98     }
99     if (info.m_readOnly) {
100          xmlstream <<
101              "      <!-- if this is set to 'yes', SyncML clients can only read\n"
102              "           from the database, but make no modifications -->\n"
103              "      <readonly>yes</readonly>\n";
104     }
105     xmlstream <<
106         "      <plugin_datastoreadmin>" <<
107         (serverModeEnabled() ? "yes" : "no") <<
108         "</plugin_datastoreadmin>\n"
109         "      <fromremoteonlysupport> yes </fromremoteonlysupport>\n"
110         "      <canrestart>yes</canrestart>\n";
111     if (info.m_globalIDs) {
112         xmlstream << 
113             "      <syncmode>1122583000</syncmode>";
114     }
115     xmlstream <<
116         "\n"
117         "      <!-- conflict strategy: Newer item wins\n"
118         "           You can set 'server-wins' or 'client-wins' as well\n"
119         "           if you want to give one side precedence\n"
120         "      -->\n"
121         "      <conflictstrategy>newer-wins</conflictstrategy>\n"
122         "\n"
123         "      <!-- on slowsync: do not duplicate items even if not fully equal\n"
124         "           You can set this to 'duplicate' to avoid possible data loss\n"
125         "           resulting from merging\n"
126         "      -->\n"
127         "      <slowsyncstrategy>newer-wins</slowsyncstrategy>\n"
128         "\n"
129         "      <!-- text db plugin is designed for UTF-8, make sure data is passed as UTF-8 (and not the ISO-8859-1 default) -->\n"
130         "      <datacharset>UTF-8</datacharset>\n"
131         "      <!-- use C-language (unix style) linefeeds (\n, 0x0A) -->\n"
132         "      <datalineends>unix</datalineends>\n"
133         "\n"
134         "      <!-- set this to 'UTC' if time values should be stored in UTC into the database\n"
135         "           rather than local time. 'SYSTEM' denotes local server time zone. -->\n"
136         "      <datatimezone>SYSTEM</datatimezone>\n"
137         "\n"
138         "      <!-- plugin DB may have its own identifiers to determine the point in time of changes, so\n"
139         "           we must make sure this identifier is stored (and not only the sync time) -->\n"
140         "      <storesyncidentifiers>yes</storesyncidentifiers>\n"
141         "\n";
142     
143     xmlstream <<
144         "      <!-- Mapping of the fields to the fieldlist -->\n"
145         "      <fieldmap fieldlist='" << info.m_fieldlist << "'>\n";
146     if (!info.m_profile.empty()) {
147         xmlstream <<
148             "        <initscript><![CDATA[\n"
149             "           string itemdata;\n"
150             "        ]]></initscript>\n"
151             "        <beforewritescript><![CDATA[\n";
152         if(!info.m_beforeWriteScript.empty()) {
153             xmlstream << 
154                 "           " << info.m_beforeWriteScript << "\n";
155         }
156         xmlstream <<
157             "           itemdata = MAKETEXTWITHPROFILE(" << info.m_profile << ", \"" << info.m_backendRule << "\");\n"
158             "        ]]></beforewritescript>\n"
159             "        <afterreadscript><![CDATA[\n"
160             "           PARSETEXTWITHPROFILE(itemdata, " << info.m_profile << ", \"" << info.m_backendRule << "\");\n";
161         if(!info.m_afterReadScript.empty()) {
162             xmlstream << 
163                 "           " << info.m_afterReadScript<< "\n";
164         }
165         xmlstream <<
166             "        ]]></afterreadscript>\n"
167             "        <map name='data' references='itemdata' type='string'/>\n";
168     }
169     xmlstream << 
170         "        <automap/>\n"
171         "      </fieldmap>\n"
172         "\n";
173
174     xmlstream <<
175         "      <!-- datatypes supported by this datastore -->\n"
176         "      <typesupport>\n" <<
177         info.m_datatypes <<
178         "      </typesupport>\n";
179
180     // arbitrary configuration options, can override the ones above
181     xmlstream << info.m_datastoreOptions;
182
183     xml = xmlstream.str();
184 }
185
186 string SyncSourceBase::getNativeDatatypeName()
187 {
188     SynthesisInfo info;
189     XMLConfigFragments fragments;
190     getSynthesisInfo(info, fragments);
191     return info.m_native;
192 }
193
194 SyncSource::SyncSource(const SyncSourceParams &params) :
195     SyncSourceConfig(params.m_name, params.m_nodes),
196     m_numDeleted(0),
197     m_forceSlowSync(false),
198     m_database("", ""),
199     m_name(params.getDisplayName()),
200     m_needChanges(true)
201 {
202 }
203
204 SDKInterface *SyncSource::getSynthesisAPI() const
205 {
206     return m_synthesisAPI.empty() ?
207         NULL :
208         static_cast<SDKInterface *>(m_synthesisAPI[m_synthesisAPI.size() - 1]);
209 }
210
211 void SyncSource::pushSynthesisAPI(sysync::SDK_InterfaceType *synthesisAPI)
212 {
213     m_synthesisAPI.push_back(synthesisAPI);
214 }
215
216 void SyncSource::popSynthesisAPI() {
217     m_synthesisAPI.pop_back();
218 }
219
220 SourceRegistry &SyncSource::getSourceRegistry()
221 {
222     static SourceRegistry sourceRegistry;
223     return sourceRegistry;
224 }
225
226 RegisterSyncSource::RegisterSyncSource(const string &shortDescr,
227                                        bool enabled,
228                                        Create_t create,
229                                        const string &typeDescr,
230                                        const Values &typeValues) :
231     m_shortDescr(shortDescr),
232     m_enabled(enabled),
233     m_create(create),
234     m_typeDescr(typeDescr),
235     m_typeValues(typeValues)
236 {
237     SourceRegistry &registry(SyncSource::getSourceRegistry());
238
239     // insert sorted by description to have deterministic ordering
240     for(SourceRegistry::iterator it = registry.begin();
241         it != registry.end();
242         ++it) {
243         if ((*it)->m_shortDescr > shortDescr) {
244             registry.insert(it, this);
245             return;
246         }
247     }
248     registry.push_back(this);
249 }
250
251 class InactiveSyncSource : public SyncSource
252 {
253 public:
254     InactiveSyncSource(const SyncSourceParams &params) : SyncSource(params) {}
255
256     virtual bool isInactive() const { return true; }
257     virtual void enableServerMode() {}
258     virtual bool serverModeEnabled() const { return false; }
259     virtual void getSynthesisInfo(SyncEvo::SyncSourceBase::SynthesisInfo&, SyncEvo::XMLConfigFragments&) { throwError("inactive"); }
260     virtual Databases getDatabases() { throwError("inactive"); return Databases(); }
261     virtual void open() { throwError("inactive"); }
262     virtual void close() { throwError("inactive"); }
263     virtual std::string getPeerMimeType() const { return ""; }
264 };
265
266 SyncSource *RegisterSyncSource::InactiveSource(const SyncSourceParams &params)
267 {
268     return new InactiveSyncSource(params);
269 }
270
271 TestRegistry &SyncSource::getTestRegistry()
272 {
273     static TestRegistry testRegistry;
274     return testRegistry;
275 }
276
277 RegisterSyncSourceTest::RegisterSyncSourceTest(const string &configName, const string &testCaseName) :
278     m_configName(configName),
279     m_testCaseName(testCaseName)
280 {
281     SyncSource::getTestRegistry().push_back(this);
282 }
283
284 static class ScannedModules {
285 public:
286     ScannedModules() {
287 #ifdef ENABLE_MODULES
288         list<pair <string, boost::shared_ptr<ReadDir> > > dirs;
289         /* If enviroment variable SYNCEVOLUTION_BACKEND_DIR is set, will search
290         backends from this path instead. */
291         string backend_dir (SYNCEVO_BACKEND);
292         char *backend_env = getenv("SYNCEVOLUTION_BACKEND_DIR");
293         if (backend_env && strlen(backend_env)){
294             backend_dir = backend_env;
295         }
296         boost::shared_ptr<ReadDir> dir (new ReadDir (backend_dir, false));
297         string dirpath (backend_dir);
298         // Base name (= no dir, no .so suffix) mapping to full file
299         // name (including .so).
300         std::map<std::string, std::string> candidates;
301         // scan directories for matching module names
302         do {
303             debug<<"Scanning backend libraries in " <<dirpath <<endl;
304             BOOST_FOREACH (const string &entry, *dir) {
305                 if (isDir (dirpath + '/' + entry)) {
306                     /* This is a 2-level dir, this corresponds to loading
307                      * backends from current building directory. The library
308                      * should reside in .libs sub directory.*/
309                     string path = dirpath + '/' + entry +"/.libs";
310                     if (isDir (path)) {
311                         boost::shared_ptr<ReadDir> subdir (new ReadDir (path, false));
312                         dirs.push_back (make_pair(path, subdir));
313                     }
314                     continue;
315                 }
316                 if (boost::ends_with(entry, ".so")) {
317                     string fullpath = dirpath + '/' + entry;
318                     fullpath = normalizePath(fullpath);
319                     candidates[entry.substr(0, entry.size() - 3)] = fullpath;
320                 }
321             }
322             if (!dirs.empty()){
323                 dirpath = dirs.front().first;
324                 dir = dirs.front().second;
325                 dirs.pop_front();
326             } else {
327                 break;
328             }
329         } while (true);
330
331         // Look at foo-<version> before foo. If there is more than
332         // one version and the version sorts lexically, the "highest"
333         // one will be checked first, too.
334         //
335         // The intention is to try loading syncebook-2 (with explicit
336         // library dependencies) first, then skip syncebook if loading
337         // of syncebook-2 succeeded. If loading of syncebook-2 fails
338         // due to missing libraries, we proceed to use syncebook.
339         BOOST_REVERSE_FOREACH (const StringPair &entry, candidates) {
340             const std::string &basename = entry.first;
341             const std::string &fullpath = entry.second;
342             std::string replacement;
343             std::string modname;
344             size_t offset = basename.rfind('-');
345             if (offset != basename.npos) {
346                 modname = basename.substr(0, offset);
347             } else {
348                 modname = basename;
349             }
350             BOOST_FOREACH (const std::string &l, m_available) {
351                 if (boost::starts_with(l, modname)) {
352                     replacement = l;
353                     break;
354                 }
355             }
356             if (!replacement.empty()) {
357                 debug << "Skipping " << basename << " = " << fullpath << " because a more recent version of it was already loaded: " << replacement;
358                 continue;
359             }
360
361             // Open the shared object so that backend can register
362             // itself. We keep that pointer, so never close the
363             // module!
364             // RTLD_LAZY is needed for the WebDAV backend, which
365             // needs to do an explicit dlopen() of libneon in compatibility
366             // mode before any of the neon functions can be resolved.
367             void *dlhandle = dlopen(fullpath.c_str(), RTLD_LAZY|RTLD_GLOBAL);
368             // remember which modules were found and which were not
369             if (dlhandle) {
370                 debug<<"Loading backend library "<<basename<<endl;
371                 info<<"Loading backend library "<<fullpath<<endl;
372                 m_available.push_back(basename);
373             } else {
374                 debug<<"Loading backend library "<<basename<<"failed "<< dlerror()<<endl;
375             }
376         }
377 #endif
378     }
379     list<string> m_available;
380     std::ostringstream debug, info;
381 } scannedModules;
382
383 string SyncSource::backendsInfo() {
384     return scannedModules.info.str();
385 }
386 string SyncSource::backendsDebug() {
387     return scannedModules.debug.str();
388 }
389
390 void SyncSource::requestAnotherSync()
391 {
392     // At the moment the per-source request to restart cannot be
393     // stored; instead only a per-session request is set. That's okay
394     // for now because restarting is limited to sessions with only
395     // one source active (intentional simplification).
396     SE_LOG_DEBUG(getDisplayName(), "requesting another sync");
397     SyncContext::requestAnotherSync();
398 }
399
400
401 SyncSource *SyncSource::createSource(const SyncSourceParams &params, bool error, SyncConfig *config)
402 {
403     SourceType sourceType = getSourceType(params.m_nodes);
404
405     if (sourceType.m_backend == "virtual") {
406         SyncSource *source = NULL;
407         source = new VirtualSyncSource(params, config);
408         if (error && !source) {
409             SyncContext::throwError(params.getDisplayName() + ": virtual source cannot be instantiated");
410         }
411         return source;
412     }
413
414     const SourceRegistry &registry(getSourceRegistry());
415     auto_ptr<SyncSource> source;
416     BOOST_FOREACH(const RegisterSyncSource *sourceInfos, registry) {
417         auto_ptr<SyncSource> nextSource(sourceInfos->m_create(params));
418         if (nextSource.get()) {
419             if (source.get()) {
420                 SyncContext::throwError(params.getDisplayName() + ": backend " + sourceType.m_backend +
421                                         " is ambiguous, avoid the alias and pick a specific backend instead directly");
422             }
423             source = nextSource;
424         }
425     }
426     if (source.get()) {
427         return source.release();
428     }
429
430     if (error) {
431         string backends;
432         if (!scannedModules.m_available.empty()) {
433             backends += "by any of the backend modules (";
434             backends += boost::join(scannedModules.m_available, ", ");
435             backends += ") ";
436         }
437         string problem =
438             StringPrintf("%s%sbackend not supported %sor not correctly configured (backend=%s databaseFormat=%s syncFormat=%s)",
439                          params.m_name.c_str(),
440                          params.m_name.empty() ? "" : ": ",
441                          backends.c_str(),
442                          sourceType.m_backend.c_str(),
443                          sourceType.m_localFormat.c_str(),
444                          sourceType.m_format.c_str());
445         SyncContext::throwError(SyncMLStatus(sysync::LOCERR_CFGPARSE), problem);
446     }
447
448     return NULL;
449 }
450
451 SyncSource *SyncSource::createTestingSource(const string &name, const string &type, bool error,
452                                             const char *prefix)
453 {
454     std::string config = "target-config@client-test";
455     const char *server = getenv("CLIENT_TEST_SERVER");
456     if (server) {
457         config += "-";
458         config += server;
459     }
460     boost::shared_ptr<SyncConfig> context(new SyncConfig(config));
461     SyncSourceNodes nodes = context->getSyncSourceNodes(name);
462     SyncSourceParams params(name, nodes, context);
463     PersistentSyncSourceConfig sourceconfig(name, nodes);
464     sourceconfig.setSourceType(type);
465     if (prefix) {
466         sourceconfig.setDatabaseID(string(prefix) + name + "_1");
467     }
468     return createSource(params, error);
469 }
470
471 VirtualSyncSource::VirtualSyncSource(const SyncSourceParams &params, SyncConfig *config) :
472     DummySyncSource(params)
473 {
474     if (config) {
475         std::string evoSyncSource = getDatabaseID();
476         BOOST_FOREACH(std::string name, getMappedSources()) {
477             if (name.empty()) {
478                 throwError(StringPrintf("configuration of underlying sources contains empty source name: database = '%s'",
479                                         evoSyncSource.c_str()));
480             }
481             SyncSourceNodes source = config->getSyncSourceNodes(name);
482             SyncSourceParams params(name, source, boost::shared_ptr<SyncConfig>(config, SyncConfigNOP()));
483             boost::shared_ptr<SyncSource> syncSource(createSource(params, true, config));
484             m_sources.push_back(syncSource);
485         }
486         if (m_sources.size() != 2) {
487             throwError(StringPrintf("configuration of underlying sources must contain exactly one calendar and one todo source (like calendar+todo): database = '%s'",
488                                     evoSyncSource.c_str()));
489         }
490     }
491 }
492
493 void VirtualSyncSource::open()
494 {
495     getDataTypeSupport();
496     BOOST_FOREACH(boost::shared_ptr<SyncSource> &source, m_sources) {
497         source->open();
498     }
499 }
500
501 void VirtualSyncSource::close()
502 {
503     BOOST_FOREACH(boost::shared_ptr<SyncSource> &source, m_sources) {
504         source->close();
505     }
506 }
507
508 std::vector<std::string> VirtualSyncSource::getMappedSources()
509 {
510     std::string evoSyncSource = getDatabaseID();
511     std::vector<std::string> mappedSources = unescapeJoinedString (evoSyncSource, ',');
512     return mappedSources;
513 }
514
515 std::string VirtualSyncSource::getDataTypeSupport()
516 {
517     string datatypes;
518     SourceType sourceType = getSourceType();
519     string type = sourceType.m_format;
520
521     datatypes = getDataTypeSupport(type, sourceType.m_forceFormat);
522     return datatypes;
523 }
524
525 SyncSource::Databases VirtualSyncSource::getDatabases()
526 {
527     SyncSource::Databases dbs;
528     BOOST_FOREACH (boost::shared_ptr<SyncSource> &source, m_sources) {
529         SyncSource::Databases sub = source->getDatabases();
530         if (sub.empty()) {
531             return dbs;
532         }
533     }
534     Database db ("calendar+todo", "");
535     dbs.push_back (db);
536     return dbs;
537 }
538
539 void SyncSourceSession::init(SyncSource::Operations &ops)
540 {
541     ops.m_startDataRead = boost::bind(&SyncSourceSession::startDataRead, this, _1, _2);
542     ops.m_endDataRead = boost::lambda::constant(sysync::LOCERR_OK);
543     ops.m_startDataWrite = boost::lambda::constant(sysync::LOCERR_OK);
544     ops.m_endDataWrite = boost::bind(&SyncSourceSession::endDataWrite, this, _1, _2);
545 }
546
547 sysync::TSyError SyncSourceSession::startDataRead(const char *lastToken, const char *resumeToken)
548 {
549     try {
550         beginSync(lastToken ? lastToken : "",
551                   resumeToken ? resumeToken : "");
552     } catch (const StatusException &ex) {
553         SyncMLStatus status = ex.syncMLStatus();
554         if (status == STATUS_SLOW_SYNC_508) {
555             // Not an error. Return it normally, without ERROR logging
556             // in our caller.
557             return status;
558         } else {
559             throw;
560         }
561     }
562     return sysync::LOCERR_OK;
563 }
564
565 sysync::TSyError SyncSourceSession::endDataWrite(bool success, char **newToken)
566 {
567     std::string token = endSync(success);
568     *newToken = StrAlloc(token.c_str());
569     return sysync::LOCERR_OK;
570 }
571
572 void SyncSourceChanges::init(SyncSource::Operations &ops)
573 {
574     ops.m_readNextItem = boost::bind(&SyncSourceChanges::iterate, this, _1, _2, _3);
575 }
576
577 SyncSourceChanges::SyncSourceChanges() :
578     m_first(true)
579 {
580 }
581
582 bool SyncSourceChanges::addItem(const string &luid, State state)
583 {
584     pair<Items_t::iterator, bool> res = m_items[state].insert(luid);
585     return res.second;
586 }
587
588 bool SyncSourceChanges::reset()
589 {
590     bool removed = false;
591     for (int i = 0; i < MAX; i++) {
592         if (!m_items[i].empty()) {
593             m_items[i].clear();
594             removed = true;
595         }
596     }
597     m_first = true;
598     return removed;
599 }
600
601 sysync::TSyError SyncSourceChanges::iterate(sysync::ItemID aID,
602                                             sysync::sInt32 *aStatus,
603                                             bool aFirst)
604 {
605     aID->item = NULL;
606     aID->parent = NULL;
607
608     if (m_first || aFirst) {
609         m_it = m_items[ANY].begin();
610         m_first = false;
611     }
612
613     if (m_it == m_items[ANY].end()) {
614         *aStatus = sysync::ReadNextItem_EOF;
615     } else {
616         const string &luid = *m_it;
617
618         if (m_items[NEW].find(luid) != m_items[NEW].end() ||
619             m_items[UPDATED].find(luid) != m_items[UPDATED].end()) {
620             *aStatus = sysync::ReadNextItem_Changed;
621         } else {
622             *aStatus = sysync::ReadNextItem_Unchanged;
623         }
624         aID->item = StrAlloc(luid.c_str());
625         ++m_it;
626     }
627
628     return sysync::LOCERR_OK;
629 }
630
631 void SyncSourceDelete::init(SyncSource::Operations &ops)
632 {
633     ops.m_deleteItem = boost::bind(&SyncSourceDelete::deleteItemSynthesis, this, _1);
634 }
635
636 sysync::TSyError SyncSourceDelete::deleteItemSynthesis(sysync::cItemID aID)
637 {
638     deleteItem(aID->item);
639     incrementNumDeleted();
640     return sysync::LOCERR_OK;
641 }
642
643
644 void SyncSourceSerialize::getSynthesisInfo(SynthesisInfo &info,
645                                            XMLConfigFragments &fragments)
646 {
647     string type = getMimeType();
648
649     // default remote rule (local-storage.xml): suppresses empty properties
650     info.m_backendRule = "LOCALSTORAGE";
651
652     // We store entire items locally and thus have to make sure that
653     // they are complete by having the engine merge incoming and local
654     // data.
655     info.m_datastoreOptions += "      <updateallfields>true</updateallfields>\n";
656
657     if (type == "text/x-vcard") {
658         info.m_native = "vCard21";
659         info.m_fieldlist = "contacts";
660         info.m_profile = "\"vCard\", 1";
661         info.m_datatypes =
662             "        <use datatype='vCard21' mode='rw' preferred='yes'/>\n"
663             "        <use datatype='vCard30' mode='rw'/>\n";
664     } else if (type == "text/vcard") {
665         info.m_native = "vCard30";
666         info.m_fieldlist = "contacts";
667         info.m_profile = "\"vCard\", 2";
668         info.m_datatypes =
669             "        <use datatype='vCard21' mode='rw'/>\n"
670             "        <use datatype='vCard30' mode='rw' preferred='yes'/>\n";
671         // If a backend overwrites the m_beforeWriteScript, then it must
672         // include $VCARD_OUTGOING_PHOTO_VALUE_SCRIPT in its own script,
673         // otherwise it will be sent invalid, empty PHOTO;TYPE=unknown;VALUE=binary:
674         // properties.
675         info.m_beforeWriteScript = "$VCARD_OUTGOING_PHOTO_VALUE_SCRIPT;\n";
676         // Likewise for reading. This is needed to ensure proper merging
677         // of contact data.
678         info.m_afterReadScript = "$VCARD_INCOMING_PHOTO_VALUE_SCRIPT;\n";
679     } else if (type == "text/x-calendar" || type == "text/x-vcalendar") {
680         info.m_native = "vCalendar10";
681         info.m_fieldlist = "calendar";
682         info.m_profile = "\"vCalendar\", 1";
683         info.m_datatypes =
684             "        <use datatype='vCalendar10' mode='rw' preferred='yes'/>\n"
685             "        <use datatype='iCalendar20' mode='rw'/>\n";
686         /**
687          * here are two default implementations. If user wants to reset it,
688          * just implement its own getSynthesisInfo. If user wants to use this default
689          * implementations and its new scripts, it is possible to append its implementations
690          * to info.m_afterReadScript and info.m_beforeWriteScript.
691          */
692         info.m_afterReadScript = "$VCALENDAR10_AFTERREAD_SCRIPT;\n";
693         info.m_beforeWriteScript = "$VCALENDAR10_BEFOREWRITE_SCRIPT;\n"
694             "$CALENDAR_BEFOREWRITE_SCRIPT;\n";
695     } else if (type == "text/calendar" ||
696                boost::starts_with(type, "text/calendar+")) {
697         info.m_native = "iCalendar20";
698         info.m_fieldlist = "calendar";
699         info.m_profile = "\"vCalendar\", 2";
700         info.m_datatypes =
701             "        <use datatype='vCalendar10' mode='rw'/>\n"
702             "        <use datatype='iCalendar20' mode='rw' preferred='yes'/>\n";
703         info.m_beforeWriteScript = "$CALENDAR_BEFOREWRITE_SCRIPT;\n";
704     } else if (type == "text/plain") {
705         info.m_fieldlist = "Note";
706         info.m_profile = "\"Note\", 2";
707     } else {
708         throwError(string("default MIME type not supported: ") + type);
709     }
710
711     SourceType sourceType = getSourceType();
712     if (!sourceType.m_format.empty()) {
713         type = sourceType.m_format;
714     }
715     info.m_datatypes = getDataTypeSupport(type, sourceType.m_forceFormat);
716 }
717
718 std::string SyncSourceBase::getDataTypeSupport(const std::string &type,
719                                                bool forceFormat)
720 {
721     std::string datatypes;
722
723     if (type == "text/x-vcard:2.1" || type == "text/x-vcard") {
724         datatypes =
725             "        <use datatype='vCard21' mode='rw' preferred='yes'/>\n";
726         if (!forceFormat) {
727             datatypes +=
728                 "        <use datatype='vCard30' mode='rw'/>\n";
729         }
730     } else if (type == "text/vcard:3.0" || type == "text/vcard") {
731         datatypes =
732             "        <use datatype='vCard30' mode='rw' preferred='yes'/>\n";
733         if (!forceFormat) {
734             datatypes +=
735                 "        <use datatype='vCard21' mode='rw'/>\n";
736         }
737     } else if (type == "text/x-vcalendar:1.0" || type == "text/x-vcalendar"
738              || type == "text/x-calendar:1.0" || type == "text/x-calendar") {
739         datatypes =
740             "        <use datatype='vcalendar10' mode='rw' preferred='yes'/>\n";
741         if (!forceFormat) {
742             datatypes +=
743                 "        <use datatype='icalendar20' mode='rw'/>\n";
744         }
745     } else if (type == "text/calendar:2.0" || type == "text/calendar") {
746         datatypes =
747             "        <use datatype='icalendar20' mode='rw' preferred='yes'/>\n";
748         if (!forceFormat) {
749             datatypes +=
750                 "        <use datatype='vcalendar10' mode='rw'/>\n";
751         }
752     } else if (type == "text/calendar+plain") {
753         datatypes =
754             "        <use datatype='icalendar20' mode='rw' preferred='yes'/>\n"
755             "        <use datatype='journaltext10' mode='rw'/>\n"
756             "        <use datatype='journaltext11' mode='rw'/>\n";
757     } else if (type == "text/plain:1.0" || type == "text/plain") {
758         // note10 are the same as note11, so ignore force format
759         datatypes =
760             "        <use datatype='note10' mode='rw' preferred='yes'/>\n"
761             "        <use datatype='note11' mode='rw'/>\n";
762     } else if (type.empty()) {
763         throwError("no MIME type configured");
764     } else {
765         throwError(string("configured MIME type not supported: ") + type);
766     }
767
768     return datatypes;
769 }
770
771 sysync::TSyError SyncSourceSerialize::readItemAsKey(sysync::cItemID aID, sysync::KeyH aItemKey)
772 {
773     std::string item;
774
775     readItem(aID->item, item);
776     TSyError res = getSynthesisAPI()->setValue(aItemKey, "data", item.c_str(), item.size());
777     return res;
778 }
779
780 SyncSource::Operations::InsertItemAsKeyResult_t SyncSourceSerialize::insertItemAsKey(sysync::KeyH aItemKey, sysync::ItemID newID)
781 {
782     SharedBuffer data;
783     TSyError res = getSynthesisAPI()->getValue(aItemKey, "data", data);
784
785     if (!res) {
786         InsertItemResult inserted = insertItem("", data.get());
787         switch (inserted.m_state) {
788         case ITEM_OKAY:
789             break;
790         case ITEM_AGAIN:
791             // Skip setting the newID.
792             return Operations::InsertItemAsKeyContinue_t(boost::bind(&SyncSourceSerialize::insertContinue, this, _2, inserted.m_continue));
793             break;
794         case ITEM_REPLACED:
795             res = sysync::DB_DataReplaced;
796             break;
797         case ITEM_MERGED:
798             res = sysync::DB_DataMerged;
799             break;
800         case ITEM_NEEDS_MERGE:
801             res = sysync::DB_Conflict;
802             break;
803         }
804         newID->item = StrAlloc(inserted.m_luid.c_str());
805     }
806
807     return res;
808 }
809
810 SyncSource::Operations::UpdateItemAsKeyResult_t SyncSourceSerialize::updateItemAsKey(sysync::KeyH aItemKey, sysync::cItemID aID, sysync::ItemID newID)
811 {
812     SharedBuffer data;
813     TSyError res = getSynthesisAPI()->getValue(aItemKey, "data", data);
814
815     if (!res) {
816         InsertItemResult inserted = insertItem(aID->item, data.get());
817         switch (inserted.m_state) {
818         case ITEM_OKAY:
819             break;
820         case ITEM_AGAIN:
821             // Skip setting the newID.
822             return Operations::UpdateItemAsKeyContinue_t(boost::bind(&SyncSourceSerialize::insertContinue, this, _3, inserted.m_continue));
823             break;
824         case ITEM_REPLACED:
825             res = sysync::DB_DataReplaced;
826             break;
827         case ITEM_MERGED:
828             res = sysync::DB_DataMerged;
829             break;
830         case ITEM_NEEDS_MERGE:
831             res = sysync::DB_Conflict;
832             break;
833         }
834         newID->item = StrAlloc(inserted.m_luid.c_str());
835     }
836
837     return res;
838 }
839
840 sysync::TSyError SyncSourceSerialize::insertContinue(sysync::ItemID newID, const InsertItemResult::Continue_t &cont)
841 {
842     // The engine cannot tell us when it needs results (for example,
843     // in the "final message received from peer" case in
844     // TSyncSession::EndMessage(), so assume that it does whenever it
845     // calls us again => flush and wait.
846     flushItemChanges();
847     finishItemChanges();
848
849     InsertItemResult inserted = cont();
850     TSyError res = sysync::LOCERR_OK;
851     switch (inserted.m_state) {
852     case ITEM_OKAY:
853         break;
854     case ITEM_AGAIN:
855         // Skip setting the newID.
856         return sysync::LOCERR_AGAIN;
857         break;
858     case ITEM_REPLACED:
859         res = sysync::DB_DataReplaced;
860         break;
861     case ITEM_MERGED:
862         res = sysync::DB_DataMerged;
863         break;
864     case ITEM_NEEDS_MERGE:
865         res = sysync::DB_Conflict;
866         break;
867     }
868     newID->item = StrAlloc(inserted.m_luid.c_str());
869     return res;
870 }
871
872 SyncSourceSerialize::InsertItemResult SyncSourceSerialize::insertItemRaw(const std::string &luid, const std::string &item)
873 {
874     InsertItemResult result = insertItem(luid, item);
875
876     while (result.m_state == ITEM_AGAIN) {
877         // Flush and wait, because caller (command line, restore) is
878         // not prepared to deal with asynchronous execution.
879         flushItemChanges();
880         finishItemChanges();
881         result = result.m_continue();
882     }
883
884     return result;
885 }
886
887 void SyncSourceSerialize::readItemRaw(const std::string &luid, std::string &item)
888 {
889     return readItem(luid, item);
890 }
891
892
893
894 void SyncSourceSerialize::init(SyncSource::Operations &ops)
895 {
896     ops.m_readItemAsKey = boost::bind(&SyncSourceSerialize::readItemAsKey,
897                                       this, _1, _2);
898     ops.m_insertItemAsKey = boost::bind(&SyncSourceSerialize::insertItemAsKey,
899                                         this, _1, _2);
900     ops.m_updateItemAsKey = boost::bind(&SyncSourceSerialize::updateItemAsKey,
901                                         this, _1, _2, _3);
902 }
903
904
905 void ItemCache::init(const SyncSource::Operations::ConstBackupInfo &oldBackup,
906                      const SyncSource::Operations::BackupInfo &newBackup,
907                      bool legacy)
908 {
909     m_counter = 1;
910     m_legacy = legacy;
911     m_backup = newBackup;
912     m_hash2counter.clear();
913     m_dirname = oldBackup.m_dirname;
914     if (m_dirname.empty() || !oldBackup.m_node) {
915         return;
916     }
917
918     long numitems;
919     if (!oldBackup.m_node->getProperty("numitems", numitems)) {
920         return;
921     }
922     for (long counter = 1; counter <= numitems; counter++) {
923         stringstream key;
924         key << counter << m_hashSuffix;
925         Hash_t hash;
926         if (oldBackup.m_node->getProperty(key.str(), hash)) {
927             m_hash2counter[hash] = counter;
928         }
929     }
930 }
931
932 void ItemCache::reset()
933 {
934     // clean directory and start counting at 1 again
935     m_counter = 1;
936     rm_r(m_backup.m_dirname);
937     mkdir_p(m_backup.m_dirname);
938     m_backup.m_node->clear();
939 }
940
941 string ItemCache::getFilename(Hash_t hash)
942 {
943     Map_t::const_iterator it = m_hash2counter.find(hash);
944     if (it != m_hash2counter.end()) {
945         stringstream dirname;
946         dirname << m_dirname << "/" << it->second;
947         return dirname.str();
948     } else {
949         return "";
950     }
951 }
952
953 const char *ItemCache::m_hashSuffix =
954 #ifdef USE_SHA256
955     "-sha256"
956 #else
957     "-hash"
958 #endif
959 ;
960
961 void ItemCache::backupItem(const std::string &item,
962                            const std::string &uid,
963                            const std::string &rev)
964 {
965     stringstream filename;
966     filename << m_backup.m_dirname << "/" << m_counter;
967
968     ItemCache::Hash_t hash = hashFunc(item);
969     string oldfilename = getFilename(hash);
970     if (!oldfilename.empty()) {
971         // found old file with same content, reuse it via hardlink
972         if (link(oldfilename.c_str(), filename.str().c_str())) {
973             // Hard linking failed. Record this, then continue
974             // by ignoring the old file.
975             SE_LOG_DEBUG(NULL, "hard linking old %s new %s: %s",
976                          oldfilename.c_str(),
977                          filename.str().c_str(),
978                          strerror(errno));
979             oldfilename.clear();
980         }
981     }
982
983     if (oldfilename.empty()) {
984         // write new file instead of reusing old one
985         ofstream out(filename.str().c_str());
986         out.write(item.c_str(), item.size());
987         out.close();
988         if (out.fail()) {
989             SE_THROW(string("error writing ") + filename.str() + ": " + strerror(errno));
990         }
991     }
992
993     stringstream key;
994     key << m_counter << "-uid";
995     m_backup.m_node->setProperty(key.str(), uid);
996     if (m_legacy) {
997         // clear() does not remove the existing content, which was
998         // intended here. This should have been key.str(""). As a
999         // result, keys for -rev are longer than intended because they
1000         // start with the -uid part. We cannot change it now, because
1001         // that would break compatibility with nodes that use the
1002         // older, longer keys for -rev.
1003         // key.clear();
1004     } else {
1005         key.str("");
1006     }
1007     key << m_counter << "-rev";
1008     m_backup.m_node->setProperty(key.str(), rev);
1009     key.str("");
1010     key << m_counter << ItemCache::m_hashSuffix;
1011     m_backup.m_node->setProperty(key.str(), hash);
1012
1013     m_counter++;
1014 }
1015
1016 void ItemCache::finalize(BackupReport &report)
1017 {
1018     stringstream value;
1019     value << m_counter - 1;
1020     m_backup.m_node->setProperty("numitems", value.str());
1021     m_backup.m_node->flush();
1022
1023     report.setNumItems(m_counter - 1);
1024 }
1025
1026 void SyncSourceRevisions::initRevisions()
1027 {
1028     if (!m_revisionsSet) {
1029         // might still be filled with garbage from previous run
1030         m_revisions.clear();
1031         listAllItems(m_revisions);
1032         m_revisionsSet = true;
1033     }
1034 }
1035
1036
1037 void SyncSourceRevisions::backupData(const SyncSource::Operations::ConstBackupInfo &oldBackup,
1038                                      const SyncSource::Operations::BackupInfo &newBackup,
1039                                      BackupReport &report)
1040 {
1041     ItemCache cache;
1042     cache.init(oldBackup, newBackup, true);
1043
1044     bool startOfSync = newBackup.m_mode == SyncSource::Operations::BackupInfo::BACKUP_BEFORE;
1045     RevisionMap_t buffer;
1046     RevisionMap_t *revisions;
1047     if (startOfSync) {
1048         initRevisions();
1049         revisions = &m_revisions;
1050     } else {
1051         listAllItems(buffer);
1052         revisions = &buffer;
1053     }
1054
1055     // Ensure that source knows what we are going to read.
1056     std::vector<std::string> uids;
1057     uids.reserve(revisions->size());
1058     BOOST_FOREACH(const StringPair &mapping, *revisions) {
1059         uids.push_back(mapping.first);
1060     }
1061
1062     // We may dump after a hint was already set when starting the
1063     // sync. Remember that and restore it when done. If we fail, we
1064     // don't need to restore, because then syncing will abort or skip
1065     // the source.
1066     ReadAheadOrder oldOrder;
1067     ReadAheadItems oldLUIDs;
1068     getReadAheadOrder(oldOrder, oldLUIDs);
1069
1070     setReadAheadOrder(READ_SELECTED_ITEMS, uids);
1071
1072     string item;
1073     errno = 0;
1074     BOOST_FOREACH(const StringPair &mapping, *revisions) {
1075         const string &uid = mapping.first;
1076         const string &rev = mapping.second;
1077         m_raw->readItemRaw(uid, item);
1078         cache.backupItem(item, uid, rev);
1079     }
1080
1081     setReadAheadOrder(oldOrder, oldLUIDs);
1082     cache.finalize(report);
1083 }
1084
1085 void SyncSourceRevisions::restoreData(const SyncSource::Operations::ConstBackupInfo &oldBackup,
1086                                       bool dryrun,
1087                                       SyncSourceReport &report)
1088 {
1089     RevisionMap_t revisions;
1090     listAllItems(revisions);
1091
1092     long numitems;
1093     string strval;
1094     strval = oldBackup.m_node->readProperty("numitems");
1095     stringstream stream(strval);
1096     stream >> numitems;
1097
1098     for (long counter = 1; counter <= numitems; counter++) {
1099         stringstream key;
1100         key << counter << "-uid";
1101         string uid = oldBackup.m_node->readProperty(key.str());
1102         key.clear();
1103         key << counter << "-rev";
1104         string rev = oldBackup.m_node->readProperty(key.str());
1105         RevisionMap_t::iterator it = revisions.find(uid);
1106         report.incrementItemStat(report.ITEM_LOCAL,
1107                                  report.ITEM_ANY,
1108                                  report.ITEM_TOTAL);
1109         if (it != revisions.end() &&
1110             it->second == rev) {
1111             // item exists in backup and database with same revision:
1112             // nothing to do
1113         } else {
1114             // add or update, so need item
1115             stringstream filename;
1116             filename << oldBackup.m_dirname << "/" << counter;
1117             string data;
1118             if (!ReadFile(filename.str(), data)) {
1119                 throwError(StringPrintf("restoring %s from %s failed: could not read file",
1120                                         uid.c_str(),
1121                                         filename.str().c_str()));
1122             }
1123             // TODO: it would be nicer to recreate the item
1124             // with the original revision. If multiple peers
1125             // synchronize against us, then some of them
1126             // might still be in sync with that revision. By
1127             // updating the revision here we force them to
1128             // needlessly receive an update.
1129             //
1130             // For the current peer for which we restore this is
1131             // avoided by the revision check above: unchanged
1132             // items aren't touched.
1133             SyncSourceReport::ItemState state =
1134                 it == revisions.end() ?
1135                 SyncSourceReport::ITEM_ADDED :   // not found in database, create anew
1136                 SyncSourceReport::ITEM_UPDATED;  // found, update existing item
1137             try {
1138                 report.incrementItemStat(report.ITEM_LOCAL,
1139                                          state,
1140                                          report.ITEM_TOTAL);
1141                 if (!dryrun) {
1142                     m_raw->insertItemRaw(it == revisions.end() ? "" : uid,
1143                                          data);
1144                 }
1145             } catch (...) {
1146                 report.incrementItemStat(report.ITEM_LOCAL,
1147                                          state,
1148                                          report.ITEM_REJECT);
1149                 throw;
1150             }
1151         }
1152
1153         // remove handled item from revision list so
1154         // that when we are done, the only remaining
1155         // items listed there are the ones which did
1156         // no exist in the backup
1157         if (it != revisions.end()) {
1158             revisions.erase(it);
1159         }
1160     }
1161
1162     // now remove items that were not in the backup
1163     BOOST_FOREACH(const StringPair &mapping, revisions) {
1164         try {
1165             report.incrementItemStat(report.ITEM_LOCAL,
1166                                      report.ITEM_REMOVED,
1167                                      report.ITEM_TOTAL);
1168             if (!dryrun) {
1169                 m_del->deleteItem(mapping.first);
1170             }
1171         } catch(...) {
1172             report.incrementItemStat(report.ITEM_LOCAL,
1173                                      report.ITEM_REMOVED,
1174                                      report.ITEM_REJECT);
1175             throw;
1176         }
1177     }
1178 }
1179
1180 bool SyncSourceRevisions::detectChanges(ConfigNode &trackingNode, ChangeMode mode)
1181 {
1182     bool forceSlowSync = false;
1183
1184     // erase content which might have been set in a previous call
1185     reset();
1186     if (!m_firstCycle) {
1187         // detectChanges() must have been called before;
1188         // don't trust our cached revisions in that case (not updated during sync!)
1189         // TODO: keep the revision map up-to-date as part of a sync and reuse it
1190         m_revisionsSet = false;
1191     } else {
1192         m_firstCycle = false;
1193     }
1194
1195     if (mode == CHANGES_NONE) {
1196         // shortcut because nothing changed: just copy our known item list
1197         ConfigProps props;
1198         trackingNode.readProperties(props);
1199
1200         RevisionMap_t revisions;
1201         BOOST_FOREACH(const StringPair &mapping, props) {
1202             const string &uid = mapping.first;
1203             const string &revision = mapping.second;
1204             addItem(uid);
1205             revisions[uid] = revision;
1206         }
1207         setAllItems(revisions);
1208         return false;
1209     }
1210
1211     if (!m_revisionsSet &&
1212         mode == CHANGES_FULL) {
1213         ConfigProps props;
1214         trackingNode.readProperties(props);
1215         if (!props.empty()) {
1216             // We were not asked to throw away all old information and
1217             // there is some that may be worth salvaging, so let's give
1218             // our derived class a chance to update it instead of having
1219             // to reread everything.
1220             //
1221             // The exact number of items at which the update method is
1222             // more efficient depends on the derived class; here we assume
1223             // that even a single item makes it worthwhile. The derived
1224             // class can always ignore the information if it has different
1225             // tradeoffs.
1226             //
1227             // TODO (?): an API which only provides the information
1228             // on demand...
1229             m_revisions.clear();
1230             m_revisions.insert(props.begin(), props.end());
1231             updateAllItems(m_revisions);
1232             // continue with m_revisions initialized below
1233             m_revisionsSet = true;
1234         }
1235     }
1236
1237     // traditional, slow fallback follows...
1238     initRevisions();
1239
1240     // Check whether we have valid revision information.  If not, then
1241     // we need to do a slow sync. The assumption here is that an empty
1242     // revision string marks missing information. When we don't need
1243     // change information, not having a revision string is okay.
1244     if (needChanges() &&
1245         !m_revisions.empty() &&
1246         m_revisions.begin()->second.empty()) {
1247         forceSlowSync = true;
1248         mode = CHANGES_SLOW;
1249     }
1250
1251     // If we don't need changes, then override the mode so that
1252     // we don't compute them below.
1253     if (!needChanges()) {
1254         mode = CHANGES_SLOW;
1255     }
1256
1257     // Delay setProperty calls until after checking all uids.
1258     // Necessary for MapSyncSource, which shares the revision among
1259     // several uids. Another advantage is that we can do the "find
1260     // deleted items" check with less entries (new items not added
1261     // yet).
1262     StringMap revUpdates;
1263
1264     if (mode == CHANGES_SLOW) {
1265         // Make tracking node identical to current set of items
1266         // by re-adding them below.
1267         trackingNode.clear();
1268     }
1269
1270     BOOST_FOREACH(const StringPair &mapping, m_revisions) {
1271         const string &uid = mapping.first;
1272         const string &revision = mapping.second;
1273
1274         // always remember the item, need full list
1275         addItem(uid);
1276
1277         // avoid unnecessary work in CHANGES_SLOW mode
1278         if (mode == CHANGES_SLOW) {
1279             trackingNode.setProperty(uid, revision);
1280         } else {
1281             // detect changes
1282             string serverRevision(trackingNode.readProperty(uid));
1283             if (!serverRevision.size()) {
1284                 addItem(uid, NEW);
1285                 revUpdates[uid] = revision;
1286             } else {
1287                 if (revision != serverRevision) {
1288                     addItem(uid, UPDATED);
1289                     revUpdates[uid] = revision;
1290                 }
1291             }
1292         }
1293     }
1294
1295     if (mode != CHANGES_SLOW) {
1296         // clear information about all items that we recognized as deleted
1297         ConfigProps props;
1298         trackingNode.readProperties(props);
1299
1300         BOOST_FOREACH(const StringPair &mapping, props) {
1301             const string &uid(mapping.first);
1302             if (getAllItems().find(uid) == getAllItems().end()) {
1303                 addItem(uid, DELETED);
1304                 trackingNode.removeProperty(uid);
1305             }
1306         }
1307
1308         // now update tracking node
1309         BOOST_FOREACH(const StringPair &update, revUpdates) {
1310             trackingNode.setProperty(update.first, update.second);
1311         }
1312     }
1313
1314     return forceSlowSync;
1315 }
1316
1317 void SyncSourceRevisions::updateRevision(ConfigNode &trackingNode,
1318                                          const std::string &old_luid,
1319                                          const std::string &new_luid,
1320                                          const std::string &revision)
1321 {
1322     if (!needChanges()) {
1323         return;
1324     }
1325
1326     databaseModified();
1327     if (old_luid != new_luid) {
1328         trackingNode.removeProperty(old_luid);
1329     }
1330     if (new_luid.empty() || revision.empty()) {
1331         throwError("need non-empty LUID and revision string");
1332     }
1333     trackingNode.setProperty(new_luid, revision);
1334 }
1335
1336 void SyncSourceRevisions::deleteRevision(ConfigNode &trackingNode,
1337                                          const std::string &luid)
1338 {
1339     if (!needChanges()) {
1340         return;
1341     }
1342     databaseModified();
1343     trackingNode.removeProperty(luid);
1344 }
1345
1346 void SyncSourceRevisions::sleepSinceModification()
1347 {
1348     Timespec current = Timespec::monotonic();
1349     // Don't let this get interrupted by user abort.
1350     // It is needed for correct change tracking.
1351     while ((current - m_modTimeStamp).duration() < m_revisionAccuracySeconds) {
1352         Sleep(m_revisionAccuracySeconds - (current - m_modTimeStamp).duration());
1353         current = Timespec::monotonic();
1354     }
1355 }
1356
1357 void SyncSourceRevisions::databaseModified()
1358 {
1359     m_modTimeStamp = Timespec::monotonic();
1360 }
1361
1362 void SyncSourceRevisions::init(SyncSourceRaw *raw,
1363                                SyncSourceDelete *del,
1364                                int granularity,
1365                                SyncSource::Operations &ops)
1366 {
1367     m_raw = raw;
1368     m_del = del;
1369     m_revisionAccuracySeconds = granularity;
1370     m_revisionsSet = false;
1371     m_firstCycle = false;
1372     if (raw) {
1373         ops.m_backupData = boost::bind(&SyncSourceRevisions::backupData,
1374                                        this, _1, _2, _3);
1375     }
1376     if (raw && del) {
1377         ops.m_restoreData = boost::bind(&SyncSourceRevisions::restoreData,
1378                                         this, _1, _2, _3);
1379     }
1380     ops.m_endDataWrite.getPostSignal().connect(boost::bind(&SyncSourceRevisions::sleepSinceModification,
1381                                                            this));
1382 }
1383
1384 std::string SyncSourceLogging::getDescription(sysync::KeyH aItemKey)
1385 {
1386     try {
1387         std::list<std::string> values;
1388
1389         BOOST_FOREACH(const std::string &field, m_fields) {
1390             SharedBuffer value;
1391             if (!getSynthesisAPI()->getValue(aItemKey, field, value) &&
1392                 value.size()) {
1393                 values.push_back(std::string(value.get()));
1394             }
1395         }
1396
1397         std::string description = boost::join(values, m_sep);
1398         return description;
1399     } catch (...) {
1400         // Instead of failing we log the error and ask
1401         // the caller to log the UID. That way transient
1402         // errors or errors in the logging code don't
1403         // prevent syncs.
1404         handleException();
1405         return "";
1406     }
1407 }
1408
1409 std::string SyncSourceLogging::getDescription(const string &luid)
1410 {
1411     return "";
1412 }
1413
1414 void SyncSourceLogging::insertItemAsKey(sysync::KeyH aItemKey, sysync::ItemID newID)
1415 {
1416     std::string description = getDescription(aItemKey);
1417     SE_LOG_INFO(getDisplayName(),
1418                 description.empty() ? "%s <%s>" : "%s \"%s\"",
1419                 "adding",
1420                 !description.empty() ? description.c_str() : "???");
1421 }
1422
1423 void SyncSourceLogging::updateItemAsKey(sysync::KeyH aItemKey, sysync::cItemID aID, sysync::ItemID newID)
1424 {
1425     std::string description = getDescription(aItemKey);
1426     SE_LOG_INFO(getDisplayName(),
1427                 description.empty() ? "%s <%s>" : "%s \"%s\"",
1428                 "updating",
1429                 !description.empty() ? description.c_str() : aID ? aID->item : "???");
1430 }
1431
1432 void SyncSourceLogging::deleteItem(sysync::cItemID aID)
1433 {
1434     std::string description = getDescription(aID->item);
1435     SE_LOG_INFO(getDisplayName(),
1436                 description.empty() ? "%s <%s>" : "%s \"%s\"",
1437                 "deleting",
1438                 !description.empty() ? description.c_str() : aID->item);
1439 }
1440
1441 void SyncSourceLogging::init(const std::list<std::string> &fields,
1442                              const std::string &sep,
1443                              SyncSource::Operations &ops)
1444 {
1445     m_fields = fields;
1446     m_sep = sep;
1447
1448     ops.m_insertItemAsKey.getPreSignal().connect(boost::bind(&SyncSourceLogging::insertItemAsKey,
1449                                                              this, _2, _3));
1450     ops.m_updateItemAsKey.getPreSignal().connect(boost::bind(&SyncSourceLogging::updateItemAsKey,
1451                                                              this, _2, _3, _4));
1452     ops.m_deleteItem.getPreSignal().connect(boost::bind(&SyncSourceLogging::deleteItem,
1453                                                         this, _2));
1454 }
1455
1456 sysync::TSyError SyncSourceAdmin::loadAdminData(const char *aLocDB,
1457                                                 const char *aRemDB,
1458                                                 char **adminData)
1459 {
1460     std::string data = m_configNode->readProperty(m_adminPropertyName);
1461     *adminData = StrAlloc(StringEscape::unescape(data, '!').c_str());
1462     resetMap();
1463     return sysync::LOCERR_OK;
1464 }
1465
1466 sysync::TSyError SyncSourceAdmin::saveAdminData(const char *adminData)
1467 {
1468     m_configNode->setProperty(m_adminPropertyName,
1469                               StringEscape::escape(adminData, '!', StringEscape::INI_VALUE));
1470
1471     // Flush here, because some calls to saveAdminData() happend
1472     // after SyncSourceAdmin::flush() (= session end).
1473     m_configNode->flush();
1474     return sysync::LOCERR_OK;
1475 }
1476
1477 bool SyncSourceAdmin::readNextMapItem(sysync::MapID mID, bool aFirst)
1478 {
1479     if (aFirst) {
1480         resetMap();
1481     }
1482     if (m_mappingIterator != m_mapping.end()) {
1483         entry2mapid(m_mappingIterator->first, m_mappingIterator->second, mID);
1484         ++m_mappingIterator;
1485         return true;
1486     } else {
1487         return false;
1488     }
1489 }
1490
1491 sysync::TSyError SyncSourceAdmin::insertMapItem(sysync::cMapID mID)
1492 {
1493     string key, value;
1494     mapid2entry(mID, key, value);
1495
1496 #if 0
1497     StringMap::iterator it = m_mapping.find(key);
1498     if (it != m_mapping.end()) {
1499         // error, exists already
1500         return sysync::DB_Forbidden;
1501     } else {
1502         m_mapping[key] = value;
1503         return sysync::LOCERR_OK;
1504     }
1505 #else
1506     m_mapping[key] = value;
1507     m_mappingNode->clear();
1508     m_mappingNode->writeProperties(m_mapping);
1509     m_mappingNode->flush();
1510     return sysync::LOCERR_OK;
1511 #endif
1512 }
1513
1514 sysync::TSyError SyncSourceAdmin::updateMapItem(sysync::cMapID mID)
1515 {
1516     string key, value;
1517     mapid2entry(mID, key, value);
1518
1519     ConfigProps::iterator it = m_mapping.find(key);
1520     if (it == m_mapping.end()) {
1521         // error, does not exist
1522         return sysync::DB_Forbidden;
1523     } else {
1524         m_mapping[key] = value;
1525         m_mappingNode->clear();
1526         m_mappingNode->writeProperties(m_mapping);
1527         m_mappingNode->flush();
1528         return sysync::LOCERR_OK;
1529     }
1530 }
1531
1532 sysync::TSyError SyncSourceAdmin::deleteMapItem(sysync::cMapID mID)
1533 {
1534     string key, value;
1535     mapid2entry(mID, key, value);
1536
1537     ConfigProps::iterator it = m_mapping.find(key);
1538     if (it == m_mapping.end()) {
1539         // error, does not exist
1540         return sysync::DB_Forbidden;
1541     } else {
1542         m_mapping.erase(it);
1543         m_mappingNode->clear();
1544         m_mappingNode->writeProperties(m_mapping);
1545         m_mappingNode->flush();
1546         return sysync::LOCERR_OK;
1547     }
1548 }
1549
1550 void SyncSourceAdmin::flush()
1551 {
1552     m_configNode->flush();
1553     if (m_mappingLoaded) {
1554         m_mappingNode->clear();
1555         m_mappingNode->writeProperties(m_mapping);
1556         m_mappingNode->flush();
1557     }
1558 }
1559
1560 void SyncSourceAdmin::resetMap()
1561 {
1562     m_mapping.clear();
1563     m_mappingNode->readProperties(m_mapping);
1564     m_mappingIterator = m_mapping.begin();
1565     m_mappingLoaded = true;
1566 }
1567
1568
1569 void SyncSourceAdmin::mapid2entry(sysync::cMapID mID, string &key, string &value)
1570 {
1571     key = StringPrintf("%s-%x",
1572                        StringEscape::escape(mID->localID ? mID->localID : "", '!', StringEscape::INI_WORD).c_str(),
1573                        mID->ident);
1574     if (mID->remoteID && mID->remoteID[0]) {
1575         value = StringPrintf("%s %x",
1576                              StringEscape::escape(mID->remoteID ? mID->remoteID : "", '!', StringEscape::INI_WORD).c_str(),
1577                              mID->flags);
1578     } else {
1579         value = StringPrintf("%x", mID->flags);
1580     }
1581 }
1582
1583 void SyncSourceAdmin::entry2mapid(const string &key, const string &value, sysync::MapID mID)
1584 {
1585     size_t found = key.rfind('-');
1586     mID->localID = StrAlloc(StringEscape::unescape(key.substr(0,found), '!').c_str());
1587     if (found != key.npos) {
1588         mID->ident =  strtol(key.substr(found+1).c_str(), NULL, 16);
1589     } else {
1590         mID->ident = 0;
1591     }
1592     std::vector< std::string > tokens;
1593     boost::split(tokens, value, boost::is_from_range(' ', ' '));
1594     if (tokens.size() >= 2) {
1595         // if branch from mapid2entry above
1596         mID->remoteID = StrAlloc(StringEscape::unescape(tokens[0], '!').c_str());
1597         mID->flags = strtol(tokens[1].c_str(), NULL, 16);
1598     } else {
1599         // else branch from above
1600         mID->remoteID = NULL;
1601         mID->flags = strtol(tokens[0].c_str(), NULL, 16);
1602     }
1603 }
1604
1605 void SyncSourceAdmin::init(SyncSource::Operations &ops,
1606                            const boost::shared_ptr<ConfigNode> &config,
1607                            const std::string adminPropertyName,
1608                            const boost::shared_ptr<ConfigNode> &mapping)
1609 {
1610     m_configNode = config;
1611     m_adminPropertyName = adminPropertyName;
1612     m_mappingNode = mapping;
1613     m_mappingLoaded = false;
1614
1615     ops.m_loadAdminData = boost::bind(&SyncSourceAdmin::loadAdminData,
1616                                       this, _1, _2, _3);
1617     ops.m_saveAdminData = boost::bind(&SyncSourceAdmin::saveAdminData,
1618                                       this, _1);
1619     if (mapping->isVolatile()) {
1620         // Don't provide map item operations. SynthesisDBPlugin will
1621         // tell the Synthesis engine not to call these (normally needed
1622         // for suspend/resume, which we don't support in volatile mode
1623         // because we don't store any meta data persistently).
1624         //
1625         // ops.m_readNextMapItem = boost::lambda::constant(false);
1626         // ops.m_insertMapItem = boost::lambda::constant(sysync::LOCERR_OK);
1627         // ops.m_updateMapItem = boost::lambda::constant(sysync::LOCERR_OK);
1628         // ops.m_deleteMapItem = boost::lambda::constant(sysync::LOCERR_OK);
1629     } else {
1630         ops.m_readNextMapItem = boost::bind(&SyncSourceAdmin::readNextMapItem,
1631                                             this, _1, _2);
1632         ops.m_insertMapItem = boost::bind(&SyncSourceAdmin::insertMapItem,
1633                                           this, _1);
1634         ops.m_updateMapItem = boost::bind(&SyncSourceAdmin::updateMapItem,
1635                                           this, _1);
1636         ops.m_deleteMapItem = boost::bind(&SyncSourceAdmin::deleteMapItem,
1637                                           this, _1);
1638     }
1639     ops.m_endDataWrite.getPostSignal().connect(boost::bind(&SyncSourceAdmin::flush, this));
1640 }
1641
1642 void SyncSourceAdmin::init(SyncSource::Operations &ops,
1643                            SyncSource *source)
1644 {
1645     init(ops,
1646          source->getProperties(true),
1647          SourceAdminDataName,
1648          source->getServerNode());
1649 }
1650
1651 void SyncSourceBlob::init(SyncSource::Operations &ops,
1652                           const std::string &dir)
1653 {
1654     m_blob.Init(getSynthesisAPI(),
1655                 getName().c_str(),
1656                 dir, "", "", "");
1657     ops.m_readBlob = boost::bind(&SyncSourceBlob::readBlob, this,
1658                                  _1, _2, _3, _4, _5, _6, _7);
1659     ops.m_writeBlob = boost::bind(&SyncSourceBlob::writeBlob, this,
1660                                   _1, _2, _3, _4, _5, _6, _7);
1661     ops.m_deleteBlob = boost::bind(&SyncSourceBlob::deleteBlob, this,
1662                                    _1, _2);
1663 }
1664
1665 void TestingSyncSource::removeAllItems()
1666 {
1667     // remove longest luids first:
1668     // for luid=UID[+RECURRENCE-ID] that will
1669     // remove children from a merged event first,
1670     // which is better supported by certain servers
1671     Items_t items = getAllItems();
1672     for (Items_t::reverse_iterator it = items.rbegin();
1673          it != items.rend();
1674          ++it) {
1675         deleteItem(*it);
1676     }
1677 }
1678
1679
1680
1681 #ifdef ENABLE_UNIT_TESTS
1682
1683 class SyncSourceTest : public CppUnit::TestFixture {
1684     CPPUNIT_TEST_SUITE(SyncSourceTest);
1685     CPPUNIT_TEST(backendsAvailable);
1686     CPPUNIT_TEST_SUITE_END();
1687
1688     void backendsAvailable()
1689     {
1690         //We expect backendsInfo() to be empty if !ENABLE_MODULES
1691         //Otherwise, there should be at least some backends.
1692 #ifdef ENABLE_MODULES
1693         CPPUNIT_ASSERT( !SyncSource::backendsInfo().empty() );
1694 #endif
1695     }
1696 };
1697
1698 SYNCEVOLUTION_TEST_SUITE_REGISTRATION(SyncSourceTest);
1699
1700 #endif // ENABLE_UNIT_TESTS
1701
1702
1703 SE_END_CXX
1704